[[["容易理解","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,["# Paginate search results\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\nWeb applications often paginate data as it's presented to users. The end user\nreceives one page of results, and when they navigate to the next page, the next\nbatch of results is retrieved and presented. This page describes how to add\npagination to search results when performing a\n[full-text search](/spanner/docs/full-text-search) in Spanner.\n\nPagination options\n------------------\n\nThere are two ways to implement paginated queries in Spanner:\n*[key-based pagination](#use_key-based_pagination)* (recommended) and\n*[offset-based pagination](#use_offset-based_pagination)*.\n\nKey-based pagination is a method for retrieving search results in smaller, more\nmanageable chunks while ensuring consistent results across requests. A unique\nidentifier (the \"key\") from the last result of a page is used as a reference\npoint to fetch the next set of results.\n\nSpanner generally recommends using key-based pagination. While\noffset-based pagination is easier to implement, it has two significant\ndrawbacks:\n\n1. **Higher query cost**:Offset-based pagination repeatedly retrieves and discards the same results, leading to increased costs and decreased performance.\n2. **Inconsistent Results:** In paginated queries, each page is typically retrieved at a different read timestamp. For example, the first page might come from a query at 1 PM, and the next from a query at 1:10 PM. This means that search results can change between queries, leading to inconsistent results across pages.\n\nKey-based pagination, on the other hand, uses a unique identifier (key) from the\nlast result of a page to fetch the next set of results. This ensures both\nefficient retrieval and consistent results, even if the underlying data changes.\n\nTo provide stability in page results, the application could issue all queries\nfor different pages at the same timestamp. This, however, might fail if the\nquery exceeds the [version retention\nperiod](/spanner/docs/reference/rest/v1/projects.instances.databases#Database.FIELDS.version_retention_period)\n(the default is 1 hour). For example, this failure happens if `version_gc` is\none hour, and the end user fetched the first results at 1 PM and clicked *Next*\nat 3 PM.\n\nUse key-based pagination\n------------------------\n\nKey-based pagination remembers the last item of the previous page and uses it as\na starting point for the next page query. To achieve this, the query must return\nthe columns specified in the `ORDER BY` clause and limit the number of rows\nusing `LIMIT`.\n\nFor key-based pagination to work, the query must order results by some strict\ntotal order. The easiest way to get one is to choose any [total\norder](https://en.wikipedia.org/wiki/Total_order) and then add tie-breaker\ncolumns, if needed. In most cases, the total order is the search index sort\norder and the unique combination of columns is the base table primary key.\n\nUsing our `Albums` sample schema, for the first page, the query looks like the\nfollowing: \n\n### GoogleSQL\n\n SELECT AlbumId, ReleaseTimestamp\n FROM Albums\n WHERE SEARCH(AlbumTitle_Tokens, \"fifth symphony\")\n ORDER BY ReleaseTimestamp DESC, AlbumId\n LIMIT 10;\n\n### PostgreSQL\n\n SELECT albumid, releasetimestamp\n FROM albums\n WHERE spanner.search(albumtitle_tokens, 'fifth symphony')\n ORDER BY releasetimestamp DESC, albumid\n LIMIT 10;\n\nThe `AlbumId` is the tie breaker since `ReleaseTimestamp` isn't a key. There\nmight be two different albums with the same value for `ReleaseTimestamp`.\n\nTo resume, the application runs the same query again, but with a `WHERE` clause\nthat restricts the results from the previous page. The additional condition\nneeds to account for key direction (ascending versus descending), tie breakers,\nand the order of NULL values for nullable columns.\n\nIn our example, `AlbumId` is the only key column (in ascending order) and it\ncan't be NULL, so the condition is the following: \n\n### GoogleSQL\n\n SELECT AlbumId, ReleaseTimestamp\n FROM Albums\n WHERE (ReleaseTimestamp \u003c @last_page_release_timestamp\n OR (ReleaseTimestamp = @last_page_release_timestamp\n AND AlbumId \u003e @last_page_album_id))\n AND SEARCH(AlbumTitle_Tokens, @p)\n ORDER BY ReleaseTimestamp DESC, AlbumId ASC\n LIMIT @page_size;\n\n### PostgreSQL\n\nThis example uses query parameters `$1`, `$2`, `$3` and `$4` which are bound\nto values specified for `last_page_release_timestamp`, `last_page_album_id`,\n`query`, and `page_size`, respectively. \n\n SELECT albumid, releasetimestamp\n FROM albums\n WHERE (releasetimestamp \u003c $1\n OR (releasetimestamp = $1\n AND albumid \u003e $2))\n AND spanner.search(albumtitle_tokens, $3)\n ORDER BY releasetimestamp DESC, albumid ASC\n LIMIT $4;\n\nSpanner interprets this kind of condition as\n[seekable](/spanner/docs/query-execution-operators#filter_scan). This means that\nSpanner doesn't read the index for documents you're filtering\nout. This optimization is what makes key-based pagination much more efficient\nthan offset-based pagination.\n\nUse offset-based pagination\n---------------------------\n\nOffset-based pagination leverages the `LIMIT` and `OFFSET` clauses of SQL query\nto simulate pages. The `LIMIT` value indicates the number of results per page.\nThe `OFFSET` value is set to zero for the first page, page size for the second\npage, and double the page size for the third page.\n\nFor example, the following query fetches the third page, with a page size of 50: \n\n### GoogleSQL\n\n SELECT AlbumId\n FROM Albums\n WHERE SEARCH(AlbumTitle_Tokens, \"fifth symphony\")\n ORDER BY ReleaseTimestamp DESC, AlbumId\n LIMIT 50 OFFSET 100;\n\n### PostgreSQL\n\n SELECT albumid\n FROM albums\n WHERE spanner.search(albumtitle_tokens, 'fifth symphony')\n ORDER BY releasetimestamp DESC, albumid\n LIMIT 50 OFFSET 100;\n\nUsage Notes:\n\n- The `ORDER BY` clause is highly recommended to ensure consistent ordering between pages.\n- In production queries, use query parameters rather than constants to specify `LIMIT` and `OFFSET` to make query caching more efficient. For more information, see [Query parameters](/spanner/docs/full-text-search/query-overview#query_parameters).\n\nWhat's next\n-----------\n\n- Learn how to [rank search results](/spanner/docs/full-text-search/ranked-search).\n- Learn how to [perform a substring search](/spanner/docs/full-text-search/substring-search).\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)."]]