Database
 sql >> Teknologi Basis Data >  >> RDS >> Database

Nomor baris dengan urutan nondeterministik

Fungsi jendela ROW_NUMBER memiliki banyak aplikasi praktis, lebih dari sekadar kebutuhan peringkat yang jelas. Sebagian besar waktu, ketika Anda menghitung nomor baris, Anda perlu menghitungnya berdasarkan beberapa urutan, dan Anda memberikan spesifikasi pemesanan yang diinginkan dalam klausa urutan jendela fungsi. Namun, ada kasus di mana Anda perlu menghitung nomor baris tanpa urutan tertentu; dengan kata lain, berdasarkan tatanan nondeterministik. Ini bisa di seluruh hasil kueri, atau di dalam partisi. Contohnya termasuk menetapkan nilai unik ke baris hasil, menghapus duplikasi data, dan mengembalikan baris apa pun per grup.

Perhatikan bahwa perlu menetapkan nomor baris berdasarkan urutan nondeterministik berbeda dengan perlu menetapkannya berdasarkan urutan acak. Dengan yang pertama, Anda tidak peduli dalam urutan apa mereka ditugaskan, dan apakah eksekusi berulang dari kueri tetap menetapkan nomor baris yang sama ke baris yang sama atau tidak. Dengan yang terakhir, Anda mengharapkan eksekusi berulang untuk terus mengubah baris mana yang ditugaskan dengan nomor baris mana. Artikel ini membahas teknik yang berbeda untuk menghitung nomor baris dengan urutan nondeterministik. Harapannya adalah menemukan teknik yang handal dan optimal.

Terima kasih khusus kepada Paul White atas tipnya mengenai pelipatan konstan, untuk teknik konstan runtime, dan untuk selalu menjadi sumber informasi yang hebat!

Saat pesanan penting

Saya akan mulai dengan kasus di mana urutan nomor baris penting.

Saya akan menggunakan tabel bernama T1 dalam contoh saya. Gunakan kode berikut untuk membuat tabel ini dan mengisinya dengan contoh data:

SET NOCOUNT ON;
 
USE tempdb;
 
DROP TABLE IF EXISTS dbo.T1;
GO
 
CREATE TABLE dbo.T1
(
  id INT NOT NULL CONSTRAINT PK_T1 PRIMARY KEY,
  grp VARCHAR(10) NOT NULL,
  datacol INT NOT NULL
);
 
INSERT INTO dbo.T1(id, grp, datacol) VALUES
  (11, 'A', 50),
  ( 3, 'B', 20),
  ( 5, 'A', 40),
  ( 7, 'B', 10),
  ( 2, 'A', 50);

Pertimbangkan kueri berikut (kami akan menyebutnya Kueri 1):

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(PARTITION BY grp ORDER BY datacol) AS n 
FROM dbo.T1;

Di sini Anda ingin nomor baris ditetapkan dalam setiap grup yang diidentifikasi oleh grp kolom, diurutkan oleh kolom datacol. Ketika saya menjalankan kueri ini di sistem saya, saya mendapatkan output berikut:

id  grp  datacol  n
--- ---- -------- ---
5   A    40       1
2   A    50       2
11  A    50       3
7   B    10       1
3   B    20       2

Nomor baris ditugaskan di sini dalam urutan sebagian deterministik dan sebagian nondeterministik. Yang saya maksud dengan ini adalah bahwa Anda memiliki jaminan bahwa dalam partisi yang sama, baris dengan nilai datacol yang lebih besar akan mendapatkan nilai nomor baris yang lebih besar. Namun, karena datacol tidak unik dalam partisi grp, urutan penetapan nomor baris di antara baris dengan nilai grp dan datacol yang sama adalah nondeterministik. Demikian halnya dengan baris dengan nilai id 2 dan 11. Keduanya memiliki nilai grp A dan nilai datacol 50. Ketika saya menjalankan kueri ini pada sistem saya untuk pertama kalinya, baris dengan id 2 mendapat nomor baris 2 dan baris dengan id 11 mendapat nomor baris 3. Jangankan kemungkinan hal ini terjadi dalam praktik di SQL Server; jika saya menjalankan kueri lagi, secara teoritis, baris dengan id 2 dapat ditetapkan dengan baris nomor 3 dan baris dengan id 11 dapat diberikan dengan nomor baris 2.

