OFFSET / FETCH NEXT에서 총 행 수 가져 오기


92

따라서 웹 사이트에서 페이징을 구현하려는 여러 레코드를 반환하는 함수가 있습니다. 이 작업을 수행하기 위해 SQL Server 2012에서 Offset / Fetch Next를 사용하는 것이 좋습니다. 저희 웹 사이트에는 총 기록 수와 귀하가 현재 어떤 페이지에 있는지를 나열하는 영역이 있습니다.

이전에는 전체 레코드 세트를 가져 왔고이를 프로그래밍 방식으로 페이징 할 수있었습니다. 그러나 FETCH NEXT X ROWS ONLY와 함께 SQL 방식을 사용하면 X 행만 다시 제공되므로 총 레코드 세트가 무엇인지, 최소 및 최대 페이지를 계산하는 방법을 알 수 없습니다. 내가 이것을 알 수있는 유일한 방법은 함수를 두 번 호출하고 첫 번째 행 수를 수행 한 다음 FETCH NEXT로 두 번째 행을 실행하는 것입니다. 쿼리를 두 번 실행하지 않는 더 좋은 방법이 있습니까? 성능을 늦추는 것이 아니라 속도를 높이려고합니다.

답변:


115

COUNT(*) OVER()... 를 사용할 수 있습니다 . 다음은 다음을 사용하는 간단한 예입니다 sys.all_objects.

DECLARE 
  @PageSize INT = 10, 
  @PageNum  INT = 1;

SELECT 
  name, object_id, 
  overall_count = COUNT(*) OVER()
FROM sys.all_objects
ORDER BY name
  OFFSET (@PageNum-1)*@PageSize ROWS
  FETCH NEXT @PageSize ROWS ONLY;

그러나 이것은 작은 데이터 세트를 위해 예약되어야합니다. 더 큰 세트에서는 성능이 좋지 않을 수 있습니다. 인덱싱 된 뷰 유지 (결과가 필터링되지 않았거나 절을 미리 알고있는 경우에만 작동 함 ) 및 트릭 사용을 포함한 더 나은 대안은이 Paul White 기사를 참조하십시오 .WHEREROW_NUMBER()


44
3,500,000 개의 레코드가있는 테이블에서 COUNT (*) OVER ()는 1 분 3 초가 소요되었습니다. James Moberg가 아래에서 설명한 접근 방식은 동일한 데이터 세트를 검색하는 데 13 초가 걸렸습니다. Count Over 접근 방식은 더 작은 데이터 세트에 대해 잘 작동하지만 실제로 커지기 시작하면 상당히 느려집니다.
matthew_360

아니면 count (*)처럼 테이블에서 실제 데이터를 읽을 필요가 없기 때문에 훨씬 더 빠른 COUNT (1) OVER ()를 사용할 수 있습니다
ldx

1
@AaronBertrand 정말? 즉, 모든 열을 포함하는 인덱스가 있거나 2008R2 이후 많이 개선되었음을 의미합니다. 해당 버전에서 count (*)는 순차적으로 작동합니다. 즉, 처음 * (예 : 모든 열)가 선택된 다음 계산됩니다. count (1)을했다면 실제 데이터를 읽는 것보다 훨씬 빠른 상수를 선택하기 만하면됩니다.
LDX

5
@idx 아니요, 2008 R2에서도 그렇게 작동하지 않았습니다. 죄송합니다. 저는 6.5부터 SQL Server를 사용해 왔으며 엔진이 COUNT (*) 또는 COUNT (1) 모두에 대해 가장 좁은 인덱스를 스캔 할만큼 똑똑하지 않았던 때를 기억하지 못합니다. 2000 년 이후로는 확실하지 않습니다.하지만 2008 R2 인스턴스가 있습니다. SQLfiddle에 재현을 설정하여이 차이가 존재한다고 주장 할 수 있습니까? 나는 그것을 시도하게되어 기쁩니다.
Aaron Bertrand

2
SQL Server 2016 데이터베이스에서 약 2,500 만 개의 행이있는 테이블에서 검색하고 약 3000 개의 결과 (테이블 반환 함수를 포함하여 여러 조인 포함)를 페이징하는 데 밀리 초가 걸렸습니다.
jkerak

140

COUNT (를 사용하여 몇 가지 성능 문제가 발생했습니다. ) OVER () 메서드를 . (10 개의 레코드를 반환하는 데 40 초가 걸리고 나중에 문제가 없었기 때문에 서버인지 확실하지 않습니다.)이 기술은 COUNT ( ) OVER () 를 사용하지 않고도 모든 조건에서 작동했으며 같은 것:

DECLARE 
    @PageSize INT = 10, 
    @PageNum  INT = 1;

WITH TempResult AS(
    SELECT ID, Name
    FROM Table
), TempCount AS (
    SELECT COUNT(*) AS MaxRows FROM TempResult
)
SELECT *
FROM TempResult, TempCount
ORDER BY TempResult.Name
    OFFSET (@PageNum-1)*@PageSize ROWS
    FETCH NEXT @PageSize ROWS ONLY

32
COUNT (*) 값을 변수에 저장할 가능성이 있다면 정말 멋질 것입니다. 내 저장 프로 시저의 OUTPUT 매개 변수로 설정할 수 있습니다. 어떤 아이디어?
To Ka

1
별도의 테이블에서 개수를 얻는 방법이 있습니까? 첫 번째 선행 SELECT 문에 "TempResult"만 사용할 수있는 것 같습니다.
matthew_360 2014-10-22

4
이것이 왜 그렇게 잘 작동합니까? 첫 번째 CTE에서는 모든 행이 선택된 다음 가져 오기를 통해 축소됩니다. 첫 번째 CTE에서 모든 행을 선택하면 속도가 상당히 느려질 것이라고 생각했을 것입니다. 어쨌든 감사합니다!
jbd apr

1
제 경우에는 COUNT (1) OVER () ..보다 느려졌습니다. 아마도 select의 함수 때문일 수 있습니다.
Tiju John

1
이것은 행이 수백만 개일 때 너무 많은 시간이 걸리는 소규모 데이터베이스에 적합합니다.
Kiya '

1

James Moberg의 답변을 바탕으로 :

Row_Number()SQL Server 2012가없고 OFFSET을 사용할 수없는 경우을 사용하는 대안입니다.

DECLARE 
    @PageNumEnd INT = 10, 
    @PageNum  INT = 1;

WITH TempResult AS(
    SELECT ID, NAME
    FROM Tabla
), TempCount AS (
    SELECT COUNT(*) AS MaxRows FROM TempResult
)

select * 
from
(
    SELECT
     ROW_NUMBER() OVER ( ORDER BY PolizaId DESC) AS 'NumeroRenglon', 
     MaxRows, 
     ID,
     Name
    FROM TempResult, TempCount

)resultados
WHERE   NumeroRenglon >= @PageNum
    AND NumeroRenglon <= @PageNumEnd
ORDER BY NumeroRenglon
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.