Artikel ini adalah bagian kelima dari seri tentang ekspresi tabel. Di Bagian 1 saya memberikan latar belakang untuk ekspresi tabel. Di Bagian 2, Bagian 3, dan Bagian 4, saya membahas aspek logis dan optimasi dari tabel turunan. Bulan ini saya memulai liputan ekspresi tabel umum (CTE). Seperti tabel turunan, pertama-tama saya akan membahas perlakuan logis CTE, dan di masa mendatang saya akan membahas pertimbangan pengoptimalan.
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.
CTE
Mari kita mulai dengan istilah ekspresi tabel umum . Baik istilah ini, maupun akronimnya CTE, tidak muncul dalam spesifikasi standar ISO/IEC SQL. Jadi bisa jadi istilah tersebut berasal dari salah satu produk database dan kemudian diadopsi oleh beberapa vendor database lainnya. Anda dapat menemukannya di dokumentasi Microsoft SQL Server dan Azure SQL Database. T-SQL mendukungnya dimulai dengan SQL Server 2005. Standar ini menggunakan istilah ekspresi kueri untuk mewakili ekspresi yang mendefinisikan satu atau lebih CTE, termasuk kueri luar. Ini menggunakan istilah dengan elemen daftar untuk mewakili apa yang disebut T-SQL sebagai CTE. Saya akan segera memberikan sintaks untuk ekspresi kueri.
Selain sumber istilah, ekspresi tabel umum , atau CTE , adalah istilah yang umum digunakan oleh praktisi T-SQL untuk struktur yang menjadi fokus artikel ini. Jadi pertama-tama, mari kita bahas apakah itu istilah yang tepat. Kami telah menyimpulkan bahwa istilah ekspresi tabel sesuai untuk ekspresi yang secara konseptual mengembalikan tabel. Tabel turunan, CTE, tampilan, dan fungsi bernilai tabel sebaris adalah semua jenis ekspresi tabel bernama yang didukung T-SQL. Jadi, ekspresi tabel bagian dari ekspresi tabel umum tentu tampaknya sesuai. Adapun umum bagian dari istilah, itu mungkin ada hubungannya dengan salah satu keunggulan desain CTE dibandingkan tabel turunan. Ingatlah bahwa Anda tidak dapat menggunakan kembali nama tabel turunan (atau lebih tepatnya nama variabel rentang) lebih dari sekali dalam kueri luar. Sebaliknya, nama CTE dapat digunakan beberapa kali dalam kueri luar. Dengan kata lain, nama CTE adalah umum ke kueri luar. Tentu saja, saya akan menunjukkan aspek desain ini di artikel ini.
CTE memberi Anda manfaat serupa dengan tabel turunan, termasuk memungkinkan pengembangan solusi modular, menggunakan kembali alias kolom, berinteraksi secara tidak langsung dengan fungsi jendela dalam klausa yang biasanya tidak mengizinkannya, mendukung modifikasi yang secara tidak langsung mengandalkan TOP atau OFFSET FETCH dengan spesifikasi pesanan, dan lain-lain. Namun ada keunggulan desain tertentu dibandingkan dengan tabel turunan, yang akan saya bahas secara mendetail setelah saya memberikan sintaks untuk strukturnya.
Sintaks
Berikut sintaks standar untuk ekspresi kueri:
7.17
Fungsi
Tentukan tabel.
Format
[
[
SEBAGAI
|
[
|
[
|
[
|
[
SESUAI [ OLEH
FETCH { PERTAMA | BERIKUTNYA } [
|
7.18
Fungsi
Menentukan pembuatan informasi pengurutan dan deteksi siklus dalam hasil ekspresi kueri rekursif.
Format
SEARCH
KEDALAMAN PERTAMA OLEH
CYCLE
DEFAULT
7.3
Fungsi
Tentukan satu set
Istilah standar ekspresi kueri mewakili ekspresi yang melibatkan klausa WITH, dengan daftar , yang terbuat dari satu atau lebih dengan elemen daftar , dan kueri luar. T-SQL mengacu pada standar dengan elemen daftar sebagai CTE.
T-SQL tidak mendukung semua elemen sintaks standar. Misalnya, ini tidak mendukung beberapa elemen kueri rekursif lanjutan yang memungkinkan Anda mengontrol arah penelusuran dan menangani siklus dalam struktur grafik. Kueri rekursif adalah fokus artikel bulan depan.
Berikut sintaks T-SQL untuk kueri yang disederhanakan terhadap CTE:
Berikut ini contoh kueri sederhana terhadap CTE yang mewakili pelanggan AS:
Anda akan menemukan tiga bagian yang sama dalam pernyataan terhadap CTE seperti yang Anda lakukan dengan pernyataan terhadap tabel turunan:
Apa yang berbeda tentang desain CTE dibandingkan dengan tabel turunan adalah di mana dalam kode ketiga elemen ini berada. Dengan tabel turunan, kueri dalam disarangkan di dalam klausa FROM kueri luar, dan nama ekspresi tabel ditetapkan setelah ekspresi tabel itu sendiri. Elemen-elemennya semacam saling terkait. Sebaliknya, dengan CTE, kode memisahkan tiga elemen:pertama Anda menetapkan nama ekspresi tabel; kedua Anda menentukan ekspresi tabel—dari awal hingga akhir tanpa interupsi; ketiga Anda menentukan kueri luar—dari awal hingga akhir tanpa interupsi. Nanti, di bagian “Pertimbangan desain”, saya akan menjelaskan implikasi dari perbedaan desain ini.
Sepatah kata tentang CTE dan penggunaan titik koma sebagai terminator pernyataan. Sayangnya, tidak seperti SQL standar, T-SQL tidak memaksa Anda untuk mengakhiri semua pernyataan dengan titik koma. Namun, ada sangat sedikit kasus di T-SQL di mana tanpa terminator kodenya ambigu. Dalam kasus tersebut, penghentian adalah wajib. Salah satu kasus tersebut menyangkut fakta bahwa klausa WITH digunakan untuk berbagai tujuan. Salah satunya adalah untuk mendefinisikan CTE, yang lain adalah untuk mendefinisikan petunjuk tabel untuk kueri, dan ada beberapa kasus penggunaan tambahan. Sebagai contoh, dalam pernyataan berikut, klausa WITH digunakan untuk memaksa tingkat isolasi serial dengan petunjuk tabel:
Potensi ambiguitas adalah ketika Anda memiliki pernyataan yang tidak diakhiri sebelum definisi CTE, dalam hal ini pengurai mungkin tidak dapat mengetahui apakah klausa WITH termasuk dalam pernyataan pertama atau kedua. Berikut ini contoh yang menunjukkan hal ini:
Di sini parser tidak dapat menentukan apakah klausa WITH seharusnya digunakan untuk mendefinisikan petunjuk tabel untuk tabel Pelanggan dalam pernyataan pertama, atau memulai definisi CTE. Anda mendapatkan kesalahan berikut:
Perbaikannya tentu saja untuk mengakhiri pernyataan sebelum definisi CTE, tetapi sebagai praktik terbaik, Anda benar-benar harus menghentikan semua pernyataan Anda:
Anda mungkin telah memperhatikan bahwa beberapa orang memulai definisi CTE mereka dengan titik koma sebagai praktik, seperti:
Inti dari praktik ini adalah untuk mengurangi potensi kesalahan di masa mendatang. Bagaimana jika di kemudian hari seseorang menambahkan pernyataan yang tidak diakhiri tepat sebelum definisi CTE Anda dalam skrip, dan tidak repot-repot memeriksa skrip lengkap, melainkan hanya pernyataan mereka? Titik koma Anda tepat sebelum klausa WITH secara efektif menjadi terminator pernyataan mereka. Anda tentu dapat melihat kepraktisan dari latihan ini, tetapi ini agak tidak wajar. Apa yang direkomendasikan, meskipun lebih sulit untuk dicapai, adalah menanamkan praktik pemrograman yang baik dalam organisasi, termasuk penghentian semua pernyataan.
Dalam hal aturan sintaks yang berlaku untuk ekspresi tabel yang digunakan sebagai kueri dalam dalam definisi CTE, aturan tersebut sama dengan aturan yang berlaku untuk ekspresi tabel yang digunakan sebagai kueri dalam dalam definisi tabel turunan. Yaitu:
Untuk detailnya, lihat bagian “Ekspresi tabel adalah tabel” di Bagian 2 seri ini.
Jika Anda mensurvei pengembang T-SQL yang berpengalaman tentang apakah mereka lebih suka menggunakan tabel turunan atau CTE, tidak semua orang akan setuju mana yang lebih baik. Secara alami, orang yang berbeda memiliki preferensi gaya yang berbeda. Saya terkadang menggunakan tabel turunan dan terkadang CTE. Ada baiknya jika Anda secara sadar mengidentifikasi perbedaan desain bahasa tertentu antara kedua alat tersebut, dan memilih berdasarkan prioritas Anda dalam setiap solusi yang diberikan. Dengan waktu dan pengalaman, Anda membuat pilihan dengan lebih intuitif.
Selain itu, penting untuk tidak membingungkan penggunaan ekspresi tabel dan tabel sementara, tetapi itu adalah diskusi terkait kinerja yang akan saya bahas di artikel mendatang.
CTE memiliki kemampuan kueri rekursif dan tabel turunan tidak. Jadi, jika Anda perlu mengandalkan itu, Anda tentu akan menggunakan CTE. Kueri rekursif adalah fokus artikel bulan depan.
Di Bagian 2 saya menjelaskan bahwa saya melihat penggabungan tabel turunan sebagai penambahan kompleksitas pada kode, karena membuatnya sulit untuk mengikuti logika. Saya memberikan contoh berikut, mengidentifikasi tahun pemesanan di mana lebih dari 70 pelanggan melakukan pemesanan:
CTE tidak mendukung bersarang. Jadi, saat Anda meninjau atau memecahkan masalah solusi berdasarkan CTE, Anda tidak tersesat dalam logika bersarang. Alih-alih bersarang, Anda membangun lebih banyak solusi modular dengan mendefinisikan beberapa CTE di bawah pernyataan WITH yang sama, dipisahkan dengan koma. Setiap CTE didasarkan pada kueri yang ditulis dari awal hingga akhir tanpa interupsi. Saya melihatnya sebagai hal yang baik dari perspektif kejelasan kode dan pemeliharaan.
Berikut solusi untuk tugas yang disebutkan di atas menggunakan CTE:
Saya lebih suka solusi berbasis CTE. Tetapi sekali lagi, tanyakan kepada pengembang berpengalaman yang mana dari dua solusi di atas yang mereka sukai, dan mereka tidak akan semua setuju. Beberapa sebenarnya lebih menyukai logika bersarang, dan dapat melihat semuanya di satu tempat.
Satu keuntungan yang sangat jelas dari CTE dibandingkan tabel turunan, adalah ketika Anda perlu berinteraksi dengan beberapa instance dari ekspresi tabel yang sama dalam solusi Anda. Ingat contoh berikut berdasarkan tabel turunan dari Bagian 2 dalam seri:
Solusi ini mengembalikan tahun pesanan, jumlah pesanan per tahun, dan perbedaan antara jumlah tahun ini dan tahun sebelumnya. Ya, Anda dapat melakukannya dengan lebih mudah dengan fungsi LAG, tetapi fokus saya di sini bukanlah menemukan cara terbaik untuk mencapai tugas yang sangat spesifik ini. Saya menggunakan contoh ini untuk mengilustrasikan aspek desain bahasa tertentu dari ekspresi tabel bernama.
Masalah dengan solusi ini adalah Anda tidak dapat menetapkan nama ke ekspresi tabel dan menggunakannya kembali dalam langkah pemrosesan kueri logis yang sama. Anda memberi nama tabel turunan setelah ekspresi tabel itu sendiri dalam klausa FROM. Jika Anda mendefinisikan dan memberi nama tabel turunan sebagai input pertama dari gabungan, Anda juga tidak dapat menggunakan kembali nama tabel turunan itu sebagai input kedua dari gabungan yang sama. Jika Anda perlu menggabungkan sendiri dua instance dari ekspresi tabel yang sama, dengan tabel turunan Anda tidak punya pilihan selain menduplikasi kode. Itulah yang Anda lakukan dalam contoh di atas. Sebaliknya, nama CTE ditetapkan sebagai elemen pertama dari kode di antara tiga yang disebutkan di atas (nama CTE, kueri dalam, kueri luar). Dalam istilah pemrosesan kueri logis, pada saat Anda masuk ke kueri luar, nama CTE sudah ditentukan dan tersedia. Ini berarti Anda dapat berinteraksi dengan beberapa contoh nama CTE di kueri luar, seperti:
Solusi ini memiliki keunggulan programabilitas yang jelas dibandingkan solusi yang didasarkan pada tabel turunan karena Anda tidak perlu mempertahankan dua salinan dari ekspresi tabel yang sama. Masih banyak yang bisa dikatakan tentangnya dari perspektif pemrosesan fisik, dan membandingkannya dengan penggunaan tabel sementara, tetapi saya akan melakukannya di artikel mendatang yang berfokus pada kinerja.
Satu keuntungan bahwa kode berdasarkan tabel turunan dibandingkan dengan kode berdasarkan CTE berkaitan dengan properti penutupan yang seharusnya dimiliki oleh ekspresi tabel. Ingat bahwa properti penutupan dari ekspresi relasional mengatakan bahwa baik input maupun output adalah relasi, dan oleh karena itu ekspresi relasional dapat digunakan di mana suatu relasi diharapkan, sebagai input ke ekspresi relasional lainnya. Demikian pula, ekspresi tabel mengembalikan tabel dan seharusnya tersedia sebagai tabel input untuk ekspresi tabel lain. Ini berlaku untuk kueri yang didasarkan pada tabel turunan—Anda bisa menggunakannya di tempat yang diharapkan memiliki tabel. Misalnya, Anda bisa menggunakan kueri yang didasarkan pada tabel turunan sebagai kueri dalam dari definisi CTE, seperti dalam contoh berikut:
Namun, hal yang sama tidak berlaku untuk kueri yang didasarkan pada CTE. Meskipun secara konseptual dianggap sebagai ekspresi tabel, Anda tidak dapat menggunakannya sebagai kueri dalam dalam definisi tabel turunan, subkueri, dan CTE itu sendiri. Misalnya, kode berikut ini tidak valid di T-SQL:
Kabar baiknya adalah Anda dapat menggunakan kueri yang didasarkan pada CTE sebagai kueri dalam dalam tampilan dan fungsi bernilai tabel sebaris, yang akan saya bahas di artikel mendatang.
Juga, ingat, Anda selalu dapat menentukan CTE lain berdasarkan kueri terakhir, dan kemudian meminta kueri terluar berinteraksi dengan CTE itu:
Dari sudut pandang pemecahan masalah, seperti yang disebutkan, saya biasanya lebih mudah mengikuti logika kode yang didasarkan pada CTE, dibandingkan dengan kode yang didasarkan pada tabel turunan. Namun, solusi berdasarkan tabel turunan memiliki keuntungan karena Anda dapat menyorot setiap level bersarang dan menjalankannya secara independen, seperti yang ditunjukkan pada Gambar 1.
Gambar 1:Dapat menyorot dan menjalankan bagian kode dengan tabel turunan
Dengan CTE, segalanya menjadi lebih rumit. Agar kode yang melibatkan CTE dapat dijalankan, kode tersebut harus dimulai dengan klausa WITH, diikuti oleh satu atau lebih ekspresi tabel yang diberi tanda kurung yang dipisahkan dengan koma, diikuti oleh kueri yang tidak diberi tanda kurung tanpa koma sebelumnya. Anda dapat menyorot dan menjalankan salah satu kueri dalam yang benar-benar mandiri, serta kode solusi lengkapnya; namun, Anda tidak dapat menyorot dan berhasil menjalankan bagian perantara lainnya dari solusi. Misalnya, Gambar 2 menunjukkan upaya yang gagal untuk menjalankan kode yang mewakili C2.
Gambar 2:Tidak dapat menyorot dan menjalankan bagian kode dengan CTE
Jadi dengan CTE, Anda harus menggunakan cara yang agak canggung agar dapat memecahkan masalah langkah menengah dari solusi. Misalnya, satu solusi umum adalah untuk sementara menyuntikkan kueri SELECT * FROM your_cte tepat di bawah CTE yang relevan. Anda kemudian menyorot dan menjalankan kode termasuk kueri yang disuntikkan, dan setelah selesai, Anda menghapus kueri yang disuntikkan. Gambar 3 menunjukkan teknik ini.
Gambar 3:Inject SELECT * di bawah CTE yang relevan
Masalahnya adalah setiap kali Anda membuat perubahan pada kode—bahkan perubahan kecil sementara seperti di atas—ada kemungkinan ketika Anda mencoba untuk kembali ke kode asli, Anda akan menemukan bug baru.
Opsi lainnya adalah menata kode Anda sedikit berbeda, sehingga setiap definisi CTE bukan pertama dimulai dengan baris kode terpisah yang terlihat seperti ini:
Kemudian, kapan pun Anda ingin menjalankan bagian perantara dari kode ke CTE tertentu, Anda dapat melakukannya dengan sedikit perubahan pada kode Anda. Dengan menggunakan komentar baris, Anda hanya mengomentari satu baris kode yang sesuai dengan CTE itu. Anda kemudian menyorot dan menjalankan kode ke dan memasukkan kueri dalam CTE itu, yang sekarang dianggap sebagai kueri terluar, seperti yang diilustrasikan pada Gambar 4.
Gambar 4:Atur ulang sintaks untuk mengaktifkan komentar satu baris kode
Jika Anda tidak puas dengan gaya ini, Anda masih memiliki pilihan lain. Anda dapat menggunakan komentar blok yang dimulai tepat sebelum koma yang mendahului CTE yang diinginkan dan berakhir setelah kurung buka, seperti yang diilustrasikan pada Gambar 5.
Gambar 5:Gunakan komentar blokir
Itu bermuara pada preferensi pribadi. Saya biasanya menggunakan teknik kueri SELECT * yang disuntikkan sementara.
Ada batasan tertentu dalam dukungan T-SQL untuk konstruktor nilai tabel dibandingkan dengan standar. Jika Anda tidak terbiasa dengan konstruksinya, pastikan untuk memeriksa Bagian 2 dalam seri ini terlebih dahulu, di mana saya menjelaskannya secara rinci. Sementara T-SQL memungkinkan Anda untuk mendefinisikan tabel turunan berdasarkan konstruktor nilai tabel, T-SQL tidak memungkinkan Anda untuk mendefinisikan CTE berdasarkan konstruktor nilai tabel.
Berikut adalah contoh yang didukung yang menggunakan tabel turunan:
Sayangnya, kode serupa yang menggunakan CTE tidak didukung:
Kode ini menghasilkan kesalahan berikut:
Ada beberapa solusi, meskipun. Salah satunya adalah dengan menggunakan kueri terhadap tabel turunan, yang pada gilirannya didasarkan pada konstruktor nilai tabel, sebagai kueri dalam CTE, seperti:
Cara lainnya adalah menggunakan teknik yang digunakan orang sebelum konstruktor bernilai tabel diperkenalkan ke T-SQL—menggunakan serangkaian kueri FROMless yang dipisahkan oleh operator UNION ALL, seperti:
Perhatikan bahwa alias kolom diberikan tepat setelah nama CTE.
Kedua metode ini dialjabar dan dioptimalkan sama, jadi gunakan mana saja yang lebih nyaman bagi Anda.
Alat yang cukup sering saya gunakan dalam solusi saya adalah tabel angka tambahan. Salah satu opsi adalah membuat tabel angka aktual di database Anda dan mengisinya dengan urutan berukuran wajar. Lain adalah untuk mengembangkan solusi yang menghasilkan urutan angka dengan cepat. Untuk opsi terakhir, Anda ingin input menjadi pembatas dari rentang yang diinginkan (kami akan menyebutnya
Kode ini menghasilkan output berikut:
CTE pertama yang disebut L0 didasarkan pada konstruktor nilai tabel dengan dua baris. Nilai aktual di sana tidak signifikan; yang penting ada dua baris. Kemudian, ada urutan lima CTE tambahan bernama L1 hingga L5, masing-masing menerapkan gabungan silang antara dua contoh CTE sebelumnya. Kode berikut menghitung jumlah baris yang berpotensi dihasilkan oleh masing-masing CTE, di mana @L adalah nomor level CTE:
Berikut adalah angka yang Anda dapatkan untuk setiap CTE:
Naik ke level 5 memberi Anda lebih dari empat miliar baris. Ini seharusnya cukup untuk kasus penggunaan praktis apa pun yang dapat saya pikirkan. Langkah selanjutnya terjadi di CTE yang disebut Nums. Anda menggunakan fungsi ROW_NUMBER untuk menghasilkan urutan bilangan bulat yang dimulai dengan 1 berdasarkan urutan yang tidak ditentukan (ORDER BY (SELECT NULL)), dan beri nama kolom hasil rownum. Terakhir, kueri luar menggunakan filter TOP berdasarkan urutan rownum untuk memfilter sebanyak mungkin bilangan sesuai dengan kardinalitas urutan yang diinginkan (@tinggi – @rendah + 1), dan menghitung nomor hasil n sebagai @rendah + rownum – 1.
Di sini Anda dapat benar-benar menghargai keindahan dalam desain CTE dan penghematan yang dimungkinkan saat Anda membangun solusi dengan cara modular. Pada akhirnya, proses unnesting membongkar 32 tabel, masing-masing terdiri dari dua baris berdasarkan konstanta. Hal ini dapat dilihat dengan jelas pada rencana eksekusi untuk kode ini, seperti yang ditunjukkan pada Gambar 6 menggunakan SentryOne Plan Explorer.
Gambar 6:Rencana kueri yang menghasilkan urutan angka
Setiap operator Pemindaian Konstan mewakili tabel konstanta dengan dua baris. Masalahnya, operator Top adalah yang meminta baris-baris itu, dan korsleting setelah mendapat nomor yang diinginkan. Perhatikan 10 baris yang ditunjukkan di atas panah yang mengalir ke operator Top.
Saya tahu bahwa fokus artikel ini adalah perlakuan konseptual CTE dan bukan pertimbangan fisik/kinerja, tetapi dengan melihat rencananya, Anda dapat benar-benar menghargai singkatnya kode dibandingkan dengan bertele-tele dari apa yang diterjemahkan ke belakang layar.
Dengan menggunakan tabel turunan, Anda sebenarnya dapat menulis solusi yang menggantikan setiap referensi CTE dengan kueri dasar yang diwakilinya. Apa yang Anda dapatkan cukup menakutkan:
Obviously, you don’t want to write a solution like this, but it’s a good way to illustrate what SQL Server does behind the scenes with your CTE code.
If you were really planning to write a solution based on derived tables, instead of using the above nested approach, you’d be better off simplifying the logic to a single query with 31 cross joins between 32 table value constructors, each based on two rows, like so:
Still, the solution based on CTEs is obviously significantly simpler. The plans are identical.
CTEs can be used as the source and target tables in INSERT, UPDATE, DELETE and MERGE statements. They cannot be used in the TRUNCATE statement.
The syntax is pretty straightforward. You start the statement as usual with a WITH clause, followed by one or more CTEs separated by commas. Then you specify the outer modification statement, which interacts with the CTEs that were defined under the WITH clause as the source tables, target table, or both. Just like I explained in Part 2 about derived tables, also with CTEs what really gets modified is the underlying base table that the table expression uses. I’ll show a couple of examples using DELETE and UPDATE statements, but remember that you can use CTEs in MERGE and INSERT statements as well.
Here’s the general syntax of a DELETE statement against a CTE:
As an example (don’t actually run it), the following code deletes the 10 oldest orders:
Here’s the general syntax of an UPDATE statement against a CTE:
As an example, the following code updates the 10 oldest unshipped orders that have an overdue required date, increasing the required date to 10 days from today:
The code applies the update in a transaction that it then rolls back so that the change won’t stick.
This code generates the following output, showing both the old and the new required dates:
Of course you will get a different new required date based on when you run this code.
I like CTEs. They have a few advantages compared to derived tables. Instead of nesting the code, you define multiple CTEs separated by commas, typically leading to a more modular solution that is easier to review and maintain. Also, you can have multiple references to the same CTE name in the outer statement, so you don’t need to repeat the inner table expression’s code. However, unlike derived tables, CTEs cannot be defined directly based on a table value constructor, and you cannot highlight and execute some of the intermediate parts of the code. The following table summarizes the differences between derived tables and CTEs:
As the last item says, derived tables do not support recursive capabilities, whereas CTEs do. Recursive queries are the focus of next month’s article.
Format
VALUES
[ { WITH < table name > [ (< target columns >) ] AS
(
< table expression >
)
SELECT < select list >
FROM < table name >;
WITH UC AS
(
SELECT custid, companyname
FROM Sales.Customers
WHERE country = N'USA'
)
SELECT custid, companyname
FROM UC;
SELECT custid, country FROM Sales.Customers WITH (SERIALIZABLE);
SELECT custid, country FROM Sales.Customers
WITH UC AS
(
SELECT custid, companyname
FROM Sales.Customers
WHERE country = N'USA'
)
SELECT custid, companyname
FROM UC
Sintaks salah di dekat 'UC'. Jika ini dimaksudkan sebagai ekspresi tabel yang umum, Anda harus secara eksplisit mengakhiri pernyataan sebelumnya dengan titik koma. SELECT custid, country FROM Sales.Customers;
WITH UC AS
(
SELECT custid, companyname
FROM Sales.Customers
WHERE country = N'USA'
)
SELECT custid, companyname
FROM UC;
;WITH UC AS
(
SELECT custid, companyname
FROM Sales.Customers
WHERE country = N'USA'
)
SELECT custid, companyname
FROM UC;
Pertimbangan desain
SELECT orderyear, numcusts
FROM ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
FROM ( SELECT YEAR(orderdate) AS orderyear, custid
FROM Sales.Orders ) AS D1
GROUP BY orderyear ) AS D2
WHERE numcusts > 70;
WITH C1 AS
(
SELECT YEAR(orderdate) AS orderyear, custid
FROM Sales.Orders
),
C2 AS
(
SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
FROM C1
GROUP BY orderyear
)
SELECT orderyear, numcusts
FROM C2
WHERE numcusts > 70;
SELECT CUR.orderyear, CUR.numorders,
CUR.numorders - PRV.numorders AS diff
FROM ( 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;
WITH OrdCount AS
(
SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders
FROM Sales.Orders
GROUP BY YEAR(orderdate)
)
SELECT CUR.orderyear, CUR.numorders,
CUR.numorders - PRV.numorders AS diff
FROM OrdCount AS CUR
LEFT OUTER JOIN OrdCount AS PRV
ON CUR.orderyear = PRV.orderyear + 1;
WITH C AS
(
SELECT orderyear, numcusts
FROM ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
FROM ( SELECT YEAR(orderdate) AS orderyear, custid
FROM Sales.Orders ) AS D1
GROUP BY orderyear ) AS D2
WHERE numcusts > 70
)
SELECT orderyear, numcusts
FROM C;
SELECT orderyear, custid
FROM (WITH C1 AS
(
SELECT YEAR(orderdate) AS orderyear, custid
FROM Sales.Orders
),
C2 AS
(
SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
FROM C1
GROUP BY orderyear
)
SELECT orderyear, numcusts
FROM C2
WHERE numcusts > 70) AS D;
WITH C1 AS
(
SELECT YEAR(orderdate) AS orderyear, custid
FROM Sales.Orders
),
C2 AS
(
SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
FROM C1
GROUP BY orderyear
),
C3 AS
(
SELECT orderyear, numcusts
FROM C2
WHERE numcusts > 70
)
SELECT orderyear, numcusts
FROM C3;
, cte_name AS (
Konstruktor nilai tabel
SELECT custid, companyname, contractdate
FROM ( VALUES( 2, 'Cust 2', '20200212' ),
( 3, 'Cust 3', '20200118' ),
( 5, 'Cust 5', '20200401' ) )
AS MyCusts(custid, companyname, contractdate);
WITH MyCusts(custid, companyname, contractdate) AS
(
VALUES( 2, 'Cust 2', '20200212' ),
( 3, 'Cust 3', '20200118' ),
( 5, 'Cust 5', '20200401' )
)
SELECT custid, companyname, contractdate
FROM MyCusts;
Sintaks salah di dekat kata kunci 'VALUES'. WITH MyCusts AS
(
SELECT *
FROM ( VALUES( 2, 'Cust 2', '20200212' ),
( 3, 'Cust 3', '20200118' ),
( 5, 'Cust 5', '20200401' ) )
AS MyCusts(custid, companyname, contractdate)
)
SELECT custid, companyname, contractdate
FROM MyCusts;
WITH MyCusts(custid, companyname, contractdate) AS
(
SELECT 2, 'Cust 2', '20200212'
UNION ALL SELECT 3, 'Cust 3', '20200118'
UNION ALL SELECT 5, 'Cust 5', '20200401'
)
SELECT custid, companyname, contractdate
FROM MyCusts;
Membuat urutan angka
@low
dan @high
). Anda ingin solusi Anda mendukung rentang yang berpotensi besar. Inilah solusi saya untuk tujuan ini, menggunakan CTE, dengan permintaan untuk rentang 1001 hingga 1010 dalam contoh khusus ini:DECLARE @low AS BIGINT = 1001, @high AS BIGINT = 1010;
WITH
L0 AS ( SELECT 1 AS c FROM (VALUES(1),(1)) AS D(c) ),
L1 AS ( SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B ),
L2 AS ( SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B ),
L3 AS ( SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B ),
L4 AS ( SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B ),
L5 AS ( SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B ),
Nums AS ( SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rownum
FROM L5 )
SELECT TOP(@high - @low + 1) @low + rownum - 1 AS n
FROM Nums
ORDER BY rownum;
n
-----
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
DECLARE @L AS INT = 5;
SELECT POWER(2., POWER(2., @L));
CTE Kardinalitas L0 2 L1 4 L2 16 L3 256 L4 65.536 L5 4.294.967.296 DECLARE @low AS BIGINT = 1001, @high AS BIGINT = 1010;
SELECT TOP(@high - @low + 1) @low + rownum - 1 AS n
FROM ( SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rownum
FROM ( SELECT 1 AS C
FROM ( SELECT 1 AS C
FROM ( SELECT 1 AS C
FROM ( SELECT 1 AS C
FROM (VALUES(1),(1)) AS D01(c)
CROSS JOIN
(VALUES(1),(1)) AS D02(c) ) AS D3
CROSS JOIN
( SELECT 1 AS C
FROM (VALUES(1),(1)) AS D01(c)
CROSS JOIN
(VALUES(1),(1)) AS D02(c) ) AS D4 ) AS D5
CROSS JOIN
( SELECT 1 AS C
FROM ( SELECT 1 AS C
FROM (VALUES(1),(1)) AS D01(c)
CROSS JOIN
(VALUES(1),(1)) AS D02(c) ) AS D3
CROSS JOIN
( SELECT 1 AS C
FROM (VALUES(1),(1)) AS D01(c)
CROSS JOIN
(VALUES(1),(1)) AS D02(c) ) AS D4 ) AS D6 ) AS D7
CROSS JOIN
( SELECT 1 AS C
FROM ( SELECT 1 AS C
FROM ( SELECT 1 AS C
FROM (VALUES(1),(1)) AS D01(c)
CROSS JOIN
(VALUES(1),(1)) AS D02(c) ) AS D3
CROSS JOIN
( SELECT 1 AS C
FROM (VALUES(1),(1)) AS D01(c)
CROSS JOIN
(VALUES(1),(1)) AS D02(c) ) AS D4 ) AS D5
CROSS JOIN
( SELECT 1 AS C
FROM ( SELECT 1 AS C
FROM (VALUES(1),(1)) AS D01(c)
CROSS JOIN
(VALUES(1),(1)) AS D02(c) ) AS D3
CROSS JOIN
( SELECT 1 AS C
FROM (VALUES(1),(1)) AS D01(c)
CROSS JOIN
(VALUES(1),(1)) AS D02(c) ) AS D4 ) AS D6 ) AS D8 ) AS D9
CROSS JOIN
( SELECT 1 AS C
FROM ( SELECT 1 AS C
FROM ( SELECT 1 AS C
FROM ( SELECT 1 AS C
FROM (VALUES(1),(1)) AS D01(c)
CROSS JOIN
(VALUES(1),(1)) AS D02(c) ) AS D3
CROSS JOIN
( SELECT 1 AS C
FROM (VALUES(1),(1)) AS D01(c)
CROSS JOIN
(VALUES(1),(1)) AS D02(c) ) AS D4 ) AS D5
CROSS JOIN
( SELECT 1 AS C
FROM ( SELECT 1 AS C
FROM (VALUES(1),(1)) AS D01(c)
CROSS JOIN
(VALUES(1),(1)) AS D02(c) ) AS D3
CROSS JOIN
( SELECT 1 AS C
FROM (VALUES(1),(1)) AS D01(c)
CROSS JOIN
(VALUES(1),(1)) AS D02(c) ) AS D4 ) AS D6 ) AS D7
CROSS JOIN
( SELECT 1 AS C
FROM ( SELECT 1 AS C
FROM ( SELECT 1 AS C
FROM (VALUES(1),(1)) AS D01(c)
CROSS JOIN
(VALUES(1),(1)) AS D02(c) ) AS D3
CROSS JOIN
( SELECT 1 AS C
FROM (VALUES(1),(1)) AS D01(c)
CROSS JOIN
(VALUES(1),(1)) AS D02(c) ) AS D4 ) AS D5
CROSS JOIN
( SELECT 1 AS C
FROM ( SELECT 1 AS C
FROM (VALUES(1),(1)) AS D01(c)
CROSS JOIN
(VALUES(1),(1)) AS D02(c) ) AS D3
CROSS JOIN
( SELECT 1 AS C
FROM (VALUES(1),(1)) AS D01(c)
CROSS JOIN
(VALUES(1),(1)) AS D02(c) ) AS D4 ) AS D6 ) AS D8 ) AS D10 ) AS Nums
ORDER BY rownum;
DECLARE @low AS BIGINT = 1001, @high AS BIGINT = 1010;
SELECT TOP(@high - @low + 1) @low + rownum - 1 AS n
FROM ( SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rownum
FROM (VALUES(1),(1)) AS D01(c)
CROSS JOIN (VALUES(1),(1)) AS D02(c)
CROSS JOIN (VALUES(1),(1)) AS D03(c)
CROSS JOIN (VALUES(1),(1)) AS D04(c)
CROSS JOIN (VALUES(1),(1)) AS D05(c)
CROSS JOIN (VALUES(1),(1)) AS D06(c)
CROSS JOIN (VALUES(1),(1)) AS D07(c)
CROSS JOIN (VALUES(1),(1)) AS D08(c)
CROSS JOIN (VALUES(1),(1)) AS D09(c)
CROSS JOIN (VALUES(1),(1)) AS D10(c)
CROSS JOIN (VALUES(1),(1)) AS D11(c)
CROSS JOIN (VALUES(1),(1)) AS D12(c)
CROSS JOIN (VALUES(1),(1)) AS D13(c)
CROSS JOIN (VALUES(1),(1)) AS D14(c)
CROSS JOIN (VALUES(1),(1)) AS D15(c)
CROSS JOIN (VALUES(1),(1)) AS D16(c)
CROSS JOIN (VALUES(1),(1)) AS D17(c)
CROSS JOIN (VALUES(1),(1)) AS D18(c)
CROSS JOIN (VALUES(1),(1)) AS D19(c)
CROSS JOIN (VALUES(1),(1)) AS D20(c)
CROSS JOIN (VALUES(1),(1)) AS D21(c)
CROSS JOIN (VALUES(1),(1)) AS D22(c)
CROSS JOIN (VALUES(1),(1)) AS D23(c)
CROSS JOIN (VALUES(1),(1)) AS D24(c)
CROSS JOIN (VALUES(1),(1)) AS D25(c)
CROSS JOIN (VALUES(1),(1)) AS D26(c)
CROSS JOIN (VALUES(1),(1)) AS D27(c)
CROSS JOIN (VALUES(1),(1)) AS D28(c)
CROSS JOIN (VALUES(1),(1)) AS D29(c)
CROSS JOIN (VALUES(1),(1)) AS D30(c)
CROSS JOIN (VALUES(1),(1)) AS D31(c)
CROSS JOIN (VALUES(1),(1)) AS D32(c) ) AS Nums
ORDER BY rownum;
Used in modification statements
WITH < table name > [ (< target columns >) ] AS
(
< table expression >
)
DELETE [ FROM ] <table name>
[ WHERE <filter predicate> ];
WITH OldestOrders AS
(
SELECT TOP (10) *
FROM Sales.Orders
ORDER BY orderdate, orderid
)
DELETE FROM OldestOrders;
WITH < table name > [ (< target columns >) ] AS
(
< table expression >
)
UPDATE <table name>
SET <assignments>
[ WHERE <filter predicate> ];
BEGIN TRAN;
WITH OldestUnshippedOrders AS
(
SELECT TOP (10) orderid, requireddate,
DATEADD(day, 10, CAST(SYSDATETIME() AS DATE)) AS newrequireddate
FROM Sales.Orders
WHERE shippeddate IS NULL
AND requireddate < CAST(SYSDATETIME() AS DATE)
ORDER BY orderdate, orderid
)
UPDATE OldestUnshippedOrders
SET requireddate = newrequireddate
OUTPUT
inserted.orderid,
deleted.requireddate AS oldrequireddate,
inserted.requireddate AS newrequireddate;
ROLLBACK TRAN;
orderid oldrequireddate newrequireddate
----------- --------------- ---------------
11008 2019-05-06 2020-07-16
11019 2019-05-11 2020-07-16
11039 2019-05-19 2020-07-16
11040 2019-05-20 2020-07-16
11045 2019-05-21 2020-07-16
11051 2019-05-25 2020-07-16
11054 2019-05-26 2020-07-16
11058 2019-05-27 2020-07-16
11059 2019-06-10 2020-07-16
11061 2019-06-11 2020-07-16
(10 rows affected)
Ringkasan
Item Derived table CTE Supports nesting Yes No Supports multiple references No Yes Supports table value constructor Yes No Can highlight and run part of code Yes No Supports recursion No Yes