SQL Server 2005에서 최소 여러 열을 얻는 가장 효율적인 방법은 무엇입니까?


29

6 열 중 최소값을 얻으려는 상황에 있습니다.

지금 까지이 작업을 수행하는 세 가지 방법을 찾았지만 이러한 방법의 성능에 관심이 있으며 성능에 더 적합한 방법을 알고 싶습니다.

첫 번째 방법은 큰 사례 를 사용 하는 것 입니다. 위 링크의 예를 기반으로 3 개의 열이있는 예가 있습니다. 6 개의 열을 살펴볼 것이기 때문에 제 진술서는 훨씬 더 길 것입니다.

Select Id,
       Case When Col1 <= Col2 And Col1 <= Col3 Then Col1
            When Col2 <= Col3 Then Col2 
            Else Col3
            End As TheMin
From   MyTable

두 번째 옵션은 UNION여러 select 문과 함께 연산자 를 사용하는 것 입니다. Id 매개 변수를 허용하는 UDF에 이것을 넣을 것입니다.

select Id, dbo.GetMinimumFromMyTable(Id)
from MyTable

select min(col)
from
(
    select col1 [col] from MyTable where Id = @id
    union all
    select col2 from MyTable where Id = @id
    union all
    select col3 from MyTable where Id = @id
) as t

그리고 내가 찾은 세번째 옵션은했다 UNPIVOT 연산자를 사용 심지어 지금까지 몰랐던,

with cte (ID, Col1, Col2, Col3)
as
(
    select ID, Col1, Col2, Col3
    from TestTable
)
select cte.ID, Col1, Col2, Col3, TheMin from cte
join
(
    select
        ID, min(Amount) as TheMin
    from 
        cte 
        UNPIVOT (Amount for AmountCol in (Col1, Col2, Col3)) as unpvt
    group by ID
) as minValues
on cte.ID = minValues.ID

테이블 크기와이 테이블을 쿼리하고 업데이트하는 빈도 때문에 이러한 쿼리가 데이터베이스에 미치는 영향에 대해 걱정하고 있습니다.

이 쿼리는 실제로 몇 백만 개의 레코드가있는 테이블에 대한 조인에 사용되지만 반환 된 레코드는 한 번에 약 100 개의 레코드로 줄어 듭니다. 하루 종일 여러 번 실행되며 쿼리하는 6 개의 열이 자주 업데이트됩니다 (일일 통계 포함). 쿼리하는 6 열에 인덱스가 없다고 생각합니다.

최소한 여러 열을 얻으려고 할 때 성능에 더 좋은 방법은 무엇입니까? 아니면 내가 모르는 다른 더 좋은 방법이 있습니까?

SQL Server 2005를 사용하고 있습니다

샘플 데이터 및 결과

내 데이터에 다음과 같은 레코드가 포함 된 경우 :

Id Col1 Col2 Col3 Col4 Col5 Col6
12 34 5
2 2 6 10 5 7 9
3 1 2 3 4 5
4 9 5 4 6 8 9

최종 결과는

ID 가치
1 0
2 2
3 1
4 4

답변:


22

세 가지 방법 모두의 성능을 테스트했으며 여기에 내가 찾은 것이 있습니다.

  • 1 기록 : 눈에 띄는 차이 없음
  • 10 기록 : 눈에 띄는 차이가 없습니다
  • 1,000 레코드 : 눈에 띄는 차이가 없습니다
  • 레코드 10,000 개 : UNION하위 쿼리가 약간 느 렸습니다. CASE WHEN쿼리는 조금 빠른 것보다 UNPIVOT하나.
  • 100,000 개의 레코드 : UNION하위 쿼리가 상당히 느리지 만 UNPIVOT쿼리가 CASE WHEN쿼리 보다 약간 빠릅니다.
  • 500,000 개의 레코드 : UNION하위 쿼리는 여전히 상당히 느리지 만 쿼리 UNPIVOT보다 훨씬 빠릅니다.CASE WHEN

