Quiero aplicar la paginación en una tabla con una gran cantidad de datos. Todo lo que quiero saber es una mejor opción que usar OFFSET en SQL Server.
Aquí está mi consulta simple:
SELECT * FROM TableName ORDER BY Id DESC OFFSET 30000000 ROWS FETCH NEXT 20 ROWS ONLYPuede usar Paginación de conjunto de claves para esto. Es mucho más eficiente que usar la paginación de conjuntos de filas (paginación por número de fila).
En la paginación de conjuntos de filas, se deben leer todas las filas anteriores antes de poder leer la página siguiente. Mientras que en Keyset Pagination, el servidor puede saltar inmediatamente al lugar correcto en el índice, por lo que no se leen filas adicionales que no es necesario.
En este tipo de paginación, no puede saltar a un número de página específico. Salta a una clave específica y lee desde allí. Para que esto funcione bien, debe tener un índice único en esa clave, que incluye cualquier otra columna que necesite consultar.
Un gran beneficio , además de la ganancia de eficiencia obvia, es evitar el problema de "fila faltante" al paginar, causado por la eliminación de filas de páginas leídas anteriormente. Esto no sucede al paginar por clave, porque la clave no cambia.
Aquí hay un ejemplo:
Supongamos que tiene una tabla llamada TableName con un índice en Id y desea comenzar con el último valor de Id y trabajar hacia atrás.
Empiezas con:
SELECT TOP (@numRows) * FROM TableName ORDER BY Id DESC;Tenga en cuenta el uso de
ORDER BYpara garantizar que el pedido sea correcto
El cliente conservará el último valor de Id recibido (el más bajo en este caso). En la siguiente solicitud, salta a esa tecla y continúa:
SELECT TOP (@numRows) * FROM TableName WHERE Id < @lastId ORDER BY Id DESC;Tenga en cuenta el uso de
<no<=
En caso de que se lo pregunte, en un índice típico de B-Tree+, la fila con el ID indicado no se lee, es la fila posterior la que se lee.
La clave elegida debe ser única , por lo que si está paginando por una columna no única, debe agregar una segunda columna tanto a ORDER BY como a WHERE . Necesitaría un índice en OtherColumn, Id por ejemplo, para admitir este tipo de consulta. No olvides INCLUDE columnas en el índice.
SQL Server no admite comparadores de fila/tupla , por lo que no puede hacer (OtherColumn, Id) < (@lastOther, @lastId) (sin embargo, esto es compatible con PostgreSQL, MySQL, MariaDB y SQLite).
En su lugar, necesita lo siguiente:
SELECT TOP (@numRows) * FROM TableName WHERE ( OtherColumn = @lastOther AND Id < @lastId) OR OtherColumn < @lastOther ) ORDER BY OtherColumn DESC, Id DESC; Esto es más eficiente de lo que parece, ya que SQL Server puede convertirlo en un < adecuado para ambos valores.
La presencia de NULL s complica aún más las cosas. Es posible que desee consultar esas filas por separado.
En un sitio web comercial muy grande, usamos un compuesto técnico de identificadores almacenados en una tabla pseudo temporal y los unimos con esta tabla a las filas de la tabla de productos.
Permítanme hablar con un ejemplo claro.
Tenemos un diseño de mesa de esta manera:
CREATE TABLE S_TEMP.T_PAGINATION_PGN (PGN_ID BIGINT IDENTITY(-9 223 372 036 854 775 808, 1) PRIMARY KEY, PGN_SESSION_GUID UNIQUEIDENTIFIER NOT NULL, PGN_SESSION_DATE DATETIME2(0) NOT NULL, PGN_PRODUCT_ID INT NOT NULL, PGN_SESSION_ORDER INT NOT NULL); CREATE INDEX X_PGN_SESSION_GUID_ORDER ON S_TEMP.T_PAGINATION_PGN (PGN_SESSION_GUID, PGN_SESSION_ORDER) INCLUDE (PGN_SESSION_ORDER); CREATE INDEX X_PGN_SESSION_DATE ON S_TEMP.T_PAGINATION_PGN (PGN_SESSION_DATE);Tenemos una tabla de productos muy grande llamada T_PRODUIT_PRD y un cliente la filtró con muchos predicados. INSERTAMOS filas del SELECT filtrado en esta tabla de esta manera:
DECLARE @SESSION_ID UNIQUEIDENTIFIER = NEWID(); INSERT INTO S_TEMP.T_PAGINATION_PGN SELECT @SESSION_ID , SYSUTCDATETIME(), PRD_ID, ROW_NUMBER() OVER(ORDER BY --> custom order by FROM dbo.T_PRODUIT_PRD WHERE ... --> custom filterLuego, cada vez que necesitamos una página deseada, compuesta de productos @N, agregamos una unión a esta tabla como:
... JOIN S_TEMP.T_PAGINATION_PGN ON PGN_SESSION_GUID = @SESSION_ID AND 1 + (PGN_SESSION_ORDER / @N) = @DESIRED_PAGE_NUMBER AND PGN_PRODUCT_ID = dbo.T_PRODUIT_PRD.PRD_ID¡Todos los índices harán el trabajo!
Por supuesto, regularmente tenemos que purgar esta tabla y es por eso que tenemos un trabajo programado que elimina las filas cuyas sesiones se generaron hace más de 4 horas:
DELETE FROM S_TEMP.T_PAGINATION_PGN WHERE PGN_SESSION_DATE < DATEADD(hour, -4, SYSUTCDATETIME());En el mismo espíritu que la solución SQLPro, propongo:
WITH CTE AS (SELECT 30000000 AS N UNION ALL SELECT N-1 FROM CTE WHERE N > 30000000 +1 - 20) SELECT T.* FROM CTE JOIN TableName T ON CTE.N=T.ID ORDER BY CTE.N DESC¡Probé con 2 mil millones de líneas y es instantáneo! Es fácil convertirlo en un procedimiento almacenado... Por supuesto, válido si los identificadores se suceden.