SQL Server 2005'te en az çoklu sütunu elde etmenin en etkili yolu nedir?


29

6 sütundan minimum değeri almak istediğim bir durumdayım.

Bunu başarmak için şu ana kadar üç yol buldum, ancak bu yöntemlerin performansıyla ilgili endişelerim var ve hangisinin performans için daha iyi olacağını bilmek istiyorum.

İlk yöntem büyük bir durum ifadesi kullanmaktır . İşte yukarıdaki bağlantıdaki örneğe göre 3 sütunlu bir örnek. Benim durumum çok daha uzun olacaktı çünkü 6 sütuna bakacağım.

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

İkinci seçenek, UNIONoperatörü çoklu seçim ifadeleriyle kullanmaktır . Bunu bir Id parametresini kabul eden bir UDF'ye koyardım.

select Id, dbo.GetMinimumFromMyTable(Id)
from MyTable

ve

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

Ve bulduğum üçüncü seçenek , şimdiye kadar bile var olduğumu bilmediğim UNPIVOT operatörünü kullanmaktı.

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

Tablo boyutu ve bu tablonun sorgulanma ve güncellenme sıklığı nedeniyle, bu sorguların veritabanı üzerindeki etkisi hakkında endişeliyim.

Bu sorgu aslında birkaç milyon kaydın bulunduğu bir masaya katılmak için kullanılacak, ancak geri gönderilen kayıtlar bir seferde yaklaşık yüz kayda indirilecek. Gün boyunca birçok kez çalıştırılır ve sorguladığım 6 sütun sıklıkla güncellenir (günlük istatistikler içerir). Sorguladığım 6 sütunda herhangi bir dizin olduğunu sanmıyorum.

Minimum olarak birden çok sütun almaya çalışırken bu yöntemlerden hangisi performans için daha iyidir? Yoksa bilmediğim daha iyi bir yöntem var mı?

SQL Server 2005 kullanıyorum

Örnek Veri ve Sonuçlar

Verilerim böyle kayıtlar içeriyorsa:

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

Nihai sonuç olmalı

Kimlik Değeri
1 0
2 2
3 1
4 4

Yanıtlar:


22

Her 3 yöntemin performansını test ettim ve işte buldum:

  • 1 kayıt: gözle görülür bir fark yok
  • 10 kayıt: gözle görülür bir fark yok
  • 1.000 kayıt: gözle görülür bir fark yok
  • 10.000 kayıt: UNIONalt sorgu biraz daha yavaştı. CASE WHENSorgu biraz daha hızlı olduğu UNPIVOTbiri.
  • 100.000 kayıtları: UNIONalt sorgu önemli ölçüde daha yavaş olmakla birlikte, UNPIVOTsorgu biraz daha hızlı olur CASE WHENsorgusu
  • 500.000 kayıt: UNIONalt sorgu hala önemli ölçüde yavaş, ancak sorgudan UNPIVOTçok daha hızlı hale geliyorCASE WHEN

Yani sonuç olarak görünüyor

  • Daha küçük kayıt kümeleriyle, önemli bir fark yeterince görünmüyor. Okumak ve bakımı en kolay olanı kullanın.

  • Daha büyük kayıt kümelerine girmeye UNION ALLbaşladığınızda alt sorgu, diğer iki yönteme kıyasla düşük performans göstermeye başlar.

  • CASEDeyim gerçekleştirdiği en iyi (benim durumumda, etrafında 100k satır) belli bir noktaya kadar ve işaret UNPIVOTsorgu en iyi performans gösteren sorgu olur

Bir sorgunun diğerinden daha iyi olduğu gerçek sayı muhtemelen donanım, veritabanı şeması, veriler ve mevcut sunucu yükünün bir sonucu olarak değişecektir, bu nedenle performans konusunda endişeleriniz varsa kendi sisteminizle test ettiğinizden emin olun.

Mikael'in cevabını kullanarak da bazı testler yaptım ; ancak, çoğu kayıt kümesi boyutu için burada denenen diğer tüm 3 yöntemden daha yavaştı. Tek istisna, UNION ALLçok büyük kayıt kümesi boyutları için yapılan bir sorgudan daha iyi olmasıydı . En küçük değere ek olarak sütun adını göstermesi gerçeğini seviyorum.

Ben bir dba değilim, bu yüzden testlerimi optimize etmemiş ve bir şeyi kaçırmamış olabilirim. Asıl canlı verilerle test ediyordum, bu yüzden sonuçları etkilemiş olabilirdi. Her sorguyu birkaç kez çalıştırarak hesap vermeye çalıştım, ama asla bilemezsin. Birisi bunu temiz bir şekilde test etmiş ve sonuçlarını paylaşmışsa kesinlikle ilgilenirim.


