Apa cara paling efisien untuk mendapatkan minimum beberapa kolom pada SQL Server 2005?

29

Saya berada dalam situasi di mana saya ingin mendapatkan nilai minimum dari 6 kolom.

Sejauh ini saya telah menemukan tiga cara untuk mencapai hal ini, tetapi saya memiliki keprihatinan dengan kinerja metode ini dan ingin tahu mana yang lebih baik untuk kinerja.

Metode pertama adalah dengan menggunakan pernyataan kasus besar . Berikut adalah contoh dengan 3 kolom, berdasarkan contoh di tautan di atas. Pernyataan kasus saya akan jauh lebih lama karena saya akan melihat 6 kolom.

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

Opsi kedua adalah menggunakan UNIONoperator dengan beberapa pernyataan pilih . Saya akan menempatkan ini di UDF yang menerima parameter ID.

select Id, dbo.GetMinimumFromMyTable(Id)
from MyTable

dan

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

Dan opsi ke-3 yang saya temukan adalah menggunakan operator UNPIVOT , yang saya bahkan tidak tahu ada sampai sekarang

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

Karena ukuran tabel, dan frekuensi di mana tabel ini ditanyai dan diperbarui, saya khawatir tentang dampak kinerja kueri ini pada database.

Permintaan ini sebenarnya akan digunakan dalam gabungan ke tabel dengan beberapa juta catatan, namun catatan yang dikembalikan akan dikurangi menjadi sekitar seratus catatan sekaligus. Itu akan dijalankan berkali-kali sepanjang hari, dan 6 kolom yang saya tanyakan sering diperbarui (mengandung statistik harian). Saya tidak berpikir ada indeks pada 6 kolom yang saya tanyakan.

Manakah dari metode ini yang lebih baik untuk kinerja ketika mencoba untuk mendapatkan minimum beberapa kolom? Atau adakah metode lain yang lebih baik yang tidak saya ketahui?

Saya menggunakan SQL Server 2005

Sampel Data & Hasil

Jika data saya berisi catatan seperti ini:

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

Hasil akhirnya seharusnya

Nilai Id
1 0
2 2
3 1
4 4
Rachel
sumber

Jawaban:

22

Saya menguji kinerja ketiga metode, dan inilah yang saya temukan:

  • 1 catatan: Tidak ada perbedaan yang nyata
  • 10 catatan: Tidak ada perbedaan yang nyata
  • 1.000 catatan: Tidak ada perbedaan yang nyata
  • 10.000 catatan: UNIONsubquery sedikit lebih lambat. The CASE WHENpermintaan sedikit lebih cepat dari UNPIVOTsatu.
  • 100.000 catatan: UNIONsubquery secara signifikan lebih lambat, tetapi UNPIVOTkueri menjadi sedikit lebih cepat daripada CASE WHENkueri
  • 500.000 catatan: UNIONsubquery masih jauh lebih lambat, tetapi UNPIVOTmenjadi jauh lebih cepat daripada CASE WHENkueri

Jadi sepertinya hasil akhirnya

  • Dengan set rekaman yang lebih kecil tampaknya tidak ada perbedaan yang cukup berarti. Gunakan apa pun yang termudah untuk dibaca dan dipelihara.

  • Setelah Anda mulai masuk ke set rekaman yang lebih besar, UNION ALLsubquery mulai berkinerja buruk dibandingkan dengan dua metode lainnya.

  • The CASEPernyataan melakukan up terbaik sampai titik tertentu (dalam kasus saya, sekitar 100k baris), dan titik mana UNPIVOTpermintaan menjadi query berkinerja terbaik

Jumlah aktual di mana satu kueri menjadi lebih baik daripada yang lain mungkin akan berubah sebagai hasil dari perangkat keras Anda, skema basis data, data, dan beban server saat ini, jadi pastikan untuk menguji dengan sistem Anda sendiri jika Anda khawatir tentang kinerja.

Saya juga menjalankan beberapa tes menggunakan jawaban Mikael ; Namun, itu lebih lambat dari ketiga metode lain yang dicoba di sini untuk sebagian besar ukuran recordset. Satu-satunya pengecualian adalah itu lebih baik daripada UNION ALLpermintaan untuk ukuran recordset yang sangat besar. Saya suka fakta itu menunjukkan nama kolom di samping nilai terkecil sekalipun.

Saya bukan dba, jadi saya mungkin tidak mengoptimalkan tes dan melewatkan sesuatu. Saya sedang menguji dengan data langsung yang sebenarnya, sehingga mungkin mempengaruhi hasil. Saya mencoba menjelaskannya dengan menjalankan setiap kueri beberapa waktu yang berbeda, tetapi Anda tidak pernah tahu. Saya pasti akan tertarik jika seseorang menulis tes bersih ini dan membagikan hasilnya.

