Ini adalah semacam tugas sepele di homeworld C # saya, tapi saya belum membuatnya dalam SQL dan lebih suka menyelesaikannya set-based (tanpa kursor). Set hasil harus berasal dari kueri seperti ini.
SELECT SomeId, MyDate,
dbo.udfLastHitRecursive(param1, param2, MyDate) as 'Qualifying'
FROM T
Bagaimana cara kerjanya?
Saya mengirim ketiga param itu ke UDF.
UDF secara internal menggunakan params untuk mengambil baris terkait <= 90 hari yang lebih lama, dari tampilan.
UDF melintasi 'MyDate' dan mengembalikan 1 jika harus dimasukkan dalam perhitungan total.
Jika tidak, maka mengembalikan 0. Dinamakan di sini sebagai "kualifikasi".
Apa yang akan dilakukan udf
Daftar baris dalam urutan tanggal. Hitung hari di antara baris. Baris pertama dalam resultset default ke Hit = 1. Jika selisihnya mencapai 90, - lalu lanjutkan ke baris berikutnya hingga jumlah kesenjangan adalah 90 hari (hari ke-90 harus lewat) Ketika tercapai, atur Hit ke 1 dan reset celah ke 0 Ini juga akan berfungsi untuk menghilangkan baris dari hasil.
|(column by udf, which not work yet)
Date Calc_date MaxDiff | Qualifying
2014-01-01 11:00 2014-01-01 0 | 1
2014-01-03 10:00 2014-01-01 2 | 0
2014-01-04 09:30 2014-01-03 1 | 0
2014-04-01 10:00 2014-01-04 87 | 0
2014-05-01 11:00 2014-04-01 30 | 1
Dalam tabel di atas, kolom MaxDiff adalah jarak dari tanggal di baris sebelumnya. Masalah dengan upaya saya sejauh ini adalah bahwa saya tidak dapat mengabaikan baris terakhir kedua dalam contoh di atas.
[EDIT]
Sesuai komentar saya menambahkan tag dan juga menempelkan udf yang telah saya kompilasi tadi. Padahal, hanya penampung dan tidak akan memberikan hasil yang bermanfaat.
;WITH cte (someid, otherkey, mydate, cost) AS
(
SELECT someid, otherkey, mydate, cost
FROM dbo.vGetVisits
WHERE someid = @someid AND VisitCode = 3 AND otherkey = @otherkey
AND CONVERT(Date,mydate) = @VisitDate
UNION ALL
SELECT top 1 e.someid, e.otherkey, e.mydate, e.cost
FROM dbo.vGetVisits AS E
WHERE CONVERT(date, e.mydate)
BETWEEN DateAdd(dd,-90,CONVERT(Date,@VisitDate)) AND CONVERT(Date,@VisitDate)
AND e.someid = @someid AND e.VisitCode = 3 AND e.otherkey = @otherkey
AND CONVERT(Date,e.mydate) = @VisitDate
order by e.mydate
)
Saya memiliki permintaan lain yang saya tentukan secara terpisah yang lebih dekat dengan yang saya butuhkan, tetapi diblokir dengan fakta yang tidak dapat saya hitung pada kolom berjendela. Saya juga mencoba satu serupa yang memberikan hasil yang kurang lebih sama hanya dengan LAG () lebih dari MyDate, dikelilingi dengan tanggaliff.
SELECT
t.Mydate, t.VisitCode, t.Cost, t.SomeId, t.otherkey, t.MaxDiff, t.DateDiff
FROM
(
SELECT *,
MaxDiff = LAST_VALUE(Diff.Diff) OVER (
ORDER BY Diff.Mydate ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
FROM
(
SELECT *,
Diff = ISNULL(DATEDIFF(DAY, LAST_VALUE(r.Mydate) OVER (
ORDER BY r.Mydate ASC
ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING),
r.Mydate),0),
DateDiff = ISNULL(LAST_VALUE(r.Mydate) OVER (
ORDER BY r.Mydate ASC
ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING),
r.Mydate)
FROM dbo.vGetVisits AS r
WHERE r.VisitCode = 3 AND r.SomeId = @SomeID AND r.otherkey = @otherkey
) AS Diff
) AS t
WHERE t.VisitCode = 3 AND t.SomeId = @SomeId AND t.otherkey = @otherkey
AND t.Diff <= 90
ORDER BY
t.Mydate ASC;
sumber
Jawaban:
Ketika saya membaca pertanyaan, algoritma rekursif dasar yang diperlukan adalah:
Ini relatif mudah diimplementasikan dengan ekspresi tabel umum rekursif.
Misalnya, menggunakan data sampel berikut (berdasarkan pertanyaan):
Kode rekursif adalah:
Hasilnya adalah:
Dengan indeks
TheDate
sebagai kunci utama, rencana eksekusi sangat efisien:Anda dapat memilih untuk membungkus ini dalam suatu fungsi dan menjalankannya langsung terhadap pandangan yang disebutkan dalam pertanyaan, tetapi naluri saya menentangnya. Biasanya, kinerja lebih baik ketika Anda memilih baris dari tampilan ke tabel sementara, memberikan indeks yang sesuai pada tabel sementara, lalu menerapkan logika di atas. Detailnya tergantung pada detail tampilan, tetapi ini adalah pengalaman umum saya.
Untuk kelengkapan (dan diminta oleh jawaban ypercube) Saya harus menyebutkan bahwa solusi masuk saya yang lain untuk jenis masalah ini (sampai T-SQL mendapatkan fungsi set yang tepat) adalah kursor SQLCLR ( lihat jawaban saya di sini untuk contoh teknik ini ). Ini melakukan jauh lebih baik daripada kursor T-SQL, dan nyaman bagi mereka yang memiliki keterampilan dalam bahasa .NET dan kemampuan untuk menjalankan SQLCLR di lingkungan produksi mereka. Ini mungkin tidak menawarkan banyak dalam skenario ini daripada solusi rekursif karena mayoritas biayanya adalah semacam itu, tetapi perlu disebutkan.
sumber
Karena ini adalah pertanyaan SQL Server 2014 saya mungkin juga menambahkan versi prosedur tersimpan yang disimpan secara asli dari "kursor".
Tabel sumber dengan beberapa data:
Jenis tabel yang merupakan parameter untuk prosedur tersimpan. Sesuaikan dengan
bucket_count
tepat .Dan prosedur tersimpan yang loop melalui tabel bernilai parameter dan mengumpulkan baris
@R
.Kode untuk mengisi variabel tabel dioptimalkan memori yang digunakan sebagai parameter untuk prosedur tersimpan yang disusun secara asli dan memanggil prosedur.
Hasil:
Memperbarui:
Jika karena alasan tertentu Anda tidak perlu mengunjungi setiap baris dalam tabel, Anda dapat melakukan yang setara dengan versi "lompat ke tanggal berikutnya" yang diterapkan dalam CTE rekursif oleh Paul White.
Tipe data tidak perlu kolom ID dan Anda tidak harus menggunakan indeks hash.
Dan prosedur tersimpan menggunakan a
select top(1) ..
untuk menemukan nilai selanjutnya.sumber
T.TheDate >= dateadd(day, 91, @CurDate)
menjadi baik-baik saja, bukan?TheDate
dalamTType
untukDate
.Solusi yang menggunakan kursor.
(pertama, beberapa tabel dan variabel yang diperlukan) :
Kursor aktual:
Dan mendapatkan hasilnya:
Diuji di SQLFiddle
sumber
INSERT @cd
hanya ketika@Qualify=1
(dan dengan demikian tidak memasukkan baris 13M jika Anda tidak membutuhkan semuanya dalam output). Dan solusinya tergantung pada menemukan indeksTheDate
. Jika tidak ada, itu tidak akan efisien.Hasil
Juga lihat Cara Menghitung Menjalankan Total di SQL Server
pembaruan: silakan lihat di bawah hasil pengujian kinerja.
Karena logika yang berbeda yang digunakan dalam menemukan "celah 90 hari" solusi ypercube dan saya jika dibiarkan utuh dapat mengembalikan hasil yang berbeda dengan solusi Paul White. Ini karena penggunaan DATEIFF dan DATEADD fungsi masing-masing.
Sebagai contoh:
mengembalikan '2014-04-01 00: 00: 00.000' yang berarti bahwa '2014-04-01 01: 00: 00.000' melebihi batas 90 hari
tapi
Mengembalikan '90' yang berarti bahwa itu masih dalam celah.
Pertimbangkan contoh pengecer. Dalam hal ini menjual produk yang mudah rusak yang telah terjual berdasarkan tanggal '2014-01-01' di '2014-01-01 23: 59: 59: 999' tidak masalah. Jadi nilai DATEDIFF (HARI, ...) dalam hal ini OK.
Contoh lain adalah pasien menunggu untuk dilihat. Untuk seseorang yang datang pada '2014-01-01 00: 00: 00: 000' dan pergi pada '2014-01-01 23: 59: 59: 999' itu adalah 0 (nol) hari jika DATEIFF digunakan meskipun menunggu sebenarnya hampir 24 jam. Sekali lagi pasien yang datang di '2014-01-01 23:59:59' dan berjalan pergi di '2014-01-02 00:00:01' menunggu satu hari jika DATEIFF digunakan.
Tapi saya ngelantur.
Saya meninggalkan solusi DATEIFF dan bahkan menguji kinerja mereka tetapi mereka harus benar-benar di liga mereka sendiri.
Juga dicatat bahwa untuk dataset besar tidak mungkin untuk menghindari nilai hari yang sama. Jadi jika kita mengatakan 13 Juta catatan yang mencakup 2 tahun data, maka kita akan memiliki lebih dari satu catatan selama beberapa hari. Catatan-catatan itu sedang disaring pada kesempatan paling awal dalam solusi DATEIFF saya dan ypercube. Semoga ypercube tidak keberatan dengan ini.
Solusi diuji pada tabel berikut
dengan dua indeks pengelompokan berbeda (tanggal saya dalam kasus ini):
Tabel diisi dengan cara berikut
Untuk kasus baris jutaan, INSERT diubah sedemikian rupa sehingga 0-20 menit entri ditambahkan secara acak.
Semua solusi dengan hati-hati terbungkus dalam kode berikut
Kode aktual diuji (tanpa urutan tertentu):
Solusi DATEIFF Ypercube ( YPC, DATEDIFF )
Solusi DATEADD Ypercube ( YPC, DATEADD )
Solusi Paul White ( PW )
Solusi DATEADD saya ( PN, DATEADD )
Solusi DATEDIFF saya ( PN, DATEDIFF )
Saya menggunakan SQL Server 2012, jadi permintaan maaf kepada Mikael Eriksson, tetapi kodenya tidak akan diuji di sini. Saya masih mengharapkan solusinya dengan DATADIFF dan DATEADD untuk mengembalikan nilai yang berbeda pada beberapa dataset.
Dan hasil sebenarnya adalah:
sumber
Ok, apakah saya melewatkan sesuatu atau mengapa Anda tidak akan melewatkan rekursi dan bergabung kembali dengan diri Anda sendiri? Jika tanggal adalah kunci utama, itu harus unik, dan dalam urutan kronologis jika Anda berencana menghitung offset ke baris berikutnya
Hasil
Kecuali saya benar-benar melewatkan sesuatu yang penting ....
sumber
WHERE [TheDate] > [T1].[TheDate]
untuk memperhitungkan ambang perbedaan 90 hari. Tapi tetap saja, output Anda bukan yang diinginkan.