6

En hızlı olanı bilmiyorum ama böyle bir şey deneyebilirsiniz.

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

Sonuç:

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

Hangi sütunun minimum değere sahip olmasıyla ilgilenmiyorsanız, bunun yerine bunu kullanabilirsiniz.

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

Basitleştirilmiş bir unpivot sorgusu.

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

CASEGereksinim duyduğunuz mantığı yapmak için bir ifade kullanan kalıcı bir hesaplanmış sütun ekleyin .

Asgari değer, o zaman bu değere dayalı bir birleşme (veya başka bir şey) yapmanız gerektiğinde her zaman etkili bir şekilde kullanılabilir olacaktır.

Kaynak değerlerden herhangi biri ( INSERT/ UPDATE/ MERGE) her değiştiğinde değer yeniden hesaplanır . Bunun illa ki iş yükü için en iyi çözüm olduğunu söylemiyorum , sadece diğer cevaplar gibi sadece bir çözüm olarak öneriyorum . Yalnızca OP hangisinin iş yükü için en uygun olduğunu belirleyebilir.


1

6 tarih için dava bildirimi. Daha azını yapmak için, gerçek şubeyi ilk vaka ifadesinden kopyalayın. En kötü durum, Date1 en düşük değer olduğunda, en iyi durum ise Date6 en düşük değer olduğunda, bu nedenle en olası tarihi Date6'ya yerleştirin. Bunu hesaplanan sütunların sınırlamaları nedeniyle yazdım.

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

Bu sayfaya rastladıysanız, tarihleri ​​karşılaştırmak istiyor ve performans veya uyumluluk hakkında endişelenmiyorsanız, alt seçmelere izin verilen her yerde kullanılabilen bir Tablo Değer Oluşturucu kullanabilirsiniz (SQL Server 2008 ve üstü):

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

1

İfadeniz caseverimli değil. En kötü durumda 5, en iyi durumda ise 2 karşılaştırma yapıyorsunuz; oysa ki minimum bulmayı nen çok n-1kıyaslama yapmalı .

Her satır için ortalama olarak 2 yerine 3.5 karşılaştırma yapıyorsunuz. Bu nedenle daha cpu zamanı alıyor ve yavaştır. Aşağıdaki caseifadeyi kullanarak testlerinizi tekrar deneyin . Bu sadece satır başına 2 karşılaştırmaları kullanıyor ve daha verimli olmalı unpivotve 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 allEğer satır başına değil, tüm tablo için değil minimum değeri alıyorsanız olarak yöntem durumda yanlıştır. Ayrıca, aynı tabloyu 3 kez tarayacağınız için verimli olmaz. Tablo küçük olduğunda, G / Ç çok fazla fark yaratmaz, ancak büyük tablolar için olacaktır. Bu yöntemi kullanmayın.

Unpivottablonun çapraz birleşimini kullanarak elinizle açmayı deneyin ve iyi deneyin (select 1 union all select 2 union all select 3). Bu kadar verimli olmalı unpivot.

Alan sorunlarınız yoksa en iyi çözüm, hesaplanmış bir kalıcı sütuna sahip olmak olacaktır. Satır büyüklüğüne 4 bayt ekleyecektir (sanırım yazacaksınız int), bu da tablonun boyutunu artıracaktır.

Ancak, sisteminizde alan ve bellek sorunu vardır ve CPU daha sonra kalıcı kılmaz, ancak case deyimini kullanarak basit bir hesaplanan sütun kullanın. Kodu daha kolay hale getirecek.


-1

İlk seçeneğin en hızlı olduğunu tahmin ediyorum (programlama açısından çok kaygan görünmese de!). Bunun nedeni tam olarak N satırları ile ilgilenmesidir (burada N, tablo büyüklüğüdür) ve yöntem veya 2 veya 3 gibi bir sıralama yapmak zorunda değildir.

Büyük numuneli bir test bu noktayı kanıtlamalıdır.

Dikkate alınması gereken başka bir seçenek olarak (daha fazlasına ihtiyacınız var!), Tablonuz üzerinde ayrıntılı bir görünüm oluşturmaktır . Masa ölçünüz 100'lerde veya daha fazla ise. Bu şekilde, satır değiştirilirken min değeri hesaplanır ve tüm tablonun her sorguda işlenmesi gerekmez. SQL Server'da, materyalize görünümler Dizine Alınmış Görünümler olarak adlandırılır.


-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'ları hesaba katmıyorsunuz - bu, CASE ifadenizi nispeten basit hale getirir. Ancak, sütunlardan en az birinin gerçekten NULL olması durumunda, çözümünüz Year1mutlaka doğru olmayabilir, sonuç olarak geri döner .
Andriy M
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.