따라서 최종 결과는

  • 더 작은 레코드 세트를 사용하면 중요한 차이가 충분하지 않은 것 같습니다. 읽고 유지하기 가장 쉬운 것을 사용하십시오.

  • 더 큰 레코드 세트를 UNION ALL시작하면 다른 두 가지 방법에 비해 하위 쿼리의 성능이 저하됩니다.

  • CASE문장은 특정 지점 (제 경우 약 100k 행)까지 UNPIVOT쿼리가 가장 잘 수행되고 쿼리가 가장 우수한 쿼리가 되는 지점

하나의 쿼리가 다른 쿼리보다 나아지는 실제 수는 하드웨어, 데이터베이스 스키마, 데이터 및 현재 서버로드의 결과로 변경 될 수 있으므로 성능이 염려되면 자체 시스템으로 테스트하십시오.

또한 Mikael의 답변을 사용하여 몇 가지 테스트를 실행했습니다 . 그러나 대부분의 레코드 세트 크기에 대해 여기에서 시도한 다른 세 가지 방법보다 느 렸습니다. 유일한 예외는 UNION ALL매우 큰 레코드 세트 크기에 대한 쿼리 보다 낫다는 것 입니다. 그래도 가장 작은 값 외에도 열 이름을 표시한다는 사실이 마음에 듭니다.

나는 dba가 아니므로 테스트를 최적화하지 않았고 무언가를 놓쳤을 수 있습니다. 실제 라이브 데이터로 테스트했기 때문에 결과에 영향을 줄 수 있습니다. 각 쿼리를 몇 번 다른 방법으로 실행하여 설명하려고 시도했지만 결코 알 수 없습니다. 누군가가 이것에 대한 깨끗한 테스트를 작성하고 그들의 결과를 공유한다면 나는 확실히 관심이있을 것입니다.


6

가장 빠른 것이 무엇인지 모르지만 이와 같은 것을 시도해 볼 수 있습니다.

declare @T table
(
  Col1 int,
  Col2 int,
  Col3 int,
  Col4 int,
  Col5 int,
  Col6 int
)

insert into @T values(1, 2, 3, 4, 5, 6)
insert into @T values(2, 3, 1, 4, 5, 6)

select T4.ColName, T4.ColValue
from @T as T1
  cross apply (
                select T3.ColValue, T3.ColName
                from (
                       select row_number() over(order by T2.ColValue) as rn,
                              T2.ColValue,
                              T2.ColName
                       from (
                              select T1.Col1, 'Col1' union all
                              select T1.Col2, 'Col2' union all
                              select T1.Col3, 'Col3' union all
                              select T1.Col4, 'Col4' union all
                              select T1.Col5, 'Col5' union all
                              select T1.Col6, 'Col6'
                            ) as T2(ColValue, ColName)
                     ) as T3
                where T3.rn = 1
              ) as T4

결과:

ColName ColValue
------- -----------
Col1    1
Col3    1

어떤 열에 최소값이 있는지에 관심이 없다면 이것을 대신 사용할 수 있습니다.

declare @T table
(
  Id int,
  Col1 int,
  Col2 int,
  Col3 int,
  Col4 int,
  Col5 int,
  Col6 int
)

insert into @T
select 1,        3,       4,       0,       2,       1,       5 union all
select 2,        2,       6,      10,       5,       7,       9 union all
select 3,        1,       1,       2,       3,       4,       5 union all
select 4,        9,       5,       4,       6,       8,       9

select T.Id, (select min(T1.ColValue)
              from (
                      select T.Col1 union all
                      select T.Col2 union all
                      select T.Col3 union all
                      select T.Col4 union all
                      select T.Col5 union all
                      select T.Col6
                    ) as T1(ColValue)
             ) as ColValue
from @T as T

단순화 된 피벗 해제 쿼리

select Id, min(ColValue) as ColValue
from @T
unpivot (ColValue for Col in (Col1, Col2, Col3, Col4, Col5, Col6)) as U
group by Id

6

CASE명령문을 사용하여 필요한 논리를 수행 하는 지속 형 계산 열을 추가하십시오 .

그런 다음 해당 값을 기준으로 조인 (또는 다른 작업)을 수행해야 할 때 최소값을 항상 효율적으로 사용할 수 있습니다.