Jika Anda perlu menetapkan nomor baris berdasarkan urutan yang sepenuhnya deterministik, menjamin hasil yang dapat diulang di seluruh eksekusi kueri selama data yang mendasarinya tidak berubah, Anda memerlukan kombinasi elemen dalam partisi jendela dan klausa pemesanan menjadi unik. Ini dapat dicapai dalam kasus kami dengan menambahkan id kolom ke klausa urutan jendela sebagai pemutus. Klausa OVER akan menjadi:

OVER (PARTITION BY grp ORDER BY datacol, id)

Bagaimanapun, ketika menghitung nomor baris berdasarkan beberapa spesifikasi pemesanan yang berarti seperti di Query 1, SQL Server perlu memproses baris yang dipesan oleh kombinasi partisi jendela dan elemen pemesanan. Ini dapat dicapai dengan menarik data yang telah dipesan sebelumnya dari indeks, atau dengan menyortir data. Saat ini tidak ada indeks di T1 untuk mendukung perhitungan ROW_NUMBER di Query 1, jadi SQL Server harus memilih untuk menyortir data. Hal ini dapat dilihat pada rencana untuk Query 1 yang ditunjukkan pada Gambar 1.

Gambar 1:Rencanakan Kueri 1 tanpa indeks pendukung

Perhatikan bahwa paket memindai data dari indeks berkerumun dengan properti Dipesan:Salah. Ini berarti bahwa pemindaian tidak perlu mengembalikan baris yang diurutkan oleh kunci indeks. Itulah yang terjadi karena indeks berkerumun digunakan di sini hanya karena kebetulan mencakup kueri dan bukan karena urutan kuncinya. Rencana tersebut kemudian menerapkan penyortiran, menghasilkan biaya tambahan, penskalaan N Log N, dan waktu respons yang tertunda. Operator Segmen menghasilkan tanda yang menunjukkan apakah baris tersebut adalah yang pertama dalam partisi atau tidak. Terakhir, operator Proyek Urutan memberikan nomor baris yang dimulai dengan 1 di setiap partisi.

Jika Anda ingin menghindari perlunya penyortiran, Anda dapat menyiapkan indeks penutup dengan daftar kunci yang didasarkan pada elemen partisi dan pengurutan, dan daftar penyertaan yang didasarkan pada elemen penutup. Saya suka menganggap indeks ini sebagai indeks POC (untuk mempartisi , memesan dan menutupi ). Berikut definisi POC yang mendukung kueri kami:

CREATE INDEX idx_grp_data_i_id ON dbo.T1(grp, datacol) INCLUDE(id);

Jalankan Kueri 1 lagi:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(PARTITION BY grp ORDER BY datacol) AS n 
FROM dbo.T1;

Rencana eksekusi ini ditunjukkan pada Gambar 2.

Gambar 2:Rencanakan Kueri 1 dengan indeks POC

Perhatikan bahwa kali ini rencana memindai indeks POC dengan properti Dipesan:Benar. Ini berarti bahwa pemindaian menjamin bahwa baris akan dikembalikan dalam urutan kunci indeks. Karena data ditarik sebelumnya dari indeks seperti yang dibutuhkan fungsi jendela, tidak perlu penyortiran eksplisit. Skala rencana ini linier dan waktu responsnya bagus.

Saat pesanan tidak masalah