Rachel
sumber
6

Tidak tahu apa yang tercepat tetapi Anda bisa mencoba sesuatu seperti ini.

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

Hasil:

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

Jika Anda tidak tertarik pada kolom mana yang memiliki nilai min, Anda dapat menggunakannya.

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

Permintaan unpivot yang disederhanakan.

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

Tambahkan kolom yang dihitung terus-menerus yang menggunakan CASEpernyataan untuk melakukan logika yang Anda butuhkan.

Nilai minimum akan selalu tersedia secara efisien ketika Anda perlu melakukan join (atau apa pun) berdasarkan nilai itu.

Nilai akan dihitung ulang setiap kali salah satu nilai sumber berubah ( INSERT/ UPDATE/ MERGE). Saya tidak mengatakan ini adalah tentu solusi terbaik untuk beban kerja, saya hanya menawarkan sebagai sebuah solusi, seperti jawaban yang lain. Hanya OP yang dapat menentukan mana yang terbaik untuk beban kerja.

Jon Seigel
sumber
1

Pernyataan kasus untuk 6 tanggal. Untuk berbuat lebih sedikit, salin cabang asli dari pernyataan kasus pertama. Kasus terburuk adalah ketika Date1 adalah nilai terendah, kasus terbaik adalah ketika Date6 adalah nilai terendah, jadi cantumkan tanggal yang paling mungkin di Date6. Saya menulis ini karena keterbatasan kolom yang dihitung.

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

Jika Anda menemukan halaman ini hanya mencari untuk membandingkan tanggal dan tidak begitu peduli tentang kinerja atau kompatibilitas, Anda dapat menggunakan Konstruktor Nilai Tabel, yang dapat digunakan di mana saja subselek diizinkan (SQL Server 2008 dan lebih tinggi):

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

casePernyataan Anda tidak efisien. Anda melakukan 5 perbandingan dalam kasus terburuk, dan 2 dalam kasus terbaik; sedangkan menemukan minimum nharus dilakukan paling banyak n-1perbandingan.

Untuk setiap baris, rata-rata Anda melakukan perbandingan 3,5 bukannya 2. Dengan demikian dibutuhkan lebih banyak waktu CPU dan lambat. Coba tes Anda lagi menggunakan casepernyataan di bawah ini . Itu hanya menggunakan 2 perbandingan per baris dan harus lebih efisien daripada unpivotdan 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

Itu union all Metode yang salah dalam kasus Anda sebagai Anda mendapatkan nilai minimum tidak per baris tapi untuk seluruh meja. Juga, itu tidak akan efisien karena Anda akan memindai tabel yang sama 3 kali. Ketika tabel kecil, I / O tidak akan membuat banyak perbedaan, tetapi untuk tabel besar akan terjadi. Jangan gunakan metode itu.

Unpivotbagus dan cobalah untuk memproteksi manual juga dengan menggunakan cross join table Anda (select 1 union all select 2 union all select 3). Itu harus seefisien unpivot.

Solusi terbaik adalah memiliki kolom yang tetap dikomputasi, jika Anda tidak memiliki masalah ruang. Ini akan menambah ukuran baris dengan 4 byte (saya kira Anda akan memiliki inttipe), yang pada gilirannya akan menambah ukuran tabel.

Namun, ruang dan memori menjadi masalah di sistem Anda dan CPU tidak membuatnya bertahan tetapi menggunakan kolom yang dihitung dengan menggunakan pernyataan kasus. Ini akan membuat kode lebih sederhana.

Gulli Meel
sumber
-1

Saya kira pilihan pertama adalah yang tercepat (walaupun tidak terlihat sangat apik dari perspektif pemrograman!). Ini karena ini berkaitan dengan tepat N baris (di mana N adalah ukuran tabel) dan tidak harus melakukan pencarian atau pengurutan seperti metode 2 atau 3.

Tes dengan sampel besar harus membuktikan maksudnya.

Sebagai pilihan lain untuk dipertimbangkan (seolah-olah Anda membutuhkan lebih banyak!), Adalah untuk membuat tampilan terwujud atas tabel Anda. jika ukuran meja Anda adalah 100-an ribu atau lebih. Dengan cara ini, nilai min dihitung saat baris diubah dan seluruh tabel tidak harus diproses dengan setiap kueri. Dalam SQL Server, pandangan terwujud disebut Tampilan Terindeks

Tidak ada kesempatan
sumber
-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
Ravi
sumber
Anda tidak memperhitungkan NULL - yang membuat ekspresi KASUS Anda relatif sederhana. Namun, jika setidaknya salah satu kolom memang NULL, solusi Anda akan kembali Year1sebagai hasilnya, yang mungkin belum tentu benar.
Andriy M