[[["容易理解","easyToUnderstand","thumb-up"],["確實解決了我的問題","solvedMyProblem","thumb-up"],["其他","otherUp","thumb-up"]],[["難以理解","hardToUnderstand","thumb-down"],["資訊或程式碼範例有誤","incorrectInformationOrSampleCode","thumb-down"],["缺少我需要的資訊/範例","missingTheInformationSamplesINeed","thumb-down"],["翻譯問題","translationIssue","thumb-down"],["其他","otherDown","thumb-down"]],["上次更新時間:2025-09-05 (世界標準時間)。"],[],[],null,["# Perform efficient top-k retrieval\n\n| **Note:** This feature is available with the Spanner Enterprise edition and Enterprise Plus edition. For more information, see the [Spanner editions overview](/spanner/docs/editions-overview).\n\n\u003cbr /\u003e\n\nMany applications query a database to populate a single page in their\napplications. In such applications, the application doesn't need all of the\nmatches, but only the top-k matches based on index sort order. Search indexes\ncan implement this type of search very efficiently. This page describes how to\ncreate and search an index that has top-k matching.\n\nCreate search indexes for top-k matches\n---------------------------------------\n\nTo configure a search index for top-k matching, use `ORDER BY` to order the\nsearch index by a specific column. Queries need to have an `ORDER BY` clause\nthat exactly matches the search index sort order (including ascending versus\ndescending direction) and a `LIMIT` clause that requests the query to stop after\nfinding k-matching rows.\n\nYou can also implement pagination using these clauses. For more information, see\n[Paginate search queries](/spanner/docs/full-text-search/paginate-search-results).\n\nFor some use cases, it might make sense to maintain multiple search indexes\nsorted by different columns. Like\n[partitioning](/spanner/docs/full-text-search/partition-search-index),\nit's a trade-off between storage and write cost versus query latency.\n\nFor example, consider a table that uses the following schema: \n\n### GoogleSQL\n\n CREATE TABLE Albums (\n AlbumId STRING(MAX) NOT NULL,\n RecordTimestamp INT64 NOT NULL,\n ReleaseTimestamp INT64 NOT NULL,\n ListenTimestamp INT64 NOT NULL,\n AlbumTitle STRING(MAX),\n AlbumTitle_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN\n ) PRIMARY KEY(AlbumId);\n\n CREATE SEARCH INDEX AlbumsRecordTimestampIndex\n ON Albums(AlbumTitle_Tokens, SingerId_Tokens)\n STORING (ListenTimestamp)\n ORDER BY RecordTimestamp DESC\n\n CREATE SEARCH INDEX AlbumsReleaseTimestampIndex\n ON Albums(AlbumTitle_Tokens)\n STORING (ListenTimestamp)\n ORDER BY ReleaseTimestamp DESC\n\n### PostgreSQL\n\n CREATE TABLE albums (\n albumid character varying NOT NULL,\n recordtimestamp bigint NOT NULL,\n releasetimestamp bigint NOT NULL,\n listentimestamp bigint NOT NULL,\n albumtitle character varying,\n albumtitle_tokens spanner.tokenlist\n GENERATED ALWAYS AS (spanner.tokenize_fulltext(albumtitle)) VIRTUAL HIDDEN,\n PRIMARY KEY(albumid));\n\n CREATE SEARCH INDEX albumsrecordtimestampindex\n ON Albums(albumtitle_tokens, singerid_tokens)\n INCLUDE (listentimestamp)\n ORDER BY recordtimestamp DESC\n\n CREATE SEARCH INDEX albumsreleasetimestampindex\n ON Albums(albumtitle_tokens)\n INCLUDE (listentimestamp)\n ORDER BY releasetimestamp DESC\n\nQuery search indexes for top-k matches\n--------------------------------------\n\nAs stated previously, queries need to have an `ORDER BY` clause\nthat exactly matches the search index sort order (including ascending versus\ndescending direction) and a `LIMIT` clause that requests the query to stop after\nfinding k-matching rows.\n\nThe following list analyzes the efficiency of some common queries.\n\n- This query is very efficient. It selects the `AlbumsRecordTimestampIndex`\n index. Even if there are many albums with the word \"happy\", the query only\n scans a small number of rows:\n\n ### GoogleSQL\n\n SELECT AlbumId\n FROM Albums\n WHERE SEARCH(AlbumTitle_Tokens, 'happy')\n ORDER BY RecordTimestamp DESC\n LIMIT 10\n\n ### PostgreSQL\n\n SELECT albumid\n FROM albums\n WHERE spanner.search(albumtitle_tokens, 'happy')\n ORDER BY recordtimestamp DESC\n LIMIT 10\n\n- The same query, requesting sort order by `ReleaseTimestamp` in descending\n order, uses the `AlbumsReleaseTimestampIndex` index and is equally\n efficient:\n\n ### GoogleSQL\n\n SELECT AlbumId\n FROM Albums\n WHERE SEARCH(AlbumTitle_Tokens, 'happy')\n ORDER BY ReleaseTimestamp DESC\n LIMIT 10\n\n ### PostgreSQL\n\n SELECT albumid\n FROM albums\n WHERE spanner.search(albumtitle_tokens, 'happy')\n ORDER BY releasetimestamp DESC\n LIMIT 10\n\n- A query that requests sort order by `ListenTimestamp` doesn't execute a\n top-k query efficiently. It has to fetch all matching albums, sort them by\n `ListenTimestamp,` and return the top 10. Such a query uses more resources\n if there's a large number of documents that contain the term \"happy\".\n\n ### GoogleSQL\n\n SELECT AlbumId\n FROM Albums\n WHERE SEARCH(AlbumTitle_Tokens, 'happy')\n ORDER BY ListenTimestamp DESC\n LIMIT 10\n\n ### PostgreSQL\n\n SELECT albumid\n FROM albums\n WHERE spanner.search(albumtitle_tokens, 'happy')\n ORDER BY listentimestamp DESC\n LIMIT 10\n\n- Similarly, a query doesn't run efficiently if it requests that results are\n ordered using the `RecordTimestamp` column in ascending order. It scans all\n rows with the word \"happy\", despite having a `LIMIT`.\n\n ### GoogleSQL\n\n SELECT AlbumId\n FROM Albums\n WHERE SEARCH(AlbumTitle_Tokens, 'happy')\n ORDER BY RecordTimestamp ASC\n LIMIT 10\n\n ### PostgreSQL\n\n SELECT albumid\n FROM albums\n WHERE spanner.search(albumtitle_tokens, 'happy')\n ORDER BY recordtimestamp ASC\n LIMIT 10\n\nWhat's next\n-----------\n\n- Learn about [full-text search queries](/spanner/docs/full-text-search/query-overview).\n- Learn how to [rank search results](/spanner/docs/full-text-search/ranked-search).\n- Learn how to [paginate search results](/spanner/docs/full-text-search/paginate-search-results).\n- Learn how to [mix full-text and non-text queries](/spanner/docs/full-text-search/mix-full-text-and-non-text-queries).\n- Learn how to [search multiple columns](/spanner/docs/full-text-search/search-multiple-columns)."]]