거대한 테이블의 정렬 된 열에서 마지막 null이 아닌 값을 얻는 방법은 무엇입니까?


13

다음과 같은 입력이 있습니다.

 id | value 
----+-------
  1 |   136
  2 |  NULL
  3 |   650
  4 |  NULL
  5 |  NULL
  6 |  NULL
  7 |   954
  8 |  NULL
  9 |   104
 10 |  NULL

다음과 같은 결과가 기대됩니다.

 id | value 
----+-------
  1 |   136
  2 |   136
  3 |   650
  4 |   650
  5 |   650
  6 |   650
  7 |   954
  8 |   954
  9 |   104
 10 |   104

사소한 해결책은 <관계형으로 테이블을 조인 한 다음 MAX값 을 선택합니다 GROUP BY.

WITH tmp AS (
  SELECT t2.id, MAX(t1.id) AS lastKnownId
  FROM t t1, t t2
  WHERE
    t1.value IS NOT NULL
    AND
    t2.id >= t1.id
  GROUP BY t2.id
)
SELECT
  tmp.id, t.value
FROM t, tmp
WHERE t.id = tmp.lastKnownId;

그러나이 코드를 간단하게 실행하면 내부적으로 입력 테이블 행 수의 제곱이 생성됩니다 ( O (n ^ 2) ). 블록 / 레코드 수준에서 t-sql이 최적화 할 것으로 예상했습니다. 할 일은 매우 쉽고 선형이며 본질적으로 for 루프 ( O (n) )입니다.

그러나 내 실험에서 최신 MS SQL 2016 은이 쿼리를 올바르게 최적화 할 수 없으므로 큰 입력 테이블에 대해서는이 쿼리를 실행할 수 없습니다.

또한 쿼리는 빠르게 실행되어야하므로 유사하지만 매우 다른 커서 기반 솔루션을 실행할 수 없습니다.

일부 메모리 백업 임시 테이블을 사용하면 좋은 타협이 될 수 있지만 하위 쿼리를 사용하는 예제 쿼리가 작동하지 않는다는 것을 고려할 때 훨씬 빠르게 실행할 수 있는지 확실하지 않습니다.

또한 t-sql 문서에서 일부 윈도우 기능을 발굴하려고 생각하고 있습니다. 예를 들어, 누적 합계 는 매우 유사한 작업을 수행하지만 이전의 요소 합계가 아닌 최신 null이 아닌 요소를 제공하도록 속일 수는 없었습니다.

이상적인 솔루션은 절차 코드 나 임시 테이블이없는 빠른 쿼리입니다. 또는 임시 테이블이있는 솔루션은 괜찮지 만 절차 적으로 테이블을 반복하는 것은 아닙니다.

답변:


12

이 유형의 문제에 대한 일반적인 해결책은 Itzik Ben-Gan이 그의 기사 인 마지막 비 NULL 퍼즐에서 제공합니다 .

DROP TABLE IF EXISTS dbo.Example;

CREATE TABLE dbo.Example
(
    id integer PRIMARY KEY,
    val integer NULL
);

INSERT dbo.Example
    (id, val)
VALUES
    (1, 136),
    (2, NULL),
    (3, 650),
    (4, NULL),
    (5, NULL),
    (6, NULL),
    (7, 954),
    (8, NULL),
    (9, 104),
    (10, NULL);

SELECT
    E.id,
    E.val,
    lastval =
        CAST(
            SUBSTRING(
                MAX(CAST(E.id AS binary(4)) + CAST(E.val AS binary(4))) OVER (
                    ORDER BY E.id
                    ROWS UNBOUNDED PRECEDING),
            5, 4)
        AS integer)
FROM dbo.Example AS E
ORDER BY
    E.id;

데모 : db <> fiddle


11

블록 / 레코드 수준에서 t-sql이 최적화 할 것으로 예상했습니다. 할 일은 매우 쉽고 선형이며 본질적으로 for 루프 (O (n))입니다.

