Basis data yang melayani aplikasi bisnis harus sering mendukung data temporal. Misalnya, kontrak dengan pemasok hanya berlaku untuk waktu yang terbatas. Ini bisa valid dari titik waktu tertentu dan seterusnya, atau bisa valid untuk interval waktu tertentu—dari titik waktu awal hingga titik waktu akhir. Selain itu, sering kali Anda perlu mengaudit semua perubahan dalam satu atau beberapa tabel. Anda mungkin juga harus dapat menunjukkan status pada titik waktu tertentu atau semua perubahan yang dibuat pada tabel dalam periode waktu tertentu. Dari perspektif integritas data, Anda mungkin perlu menerapkan banyak batasan khusus temporal tambahan.
Memperkenalkan Data Temporal
Dalam tabel dengan dukungan temporal, header mewakili predikat dengan setidaknya satu kali parameter yang mewakili interval ketika sisa predikat valid—predikat lengkap, oleh karena itu, adalah predikat yang diberi cap waktu. Baris mewakili proposisi yang diberi cap waktu, dan periode waktu valid baris biasanya dinyatakan dengan dua atribut:dari dan ke , atau mulai dan selesai .
Jenis Tabel Temporal
Anda mungkin telah memperhatikan selama bagian pendahuluan bahwa ada dua jenis masalah temporal. Yang pertama adalah waktu validitas dari proposisi – di periode mana proposisi yang diwakili oleh baris berstempel waktu dalam tabel sebenarnya benar. Misalnya, kontrak dengan pemasok hanya berlaku dari titik waktu 1 ke titik waktu 2. Keabsahan semacam ini berarti bagi orang-orang, berarti bagi bisnis. Waktu validitas juga disebut waktu aplikasi atau waktu manusia . Kami dapat memiliki beberapa periode valid untuk entitas yang sama. Misalnya, kontrak yang disebutkan di atas yang berlaku dari titik waktu 1 hingga titik waktu 2 mungkin juga berlaku dari titik waktu 7 hingga titik waktu 9.
Masalah temporal kedua adalah waktu transaksi . Baris untuk kontrak yang disebutkan di atas dimasukkan pada titik waktu 1 dan merupakan satu-satunya versi kebenaran yang diketahui database sampai seseorang mengubahnya, atau bahkan sampai akhir waktu. Ketika baris diperbarui pada titik waktu 2, baris asli diketahui benar untuk database dari titik waktu 1 ke titik waktu 2. Baris baru untuk proposisi yang sama dimasukkan dengan waktu yang valid untuk database dari titik waktu 2 ke akhir waktu. Waktu transaksi juga dikenal sebagai waktu sistem atau waktu basis data .
Tentu saja, Anda juga dapat mengimplementasikan tabel versi aplikasi dan sistem. Tabel seperti ini disebut bittemporal tabel.
Di SQL Server 2016, Anda mendapatkan dukungan untuk waktu sistem di luar kotak dengan tabel temporal berversi sistem . Jika Anda perlu menerapkan waktu aplikasi, Anda perlu mengembangkan solusi sendiri.
Operator Interval Allen
Teori untuk data temporal dalam model relasional mulai berkembang lebih dari tiga puluh tahun yang lalu. Saya akan memperkenalkan beberapa operator Boolean yang berguna dan beberapa operator yang bekerja pada interval dan mengembalikan interval. Operator ini dikenal sebagai operator Allen, dinamai J. F. Allen, yang mendefinisikan beberapa dari mereka dalam makalah penelitian 1983 tentang interval temporal. Semuanya masih diterima sebagai sah dan dibutuhkan. Sistem manajemen basis data dapat membantu Anda menangani waktu aplikasi dengan menerapkan operator ini secara langsung.
Mari saya perkenalkan dulu notasi yang akan saya gunakan. Saya akan mengerjakan dua interval, dilambangkan i1 dan i2 . Titik waktu awal interval pertama adalah b1 , dan akhirnya adalah e1 ; titik waktu awal interval kedua adalah b2 dan akhirnya e2 . Operator Boolean Allen's didefinisikan dalam tabel berikut.
[table id=2 /]
Selain operator Boolean, ada tiga operator Allen yang menerima interval sebagai parameter input dan mengembalikan interval. Operator-operator ini merupakan aljabar interval sederhana . Perhatikan bahwa operator tersebut memiliki nama yang sama dengan operator relasional yang mungkin sudah Anda kenal:Union, Intersect, dan Minus. Namun, mereka tidak berperilaku persis seperti rekan relasional mereka. Secara umum, dengan menggunakan salah satu dari tiga operator interval, jika operasi akan menghasilkan himpunan titik waktu yang kosong atau himpunan yang tidak dapat dijelaskan oleh satu interval, maka operator harus mengembalikan NULL. Penyatuan dua interval masuk akal hanya jika interval bertemu atau tumpang tindih. Persimpangan masuk akal hanya jika intervalnya tumpang tindih. Operator interval Minus hanya masuk akal dalam beberapa kasus. Misalnya, (3:10) Minus (5:7) mengembalikan NULL karena hasilnya tidak dapat dijelaskan dengan satu interval. Tabel berikut merangkum definisi operator aljabar interval.
[id tabel=3 /]
Tumpang Tindih Masalah Kinerja Kueri Salah satu operator yang paling kompleks untuk diterapkan adalah tumpang tindih operator. Kueri yang perlu menemukan interval yang tumpang tindih tidak mudah untuk dioptimalkan. Namun, kueri seperti itu cukup sering terjadi pada tabel temporal. Dalam dua artikel ini dan berikutnya, saya akan menunjukkan kepada Anda beberapa cara untuk mengoptimalkan kueri semacam itu. Namun sebelum saya memperkenalkan solusinya, izinkan saya memperkenalkan masalahnya.
Untuk menjelaskan masalah ini, saya memerlukan beberapa data. Kode berikut menunjukkan contoh cara membuat tabel dengan interval validitas yang dinyatakan dengan b dan e kolom, di mana awal dan akhir interval direpresentasikan sebagai bilangan bulat. Tabel diisi dengan data demo dari tabel WideWorldImporters.Sales.OrderLines. Harap perhatikan bahwa ada beberapa versi WideWorldImporters database, sehingga Anda mungkin mendapatkan hasil yang sedikit berbeda. Saya menggunakan file cadangan WideWorldImporters-Standard.bak dari https://github.com/Microsoft/sql-server-samples/releases/tag/wide-world-importers-v1.0 untuk memulihkan database demo ini pada contoh SQL Server saya .
Membuat Data Demo
Saya membuat tabel demo dbo.Intervals dalam tempd database dengan kode berikut.
USE tempdb; GO SELECT OrderLineID AS id, StockItemID * (OrderLineID % 5 + 1) AS b, LastEditedBy + StockItemID * (OrderLineID % 5 + 1) AS e INTO dbo.Intervals FROM WideWorldImporters.Sales.OrderLines; -- 231412 rows GO ALTER TABLE dbo.Intervals ADD CONSTRAINT PK_Intervals PRIMARY KEY(id); CREATE INDEX idx_b ON dbo.Intervals(b) INCLUDE(e); CREATE INDEX idx_e ON dbo.Intervals(e) INCLUDE(b); GO
Harap perhatikan juga indeks dibuat. Kedua indeks optimal untuk pencarian di awal interval atau di akhir interval. Anda dapat memeriksa awal minimal dan akhir maksimal dari semua interval dengan kode berikut.
SELECT MIN(b), MAX(e) FROM dbo.Intervals;
Anda dapat melihat pada hasil bahwa titik waktu mulai minimal adalah 1 dan titik waktu akhir maksimal adalah 1155.
Memberikan Konteks pada Data
Anda mungkin memperhatikan bahwa saya mewakili awal dan akhir titik waktu sebagai bilangan bulat. Sekarang saya perlu memberikan interval beberapa konteks waktu. Dalam hal ini, satu titik waktu mewakili hari . Kode berikut membuat tabel pencarian tanggal dan mengisinya. Perhatikan bahwa tanggal mulainya adalah 1 Juli 2014.
CREATE TABLE dbo.DateNums (n INT NOT NULL PRIMARY KEY, d DATE NOT NULL); GO DECLARE @i AS INT = 1, @d AS DATE = '20140701'; WHILE @i <= 1200 BEGIN INSERT INTO dbo.DateNums (n, d) SELECT @i, @d; SET @i += 1; SET @d = DATEADD(day,1,@d); END; GO
Sekarang, Anda dapat menggabungkan tabel dbo.Intervals ke tabel dbo.DateNums dua kali, untuk memberikan konteks ke bilangan bulat yang mewakili awal dan akhir interval.
SELECT i.id, i.b, d1.d AS dateB, i.e, d2.d AS dateE FROM dbo.Intervals AS i INNER JOIN dbo.DateNums AS d1 ON i.b = d1.n INNER JOIN dbo.DateNums AS d2 ON i.e = d2.n ORDER BY i.id;
Memperkenalkan Masalah Performa
Masalah dengan kueri temporal adalah ketika membaca dari tabel, SQL Server hanya dapat menggunakan satu indeks, dan berhasil menghilangkan baris yang bukan kandidat untuk hasil dari satu sisi saja, lalu memindai data lainnya. Misalnya, Anda perlu menemukan semua interval dalam tabel yang tumpang tindih dengan interval tertentu. Ingat, dua interval tumpang tindih ketika awal yang pertama lebih rendah atau sama dengan akhir yang kedua dan awal yang kedua lebih rendah atau sama dengan akhir yang pertama, atau secara matematis ketika (b1 e2) DAN (b2 e1).
Kueri berikut menelusuri semua interval yang tumpang tindih dengan interval (10, 30). Perhatikan bahwa kondisi kedua (b2 e1) dibalik ke (e1 b2) untuk pembacaan yang lebih sederhana (awal dan akhir interval dari tabel selalu di sisi kiri kondisi). Yang diberikan, atau interval yang dicari, berada di awal garis waktu untuk semua interval dalam tabel.
SET STATISTICS IO ON; DECLARE @b AS INT = 10, @e AS INT = 30; SELECT id, b, e FROM dbo.Intervals WHERE b <= @e AND e >= @b OPTION (RECOMPILE);
Kueri menggunakan 36 pembacaan logis. Jika Anda memeriksa rencana eksekusi, Anda dapat melihat bahwa kueri menggunakan pencarian indeks dalam indeks idx_b dengan predikat pencarian [tempdb].[dbo].[Interval].b <=Operator Skalar((30)) dan kemudian memindai baris dan pilih baris yang dihasilkan menggunakan predikat residual [tempdb].[dbo].[Interval].[e]>=(10). Karena interval yang dicari berada di awal timeline, predikat seek berhasil menghilangkan sebagian besar baris; hanya beberapa interval dalam tabel yang memiliki titik awal lebih rendah atau sama dengan 30.
Anda akan mendapatkan kueri yang sama efisiennya jika interval yang dicari berada di akhir garis waktu, hanya saja SQL Server akan menggunakan indeks idx_e untuk pencarian. Namun, apa yang terjadi jika interval yang dicari berada di tengah timeline, seperti yang ditunjukkan oleh kueri berikut?
DECLARE @b AS INT = 570, @e AS INT = 590; SELECT id, b, e FROM dbo.Intervals WHERE b <= @e AND e >= @b OPTION (RECOMPILE);
Kali ini, kueri menggunakan 111 pembacaan logis. Dengan tabel yang lebih besar, perbedaan dengan kueri pertama akan lebih besar. Jika Anda memeriksa rencana eksekusi, Anda dapat menemukan bahwa SQL Server menggunakan indeks idx_e dengan [tempdb].[dbo].[Interval].e>=Operator Skalar((570)) mencari predikat dan [tempdb].[ dbo].[Interval].[b]<=(590) predikat residual. Predikat pencarian mengecualikan sekitar setengah dari baris dari satu sisi, sementara setengah dari baris dari sisi lain dipindai dan baris yang dihasilkan diekstraksi dengan predikat residual.
Solusi T-SQL yang Disempurnakan
Ada solusi yang akan menggunakan indeks itu untuk menghilangkan baris dari kedua sisi interval yang dicari dengan menggunakan indeks tunggal. Gambar berikut menunjukkan logika ini.
Interval pada gambar diurutkan berdasarkan batas bawah, yang mewakili penggunaan indeks idx_b SQL Server. Menghilangkan interval dari sisi kanan interval (dicari) yang diberikan sederhana:cukup hilangkan semua interval di mana awal setidaknya satu unit lebih besar (lebih ke kanan) dari akhir interval yang diberikan. Anda dapat melihat batas ini pada gambar yang dilambangkan dengan garis putus-putus paling kanan. Namun, menghilangkan dari kiri lebih kompleks. Untuk menggunakan indeks yang sama, indeks idx_b untuk menghilangkan dari kiri, saya perlu menggunakan awal interval dalam tabel di klausa WHERE dari kueri. Saya harus pergi ke sisi kiri jauh dari awal interval (dicari) yang diberikan setidaknya untuk panjang interval terpanjang dalam tabel, yang ditandai dengan keterangan pada gambar. Interval yang dimulai sebelum garis kuning kiri tidak boleh tumpang tindih dengan interval (biru) yang diberikan.
Karena saya sudah tahu bahwa panjang interval terpanjang adalah 20, saya dapat menulis kueri yang disempurnakan dengan cara yang cukup sederhana.
DECLARE @b AS INT = 570, @e AS INT = 590; DECLARE @max AS INT = 20; SELECT id, b, e FROM dbo.Intervals WHERE b <= @e AND b >= @b - @max AND e >= @b AND e <= @e + @max OPTION (RECOMPILE);
Kueri ini mengambil baris yang sama seperti baris sebelumnya dengan 20 pembacaan logis saja. Jika Anda memeriksa rencana eksekusi, Anda dapat melihat bahwa idx_b digunakan, dengan predikat seek Seek Keys[1]:Start:[tempdb].[dbo].[Intervals].b>=Scalar Operator((550)) , End:[tempdb].[dbo].[Intervals].b <=Operator Skalar((590)), yang berhasil menghilangkan baris dari kedua sisi timeline, dan kemudian predikat residual [tempdb].[dbo]. [Interval].[e]>=(570) DAN [tempdb].[dbo].[Interval].[e]<=(610) digunakan untuk memilih baris dari pemindaian parsial yang sangat terbatas.
Tentu saja, angka tersebut dapat dibalik untuk menutupi kasus-kasus ketika indeks idx_e akan lebih berguna. Dengan indeks ini, eliminasi dari kiri sederhana – hilangkan semua interval yang berakhir setidaknya satu unit sebelum awal interval yang diberikan. Kali ini, eliminasi dari kanan lebih kompleks – ujung interval dalam tabel tidak boleh lebih ke kanan daripada akhir interval yang diberikan ditambah panjang maksimal semua interval dalam tabel.
Harap dicatat bahwa kinerja ini adalah konsekuensi dari data spesifik dalam tabel. Panjang maksimal interval adalah 20. Dengan cara ini, SQL Server dapat dengan sangat efisien menghilangkan interval dari kedua sisi. Namun, jika hanya ada satu interval panjang dalam tabel, kode akan menjadi kurang efisien, karena SQL Server tidak akan dapat menghilangkan banyak baris dari satu sisi, kiri atau kanan, tergantung indeks mana yang akan digunakan. . Bagaimanapun, dalam kehidupan nyata, panjang interval tidak banyak berubah berkali-kali, jadi teknik optimasi ini mungkin sangat berguna, terutama karena sederhana.
Kesimpulan
Harap dicatat bahwa ini hanyalah salah satu solusi yang mungkin. Anda dapat menemukan solusi yang lebih kompleks, namun menghasilkan kinerja yang dapat diprediksi berapa pun panjangnya interval terpanjang dalam artikel Interval Query in SQL Server oleh Itzik Ben-Gan (http://sqlmag.com/t-sql/ sql-server-interval-query). Namun, saya sangat menyukai T-SQL yang disempurnakan solusi yang saya sajikan dalam artikel ini. Solusinya sangat sederhana; yang perlu Anda lakukan adalah menambahkan dua predikat ke klausa WHERE dari kueri Anda yang tumpang tindih. Namun, ini bukan akhir dari kemungkinan. Nantikan, dalam dua artikel berikutnya saya akan menunjukkan lebih banyak solusi, sehingga Anda akan memiliki banyak kemungkinan di kotak alat pengoptimalan Anda.
Alat yang berguna:
dbForge Query Builder untuk SQL Server – memungkinkan pengguna membuat kueri SQL yang kompleks dengan cepat dan mudah melalui antarmuka visual yang intuitif tanpa penulisan kode manual.