Hal-hal menjadi sedikit rumit ketika Anda perlu menetapkan nomor baris dengan urutan yang sepenuhnya nondeterministik. Hal wajar yang ingin dilakukan dalam kasus seperti itu adalah menggunakan fungsi ROW_NUMBER tanpa menentukan klausa urutan jendela. Pertama, mari kita periksa apakah standar SQL mengizinkan ini. Inilah bagian relevan dari standar yang mendefinisikan aturan sintaks untuk fungsi jendela:

Aturan Sintaks

5) Biarkan WNS menjadi . Biarkan WDX menjadi deskriptor struktur jendela yang menjelaskan jendela yang ditentukan oleh WNS.

6) Jika , , atau ROW_NUMBER ditentukan, maka:

a) Jika , , RANK atau DENSE_RANK ditentukan, maka klausa pemesanan jendela WOC dari WDX harus ada.

f) ROW_NUMBER() OVER WNS setara dengan :COUNT (*) OVER (WNS1 ROWS UNBOUNDED PRECEDING)

Perhatikan bahwa item 6 mencantumkan fungsi , , atau ROW_NUMBER, dan kemudian item 6a mengatakan bahwa untuk fungsi , , RANK atau DENSE_RANK klausa urutan jendela harus ada. Tidak ada bahasa eksplisit yang menyatakan apakah ROW_NUMBER memerlukan klausa urutan jendela atau tidak, tetapi penyebutan fungsi pada item 6 dan penghilangannya dalam 6a dapat menyiratkan bahwa klausa tersebut opsional untuk fungsi ini. Cukup jelas mengapa fungsi seperti RANK dan DENSE_RANK memerlukan klausa urutan jendela, karena fungsi-fungsi ini berspesialisasi dalam menangani ikatan, dan ikatan hanya ada ketika ada spesifikasi pemesanan. Namun, Anda pasti dapat melihat bagaimana fungsi ROW_NUMBER dapat mengambil manfaat dari klausa urutan jendela opsional.

Jadi, mari kita coba, dan coba hitung nomor baris tanpa urutan jendela di SQL Server:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER() AS n 
FROM dbo.T1;

Upaya ini menghasilkan kesalahan berikut:

Msg 4112, Level 15, State 1, Line 53
Fungsi 'ROW_NUMBER' harus memiliki klausa OVER dengan ORDER BY.

Memang, jika Anda memeriksa dokumentasi SQL Server tentang fungsi ROW_NUMBER, Anda akan menemukan teks berikut:

“pesan_dengan_klausa

Klausa ORDER BY menentukan urutan di mana baris diberi ROW_NUMBER unik dalam partisi yang ditentukan. Itu wajib.”

Jadi ternyata klausa urutan jendela wajib untuk fungsi ROW_NUMBER di SQL Server. Omong-omong, itu juga kasus di Oracle.

Saya harus mengatakan bahwa saya tidak yakin saya memahami alasan di balik persyaratan ini. Ingat bahwa Anda mengizinkan untuk menentukan nomor baris berdasarkan urutan nondeterministik sebagian, seperti di Kueri 1. Jadi mengapa tidak mengizinkan nondeterminisme sepenuhnya? Mungkin ada beberapa alasan yang tidak saya pikirkan. Jika Anda dapat memikirkan alasan seperti itu, silakan bagikan.

Bagaimanapun, Anda dapat berargumen bahwa jika Anda tidak peduli dengan pesanan, mengingat klausa pesanan jendela adalah wajib, Anda dapat menentukan pesanan apa pun. Masalah dengan pendekatan ini adalah jika Anda memesan berdasarkan beberapa kolom dari tabel yang ditanyakan, ini bisa melibatkan penalti kinerja yang tidak perlu. Ketika tidak ada indeks pendukung, Anda akan membayar untuk penyortiran eksplisit. Saat ada indeks pendukung, Anda membatasi mesin penyimpanan ke strategi pemindaian urutan indeks (mengikuti daftar tertaut indeks). Anda tidak mengizinkannya lebih fleksibel seperti biasanya ketika pesanan tidak masalah dalam memilih antara pemindaian urutan indeks dan pemindaian urutan alokasi (berdasarkan halaman IAM).

