Який найефективніший спосіб отримати мінімум кількох стовпців на 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оператора з декількома операторами вибору . Я ставлю це до UDF, який приймає параметр Id.

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

Через розмір таблиці та частоту запитів та оновлення цієї таблиці, я стурбований впливом на ефективність цих запитів на базу даних.

Цей запит буде фактично використаний при приєднанні до таблиці з кількома мільйонами записів, однак повернені записи будуть зменшені приблизно до ста записів за один раз. Він буде працювати багато разів протягом дня, і 6 стовпців, які я запитую, часто оновлюються (містять щоденну статистику). Я не думаю, що в 6 стовпцях, які я запитую, немає індексів.

Який із цих методів кращий для продуктивності, намагаючись отримати мінімум кількох стовпців? Або є ще один кращий метод, про який я не знаю?

Я використовую SQL Server 2005

Зразки даних та результати

Якщо мої дані містили такі записи:

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

Кінцевий результат повинен бути

Значення Id
1 0
2 2
3 1
4 4

Відповіді:


22

Я перевірив ефективність усіх трьох методів, і ось що я знайшов:

  • 1 запис: Немає помітної різниці
  • 10 записів: різниці відсутні
  • 1000 записів: різниці немає
  • 10 000 записів: UNIONпідзапит був трохи повільніше. CASE WHENЗапит трохи швидше , ніж UNPIVOTодин.
  • 100 000 записів: UNIONпідзапит значно повільніше, але UNPIVOTзапит стає трохи швидшим, ніж CASE WHENзапит
  • 500 000 записів: UNIONпідзапит все ще значно повільніше, але UNPIVOTстає набагато швидшим, ніж CASE WHENзапит

Таким чином, здається, кінцеві результати

  • З меншими наборами записів, схоже, різниці не має значення. Використовуйте все, що найпростіше читати та підтримувати.

  • Як тільки ви почнете отримувати більші набори записів, UNION ALLпідзапит починає погано виконуватись порівняно з іншими двома методами.

  • У CASEзаяві виконує найкраще тільки до певного моменту (в моєму випадку, близько 100к рядків), і цей момент UNPIVOTзапит стає найбільш ефективних запитів

Фактичне число, при якому один запит стає кращим за інший, ймовірно, зміниться внаслідок вашого обладнання, схеми бази даних, даних та поточного завантаження сервера, тому обов'язково протестуйте у власній системі, якщо ви стурбовані продуктивністю.

Я також провів кілька тестів, використовуючи відповідь Мікаеля ; однак, це було повільніше, ніж усі 3 інших методів, що пробувались тут для більшості розмірів запису. Єдиним винятком було те, що це було краще, ніж UNION ALLзапит щодо дуже великих розмірів набору записів. Мені подобається те, що він показує назву стовпця на додаток до найменшого значення.

Я не дба, тому я, можливо, не оптимізував свої тести і щось пропустив. Я тестував фактичні дані, так що це може вплинути на результати. Я спробував це пояснити, виконавши кожен запит кілька разів, але ви ніколи не знаєте. Мені напевно було б цікаво, якби хтось написав чистий тест на це і поділився своїми результатами.


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

Спрощений запит unpivot.

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). Я не кажу , що це обов'язково є кращим рішенням для робочого навантаження, я просто запропонувати його як в розчині, так само як і інші відповіді. Тільки ОП може визначити, що найкраще для навантаження.


1

Заява справи на 6 дат. Щоб зробити менше, скопіюйте справжню гілку з першого твердження справи. Найгірший випадок, коли 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порівнянь.

Для кожного ряду в середньому ви робите 3,5 порівнянь замість 2. Таким чином, це займає більше процесорного часу і йде повільно. Спробуйте свої тести ще раз, використовуючи наведене нижче 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 рази. Коли таблиця невелика, введення / виведення не зміниться, але для великих таблиць це буде. Не використовуйте цей метод.

Unpivotце добре, і спробуйте вручну зняти програму, скориставшись перехресним приєднанням до таблиці (select 1 union all select 2 union all select 3). Він повинен бути настільки ж ефективним, як і unpivot.

Найкращим рішенням буде мати обчислюваний стійкий стовпець, якщо у вас немає проблем з простором. Він додасть розміру рядка на 4 байти (я думаю, у вас буде intтип), що в свою чергу збільшить розмір таблиці.

Однак, у вашій системі є простір та пам'ять, і процесор тоді не робить його збереженим, а використовуйте прості обчислені стовпці, використовуючи оператор case. Це зробить код простішим.


-1

Я здогадуюсь, що перший варіант найшвидший (хоча з точки зору програмування він виглядає не дуже гладко!). Це пояснюється тим, що в ньому розміщено рівно N рядків (де N - розмір таблиці) і не потрібно здійснювати пошук або сортувати, як метод 2 або 3.

Тест з великим зразком повинен довести суть.

Ще один варіант, який слід врахувати (як ніби вам потрібно більше!), - це створити матеріалізований вигляд над вашим столом. якщо розмір столу становить 100 тисяч тисяч або більше. Таким чином, мінімальне значення обчислюється під час зміни рядка, і вся таблиця не повинна буде оброблятися з кожним запитом. У 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

Ви не враховуєте NULL - це робить ваш вираз CASE відносно простим. Однак якщо хоча б один із стовпців дійсно NULL, ваше рішення повернеться Year1як результат, що може не обов’язково бути правильним.
Андрій М
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.