Bulan lalu saya memberikan latar belakang untuk ekspresi tabel di T-SQL. Saya menjelaskan konteks dari teori relasional dan standar SQL. Saya menjelaskan bagaimana tabel dalam SQL adalah upaya untuk merepresentasikan relasi dari teori relasional. Saya juga menjelaskan bahwa ekspresi relasional adalah ekspresi yang beroperasi pada satu atau lebih relasi sebagai input dan menghasilkan sebuah relasi. Demikian pula, dalam SQL, ekspresi tabel adalah ekspresi yang beroperasi pada satu atau lebih tabel input, dan menghasilkan tabel. Ekspresi dapat berupa kueri, tetapi tidak harus. Misalnya, ekspresi dapat berupa konstruktor nilai tabel, seperti yang akan saya jelaskan nanti di artikel ini. Saya juga menjelaskan bahwa dalam seri ini, saya fokus pada empat tipe spesifik dari ekspresi tabel bernama yang didukung T-SQL:tabel turunan, ekspresi tabel umum (CTE), tampilan, dan fungsi bernilai tabel sebaris (TVF).
Jika Anda telah bekerja dengan T-SQL untuk beberapa waktu, Anda mungkin menemukan beberapa kasus di mana Anda harus menggunakan ekspresi tabel, atau entah bagaimana lebih nyaman dibandingkan dengan solusi alternatif yang tidak menggunakannya. Berikut adalah beberapa contoh kasus penggunaan yang muncul dalam pikiran:
- Buat solusi modular dengan memecah tugas kompleks menjadi beberapa langkah, masing-masing diwakili oleh ekspresi tabel yang berbeda.
- Mencampur hasil kueri dan detail yang dikelompokkan, jika Anda memutuskan untuk tidak menggunakan fungsi jendela untuk tujuan ini.
- Pemrosesan kueri logis menangani klausa kueri dalam urutan berikut:FROM>WHERE>GROUP BY>HAVING>SELECT>ORDER BY. Akibatnya, di tingkat penyarangan yang sama, alias kolom yang Anda tentukan dalam klausa SELECT hanya tersedia untuk klausa ORDER BY. Mereka tidak tersedia untuk klausa kueri lainnya. Dengan ekspresi tabel, Anda dapat menggunakan kembali alias yang Anda definisikan dalam kueri dalam di klausa kueri luar mana pun, dan dengan cara ini menghindari pengulangan ekspresi yang panjang/kompleks.
- Fungsi jendela hanya dapat muncul dalam klausa SELECT dan ORDER BY kueri. Dengan ekspresi tabel, Anda dapat menetapkan alias ke ekspresi berdasarkan fungsi jendela, lalu menggunakan alias itu dalam kueri terhadap ekspresi tabel.
- Operator PIVOT melibatkan tiga elemen:pengelompokan, penyebaran, dan agregasi. Operator ini mengidentifikasi elemen pengelompokan secara implisit dengan eliminasi. Dengan menggunakan ekspresi tabel, Anda dapat memproyeksikan dengan tepat tiga elemen yang seharusnya terlibat, dan membuat kueri luar menggunakan ekspresi tabel sebagai tabel input operator PIVOT, sehingga mengontrol elemen mana yang merupakan elemen pengelompokan.
- Modifikasi dengan TOP tidak mendukung klausa ORDER BY. Anda dapat mengontrol baris mana yang dipilih secara tidak langsung dengan mendefinisikan ekspresi tabel berdasarkan kueri SELECT dengan filter TOP atau OFFSET-FETCH dan klausa ORDER BY, dan menerapkan modifikasi terhadap ekspresi tabel.
Ini jauh dari daftar lengkap. Saya akan mendemonstrasikan beberapa kasus penggunaan di atas dan lainnya dalam seri ini. Saya hanya ingin menyebutkan beberapa kasus penggunaan di sini untuk mengilustrasikan betapa pentingnya ekspresi tabel dalam kode T-SQL kami, dan mengapa berinvestasi dalam memahami dasar-dasarnya dengan baik sangatlah bermanfaat.
Dalam artikel bulan ini saya fokus pada perlakuan logis dari tabel turunan secara khusus.
Dalam contoh saya, saya akan menggunakan database sampel yang disebut TSQLV5. Anda dapat menemukan skrip yang membuat dan mengisinya di sini, dan diagram ER-nya di sini.
Tabel turunan
Istilah tabel turunan digunakan dalam SQL dan T-SQL dengan lebih dari satu arti. Jadi pertama-tama saya ingin memperjelas yang mana yang saya maksud dalam artikel ini. Saya mengacu pada konstruksi bahasa tertentu yang biasanya Anda definisikan, tetapi tidak hanya, dalam klausa FROM dari kueri luar. Saya akan segera memberikan sintaks untuk konstruksi ini.
Penggunaan yang lebih umum dari istilah tabel turunan dalam SQL adalah padanan dari relasi turunan dari teori relasional. Relasi turunan adalah relasi hasil yang diturunkan dari satu atau lebih relasi basis input, dengan menerapkan operator relasional dari aljabar relasional seperti proyeksi, perpotongan dan lain-lain ke relasi basis tersebut. Demikian pula, dalam pengertian umum, tabel turunan dalam SQL adalah tabel hasil yang diturunkan dari satu atau lebih tabel dasar, dengan mengevaluasi ekspresi terhadap tabel dasar input tersebut.
Selain itu, saya memeriksa bagaimana standar SQL mendefinisikan tabel dasar dan segera maaf saya mengganggu.
4.15.2 Tabel dasarTabel dasar adalah tabel dasar persisten atau tabel sementara.
Tabel dasar persisten adalah tabel dasar persisten biasa atau tabel versi sistem.
Tabel dasar reguler adalah tabel dasar tetap biasa atau tabel sementara.”
Ditambahkan di sini tanpa komentar lebih lanjut…
Di T-SQL, Anda dapat membuat tabel dasar dengan pernyataan CREATE TABLE, tetapi ada opsi lain, misalnya, SELECT INTO dan DECLARE @T AS TABLE.
Berikut definisi standar untuk tabel turunan secara umum:
4.15.3 Tabel turunan
Tabel turunan adalah tabel yang diturunkan secara langsung atau tidak langsung dari satu atau beberapa tabel lain dengan evaluasi ekspresi, seperti
Ada beberapa hal menarik yang perlu diperhatikan di sini tentang tabel turunan dalam pengertian umum. Satu harus dilakukan dengan komentar tentang memesan. Saya akan membahas ini nanti di artikel. Yang lainnya adalah bahwa tabel turunan dalam SQL dapat menjadi ekspresi tabel mandiri yang valid, tetapi tidak harus demikian. Misalnya, ekspresi berikut mewakili tabel turunan, dan adalah juga dianggap sebagai ekspresi tabel mandiri yang valid (Anda dapat menjalankannya):
PILIH custid, companynameFROM Sales.CustomersWHERE country =N'USA'
Sebaliknya, ekspresi berikut mewakili tabel turunan, tetapi bukan ekspresi tabel mandiri yang valid:
T1 INNER GABUNG T2 PADA T1.keycol =T2.keycol
T-SQL mendukung sejumlah operator tabel yang menghasilkan tabel turunan, tetapi tidak didukung sebagai ekspresi yang berdiri sendiri. Yaitu:JOIN, PIVOT, UNPIVOT dan APPLY. Anda memang memerlukan klausa agar mereka dapat beroperasi di dalamnya (biasanya FROM, tetapi juga klausa USING pernyataan MERGE), dan kueri host.
Dari sini, saya akan menggunakan istilah tabel turunan untuk mendeskripsikan konstruksi bahasa yang lebih spesifik dan bukan dalam pengertian umum yang dijelaskan di atas.
Sintaks
Tabel turunan dapat didefinisikan sebagai bagian dari pernyataan SELECT luar dalam klausa FROM-nya. Itu juga dapat didefinisikan sebagai bagian dari pernyataan DELETE dan UPDATE dalam klausa FROM mereka, dan sebagai bagian dari pernyataan MERGE dalam klausa USING-nya. Saya akan memberikan detail lebih lanjut tentang sintaks saat digunakan dalam pernyataan modifikasi nanti di artikel ini.
Berikut sintaks untuk kueri SELECT yang disederhanakan terhadap tabel turunan:
PILIHFROM (
Definisi tabel turunan muncul di mana tabel dasar biasanya dapat muncul, di klausa FROM kueri luar. Ini bisa menjadi input ke operator tabel seperti JOIN, APPLY, PIVOT dan UNPIVOT. Saat digunakan sebagai input yang tepat untuk operator APPLY, bagian
Pernyataan luar dapat memiliki semua elemen kueri yang biasa. Dalam kasus pernyataan SELECT:WHERE, GROUP BY, HAVING, ORDER BY dan seperti yang disebutkan, operator tabel dalam klausa FROM.
Berikut ini contoh kueri sederhana terhadap tabel turunan yang mewakili pelanggan AS:
SELECT custid, companynameFROM ( SELECT custid, companyname FROM Sales.Customers WHERE country =N'USA' ) AS UC;
Kueri ini menghasilkan keluaran berikut:
nama perusahaan custid------- ---------------32 Pelanggan YSIQX36 Pelanggan LVJSO43 Pelanggan UISOJ45 Pelanggan QXPPT48 Pelanggan DVFMB55 Pelanggan KZQZT65 Pelanggan NYUHS71 Pelanggan LCOUJ75 Pelanggan XOJYP77 Pelanggan LCYBZ78 Pelanggan NLTYP82 Pelanggan EYHKM89 Pelanggan YBQTI
Ada tiga bagian utama yang harus diidentifikasi dalam pernyataan yang melibatkan definisi tabel turunan:
- Ekspresi tabel (kueri dalam)
- Nama tabel turunan, atau lebih tepatnya, apa yang dalam teori relasional dianggap sebagai variabel rentang
- Pernyataan luar
Ekspresi tabel seharusnya mewakili tabel, dan dengan demikian, harus memenuhi persyaratan tertentu yang tidak harus dipenuhi oleh kueri normal. Saya akan memberikan detailnya segera di bagian “Ekspresi tabel adalah tabel”.
Adapun nama tabel turunan target; asumsi umum di antara pengembang T-SQL adalah bahwa itu hanyalah nama, atau alias yang Anda tetapkan ke tabel target. Demikian pula, pertimbangkan kueri berikut:
PILIH custid, companynameFROM Sales.Customers AS CWHERE country =N'USA';
Juga di sini, asumsi umum adalah bahwa AS C hanyalah cara untuk mengganti nama, atau alias, tabel Pelanggan untuk keperluan kueri ini, dimulai dengan langkah pemrosesan kueri logis di mana nama ditetapkan dan seterusnya. Namun, dari sudut pandang teori relasional, ada makna yang lebih dalam dari apa yang diwakili oleh C. C adalah apa yang dikenal sebagai variabel rentang. C adalah variabel relasi turunan yang berkisar di atas tupel dalam variabel relasi input Pelanggan. Dalam contoh di atas, rentang C di atas tupel di Pelanggan dan mengevaluasi negara predikat =N'USA'. Tupel yang predikatnya bernilai true menjadi bagian dari relasi hasil C.
Ekspresi tabel adalah tabel
Dengan latar belakang yang saya berikan sejauh ini, apa yang akan saya jelaskan selanjutnya seharusnya tidak mengejutkan. Bagian
- Semua kolom ekspresi tabel harus memiliki nama
- Semua nama kolom ekspresi tabel harus unik
- Baris ekspresi tabel tidak memiliki urutan
Mari kita uraikan persyaratan ini satu per satu, membahas relevansinya dengan teori relasional dan SQL.
Semua kolom harus memiliki nama
Ingatlah bahwa suatu relasi memiliki heading dan body. Judul relasi adalah sekumpulan atribut (kolom dalam SQL). Atribut memiliki nama dan nama tipe, dan diidentifikasi dengan namanya. Kueri yang tidak digunakan sebagai ekspresi tabel tidak harus menetapkan nama ke semua kolom target. Pertimbangkan kueri berikut sebagai contoh:
SELECT empid, firstname, lastname, CONCAT_WS(N'/', country, region, city)FROM HR.Employees;
Kueri ini menghasilkan keluaran berikut:
empid firstname lastname (Tanpa nama kolom)------ ---------- ---------- ------------- ----1 Sara Davis USA/WA/Seattle2 Don Funk USA/WA/Tacoma3 Judy Lew USA/WA/Kirkland4 Yael Peled USA/WA/Redmond5 Sven Mortensen UK/London6 Paul Suurs UK/London7 Russell King UK/London8 Maria Cameron AS/WA/Seattle9 Patricia Doyle Inggris/London
Keluaran kueri memiliki kolom anonim yang dihasilkan dari penggabungan atribut lokasi menggunakan fungsi CONCAT_WS. (Omong-omong, fungsi ini telah ditambahkan di SQL Server 2017, jadi jika Anda menjalankan kode di versi sebelumnya, silakan ganti komputasi ini dengan komputasi alternatif pilihan Anda.) Oleh karena itu, kueri ini tidak mengembalikan tabel, belum lagi relasi. Oleh karena itu, tidak valid untuk menggunakan kueri seperti ekspresi tabel/kueri bagian dalam dari definisi tabel turunan.
Cobalah:
SELECT *FROM ( SELECT empid, firstname, lastname, CONCAT_WS(N'/', country, region, city) FROM HR.Employees ) AS D;
Anda mendapatkan kesalahan berikut:
Msg 8155, Level 16, State 2, Line 50Tidak ada nama kolom yang ditentukan untuk kolom 4 dari 'D'.
Sebagai tambahan, perhatikan sesuatu yang menarik tentang pesan kesalahan? Itu mengeluh tentang kolom 4, menyoroti perbedaan antara kolom dalam SQL dan atribut dalam teori relasional.
Solusinya adalah, tentu saja, untuk memastikan bahwa Anda secara eksplisit menetapkan nama ke kolom yang dihasilkan dari perhitungan. T-SQL mendukung beberapa teknik penamaan kolom. Saya akan menyebutkan dua di antaranya.
Anda dapat menggunakan teknik penamaan sebaris di mana Anda menetapkan nama kolom target setelah perhitungan dan klausa AS opsional, seperti dalam < expression > [ AS ] < column name >
, seperti ini:
SELECT empid, firstname, lastname, custlocationFROM ( SELECT empid, firstname, lastname, CONCAT_WS(N'/', country, region, city) AS custlocation FROM HR.Employeees ) AS D;
Kueri ini menghasilkan keluaran berikut:
empid firstname lastname custlocation------ ---------- ---------- ----------------1 Sara Davis USA/WA/Seattle2 Don Funk USA/WA/Tacoma3 Judy Lew USA/WA/Kirkland4 Yael Peled USA/WA/Redmond5 Sven Mortensen UK/London6 Paul Suurs UK/London7 Russell King UK/London8 Maria Cameron USA/WA/Seattle9 Patricia Doyle Inggris/London
Dengan menggunakan teknik ini, sangat mudah saat meninjau kode untuk mengetahui nama kolom target yang ditetapkan ke ekspresi mana. Selain itu, Anda hanya perlu memberi nama kolom yang belum memiliki nama.
Anda juga dapat menggunakan teknik penamaan kolom yang lebih eksternal di mana Anda menentukan nama kolom target dalam tanda kurung tepat setelah nama tabel turunan, seperti:
SELECT empid, firstname, lastname, custlocationFROM ( SELECT empid, firstname, lastname, CONCAT_WS(N'/', country, region, city) FROM HR.Employees ) AS D(empid, firstname, lastname, custlocation);Dengan teknik ini, Anda harus membuat daftar nama untuk semua kolom—termasuk yang sudah memiliki nama. Penetapan nama kolom target dilakukan berdasarkan posisi, kiri ke kanan, yaitu, nama kolom target pertama mewakili ekspresi pertama dalam daftar SELECT kueri dalam; nama kolom target kedua mewakili ekspresi kedua; dan seterusnya.
Perhatikan bahwa dalam kasus inkonsistensi antara nama kolom dalam dan luar, katakanlah, karena bug dalam kode, cakupan nama dalam adalah kueri dalam—atau, lebih tepatnya, variabel rentang dalam (di sini secara implisit HR.Employees AS Karyawan)—dan ruang lingkup nama luar adalah variabel rentang luar (D dalam kasus kami). Ada sedikit lebih banyak yang terlibat dalam pelingkupan nama kolom yang berkaitan dengan pemrosesan kueri logis, tetapi itu adalah item untuk diskusi nanti.
Potensi bug dengan sintaks penamaan eksternal paling baik dijelaskan dengan sebuah contoh.
Periksa output dari kueri sebelumnya, dengan set lengkap karyawan dari tabel HR.Employees. Kemudian, pertimbangkan kueri berikut, dan sebelum menjalankannya, coba cari tahu karyawan mana yang Anda harapkan akan dilihat dalam hasil:
SELECT empid, firstname, lastname, custlocationFROM ( SELECT empid, firstname, lastname, CONCAT_WS(N'/', country, region, city) FROM HR.Employees WHERE lastname LIKE N'D%' ) AS D(empid, lastname, firstname, custlocation)WHERE firstname LIKE N'D%';Jika Anda mengharapkan kueri mengembalikan kumpulan kosong untuk data sampel yang diberikan, karena saat ini tidak ada karyawan dengan nama belakang dan nama depan yang dimulai dengan huruf D, Anda kehilangan bug dalam kode.
Sekarang jalankan kueri, dan periksa keluaran sebenarnya:
empid firstname lastname custlocation------ ---------- --------- ---------------1 Davis Sara AS/WA/Seattle9 Doyle Patricia Inggris/LondonApa yang terjadi?
Kueri dalam menentukan nama depan sebagai kolom kedua, dan nama belakang sebagai kolom ketiga dalam daftar SELECT. Kode yang menetapkan nama kolom target tabel turunan di kueri luar menentukan nama belakang kedua dan nama depan ketiga. Kode menamai nama depan sebagai nama belakang dan nama belakang sebagai nama depan dalam variabel rentang D. Secara efektif, Anda hanya memfilter karyawan yang nama belakangnya dimulai dengan huruf D. Anda tidak memfilter karyawan dengan nama belakang dan nama depan yang diawali dengan huruf D.
Sintaks aliasing sebaris tidak rentan terhadap bug semacam itu. Pertama, Anda biasanya tidak membuat alias kolom yang sudah memiliki nama yang Anda sukai. Kedua, bahkan jika Anda ingin menetapkan alias yang berbeda untuk kolom yang sudah memiliki nama, kemungkinan besar dengan sintaks
AS Anda akan menetapkan alias yang salah. Pikirkan tentang itu; seberapa besar kemungkinan Anda akan menulis seperti ini: SELECT empid, firstname, lastname, custlocationFROM ( SELECT empid AS empid, firstname AS lastname, lastname AS firstname, CONCAT_WS(N'/', country, region, city) AS custlocation FROM HR.Employees WHERE lastname LIKE N'D %' ) SEBAGAI DWHERE firstname LIKE N'D%';Jelas, sangat tidak mungkin.
Semua nama kolom harus unik
Kembali ke fakta bahwa heading suatu relasi adalah sekumpulan atribut, dan mengingat bahwa suatu atribut diidentifikasi dengan nama, nama atribut harus unik untuk relasi yang sama. Dalam kueri tertentu, Anda selalu dapat merujuk ke atribut menggunakan nama dua bagian dengan nama variabel rentang sebagai penentu, seperti dalam
. . Jika nama kolom tanpa qualifier tidak ambigu, Anda dapat menghilangkan awalan nama variabel range. Yang penting untuk diingat adalah apa yang saya katakan sebelumnya tentang ruang lingkup nama kolom. Dalam kode yang melibatkan ekspresi tabel bernama, dengan kueri dalam (ekspresi tabel) dan kueri luar, cakupan nama kolom di kueri dalam adalah variabel rentang dalam, dan cakupan nama kolom di luar query adalah variabel jangkauan luar. Jika kueri dalam melibatkan beberapa tabel sumber dengan nama kolom yang sama, Anda masih dapat merujuk ke kolom tersebut dengan cara yang jelas dengan menambahkan nama variabel rentang sebagai awalan. Jika Anda tidak menetapkan nama variabel rentang secara eksplisit, Anda akan menetapkannya secara implisit, seolah-olah Anda menggunakan AS . Pertimbangkan kueri mandiri berikut sebagai contoh:
PILIH C.custid, O.custid, O.orderidFROM Sales.Customers AS C LEFT OUTER JOIN Sales.Orders AS O ON C.custid =O.custid;Kueri ini tidak gagal dengan kesalahan nama kolom duplikat karena satu kolom custid sebenarnya bernama C.custid dan O.custid lainnya dalam lingkup kueri saat ini. Kueri ini menghasilkan keluaran berikut:
pesanan custid custid----------- ----------- -----------1 1 106431 1 106921 1 107021 1 108351 1 109521 1 110112 2 103082 2 106252 2 107592 2 10926...Namun, coba gunakan kueri ini sebagai ekspresi tabel dalam definisi tabel turunan bernama CO, seperti:
PILIH *FROM ( SELECT C.custid, O.custid, O.orderid FROM Sales.Customers AS C LEFT OUTER JOIN Sales.Orders AS O ON C.custid =O.custid ) AS CO;Sejauh menyangkut kueri luar, Anda memiliki satu variabel rentang bernama CO, dan cakupan semua nama kolom di kueri luar adalah variabel rentang itu. Nama semua kolom dalam variabel rentang tertentu (ingat, variabel rentang adalah variabel relasi) harus unik. Karenanya, Anda mendapatkan kesalahan berikut:
Msg 8156, Level 16, State 1, Line 80
Kolom 'custid' ditentukan beberapa kali untuk 'CO'.Cara mengatasinya tentu saja dengan menetapkan nama kolom yang berbeda ke dua kolom custid sejauh menyangkut variabel rentang CO, seperti:
PILIH *FROM ( PILIH C.custid AS custcustid, O.custid AS ordercustid, O.orderid FROM Sales.Customers AS C LEFT OUTER JOIN Sales.Orders AS O ON C.custid =O.custid ) AS CO;Kueri ini menghasilkan keluaran berikut:
pesanan custidcustid orderid----------- ----------- -----------1 1 106431 1 106921 1 107021 1 108351 1 109521 1 110112 2 103082 2 106252 2 107592 2 10926...Jika Anda mengikuti praktik yang baik, Anda secara eksplisit mencantumkan nama kolom di daftar SELECT kueri terluar. Karena hanya ada satu variabel rentang yang terlibat, Anda tidak perlu menggunakan nama dua bagian untuk referensi kolom luar. Jika Anda ingin menggunakan nama dua bagian, Anda mengawali nama kolom dengan nama variabel rentang luar CO, seperti:
PILIH CO.custcustid, CO.ordercustid, CO.orderidFROM ( PILIH C.custid AS custcustid, O.custid AS ordercustid, O.orderid FROM Sales.Customers AS C LEFT OUTER JOIN Sales.Orders AS O ON C.custid =O.custid ) SEBAGAI CO;Tidak ada pesanan
Ada cukup banyak yang harus saya katakan tentang ekspresi dan pengurutan tabel bernama — cukup untuk sebuah artikel sendiri — jadi saya akan mendedikasikan artikel mendatang untuk topik ini. Namun, saya ingin menyentuh topik ini secara singkat di sini karena ini sangat penting. Ingatlah bahwa tubuh suatu relasi adalah kumpulan tupel, dan demikian pula, tubuh tabel adalah kumpulan baris. Satu set tidak memiliki urutan. Namun, SQL mengizinkan kueri terluar untuk memiliki klausa ORDER BY yang menyajikan makna pengurutan presentasi, seperti yang ditunjukkan oleh kueri berikut:
PILIH orderid, valFROM Sales.OrderValuesORDER BY val DESC;Namun, yang perlu Anda pahami adalah bahwa kueri ini tidak mengembalikan relasi sebagai hasilnya. Bahkan dari perspektif SQL, kueri tidak mengembalikan tabel sebagai hasilnya, dan karenanya tidak dianggap sebagai ekspresi tabel. Akibatnya, tidak valid untuk menggunakan kueri seperti itu sebagai bagian ekspresi tabel dari definisi tabel turunan.
Coba jalankan kode berikut:
SELECT orderid, valFROM ( SELECT orderid, val FROM Sales.OrderValues ORDER BY val DESC ) AS D;Anda mendapatkan kesalahan berikut:
Msg 1033, Level 15, State 1, Line 124
Klausa ORDER BY tidak valid dalam tampilan, fungsi inline, tabel turunan, subkueri, dan ekspresi tabel umum, kecuali TOP, OFFSET atau FOR XML juga ditentukan.Saya akan menangani kecuali bagian dari pesan kesalahan segera.
Jika Anda ingin kueri terluar mengembalikan hasil yang diurutkan, Anda perlu menentukan klausa ORDER BY di kueri terluar, seperti:
SELECT orderid, valFROM ( SELECT orderid, val FROM Sales.OrderValues ) AS DORDER BY val DESC;Adapun kecuali bagian dari pesan kesalahan; T-SQL mendukung filter TOP berpemilik serta filter OFFSET-FETCH standar. Kedua filter mengandalkan klausa ORDER BY dalam cakupan kueri yang sama untuk menentukan baris teratas mana yang akan difilter. Sayangnya, ini adalah hasil jebakan dalam desain fitur ini, yang tidak memisahkan urutan presentasi dari urutan filter. Bagaimanapun, baik Microsoft dengan filter TOP-nya, dan standar dengan filter OFFSET-FETCH-nya, memungkinkan menentukan klausa ORDER BY dalam kueri dalam selama itu juga menentukan filter TOP atau OFFSET-FETCH, masing-masing. Jadi, kueri ini valid, misalnya:
SELECT orderid, valFROM ( SELECT TOP (3) orderid, val FROM Sales.OrderValues ORDER BY val DESC ) AS D;Ketika saya menjalankan kueri ini di sistem saya, itu menghasilkan output berikut:
nilai pemesanan-------- ---------10865 16387.5010981 15810.0011030 12615.05Yang penting untuk ditekankan, adalah bahwa satu-satunya alasan klausa ORDER BY diperbolehkan dalam kueri dalam adalah untuk mendukung filter TOP. Itulah satu-satunya jaminan yang Anda dapatkan sejauh menyangkut pemesanan. Karena kueri luar juga tidak memiliki klausa ORDER BY, Anda tidak mendapatkan jaminan untuk pemesanan presentasi tertentu dari kueri ini, terlepas dari apa pun perilaku yang diamati. Itulah yang terjadi di T-SQL, dan juga dalam standar. Berikut kutipan dari standar yang menangani bagian ini:
“Pengurutan baris tabel yang ditentukan olehdijamin hanya untuk yang segera berisi .” Seperti yang disebutkan, ada banyak lagi yang bisa dikatakan tentang ekspresi dan pengurutan tabel, yang akan saya lakukan di artikel mendatang. Saya juga akan memberikan contoh yang menunjukkan bagaimana kurangnya klausa ORDER BY di kueri luar berarti Anda tidak mendapatkan jaminan pemesanan presentasi.
Jadi, ekspresi tabel, misalnya, kueri dalam dalam definisi tabel turunan, adalah tabel. Demikian pula, tabel turunan (dalam arti khusus) itu sendiri juga merupakan tabel. Ini bukan meja dasar, tetapi tetap saja meja. Hal yang sama berlaku untuk CTE, tampilan, dan TVF sebaris. Mereka bukan tabel dasar, melainkan tabel turunan (dalam pengertian yang lebih umum), tetapi bagaimanapun juga tabel.
Kekurangan desain
Tabel turunan memiliki dua kekurangan utama dalam desainnya. Keduanya berkaitan dengan fakta bahwa tabel turunan didefinisikan dalam klausa FROM dari kueri luar.
Satu cacat desain berkaitan dengan fakta bahwa jika Anda perlu membuat kueri tabel turunan dari kueri luar, dan pada gilirannya menggunakan kueri itu sebagai ekspresi tabel dalam definisi tabel turunan lain, Anda akhirnya membuat kueri tabel turunan tersebut. Dalam komputasi, penyatuan kode secara eksplisit yang melibatkan berbagai tingkat penyarangan cenderung menghasilkan kode kompleks yang sulit untuk dipelihara.
Berikut adalah contoh yang sangat mendasar yang menunjukkan hal ini:
SELECT orderyear, numcustsFROM ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM ( SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders ) AS D1 GROUP BY orderyear ) AS D2WHERE numcusts> 70;Kode ini mengembalikan tahun pemesanan dan jumlah pelanggan yang melakukan pemesanan selama setiap tahun, hanya untuk tahun di mana jumlah pelanggan yang melakukan pemesanan lebih besar dari 70.
Motivasi utama menggunakan ekspresi tabel di sini adalah agar dapat merujuk ke kolom alias beberapa kali. Kueri terdalam yang digunakan sebagai ekspresi tabel untuk tabel turunan D1 mengkueri tabel Sales.Orders, dan menetapkan nama kolom orderyear ke ekspresi YEAR(orderdate), dan juga mengembalikan kolom custid. Kueri terhadap D1 mengelompokkan baris dari D1 menurut tahun pesanan, dan mengembalikan tahun pesanan serta jumlah pelanggan yang berbeda yang memesan selama tahun yang bersangkutan alias sebagai numcusts. Kode mendefinisikan tabel turunan yang disebut D2 berdasarkan kueri ini. Kueri terluar dari kueri D2 dan filter hanya tahun di mana jumlah pelanggan yang melakukan pemesanan lebih besar dari 70.
Upaya untuk meninjau kode ini atau memecahkan masalah jika terjadi masalah sulit dilakukan karena beberapa tingkat penyarangan. Alih-alih meninjau kode dengan cara yang lebih alami dari atas ke bawah, Anda mendapati diri Anda harus menganalisisnya mulai dari unit terdalam dan secara bertahap keluar, karena itu lebih praktis.
Inti dari penggunaan tabel turunan dalam contoh ini adalah untuk menyederhanakan kode dengan menghindari kebutuhan untuk mengulang ekspresi. Tapi saya tidak yakin bahwa solusi ini mencapai tujuan ini. Dalam hal ini, Anda mungkin lebih baik mengulangi beberapa ekspresi, menghindari kebutuhan untuk menggunakan tabel turunan sama sekali, seperti:
PILIH TAHUN(tanggal pemesanan) SEBAGAI tahun pesanan, COUNT(JUMLAH BERBEDA) SEBAGAI numcustsFROM Penjualan.OrdersGROUP BY YEAR(orderdate)HAVING COUNT(DISTINCT custid)> 70;Ingatlah bahwa saya menunjukkan contoh yang sangat sederhana di sini untuk tujuan ilustrasi. Bayangkan kode produksi dengan tingkat yang lebih tinggi, dan dengan kode yang lebih panjang dan rumit, dan Anda dapat melihat bagaimana pemeliharaannya menjadi jauh lebih rumit.
Kelemahan lain dalam desain tabel turunan berkaitan dengan kasus di mana Anda perlu berinteraksi dengan beberapa instance dari tabel turunan yang sama. Pertimbangkan kueri berikut sebagai contoh:
SELECT CUR.orderyear, CUR.numorders, CUR.numorders - PRV.numorders AS diffFROM ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders FROM Sales.Orders GROUP BY YEAR(orderdate) ) AS CUR LEFT OUTER JOIN ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders FROM Sales.Orders GROUP BY YEAR(orderdate) ) AS PRV ON CUR.orderyear =PRV.orderyear + 1;Kode ini menghitung jumlah pesanan yang diproses di setiap tahun, serta perbedaan dari tahun sebelumnya. Abaikan fakta bahwa ada cara yang lebih sederhana untuk mencapai tugas yang sama dengan fungsi jendela—saya menggunakan kode ini untuk mengilustrasikan poin tertentu, jadi tugas itu sendiri dan berbagai cara untuk menyelesaikannya tidak signifikan.
Gabung adalah operator tabel yang memperlakukan dua inputnya sebagai satu set—artinya tidak ada urutan di antara keduanya. Mereka disebut sebagai input kiri dan kanan sehingga Anda dapat menandai salah satunya (atau keduanya) sebagai tabel yang diawetkan di gabungan luar, tetapi tetap saja, tidak ada yang pertama dan kedua di antara keduanya. Anda diperbolehkan menggunakan tabel turunan sebagai input gabungan, tetapi nama variabel rentang yang Anda tetapkan ke input kiri tidak dapat diakses dalam definisi input kanan. Itu karena keduanya secara konseptual didefinisikan dalam langkah logis yang sama, seolah-olah pada titik waktu yang sama. Akibatnya, saat menggabungkan tabel turunan, Anda tidak dapat mendefinisikan dua variabel rentang berdasarkan satu ekspresi tabel. Sayangnya, Anda harus mengulang kode, mendefinisikan dua variabel rentang berdasarkan dua salinan kode yang identik. Ini tentu saja memperumit pemeliharaan kode, dan meningkatkan kemungkinan bug. Setiap perubahan yang Anda buat pada satu ekspresi tabel juga perlu diterapkan ke yang lain.
Seperti yang akan saya jelaskan di artikel mendatang, CTE, dalam desainnya, tidak menimbulkan dua kekurangan yang ditimbulkan oleh tabel turunan ini.
Konstruktor nilai tabel
Konstruktor nilai tabel memungkinkan Anda membuat nilai tabel berdasarkan ekspresi skalar mandiri. Anda kemudian dapat menggunakan tabel seperti itu dalam kueri luar seperti Anda menggunakan tabel turunan yang didasarkan pada kueri dalam. Dalam artikel mendatang saya membahas tabel turunan lateral and correlations in detail, and I’ll show more sophisticated forms of table value constructors. In this article, though, I’ll focus on a simple form that is based purely on self-contained scalar expressions.
The general syntax for a query against a table value constructor is as follows:
SELECT