소스 값이 변경 될 때마다 ( INSERT/ UPDATE/ MERGE) 값이 다시 계산됩니다 . 나는 단지로 제공이 반드시 워크로드를위한 최고의 솔루션입니다 말하는 게 아니에요 단지 다른 답변처럼, 솔루션입니다. OP만이 워크로드에 가장 적합한 것을 결정할 수 있습니다.


1

6 개의 날짜에 대한 사례 진술. 덜 줄이려면 첫 번째 case 문에서 실제 분기를 복사하십시오. 가장 나쁜 경우는 Date1이 가장 낮은 값이고, 가장 좋은 경우는 Date6이 가장 낮은 값이므로 가장 가능성이 높은 날짜를 Date6에 두십시오. 나는 계산 열의 한계 때문에 이것을 썼습니다.

CASE WHEN Date1 IS NULL OR Date1 > Date2 THEN
        CASE WHEN Date2 IS NULL OR Date2 > Date3 THEN
            CASE WHEN Date3 IS NULL OR Date3 > Date4 THEN
                CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
                    CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                        Date6
                    ELSE
                        Date5
                    END
                ELSE
                    CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
                        Date6
                    ELSE
                        Date4
                    END
                END
            ELSE
                CASE WHEN Date3 IS NULL OR Date3 > Date5 THEN
                    CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                        Date6
                    ELSE
                        Date5
                    END
                ELSE
                    CASE WHEN Date3 IS NULL OR Date3 > Date6 THEN
                        Date6
                    ELSE
                        Date3
                    END
                END
            END
        ELSE
            CASE WHEN Date2 IS NULL OR Date2 > Date4 THEN
                CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
                    CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                        Date6
                    ELSE
                        Date5
                    END
                ELSE
                    CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
                        CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                            Date6
                        ELSE
                            Date5
                        END
                    ELSE
                        CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
                            Date6
                        ELSE
                            Date4
                        END
                    END
                END
            ELSE
                CASE WHEN Date2 IS NULL OR Date2 > Date5 THEN
                    CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                        Date6
                    ELSE
                        Date5
                    END
                ELSE
                    CASE WHEN Date2 IS NULL OR Date2 > Date6 THEN
                        Date6
                    ELSE
                        Date2
                    END
                END
            END
        END
ELSE
    CASE WHEN Date1 IS NULL OR Date1 > Date3 THEN
        CASE WHEN Date3 IS NULL OR Date3 > Date4 THEN
            CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
                CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                    Date6
                ELSE
                    Date5
                END
            ELSE
                CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
                    Date6
                ELSE
                    Date4
                END
            END
        ELSE
            CASE WHEN Date3 IS NULL OR Date3 > Date5 THEN
                CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                    Date6
                ELSE
                    Date5
                END
            ELSE
                CASE WHEN Date3 IS NULL OR Date3 > Date6 THEN
                    Date6
                ELSE
                    Date3
                END
            END
        END
    ELSE
        CASE WHEN Date1 IS NULL OR Date1 > Date4 THEN
            CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
                CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                    Date6
                ELSE
                    Date5
                END
            ELSE
                CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
                    Date6
                ELSE
                    Date4
                END
            END
        ELSE
            CASE WHEN Date1 IS NULL OR Date1 > Date5 THEN
                CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
                    Date6
                ELSE
                    Date5
                END
            ELSE
                CASE WHEN Date1 IS NULL OR Date1 > Date6 THEN
                    Date6
                ELSE
                    Date1
                END
            END
        END
    END
END

이 페이지를 방문하여 단순히 날짜를 비교하려고했지만 성능이나 호환성에 관심이없는 경우 하위 선택이 허용되는 모든 위치에서 사용할 수있는 테이블 값 생성자를 사용할 수 있습니다 (SQL Server 2008 이상).

Lowest =    
(
    SELECT MIN(TVC.d) 
    FROM 
    (
        VALUES
            (Date1), 
            (Date2), 
            (Date3), 
            (Date4), 
            (Date5), 
            (Date6)
    ) 
    AS TVC(d)
)

1