Satu ide yang patut dicoba adalah untuk menentukan konstanta, seperti 1, dalam klausa urutan jendela. Jika didukung, Anda berharap pengoptimal cukup pintar untuk menyadari bahwa semua baris memiliki nilai yang sama, jadi tidak ada relevansi pengurutan yang nyata dan oleh karena itu tidak perlu memaksa pengurutan atau pemindaian urutan indeks. Berikut kueri yang mencoba pendekatan ini:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1) AS n 
FROM dbo.T1;

Sayangnya, SQL Server tidak mendukung solusi ini. Ini menghasilkan kesalahan berikut:

Pesan 5308, Level 16, Status 1, Baris 56
Fungsi berjendela, agregat, dan fungsi NEXT VALUE FOR tidak mendukung indeks bilangan bulat sebagai ekspresi klausa ORDER BY.

Rupanya, SQL Server mengasumsikan bahwa jika Anda menggunakan konstanta bilangan bulat di klausa urutan jendela, itu mewakili posisi ordinal elemen dalam daftar SELECT, seperti ketika Anda menentukan bilangan bulat dalam presentasi klausa ORDER BY. Jika itu masalahnya, opsi lain yang patut dicoba adalah menentukan konstanta noninteger, seperti:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 'No Order') AS n 
FROM dbo.T1;

Ternyata solusi ini juga tidak didukung. SQL Server menghasilkan kesalahan berikut:

Pesan 5309, Level 16, Status 1, Baris 65
Fungsi berjendela, agregat, dan fungsi NEXT VALUE FOR tidak mendukung konstanta sebagai ekspresi klausa ORDER BY.

Rupanya, klausa urutan jendela tidak mendukung jenis konstanta apa pun.

Sejauh ini kita telah mempelajari hal berikut tentang relevansi pengurutan jendela fungsi ROW_NUMBER di SQL Server:

  1. ORDER BY diperlukan.
  2. Tidak dapat memesan dengan konstanta bilangan bulat karena SQL Server mengira Anda mencoba menentukan posisi ordinal di SELECT.
  3. Tidak dapat mengurutkan menurut jenis konstanta apa pun.

Kesimpulannya adalah Anda seharusnya mengurutkan dengan ekspresi yang bukan konstanta. Jelas, Anda dapat memesan dengan daftar kolom dari tabel yang ditanyakan. Namun kami sedang mencari solusi yang efisien di mana pengoptimal dapat menyadari bahwa tidak ada relevansi pemesanan.

Pelipatan konstan

Kesimpulannya sejauh ini adalah Anda tidak dapat menggunakan konstanta dalam klausa urutan jendela ROW_NUMBER, tetapi bagaimana dengan ekspresi berdasarkan konstanta, seperti dalam kueri berikut:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1+0) AS n 
FROM dbo.T1;

Namun, upaya ini menjadi korban proses yang dikenal sebagai pelipatan konstan, yang biasanya memiliki dampak kinerja positif pada kueri. Ide di balik teknik ini adalah untuk meningkatkan kinerja kueri dengan melipat beberapa ekspresi berdasarkan konstanta ke konstanta hasil mereka pada tahap awal pemrosesan kueri. Anda dapat menemukan detail tentang jenis ekspresi apa yang dapat dilipat secara konstan di sini. Ekspresi kami 1+0 dilipat menjadi 1, menghasilkan kesalahan yang sama seperti yang Anda dapatkan saat menentukan konstanta 1 secara langsung:

Pesan 5308, Level 16, Status 1, Baris 79
Fungsi berjendela, agregat, dan fungsi NEXT VALUE FOR tidak mendukung indeks bilangan bulat sebagai ekspresi klausa ORDER BY.

Anda akan menghadapi situasi serupa ketika mencoba menggabungkan dua literal string karakter, seperti:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 'No' + ' Order') AS n 
FROM dbo.T1;