그것은 당신이 쓴 쿼리가 아닙니다. 테이블 스키마에 대한 사소한 세부 사항에 따라 작성한 쿼리와 같지 않을 수 있습니다. 쿼리 최적화 프로그램에서 너무 많은 것을 기대하고 있습니다.

올바른 인덱싱을 사용하면 다음 T-SQL을 통해 원하는 알고리즘을 얻을 수 있습니다.

SELECT t1.id, ca.[VALUE] 
FROM dbo.[BIG_TABLE(FOR_U)] t1
CROSS APPLY (
    SELECT TOP (1) [VALUE]
    FROM dbo.[BIG_TABLE(FOR_U)] t2
    WHERE t2.ID <= t1.ID AND t2.[VALUE] IS NOT NULL
    ORDER BY t2.ID DESC
) ca; --ORDER BY t1.ID ASC

각 행에 대해 쿼리 프로세서는 인덱스를 뒤로 이동하고에 대해 null이 아닌 값을 가진 행을 찾으면 중지합니다 [VALUE]. 내 컴퓨터 에서 소스 테이블의 1 억 행에 대해 약 90 초 안에 완료됩니다 . 클라이언트에서 해당 행을 모두 버리고 시간이 낭비되므로 쿼리가 필요 이상으로 실행됩니다.

주문한 결과가 필요한지 또는 그러한 큰 결과 집합으로 수행하려는 계획이 있는지 명확하지 않습니다. 실제 시나리오에 맞게 쿼리를 조정할 수 있습니다. 이 방법의 가장 큰 장점은 쿼리 계획에서 정렬 할 필요가 없다는 것입니다. 더 큰 결과 집합에 도움이 될 수 있습니다. 한 가지 단점은 인덱스에서 많은 행을 읽고 버려서 테이블에 많은 NULL이 있으면 성능이 최적화되지 않는다는 것입니다. 이 경우 NULL을 제외하는 필터링 된 인덱스를 사용하여 성능을 향상시킬 수 있어야합니다.

테스트를위한 샘플 데이터 :

DROP TABLE IF EXISTS #t;

CREATE TABLE #t (
ID BIGINT NOT NULL
);

INSERT INTO #t WITH (TABLOCK)
SELECT TOP (10000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);

DROP TABLE IF EXISTS dbo.[BIG_TABLE(FOR_U)];

CREATE TABLE dbo.[BIG_TABLE(FOR_U)] (
ID BIGINT NOT NULL,
[VALUE] BIGINT NULL
);

INSERT INTO dbo.[BIG_TABLE(FOR_U)] WITH (TABLOCK)
SELECT 10000 * t1.ID + t2.ID, CASE WHEN (t1.ID + t2.ID) % 3 = 1 THEN t2.ID ELSE NULL END
FROM #t t1
CROSS JOIN #t t2;

CREATE UNIQUE CLUSTERED INDEX ADD_ORDERING ON dbo.[BIG_TABLE(FOR_U)] (ID);

7

한 가지 방법, 사용하여 OVER()MAX()COUNT()에 따라 이 소스 가 될 수 :

SELECT ID, MAX(value) OVER (PARTITION BY Value2) as value
FROM
(
    SELECT ID, value
        ,COUNT(value) OVER (ORDER BY ID) AS Value2
    FROM dbo.HugeTable
) a
ORDER BY ID;

결과

Id  UpdatedValue
1   136
2   136
3   650
4   650
5   650
6   650
7   954
8   954
9   104
10  104

첫 번째 예제와 밀접하게 관련된 이 소스를 기반으로하는 다른 방법

;WITH CTE As 
( 
SELECT  value,
        Id, 
        COUNT(value) 
        OVER(ORDER BY Id) As  Value2 
FROM dbo.HugeTable
),

CTE2 AS ( 
SELECT Id,
       value,
       First_Value(value)  
       OVER( PARTITION BY Value2
             ORDER BY Id) As UpdatedValue 
FROM CTE 
            ) 
SELECT Id,UpdatedValue 
FROM CTE2;

3
이러한 접근 방식이 "거대한 테이블"로 수행되는 방식에 대한 세부 사항을 추가하십시오.
Joe Obbish 2018 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.