귀하의 case진술은 비효율적입니다. 최악의 경우 5 번, 최상의 경우 2 번을 비교하고 있습니다. 반면에 최소값을 찾는 것은 n최대 n-1비교 해야합니다 .

각 행에 대해 평균적으로 2 대신 3.5를 비교하고 있습니다. 따라서 더 많은 CPU 시간이 걸리고 느립니다. 아래 case설명을 사용하여 테스트를 다시 시도하십시오 . 행당 2 개의 비교 만 사용하며 unpivot및 보다 효율적이어야 union all합니다.

Select Id, 
       Case 
           When Col1 <= Col2 then case when Col1 <= Col3 Then Col1  else col3 end
            When  Col2 <= Col3 Then Col2  
            Else Col3 
            End As TheMin 
From   YourTableNameHere

union all당신이 행 당하지만 전체 테이블이 아닌 최소값을 받고 같은 방법은 귀하의 경우 잘못된 것입니다. 또한 동일한 표를 3 번 ​​스캔 할 때 효율적이지 않습니다. 테이블이 작 으면 I / O는 큰 차이를 만들지 않지만 큰 테이블의 경우 큰 차이가 없습니다. 그 방법을 사용하지 마십시오.

Unpivot와 함께 테이블을 교차 조인하여 수동 피벗 해제를 시도해보십시오 (select 1 union all select 2 union all select 3). 만큼 효율적이어야합니다 unpivot.

공간 문제가없는 경우 가장 좋은 솔루션은 계산 된 지속 열을 갖는 것입니다. 행 크기에 4 바이트를 더하고 ( int유형 이 있다고 가정 ) 결과적으로 테이블 크기가 증가합니다.

그러나 시스템에 공간과 메모리가 문제가되고 CPU가 유지되지 않고 case 문을 사용하여 간단한 계산 열을 사용하십시오. 코드가 단순 해집니다.


-1

첫 번째 옵션이 가장 빠르다고 생각합니다 (프로그래밍 관점에서 볼 때 매끄럽지 않은 것처럼 보이지만!). 이는 정확히 N 개의 행 (N은 테이블 크기)을 처리하며 방법 2 또는 3과 같이 검색하거나 정렬 할 필요가 없기 때문입니다.

큰 샘플을 사용한 테스트는 그 점을 증명해야합니다.

고려해야 할 또 다른 옵션은 (더 많은 것이 필요한 것처럼) 테이블 위에 구체화 된 뷰 를 만드는 것 입니다. 테이블 크기가 수십만 이상인 경우 이런 식으로 행이 변경되는 동안 최소값이 계산되며 전체 테이블을 모든 쿼리에서 처리 할 필요는 없습니다. SQL Server에서 구체화 된 뷰를 인덱싱 된 뷰 라고합니다.


-1
Create table #temp
   (
    id int identity(1,1),
    Name varchar(30),
    Year1 int,
    Year2 int,
    Year3 int,
    Year4 int
   )

   Insert into #temp values ('A' ,2015,2016,2014,2010)
   Insert into #temp values ('B' ,2016,2013,2017,2018)
   Insert into #temp values ('C' ,2010,2016,2014,2017)
   Insert into #temp values ('D' ,2017,2016,2014,2015)
   Insert into #temp values ('E' ,2016,2016,2016,2016)
   Insert into #temp values ('F' ,2016,2017,2018,2019)
   Insert into #temp values ('G' ,2016,2017,2020,2019)

   Select *, Case 
                 when Year1 >= Year2 and Year1 >= Year3 and Year1 >= Year4 then Year1
                 when Year2 >= Year3 and Year2 >= Year4 and Year2 >= Year1 then Year2
                 when Year3 >= Year4 and Year3 >= Year1 and Year3 >= Year2 then Year3
                 when Year4 >= Year1 and Year4 >= Year2 and Year4 >= Year3 then Year4  
                 else Year1 end as maxscore  
                 from #temp

CASE 표현식을 비교적 단순하게 만드는 NULL을 고려하지 않습니다. 그러나 열 중 하나 이상이 실제로 NULL이면 솔루션이 Year1결과로 반환 되므로 반드시 정확하지는 않습니다.
Andriy M
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.