Anda mendapatkan kesalahan yang sama yang Anda dapatkan saat menentukan literal 'No Order' secara langsung:

Pesan 5309, Level 16, Status 1, Baris 55
Fungsi berjendela, agregat, dan fungsi NEXT VALUE FOR tidak mendukung konstanta sebagai ekspresi klausa ORDER BY.

Dunia yang aneh – kesalahan yang mencegah kesalahan

Hidup ini penuh kejutan…

Satu hal yang mencegah pelipatan konstan adalah ketika ekspresi biasanya menghasilkan kesalahan. Misalnya, ekspresi 2147483646+1 dapat dilipat secara konstan karena menghasilkan nilai tipe INT yang valid. Akibatnya, upaya untuk menjalankan kueri berikut gagal:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 2147483646+1) AS n 
FROM dbo.T1;
Pesan 5308, Level 16, Status 1, Baris 109
Fungsi berjendela, agregat, dan fungsi NEXT VALUE FOR tidak mendukung indeks bilangan bulat sebagai ekspresi klausa ORDER BY.

Namun, ekspresi 2147483647+1 tidak dapat dilipat secara konstan karena upaya seperti itu akan menghasilkan kesalahan INT-overflow. Implikasinya pada pemesanan cukup menarik. Coba kueri berikut (kami akan menyebutnya Kueri 2):

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 2147483647+1) AS n 
FROM dbo.T1;

Anehnya, kueri ini berhasil dijalankan! Apa yang terjadi adalah bahwa di satu sisi, SQL Server gagal menerapkan pelipatan konstan, dan oleh karena itu pengurutan didasarkan pada ekspresi yang bukan konstanta tunggal. Di sisi lain, pengoptimal memperkirakan bahwa nilai pengurutan sama untuk semua baris, sehingga mengabaikan ekspresi pengurutan sama sekali. Ini dikonfirmasi ketika memeriksa rencana untuk kueri ini seperti yang ditunjukkan pada Gambar 3.

Gambar 3:Rencana untuk Kueri 2

Perhatikan bahwa paket memindai beberapa indeks penutup dengan properti Dipesan:Salah. Ini persis sasaran kinerja kami.

Dengan cara yang sama, kueri berikut melibatkan upaya pelipatan konstan yang berhasil, dan karena itu gagal:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1/1) AS n 
FROM dbo.T1;
Pesan 5308, Level 16, Status 1, Baris 123
Fungsi berjendela, agregat, dan fungsi NEXT VALUE FOR tidak mendukung indeks bilangan bulat sebagai ekspresi klausa ORDER BY.

Kueri berikut melibatkan upaya pelipatan konstan yang gagal, dan oleh karena itu berhasil, menghasilkan rencana yang ditunjukkan sebelumnya pada Gambar 3:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1/0) AS n 
FROM dbo.T1;

Kueri berikut melibatkan upaya pelipatan konstan yang berhasil (VARCHAR literal '1' secara implisit dikonversi ke INT 1, dan kemudian 1 + 1 dilipat menjadi 2), dan karena itu gagal:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1+'1') AS n 
FROM dbo.T1;
Pesan 5308, Level 16, Status 1, Baris 134
Fungsi berjendela, agregat, dan fungsi NEXT VALUE FOR tidak mendukung indeks bilangan bulat sebagai ekspresi klausa ORDER BY.

Kueri berikut melibatkan upaya pelipatan konstan yang gagal (tidak dapat mengonversi 'A' ke INT), dan oleh karena itu berhasil, menghasilkan rencana yang ditunjukkan sebelumnya pada Gambar 3:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1+'A') AS n 
FROM dbo.T1;

Sejujurnya, meskipun teknik bizarro ini mencapai tujuan performa awal kami, saya tidak dapat mengatakan bahwa saya menganggapnya aman dan oleh karena itu saya tidak begitu nyaman untuk mengandalkannya.

Konstanta runtime berdasarkan fungsi

