Saya memiliki tabel yang menyertakan kolom nilai desimal, seperti ini:
id value size
-- ----- ----
1 100 .02
2 99 .38
3 98 .13
4 97 .35
5 96 .15
6 95 .57
7 94 .25
8 93 .15
Apa yang perlu saya capai sedikit sulit untuk dijelaskan, jadi tolong tahan dengan saya. Apa yang saya coba lakukan adalah membuat nilai agregat size
kolom yang bertambah 1 setiap kali baris sebelumnya berjumlah 1, ketika dalam urutan menurun menurut value
. Hasilnya akan terlihat seperti ini:
id value size bucket
-- ----- ---- ------
1 100 .02 1
2 99 .38 1
3 98 .13 1
4 97 .35 1
5 96 .15 2
6 95 .57 2
7 94 .25 2
8 93 .15 3
Upaya naif pertama saya adalah tetap berjalan SUM
dan kemudian CEILING
nilai itu, namun itu tidak menangani kasus di mana beberapa catatan size
akhirnya berkontribusi terhadap total dua ember terpisah. Contoh di bawah ini dapat menjelaskan hal ini:
id value size crude_sum crude_bucket distinct_sum bucket
-- ----- ---- --------- ------------ ------------ ------
1 100 .02 .02 1 .02 1
2 99 .38 .40 1 .40 1
3 98 .13 .53 1 .53 1
4 97 .35 .88 1 .88 1
5 96 .15 1.03 2 .15 2
6 95 .57 1.60 2 .72 2
7 94 .25 1.85 2 .97 2
8 93 .15 2.00 2 .15 3
Seperti yang Anda lihat, jika saya hanya menggunakan CEILING
pada crude_sum
record # 8 akan ditugaskan ke bucket 2. Ini disebabkan oleh size
record # 5 dan # 8 yang dibagi menjadi dua ember. Sebagai gantinya, solusi ideal adalah mengatur ulang jumlah setiap kali mencapai 1, yang kemudian menambah bucket
kolom dan memulai SUM
operasi baru mulai dari size
nilai catatan saat ini. Karena urutan catatan penting untuk operasi ini, saya telah memasukkan value
kolom, yang dimaksudkan untuk diurutkan dalam urutan menurun.
Upaya awal saya telah melibatkan membuat beberapa melewati data, sekali untuk melakukan SUM
operasi, sekali lagi untuk CEILING
itu, dll. Berikut adalah contoh dari apa yang saya lakukan untuk membuat crude_sum
kolom:
SELECT
id,
value,
size,
(SELECT TOP 1 SUM(size) FROM table t2 WHERE t2.value<=t1.value) as crude_sum
FROM
table t1
Yang digunakan dalam UPDATE
operasi untuk memasukkan nilai ke dalam tabel untuk dikerjakan nanti.
Sunting: Saya ingin mengambil langkah lain dalam menjelaskan ini, jadi begini. Bayangkan setiap catatan adalah benda fisik. Item itu memiliki nilai yang terkait dengannya, dan ukuran fisiknya kurang dari satu. Saya memiliki serangkaian ember dengan kapasitas volume tepat 1, dan saya perlu menentukan berapa banyak ember yang akan saya butuhkan dan ember mana yang dimasukkan setiap item sesuai dengan nilai item, diurutkan dari tertinggi ke terendah.
Item fisik tidak dapat ada di dua tempat sekaligus, jadi item tersebut harus berada dalam satu ember atau lainnya. Inilah sebabnya saya tidak dapat menjalankan CEILING
solusi total + berjalan , karena itu akan memungkinkan catatan berkontribusi ukurannya menjadi dua ember.
distinct_count
hal-hal yang rumit. Aaron Bertrand memiliki ringkasan opsi Anda di SQL Server untuk jenis pekerjaan windowing ini. Saya telah menggunakan metode "pembaruan unik" untuk menghitungdistinct_sum
, yang dapat Anda lihat di sini di SQL Fiddle , tetapi ini tidak dapat diandalkan.Jawaban:
Saya tidak yakin jenis kinerja apa yang Anda cari, tetapi jika CLR atau aplikasi eksternal bukan pilihan, hanya kursor yang tersisa. Di laptop saya yang sudah tua, saya dapat melewati 1.000.000 baris dalam waktu sekitar 100 detik menggunakan solusi berikut. Hal yang menyenangkan tentang itu adalah skala secara linear, jadi saya akan melihat sekitar 20 menit untuk menjalankan seluruh hal. Dengan server yang layak Anda akan lebih cepat, tetapi bukan urutan besarnya, sehingga masih akan memakan waktu beberapa menit untuk menyelesaikan ini. Jika ini adalah proses satu kali saja, Anda mungkin dapat memperlambatnya. Jika Anda perlu menjalankan ini sebagai laporan atau serupa secara teratur, Anda mungkin ingin menyimpan nilai dalam tabel yang sama dan tidak memperbaruinya saat baris baru ditambahkan, misalnya dalam pemicu.
Bagaimanapun, ini kodenya:
Ini menjatuhkan dan membuat ulang tabel MyTable, mengisinya dengan 1000000 baris dan kemudian mulai bekerja.
Kursor menyalin setiap baris ke tabel temp saat menjalankan perhitungan. Pada akhirnya pilih mengembalikan hasil yang dihitung. Anda mungkin sedikit lebih cepat jika Anda tidak menyalin data sekitar tetapi lakukan pembaruan di tempat.
Jika Anda memiliki opsi untuk memutakhirkan ke SQL 2012, Anda dapat melihat agregat jendela bergerak yang didukung kumparan jendela baru, yang seharusnya memberi Anda kinerja yang lebih baik.
Sebagai catatan, jika Anda memiliki rakitan yang diinstal dengan izin_set = aman, Anda dapat melakukan lebih banyak hal buruk ke server dengan T-SQL standar daripada dengan rakitan, jadi saya akan terus bekerja untuk menghilangkan penghalang itu - Anda harus menggunakan dengan baik kasus di sini di mana CLR benar-benar akan membantu Anda.
sumber
Tidak ada fungsi windowing baru di SQL Server 2012, windowing kompleks dapat dicapai dengan menggunakan CTE rekursif. Saya bertanya-tanya seberapa baik ini akan bekerja terhadap jutaan baris.
Solusi berikut mencakup semua kasus yang Anda jelaskan. Anda dapat melihatnya beraksi di sini di SQL Fiddle .
Sekarang ambil napas dalam-dalam. Ada dua CTE utama di sini, masing-masing didahului oleh komentar singkat. Sisanya hanyalah "pembersihan" CTE, misalnya, untuk menarik baris yang tepat setelah kami memberi peringkat.
Solusi ini mengasumsikan bahwa itu
id
adalah urutan tanpa celah. Jika tidak, Anda harus membuat urutan gapless Anda sendiri dengan menambahkan CTE tambahan di awal yang memberi nomor baris denganROW_NUMBER()
sesuai dengan urutan yang diinginkan (misalnyaROW_NUMBER() OVER (ORDER BY value DESC)
).Untungnya, ini sangat bertele-tele.
sumber
crude_sum
dengandistinct_sum
danbucket
kolom terkait untuk melihat apa yang saya maksud.Ini terasa seperti solusi konyol, dan mungkin tidak akan menskala dengan baik, jadi uji dengan hati-hati jika Anda menggunakannya. Karena masalah utama berasal dari "ruang" yang tersisa di ember, pertama-tama saya harus membuat catatan pengisi untuk menyatukan ke dalam data.
http://sqlfiddle.com/#!3/72ad4/14/0
sumber
Berikut ini adalah solusi CTE rekursif lain, meskipun saya akan mengatakan itu lebih mudah daripada saran @ Nick . Ini sebenarnya lebih dekat dengan kursor @ Sebastian , hanya saya yang menggunakan menjalankan perbedaan daripada menjalankan total. (Awalnya saya bahkan berpikir bahwa jawaban Nick akan sesuai dengan apa yang saya sarankan di sini, dan setelah mengetahui bahwa jawabannya sebenarnya adalah pertanyaan yang sangat berbeda yang saya putuskan untuk menawarkan milik saya.)
Catatan: kueri ini mengasumsikan bahwa
value
kolom terdiri dari nilai unik tanpa celah. Jika bukan itu masalahnya, Anda harus memperkenalkan kolom peringkat yang dihitung berdasarkan urutan menurunvalue
dan menggunakannya dalam CTE rekursif alih-alihvalue
bergabung dengan bagian rekursif dengan jangkar.Demo SQL Fiddle untuk kueri ini dapat ditemukan di sini .
sumber
size
denganroom_left
) dibandingkan dengan membandingkan nilai tunggal dengan ekspresi (1
denganrunning_size
+size
). Saya tidak menggunakanis_new_bucket
bendera pada awalnya tetapi beberapaCASE WHEN t.size > r.room_left ...
sebagai gantinya ("beberapa" karena saya juga sedang menghitung (dan mengembalikan) ukuran total, tetapi kemudian berpikir menentangnya demi kesederhanaan), jadi saya pikir itu akan lebih elegan seperti itu.