Saya punya tabel di SQL server yang terlihat seperti ini:
Id |Version |Name |date |fieldA |fieldB ..|fieldZ
1 |1 |Foo |20120101|23 | ..|25334123
2 |2 |Foo |20120101|23 |NULL ..|NULL
3 |2 |Bar |20120303|24 |123......|NULL
4 |2 |Bee |20120303|34 |-34......|NULL
Saya sedang mengerjakan prosedur tersimpan untuk diff, yang membutuhkan input data dan nomor versi. Data input memiliki kolom dari kolom Name uptilZ. Sebagian besar kolom bidang diharapkan menjadi NULL, yaitu, setiap baris biasanya memiliki data hanya untuk beberapa bidang pertama, sisanya adalah NULL. Nama, tanggal dan versi membentuk kendala unik di atas meja.
Saya perlu diff data yang dimasukkan sehubungan dengan tabel ini, untuk versi yang diberikan. Setiap baris harus di-diff - baris diidentifikasi oleh nama, tanggal dan versi, dan setiap perubahan dalam nilai-nilai apa pun di kolom bidang harus ditampilkan di dalam diff.
Pembaruan: semua bidang tidak harus bertipe desimal. Beberapa dari mereka mungkin nvarchars. Saya lebih suka diff terjadi tanpa mengubah tipe, walaupun output diff dapat mengubah segalanya menjadi nvarchar karena hanya digunakan untuk tampilan yang dimaksudkan.
Misalkan inputnya adalah sebagai berikut, dan versi yang diminta adalah 2 ,:
Name |date |fieldA |fieldB|..|fieldZ
Foo |20120101|25 |NULL |.. |NULL
Foo |20120102|26 |27 |.. |NULL
Bar |20120303|24 |126 |.. |NULL
Baz |20120101|15 |NULL |.. |NULL
Perbedaannya harus dalam format berikut:
name |date |field |oldValue |newValue
Foo |20120101|FieldA |23 |25
Foo |20120102|FieldA |NULL |26
Foo |20120102|FieldB |NULL |27
Bar |20120303|FieldB |123 |126
Baz |20120101|FieldA |NULL |15
Solusi saya sejauh ini adalah pertama-tama menghasilkan diff, menggunakan KECUALI dan UNION. Kemudian konversikan diff ke format output yang diinginkan menggunakan JOIN dan CROSS APPLY. Meskipun ini tampaknya berhasil, saya bertanya-tanya apakah ada cara yang lebih bersih dan lebih efisien untuk melakukan ini. Jumlah bidang mendekati 100, dan setiap tempat dalam kode yang memiliki ... sebenarnya adalah sejumlah besar garis. Baik tabel input maupun tabel yang ada diharapkan cukup besar dari waktu ke waktu. Saya baru mengenal SQL dan masih mencoba mempelajari penyetelan kinerja.
Ini SQL untuknya:
CREATE TABLE #diff
( [change] [nvarchar](50) NOT NULL,
[name] [nvarchar](50) NOT NULL,
[date] [int] NOT NULL,
[FieldA] [decimal](38, 10) NULL,
[FieldB] [decimal](38, 10) NULL,
.....
[FieldZ] [decimal](38, 10) NULL
)
--Generate the diff in a temporary table
INSERT INTO #diff
SELECT * FROM
(
(
SELECT
'old' as change,
name,
date,
FieldA,
FieldB,
...,
FieldZ
FROM
myTable mt
WHERE
version = @version
AND mt.name + '_' + CAST(mt.date AS VARCHAR) IN (SELECT name + '_' + CAST(date AS VARCHAR) FROM @diffInput)
EXCEPT
SELECT 'old' as change,* FROM @diffInput
)
UNION
(
SELECT 'new' as change, * FROM @diffInput
EXCEPT
SELECT
'new' as change,
name,
date,
FieldA,
FieldB,
...,
FieldZ
FROM
myTable mt
WHERE
version = @version
AND mt.name + '_' + CAST(mt.date AS VARCHAR) IN (SELECT name + '_' + CAST(date AS VARCHAR) FROM @diffInput)
)
) AS myDiff
SELECT
d3.name, d3.date, CrossApplied.field, CrossApplied.oldValue, CrossApplied.newValue
FROM
(
SELECT
d2.name, d2.date,
d1.FieldA AS oldFieldA, d2.FieldA AS newFieldA,
d1.FieldB AS oldFieldB, d2.FieldB AS newFieldB,
...
d1.FieldZ AS oldFieldZ, d2.FieldZ AS newFieldZ,
FROM #diff AS d1
RIGHT OUTER JOIN #diff AS d2
ON
d1.name = d2.name
AND d1.date = d2.date
AND d1.change = 'old'
WHERE d2.change = 'new'
) AS d3
CROSS APPLY (VALUES ('FieldA', oldFieldA, newFieldA),
('FieldB', oldFieldB, newFieldB),
...
('FieldZ', oldFieldZ, newFieldZ))
CrossApplied (field, oldValue, newValue)
WHERE
crossApplied.oldValue != crossApplied.newValue
OR (crossApplied.oldValue IS NULL AND crossApplied.newValue IS NOT NULL)
OR (crossApplied.oldValue IS NOT NULL AND crossApplied.newValue IS NULL)
Terima kasih!
Edit bidang yang memiliki jenis berbeda, bukan hanya
decimal
.Anda dapat mencoba menggunakan
sql_variant
tipe. Saya tidak pernah menggunakannya secara pribadi, tetapi ini mungkin solusi yang bagus untuk kasus Anda. Untuk mencobanya cukup ganti semua[decimal](38, 10)
dengansql_variant
skrip SQL. Permintaan itu sendiri tetap persis seperti apa adanya, tidak ada konversi eksplisit diperlukan untuk melakukan perbandingan. Hasil akhirnya akan memiliki kolom dengan nilai dari berbagai jenis di dalamnya. Kemungkinan besar, pada akhirnya Anda harus tahu entah bagaimana jenis di bidang mana untuk memproses hasil dalam aplikasi Anda, tetapi kueri itu sendiri akan berfungsi dengan baik tanpa konversi.Ngomong-ngomong, menyimpan tanggal sebagai ide yang buruk
int
.Alih-alih menggunakan
EXCEPT
danUNION
menghitung diff, saya akan menggunakanFULL JOIN
. Bagi saya, secara pribadi, sulit untuk mengikuti logika di belakangEXCEPT
danUNION
pendekatan.Saya akan mulai dengan tidak memvoting data, daripada melakukannya terakhir (menggunakan
CROSS APPLY(VALUES)
seperti yang Anda lakukan). Anda dapat menghilangkan pembekuan input, jika Anda melakukannya di muka, di sisi pemanggil.Anda harus mendaftar semua 100 kolom hanya di
CROSS APPLY(VALUES)
.Query terakhir cukup sederhana, sehingga tabel temp tidak benar-benar diperlukan. Saya pikir lebih mudah untuk menulis dan memelihara daripada versi Anda. Ini SQL Fiddle .
Siapkan data sampel
Permintaan utama
CTE_Main
adalah data asli yang tidak diproteksi difilter ke yang diberikanVersion
.CTE_Input
adalah tabel input, yang dapat disediakan dalam format ini. Menggunakan kueri utamaFULL JOIN
, yang menambah baris hasil denganBee
. Saya pikir mereka harus dikembalikan, tetapi jika Anda tidak ingin melihat mereka, Anda dapat menyaring mereka keluar dengan menambahkanAND CTE_Input.FieldValue IS NOT NULL
atau mungkin menggunakanLEFT JOIN
bukanFULL JOIN
, saya tidak melihat ke dalam rincian di sana, karena saya pikir mereka harus dikembalikan.Hasil
sumber