Melanjutkan pencarian solusi yang baik untuk menghitung nomor baris dengan urutan nondeterministik, ada beberapa teknik yang tampaknya lebih aman daripada solusi unik terakhir:menggunakan konstanta runtime berdasarkan fungsi, menggunakan subquery berdasarkan konstanta, menggunakan kolom alias berdasarkan konstanta dan menggunakan variabel.

Seperti yang saya jelaskan di T-SQL bug, jebakan, dan praktik terbaik – determinisme, sebagian besar fungsi dalam T-SQL dievaluasi hanya sekali per referensi dalam kueri—tidak sekali per baris. Ini adalah kasus bahkan dengan sebagian besar fungsi nondeterministik seperti GETDATE dan RAND. Ada sedikit pengecualian untuk aturan ini, seperti fungsi NEWID dan CRYPT_GEN_RANDOM, yang dievaluasi sekali per baris. Sebagian besar fungsi, seperti GETDATE, @@SPID, dan banyak lainnya, dievaluasi sekali di awal kueri, dan nilainya kemudian dianggap sebagai konstanta waktu proses. Referensi untuk fungsi-fungsi seperti itu tidak selalu terlipat. Karakteristik ini membuat konstanta runtime yang didasarkan pada fungsi pilihan yang baik sebagai elemen pengurutan jendela, dan memang, tampaknya T-SQL mendukungnya. Pada saat yang sama, pengoptimal menyadari bahwa dalam praktiknya tidak ada relevansi urutan, menghindari penalti kinerja yang tidak perlu.

Berikut ini contoh menggunakan fungsi GETDATE:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY GETDATE()) AS n 
FROM dbo.T1;

Kueri ini mendapatkan paket yang sama seperti yang ditunjukkan sebelumnya pada Gambar 3.

Berikut contoh lain menggunakan fungsi @@SPID (mengembalikan ID sesi saat ini):

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY @@SPID) AS n 
FROM dbo.T1;

Bagaimana dengan fungsi PI? Coba kueri berikut:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY PI()) AS n 
FROM dbo.T1;

Yang ini gagal dengan kesalahan berikut:

Pesan 5309, Level 16, Status 1, Baris 153
Fungsi berjendela, agregat, dan fungsi NEXT VALUE FOR tidak mendukung konstanta sebagai ekspresi klausa ORDER BY.

Fungsi seperti GETDATE dan @@SPID dievaluasi ulang sekali per eksekusi rencana, sehingga tidak bisa terus-menerus dilipat. PI selalu mewakili konstanta yang sama, dan karenanya selalu dilipat.

Seperti disebutkan sebelumnya, ada sangat sedikit fungsi yang dievaluasi sekali per baris, seperti NEWID dan CRYPT_GEN_RANDOM. Ini menjadikannya pilihan yang buruk sebagai elemen pengurutan jendela jika Anda memerlukan urutan nondeterministik—jangan bingung dengan urutan acak. Mengapa membayar hukuman sortir yang tidak perlu?

Berikut ini contoh penggunaan fungsi NEWID:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY NEWID()) AS n 
FROM dbo.T1;

Rencana untuk kueri ini ditunjukkan pada Gambar 4, mengonfirmasi bahwa SQL Server menambahkan pengurutan eksplisit berdasarkan hasil fungsi.

Gambar 4:Rencana untuk Kueri 3

Jika Anda ingin nomor baris ditetapkan dalam urutan acak, tentu saja, itulah teknik yang ingin Anda gunakan. Anda hanya perlu menyadari bahwa itu menimbulkan biaya sortir.

Menggunakan subkueri

Anda juga dapat menggunakan subkueri berdasarkan konstanta sebagai ekspresi pengurutan jendela (mis., ORDER BY (SELECT 'No Order')). Juga dengan solusi ini, pengoptimal SQL Server mengenali bahwa tidak ada relevansi pemesanan, dan oleh karena itu tidak memaksakan pengurutan yang tidak perlu atau membatasi pilihan mesin penyimpanan ke yang harus menjamin pesanan. Coba jalankan kueri berikut sebagai contoh:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY (SELECT 'No Order')) AS n 
FROM dbo.T1;

Anda mendapatkan paket yang sama seperti yang ditunjukkan sebelumnya pada Gambar 3.

Salah satu manfaat besar dari teknik ini adalah Anda dapat menambahkan sentuhan pribadi Anda sendiri. Mungkin Anda sangat menyukai NULL:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS n 
FROM dbo.T1;

Mungkin Anda sangat menyukai nomor tertentu:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY (SELECT 42)) AS n 
FROM dbo.T1;

Mungkin Anda ingin mengirim pesan kepada seseorang:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY (SELECT 'Lilach, will you marry me?')) AS n 
FROM dbo.T1;

Anda mengerti maksudnya.

Bisa dilakukan, tapi canggung

Ada beberapa teknik yang berhasil, tetapi agak canggung. Salah satunya adalah mendefinisikan alias kolom untuk ekspresi berdasarkan konstanta, dan kemudian menggunakan alias kolom itu sebagai elemen pengurutan jendela. Anda dapat melakukan ini baik menggunakan ekspresi tabel atau dengan operator CROSS APPLY dan konstruktor nilai tabel. Berikut ini contoh untuk yang terakhir:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY [I'm a bit ugly]) AS n 
FROM dbo.T1 CROSS APPLY ( VALUES('No Order') ) AS A([I'm a bit ugly]);

Anda mendapatkan paket yang sama seperti yang ditunjukkan sebelumnya pada Gambar 3.

Opsi lainnya adalah menggunakan variabel sebagai elemen pengurutan jendela:

DECLARE @ImABitUglyToo AS INT = NULL;
 
SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY @ImABitUglyToo) AS n 
FROM dbo.T1;

Kueri ini juga mendapatkan rencana yang ditunjukkan sebelumnya pada Gambar 3.

Bagaimana jika saya menggunakan UDF saya sendiri?

Anda mungkin berpikir bahwa menggunakan UDF Anda sendiri yang mengembalikan konstanta bisa menjadi pilihan yang baik sebagai elemen pengurutan jendela saat Anda menginginkan urutan nondeterministik, tetapi sebenarnya tidak. Pertimbangkan definisi UDF berikut sebagai contoh:

DROP FUNCTION IF EXISTS dbo.YouWillRegretThis;
GO
 
CREATE FUNCTION dbo.YouWillRegretThis() RETURNS INT
AS
BEGIN
  RETURN NULL
END;
GO

Coba gunakan UDF sebagai klausa pengurutan jendela, seperti ini (kami akan menyebutnya Kueri 4):

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY dbo.YouWillRegretThis()) AS n 
FROM dbo.T1;

Sebelum SQL Server 2019 (atau tingkat kompatibilitas paralel <150), fungsi yang ditentukan pengguna dievaluasi per baris. Bahkan jika mereka mengembalikan konstanta, mereka tidak dimasukkan. Akibatnya, di satu sisi Anda dapat menggunakan UDF seperti itu sebagai elemen pengurutan jendela, tetapi di sisi lain ini menghasilkan penalti pengurutan. Ini dikonfirmasi dengan memeriksa rencana untuk kueri ini, seperti yang ditunjukkan pada Gambar 5.

Gambar 5:Rencana untuk Kueri 4

Dimulai dengan SQL Server 2019, di bawah tingkat kompatibilitas>=150, fungsi yang ditentukan pengguna seperti itu menjadi sebaris, yang sebagian besar merupakan hal yang hebat, tetapi dalam kasus kami menghasilkan kesalahan:

Pesan 5309, Level 16, Status 1, Baris 217
Fungsi berjendela, agregat, dan fungsi NEXT VALUE FOR tidak mendukung konstanta sebagai ekspresi klausa ORDER BY.

Jadi menggunakan UDF berdasarkan konstanta sebagai elemen pengurutan jendela memaksa pengurutan atau kesalahan tergantung pada versi SQL Server yang Anda gunakan dan tingkat kompatibilitas basis data Anda. Singkatnya, jangan lakukan ini.

Nomor baris yang dipartisi dengan urutan nondeterministik

Kasus penggunaan umum untuk nomor baris yang dipartisi berdasarkan urutan nondeterministik adalah mengembalikan baris apa pun per grup. Mengingat bahwa menurut definisi elemen partisi ada dalam skenario ini, Anda akan berpikir bahwa teknik yang aman dalam kasus seperti itu akan menggunakan elemen partisi jendela juga sebagai elemen pemesanan jendela. Sebagai langkah pertama Anda menghitung nomor baris seperti ini:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(PARTITION BY grp ORDER BY grp) AS n 
FROM dbo.T1;

Rencana untuk kueri ini ditunjukkan pada Gambar 6.

Gambar 6:Rencana untuk Kueri 5

Alasan indeks pendukung kami dipindai dengan properti Dipesan:Benar adalah karena SQL Server memang perlu memproses setiap baris partisi sebagai satu unit. Itulah yang terjadi sebelum penyaringan. Jika Anda memfilter hanya satu baris per partisi, Anda memiliki algoritme berbasis pesanan dan berbasis hash sebagai opsi.

Langkah kedua adalah menempatkan kueri dengan perhitungan nomor baris dalam ekspresi tabel, dan di kueri luar memfilter baris dengan nomor baris 1 di setiap partisi, seperti:

WITH C AS
(
  SELECT id, grp, datacol,
    ROW_NUMBER() OVER(PARTITION BY grp ORDER BY grp) AS n 
  FROM dbo.T1
)
SELECT id, grp, datacol
FROM C
WHERE n = 1;

Secara teoritis teknik ini seharusnya aman, tetapi Paul white menemukan bug yang menunjukkan bahwa dengan menggunakan metode ini Anda bisa mendapatkan atribut dari baris sumber yang berbeda di baris hasil yang dikembalikan per partisi. Menggunakan konstanta runtime berdasarkan fungsi atau subquery berdasarkan konstanta karena elemen pengurutan tampaknya aman bahkan dengan skenario ini, jadi pastikan Anda menggunakan solusi seperti berikut ini:

WITH C AS
(
  SELECT id, grp, datacol,
    ROW_NUMBER() OVER(PARTITION BY grp ORDER BY (SELECT 'No Order')) AS n 
  FROM dbo.T1
)
SELECT id, grp, datacol
FROM C
WHERE n = 1;

Tidak ada yang akan melewati jalan ini tanpa izin saya

Mencoba menghitung nomor baris berdasarkan urutan nondeterministik adalah kebutuhan umum. Akan lebih baik jika T-SQL hanya membuat klausa urutan jendela opsional untuk fungsi ROW_NUMBER, tetapi ternyata tidak. Jika tidak, alangkah baiknya jika setidaknya diizinkan menggunakan konstanta sebagai elemen pemesanan, tetapi itu juga bukan opsi yang didukung. Tetapi jika Anda bertanya dengan baik, dalam bentuk subquery berdasarkan konstanta atau konstanta runtime berdasarkan fungsi, SQL Server akan mengizinkannya. Ini adalah dua opsi yang paling saya sukai. Saya tidak merasa nyaman dengan ekspresi keliru yang aneh yang tampaknya berhasil, jadi saya tidak dapat merekomendasikan opsi ini.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Cara Mendapatkan Hari Terakhir Bulan Ini di T-SQL

  2. Pushdown Agregat yang Dikelompokkan

  3. Resensi Buku :Benjamin Nevarez :Penyetelan &Pengoptimalan Kueri

  4. Apa yang sebenarnya terjadi dengan Seek itu?

  5. Menggunakan JShell di Java 9 di NetBeans 9.0, Bagian 4