Apakah Anda menggunakan subkueri SQL atau menghindari menggunakannya?
Katakanlah kepala bagian kredit dan penagihan meminta Anda untuk mencantumkan nama orang, saldo mereka yang belum dibayar per bulan, dan saldo berjalan saat ini dan ingin Anda mengimpor larik data ini ke Excel. Tujuannya adalah untuk menganalisis data dan menghasilkan penawaran yang membuat pembayaran lebih ringan untuk mengurangi dampak pandemi COVID19.
Apakah Anda memilih untuk menggunakan kueri dan subkueri bersarang atau gabungan? Keputusan apa yang akan Anda buat?
Subquery SQL – Apa Itu?
Sebelum kita mendalami sintaks, dampak kinerja, dan peringatan, mengapa tidak mendefinisikan subkueri terlebih dahulu?
Dalam istilah yang paling sederhana, subquery adalah kueri di dalam kueri. Sementara kueri yang mewujudkan subkueri adalah kueri luar, kami menyebut subkueri sebagai kueri dalam atau pilihan dalam. Dan tanda kurung mengapit subquery yang mirip dengan struktur di bawah ini:
SELECT
col1
,col2
,(subquery) as col3
FROM table1
[JOIN table2 ON table1.col1 = table2.col2]
WHERE col1 <operator> (subquery)
Kami akan melihat poin-poin berikut dalam posting ini:
- Sintaks subkueri SQL bergantung pada jenis dan operator subkueri yang berbeda.
- Kapan dan dalam jenis pernyataan apa seseorang dapat menggunakan subkueri.
- Implikasi kinerja vs. bergabung .
- Peringatan umum saat menggunakan subkueri SQL.
Seperti biasa, kami memberikan contoh dan ilustrasi untuk meningkatkan pemahaman. Namun perlu diingat bahwa fokus utama dari posting ini adalah pada subquery di SQL Server.
Sekarang, mari kita mulai.
Membuat Subquery SQL yang Mandiri atau Berkorelasi
Untuk satu hal, subkueri dikategorikan berdasarkan ketergantungannya pada kueri luar.
Mari saya jelaskan apa itu subquery mandiri.
Subkueri mandiri (atau kadang-kadang disebut sebagai subkueri yang tidak berkorelasi atau sederhana) tidak bergantung pada tabel di kueri luar. Mari saya ilustrasikan ini:
-- Get sales orders of customers from Southwest United States
-- (TerritoryID = 4)
USE [AdventureWorks]
GO
SELECT CustomerID, SalesOrderID
FROM Sales.SalesOrderHeader
WHERE CustomerID IN (SELECT [CustomerID]
FROM [AdventureWorks].[Sales].[Customer]
WHERE TerritoryID = 4)
Seperti yang ditunjukkan dalam kode di atas, subquery (terlampir dalam tanda kurung di bawah) tidak memiliki referensi ke kolom mana pun di kueri luar. Selain itu, Anda dapat menyorot subquery di SQL Server Management Studio dan menjalankannya tanpa mendapatkan kesalahan runtime.
Yang, pada gilirannya, mengarah pada debugging subkueri mandiri yang lebih mudah.
Hal berikutnya yang perlu dipertimbangkan adalah subquery yang berkorelasi. Dibandingkan dengan mitra mandiri, yang satu ini memiliki setidaknya satu kolom yang direferensikan dari kueri luar. Untuk memperjelas, saya akan memberikan contoh:
USE [AdventureWorks]
GO
SELECT DISTINCT a.LastName, a.FirstName, b.BusinessEntityID
FROM Person.Person AS p
JOIN HumanResources.Employee AS e ON p.BusinessEntityID = e.BusinessEntityID
WHERE 1262000.00 IN
(SELECT [SalesQuota]
FROM Sales.SalesPersonQuotaHistory spq
WHERE p.BusinessEntityID = spq.BusinessEntityID)
Apakah Anda cukup perhatian untuk memperhatikan referensi ke BusinessEntityID dari Orang meja? Bagus!
Setelah kolom dari kueri luar direferensikan dalam subkueri, itu menjadi subkueri yang berkorelasi. Satu hal lagi yang perlu dipertimbangkan:jika Anda menyorot subkueri dan menjalankannya, kesalahan akan terjadi.
Dan ya, Anda benar sekali:ini membuat subkueri yang berkorelasi lebih sulit untuk di-debug.
Untuk memungkinkan proses debug, ikuti langkah-langkah berikut:
- mengisolasi subkueri.
- ganti referensi ke kueri luar dengan nilai konstan.
Mengisolasi subquery untuk debugging akan membuatnya terlihat seperti ini:
SELECT [SalesQuota]
FROM Sales.SalesPersonQuotaHistory spq
WHERE spq.BusinessEntityID = <constant value>
Sekarang, mari kita gali lebih dalam tentang keluaran subkueri.
Buat Subkueri SQL Dengan 3 Kemungkinan Nilai yang Dikembalikan
Baiklah, pertama, mari kita pikirkan nilai yang dikembalikan apa yang dapat kita harapkan dari subkueri SQL.
Faktanya, ada 3 kemungkinan hasil:
- Satu nilai
- Beberapa nilai
- Seluruh tabel
Nilai Tunggal
Mari kita mulai dengan output bernilai tunggal. Jenis subkueri ini dapat muncul di mana saja di kueri luar tempat ekspresi diharapkan, seperti WHERE klausa.
-- Output a single value which is the maximum or last TransactionID
USE [AdventureWorks]
GO
SELECT TransactionID, ProductID, TransactionDate, Quantity
FROM Production.TransactionHistory
WHERE TransactionID = (SELECT MAX(t.TransactionID)
FROM Production.TransactionHistory t)
Saat Anda menggunakan MAX () fungsi, Anda mengambil satu nilai. Itulah tepatnya yang terjadi pada subquery kami di atas. Menggunakan yang sama (= ) operator memberi tahu SQL Server bahwa Anda mengharapkan satu nilai. Hal lain:jika subquery mengembalikan beberapa nilai menggunakan sama dengan (= ) operator, Anda mendapatkan kesalahan, mirip dengan yang di bawah ini:
Msg 512, Level 16, State 1, Line 20
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
Beberapa Nilai
Selanjutnya, kami memeriksa output multi-nilai. Subquery semacam ini mengembalikan daftar nilai dengan satu kolom. Selain itu, operator seperti IN dan TIDAK DI akan mengharapkan satu atau lebih nilai.
-- Output multiple values which is a list of customers with lastnames that --- start with 'I'
USE [AdventureWorks]
GO
SELECT [SalesOrderID], [OrderDate], [ShipDate], [CustomerID]
FROM Sales.SalesOrderHeader
WHERE [CustomerID] IN (SELECT c.[CustomerID] FROM Sales.Customer c
INNER JOIN Person.Person p ON c.PersonID = p.BusinessEntityID
WHERE p.lastname LIKE N'I%' AND p.PersonType='SC')
Nilai Seluruh Tabel
Dan yang tak kalah pentingnya, mengapa tidak mempelajari seluruh output tabel.
-- Output a table of values based on sales orders
USE [AdventureWorks]
GO
SELECT [ShipYear],
COUNT(DISTINCT [CustomerID]) AS CustomerCount
FROM (SELECT YEAR([ShipDate]) AS [ShipYear], [CustomerID]
FROM Sales.SalesOrderHeader) AS Shipments
GROUP BY [ShipYear]
ORDER BY [ShipYear]
Pernahkah Anda memperhatikan DARI klausa?
Alih-alih menggunakan tabel, itu menggunakan subquery. Ini disebut tabel turunan atau subquery tabel.
Dan sekarang, izinkan saya memberi Anda beberapa aturan dasar saat menggunakan kueri semacam ini:
- Semua kolom dalam subquery harus memiliki nama yang unik. Sama seperti tabel fisik, tabel turunan harus memiliki nama kolom yang unik.
- PESAN OLEH tidak diperbolehkan kecuali ATAS juga ditentukan. Itu karena tabel turunan mewakili tabel relasional di mana baris tidak memiliki urutan yang ditentukan.
Dalam hal ini, tabel turunan memiliki keunggulan tabel fisik. Itu sebabnya dalam contoh kita, kita dapat menggunakan COUNT () di salah satu kolom dari tabel turunan.
Itu saja tentang output subquery. Namun sebelum kita melangkah lebih jauh, Anda mungkin telah memperhatikan bahwa logika di balik contoh untuk beberapa nilai dan lainnya juga dapat dilakukan dengan menggunakan JOIN .
-- Output multiple values which is a list of customers with lastnames that start with 'I'
USE [AdventureWorks]
GO
SELECT o.[SalesOrderID], o.[OrderDate], o.[ShipDate], o.[CustomerID]
FROM Sales.SalesOrderHeader o
INNER JOIN Sales.Customer c on o.CustomerID = c.CustomerID
INNER JOIN Person.Person p ON c.PersonID = p.BusinessEntityID
WHERE p.LastName LIKE N'I%' AND p.PersonType = 'SC'
Bahkan, outputnya akan sama. Tapi mana yang berkinerja lebih baik?
Sebelum kita masuk ke itu, izinkan saya memberi tahu Anda bahwa saya telah mendedikasikan bagian untuk topik hangat ini. Kami akan memeriksanya dengan rencana eksekusi lengkap dan melihat ilustrasinya.
Jadi, bersabarlah denganku sebentar. Mari kita bahas cara lain untuk menempatkan subkueri Anda.
Pernyataan Lain Di Mana Anda Dapat Menggunakan Subkueri SQL
Sejauh ini, kami telah menggunakan subkueri SQL di SELECT pernyataan. Dan masalahnya, Anda dapat menikmati manfaat subkueri di INSERT , PERBARUI , dan HAPUS pernyataan atau dalam pernyataan T-SQL yang membentuk ekspresi.
Jadi, mari kita lihat serangkaian contoh lainnya.
Menggunakan Subkueri SQL dalam Pernyataan UPDATE
Cukup mudah untuk menyertakan subkueri di UPDATE pernyataan. Mengapa tidak memeriksa contoh ini?
-- In the products inventory, transfer all products of Vendor 1602 to ----
-- location 6
USE [AdventureWorks]
GO
UPDATE [Production].[ProductInventory]
SET LocationID = 6
WHERE ProductID IN
(SELECT ProductID
FROM Purchasing.ProductVendor
WHERE BusinessEntityID = 1602)
GO
Apakah Anda melihat apa yang kami lakukan di sana?
Masalahnya, Anda dapat menempatkan subquery di WHERE klausa dari UPDATE pernyataan.
Karena kami tidak memilikinya dalam contoh, Anda juga dapat menggunakan subkueri untuk SET klausa seperti SET kolom =(subkueri) . Namun berhati-hatilah:itu harus menampilkan nilai tunggal karena jika tidak, akan terjadi kesalahan.
Apa yang kita lakukan selanjutnya?
Menggunakan Subquery SQL dalam Pernyataan INSERT
Seperti yang sudah Anda ketahui, Anda dapat menyisipkan catatan ke dalam tabel menggunakan PILIH penyataan. Saya yakin Anda memiliki gambaran tentang apa yang akan menjadi struktur subquery, tetapi mari kita tunjukkan ini dengan sebuah contoh:
-- Impose a salary increase for all employees in DepartmentID 6
-- (Research and Development) by 10 (dollars, I think)
-- effective June 1, 2020
USE [AdventureWorks]
GO
INSERT INTO [HumanResources].[EmployeePayHistory]
([BusinessEntityID]
,[RateChangeDate]
,[Rate]
,[PayFrequency]
,[ModifiedDate])
SELECT
a.BusinessEntityID
,'06/01/2020' as RateChangeDate
,(SELECT MAX(b.Rate) FROM [HumanResources].[EmployeePayHistory] b
WHERE a.BusinessEntityID = b.BusinessEntityID) + 10 as NewRate
,2 as PayFrequency
,getdate() as ModifiedDate
FROM [HumanResources].[EmployeeDepartmentHistory] a
WHERE a.DepartmentID = 6
and StartDate = (SELECT MAX(c.StartDate)
FROM HumanResources.EmployeeDepartmentHistory c
WHERE c.BusinessEntityID = a.BusinessEntityID)
Jadi, apa yang kita lihat di sini?
- Subkueri pertama mengambil tingkat gaji terakhir seorang karyawan sebelum menambahkan 10 tambahan.
- Subquery kedua mendapatkan catatan gaji terakhir dari karyawan.
- Terakhir, hasil dari PILIH dimasukkan ke dalam EmployeePayHistory tabel.
Dalam Pernyataan T-SQL Lainnya
Selain PILIH , MASUKKAN , PERBARUI , dan HAPUS , Anda juga dapat menggunakan subkueri SQL sebagai berikut:
Deklarasi Variabel atau pernyataan SET dalam Prosedur dan Fungsi Tersimpan
Biarkan saya mengklarifikasi menggunakan contoh ini:
DECLARE @maxTransId int = (SELECT MAX(TransactionID)
FROM Production.TransactionHistory)
Atau, Anda dapat melakukannya dengan cara berikut:
DECLARE @maxTransId int
SET @maxTransId = (SELECT MAX(TransactionID)
FROM Production.TransactionHistory)
Dalam Ekspresi Bersyarat
Mengapa Anda tidak mengintip contoh ini:
IF EXISTS(SELECT [Name] FROM sys.tables where [Name] = 'MyVendors')
BEGIN
DROP TABLE MyVendors
END
Selain itu, kita bisa melakukannya seperti ini:
IF (SELECT count(*) FROM MyVendors) > 0
BEGIN
-- insert code here
END
Membuat Subquery SQL dengan Operator Perbandingan atau Logika
Sejauh ini, kita telah melihat persamaan (= ) dan operator IN. Tapi masih banyak lagi yang bisa dijelajahi.
Menggunakan Operator Perbandingan
Ketika operator perbandingan seperti =, <,>, <>,>=, atau <=digunakan dengan subquery, subquery harus mengembalikan satu nilai. Selain itu, kesalahan terjadi jika subquery mengembalikan beberapa nilai.
Contoh di bawah ini akan menghasilkan kesalahan runtime.
USE [AdventureWorks]
GO
SELECT b.LastName, b.FirstName, b.MiddleName, a.JobTitle, a.BusinessEntityID
FROM HumanResources.Employee a
INNER JOIN Person.Person b on a.BusinessEntityID = b.BusinessEntityID
INNER JOIN HumanResources.EmployeeDepartmentHistory c on a.BusinessEntityID
= c.BusinessEntityID
WHERE c.DepartmentID = 6
and StartDate = (SELECT d.StartDate
FROM HumanResources.EmployeeDepartmentHistory d
WHERE d.BusinessEntityID = a.BusinessEntityID)
Tahukah Anda apa yang salah dengan kode di atas?
Pertama-tama, kode menggunakan operator equals (=) dengan subquery. Selain itu, subquery mengembalikan daftar tanggal mulai.
Untuk memperbaiki masalah, buat subquery menggunakan fungsi seperti MAX () pada kolom tanggal mulai untuk mengembalikan satu nilai.
Menggunakan Operator Logika
Menggunakan EXISTS atau NOT EXISTS
ADA mengembalikan TRUE jika subquery mengembalikan baris apa pun. Jika tidak, akan mengembalikan FALSE . Sementara itu, menggunakan TIDAK ADA akan mengembalikan TRUE jika tidak ada baris dan FALSE , jika tidak.
Perhatikan contoh di bawah ini:
IF EXISTS(SELECT name FROM sys.tables where name = 'Token')
BEGIN
DROP TABLE Token
END
Pertama, izinkan saya menjelaskan. Kode di atas akan menghapus Token tabel jika ditemukan di sys.tables , artinya jika ada di database. Poin lain:referensi ke nama kolom tidak relevan.
Mengapa demikian?
Ternyata mesin database hanya perlu mendapatkan minimal 1 baris menggunakan EXISTS . Dalam contoh kita, jika subquery mengembalikan sebuah baris, tabel akan dihapus. Di sisi lain, jika subquery tidak mengembalikan satu baris pun, pernyataan berikutnya tidak akan dieksekusi.
Dengan demikian, perhatian ADA hanya baris dan tidak ada kolom.
Selain itu, ADA menggunakan logika dua nilai:TRUE atau SALAH . Tidak ada kasus yang akan mengembalikan NULL . Hal yang sama terjadi ketika Anda meniadakan ADA menggunakan TIDAK .
Menggunakan IN atau NOT IN
Subkueri yang diperkenalkan dengan IN atau TIDAK DI akan mengembalikan daftar nilai nol atau lebih. Dan tidak seperti ADA , diperlukan kolom yang valid dengan tipe data yang sesuai.
Biarkan saya mengklarifikasi ini dengan contoh lain:
-- From the product inventory, extract the products that are available
-- (Quantity >0)
-- except for products from Vendor 1676, and introduce a price cut for the --- whole month of June 2020.
-- Insert the results in product price history.
USE [AdventureWorks]
GO
INSERT INTO [Production].[ProductListPriceHistory]
([ProductID]
,[StartDate]
,[EndDate]
,[ListPrice]
,[ModifiedDate])
SELECT
a.ProductID
,'06/01/2020' as StartDate
,'06/30/2020' as EndDate
,a.ListPrice - 2 as ReducedListPrice
,getdate() as ModifiedDate
FROM [Production].[ProductListPriceHistory] a
WHERE a.StartDate = (SELECT MAX(StartDate)
FROM Production.ProductListPriceHistory
WHERE ProductID = a.ProductID)
AND a.ProductID IN (SELECT ProductID
FROM Production.ProductInventory
WHERE Quantity > 0)
AND a.ProductID NOT IN (SELECT ProductID
FROM [Purchasing].[ProductVendor]
WHERE BusinessEntityID = 1676
Seperti yang Anda lihat dari kode di atas, keduanya IN dan TIDAK DI operator diperkenalkan. Dan dalam kedua kasus, baris akan dikembalikan. Setiap baris dalam kueri luar akan dicocokkan dengan hasil setiap subkueri untuk mendapatkan produk yang ada dan produk yang bukan dari vendor 1676.
Pengumpulan Subkueri SQL
Anda dapat menyarangkan subkueri bahkan hingga 32 level. Meskipun demikian, kemampuan ini bergantung pada memori server yang tersedia dan kompleksitas ekspresi lain dalam kueri.
Apa pendapat Anda tentang ini?
Dalam pengalaman saya, saya tidak ingat bersarang hingga 4. Saya jarang menggunakan 2 atau 3 level. Tapi itu hanya saya dan persyaratan saya.
Bagaimana contoh yang baik untuk mengetahui hal ini:
-- List down the names of employees who are also customers.
USE [AdventureWorks]
GO
SELECT
LastName
,FirstName
,MiddleName
FROM Person.Person
WHERE BusinessEntityID IN (SELECT BusinessEntityID
FROM Sales.Customer
WHERE BusinessEntityID IN
(SELECT BusinessEntityID
FROM HumanResources.Employee))
Seperti yang bisa kita lihat dalam contoh ini, nesting mencapai 2 level.
Apakah Subquery SQL Buruk untuk Performa?
Singkatnya:ya dan tidak. Dengan kata lain, itu tergantung.
Dan jangan lupa, ini dalam konteks SQL Server.
Sebagai permulaan, banyak pernyataan T-SQL yang menggunakan subquery sebagai alternatif dapat ditulis ulang menggunakan JOIN s. Dan performa untuk keduanya biasanya sama. Meskipun demikian, ada kasus-kasus tertentu ketika bergabung lebih cepat. Dan terkadang subquery bekerja lebih cepat.
Contoh 1
Mari kita periksa contoh subquery. Sebelum menjalankannya, tekan Control-M atau aktifkan Sertakan Rencana Eksekusi Aktual dari toolbar SQL Server Management Studio.
USE [AdventureWorks]
GO
SELECT Name
FROM Production.Product
WHERE ListPrice = SELECT ListPrice
FROM Production.Product
WHERE Name = 'Touring End Caps')
Atau, kueri di atas dapat ditulis ulang menggunakan gabungan yang menghasilkan hasil yang sama.
USE [AdventureWorks]
GO
SELECT Prd1.Name
FROM Production.Product AS Prd1
INNER JOIN Production.Product AS Prd2 ON (Prd1.ListPrice = Prd2.ListPrice)
WHERE Prd2.Name = 'Touring End Caps'
Pada akhirnya, hasil untuk kedua kueri adalah 200 baris.
Selain itu, Anda dapat melihat rencana eksekusi untuk kedua pernyataan tersebut.
Gambar 1:Rencana Eksekusi Menggunakan Subquery
Gambar 2:Rencana Eksekusi Menggunakan Gabung
Bagaimana menurutmu? Apakah mereka praktis sama? Kecuali untuk waktu aktual yang telah berlalu dari setiap node, yang lainnya pada dasarnya sama.
Tapi inilah cara lain untuk membandingkannya selain dari perbedaan visual. Saya sarankan menggunakan Bandingkan Showplan .
Untuk melakukannya, ikuti langkah-langkah berikut:
- Klik kanan rencana eksekusi pernyataan menggunakan subquery.
- Pilih Simpan Rencana Eksekusi Sebagai .
- Beri nama file subquery-execution-plan.sqlplan .
- Buka rencana eksekusi pernyataan menggunakan join dan klik kanan.
- Pilih Bandingkan Showplan .
- Pilih nama file yang Anda simpan di #3.
Sekarang, lihat ini untuk informasi lebih lanjut tentang Bandingkan Showplan .
Anda seharusnya dapat melihat sesuatu yang mirip dengan ini:
Gambar 3:Bandingkan Showplan untuk menggunakan gabungan vs. menggunakan subkueri
Perhatikan persamaannya:
- Perkiraan baris dan biaya sama.
- QueryPlanHash juga sama, artinya mereka memiliki rencana eksekusi yang serupa.
Namun demikian, perhatikan perbedaannya:
- Ukuran paket cache lebih besar menggunakan gabungan daripada menggunakan subkueri
- Mengkompilasi CPU dan waktu (dalam ms), termasuk memori dalam KB, digunakan untuk mengurai, mengikat, dan mengoptimalkan rencana eksekusi lebih tinggi menggunakan join daripada menggunakan subquery
- Waktu CPU dan waktu yang berlalu (dalam md) untuk menjalankan rencana sedikit lebih tinggi menggunakan gabungan vs. subkueri
Dalam contoh ini, subquery adalah tic lebih cepat daripada join, meskipun baris yang dihasilkan sama.
Contoh 2
Pada contoh sebelumnya, kami hanya menggunakan satu tabel. Dalam contoh berikut, kita akan menggunakan 3 tabel berbeda.
Mari kita wujudkan ini:
-- Subquery example
USE [AdventureWorks]
GO
SELECT [SalesOrderID], [OrderDate], [ShipDate], [CustomerID]
FROM Sales.SalesOrderHeader
WHERE [CustomerID] IN (SELECT c.[CustomerID] FROM Sales.Customer c
INNER JOIN Person.Person p ON c.PersonID =
p.BusinessEntityID
WHERE p.PersonType='SC')
-- Join example
USE [AdventureWorks]
GO
SELECT o.[SalesOrderID], o.[OrderDate], o.[ShipDate], o.[CustomerID]
FROM Sales.SalesOrderHeader o
INNER JOIN Sales.Customer c on o.CustomerID = c.CustomerID
INNER JOIN Person.Person p ON c.PersonID = p.BusinessEntityID
WHERE p.PersonType = 'SC'
Kedua kueri menghasilkan 3806 baris yang sama.
Selanjutnya, mari kita lihat rencana eksekusi mereka:
Gambar 4:Rencana Eksekusi untuk contoh kedua kita menggunakan subquery
Gambar 5:Rencana Eksekusi untuk contoh kedua kita menggunakan join
Dapatkah Anda melihat 2 rencana eksekusi dan menemukan perbedaan di antara keduanya? Sekilas mereka terlihat sama.
Tapi pemeriksaan yang lebih teliti dengan Bandingkan Showplan mengungkapkan apa yang sebenarnya ada di dalam.
Gambar 6:Detail Bandingkan Showplan untuk contoh kedua
Mari kita mulai dengan menganalisis beberapa kesamaan:
- Sorotan merah muda dalam rencana eksekusi mengungkapkan operasi serupa untuk kedua kueri. Karena kueri dalam menggunakan gabungan daripada subkueri bersarang, hal ini cukup dapat dimengerti.
- Estimasi biaya operator dan subpohon adalah sama.
Selanjutnya, mari kita lihat perbedaannya:
- Pertama, kompilasi memakan waktu lebih lama ketika kami menggunakan gabungan. Anda dapat memeriksanya di Compile CPU dan Compile Time. Namun, kueri dengan subkueri membutuhkan Memori Kompilasi yang lebih tinggi dalam KB.
- Kemudian, QueryPlanHash dari kedua kueri tersebut berbeda, artinya keduanya memiliki rencana eksekusi yang berbeda.
- Terakhir, waktu yang berlalu dan waktu CPU untuk menjalankan rencana lebih cepat menggunakan gabungan daripada menggunakan subkueri.
Subquery vs. Gabung Performance Takeaway
Anda mungkin menghadapi terlalu banyak masalah terkait kueri lainnya yang dapat diselesaikan dengan menggunakan gabungan atau subkueri.
Tetapi intinya adalah subquery pada dasarnya tidak buruk dibandingkan dengan join. Dan tidak ada aturan praktis bahwa dalam situasi tertentu bergabung lebih baik daripada subquery atau sebaliknya.
Jadi, untuk memastikan bahwa Anda memiliki pilihan terbaik, periksa rencana eksekusi. Tujuannya adalah untuk mendapatkan wawasan tentang bagaimana SQL Server akan memproses kueri tertentu.
Namun, jika Anda memilih untuk menggunakan subkueri, ketahuilah bahwa masalah mungkin muncul yang akan menguji keterampilan Anda.
Peringatan Umum dalam Menggunakan Subkueri SQL
Ada 2 masalah umum yang dapat menyebabkan kueri Anda berperilaku liar saat menggunakan subkueri SQL.
Kesulitan Resolusi Nama Kolom
Masalah ini memasukkan bug logis ke dalam kueri Anda dan mungkin sangat sulit ditemukan. Sebuah contoh dapat lebih memperjelas masalah ini.
Mari kita mulai dengan membuat tabel untuk tujuan demo dan mengisinya dengan data.
USE [AdventureWorks]
GO
-- Create the table for our demonstration based on Vendors
CREATE TABLE Purchasing.MyVendors
(
BusinessEntity_id int,
AccountNumber nvarchar(15),
Name nvarchar(50)
)
GO
-- Populate some data to our new table
INSERT INTO Purchasing.MyVendors
SELECT BusinessEntityID, AccountNumber, Name
FROM Purchasing.Vendor
WHERE BusinessEntityID IN (SELECT BusinessEntityID
FROM Purchasing.ProductVendor)
AND BusinessEntityID like '14%'
GO
Sekarang tabel sudah diatur, mari kita jalankan beberapa subquery menggunakannya. Namun sebelum menjalankan kueri di bawah, ingatlah bahwa ID vendor yang kita gunakan dari kode sebelumnya dimulai dengan '14'.
SELECT b.Name, b.ListPrice, a.BusinessEntityID
FROM Purchasing.ProductVendor a
INNER JOIN Production.Product b on a.ProductID = b.ProductID
WHERE a.BusinessEntityID IN (SELECT BusinessEntityID
FROM Purchasing.MyVendors)
Kode di atas berjalan tanpa kesalahan, seperti yang Anda lihat di bawah. Bagaimanapun, perhatikan daftar BusinessEntityIDs .
Gambar 7:BusinessEntityIDs dari kumpulan hasil tidak konsisten dengan catatan tabel MyVendors
Bukankah kami memasukkan data dengan BusinessEntityID dimulai dengan '14'? Lalu ada apa? Faktanya, kita dapat melihat BusinessEntityIDs yang dimulai dengan '15' dan '16'. Dari mana asalnya?
Sebenarnya, kueri mencantumkan semua data dari ProductVendor tabel.
Dalam hal ini, Anda mungkin berpikir bahwa alias akan menyelesaikan masalah ini sehingga merujuk ke MyVendors tabel seperti di bawah ini:
Gambar 8:Menambahkan alias ke BusinessEntityID menghasilkan kesalahan
Kecuali bahwa sekarang masalah sebenarnya muncul karena kesalahan runtime.
Periksa MyVendors tabel lagi dan Anda akan melihatnya sebagai ganti BusinessEntityID , nama kolom harus BusinessEntity_id (dengan garis bawah).
Jadi, menggunakan nama kolom yang benar pada akhirnya akan memperbaiki masalah ini, seperti yang Anda lihat di bawah:
Gambar 9:Mengubah subquery dengan nama kolom yang benar memecahkan masalah
Seperti yang Anda lihat di atas, sekarang kita dapat mengamati BusinessEntityIDs dimulai dengan '14' seperti yang kami harapkan sebelumnya.
Tetapi Anda mungkin bertanya-tanya: mengapa SQL Server memungkinkan untuk menjalankan kueri dengan sukses sejak awal?
Inilah kickernya:Resolusi nama kolom tanpa alias berfungsi dalam konteks subquery dari dirinya sendiri keluar ke kueri luar. Itulah mengapa referensi ke BusinessEntityID di dalam subkueri tidak memicu kesalahan karena ditemukan di luar subkueri – di ProductVendor tabel.
Dengan kata lain, SQL Server mencari kolom non-alias BusinessEntityID di MyVendors meja. Karena tidak ada di sana, ia melihat ke luar dan menemukannya di ProductVendor meja. Gila, bukan?
Anda mungkin mengatakan bahwa itu adalah bug di SQL Server, tetapi sebenarnya, ini dirancang dalam standar SQL dan Microsoft mengikutinya.
Baiklah, itu jelas, kami tidak dapat melakukan apa pun tentang standar, tetapi bagaimana kami dapat menghindari kesalahan?
- Pertama, awali nama kolom dengan nama tabel atau gunakan alias. Dengan kata lain, hindari nama tabel yang tidak diawali atau tidak alias.
- Kedua, memiliki penamaan kolom yang konsisten. Hindari memiliki keduanya BusinessEntityID dan BusinessEntity_id , misalnya.
Kedengarannya bagus? Yup, ini membawa kewarasan ke dalam situasi.
Tapi ini bukan akhir dari segalanya.
NULL Gila
Seperti yang saya sebutkan, ada lebih banyak untuk dibahas. T-SQL menggunakan logika 3-nilai karena dukungannya untuk NULL . Dan NULL hampir bisa membuat kita gila ketika kita menggunakan subkueri SQL dengan TIDAK DI .
Mari saya mulai dengan memperkenalkan contoh ini:
SELECT b.Name, b.ListPrice, a.BusinessEntityID
FROM Purchasing.ProductVendor a
INNER JOIN Production.Product b on a.ProductID = b.ProductID
WHERE a.BusinessEntityID NOT IN (SELECT c.BusinessEntity_id
FROM Purchasing.MyVendors c)
Keluaran kueri membawa kita ke daftar produk yang tidak ada di MyVendors tabel., seperti yang terlihat di bawah ini:
Gambar 10:Output dari contoh query menggunakan NOT IN
Sekarang, misalkan seseorang secara tidak sengaja menyisipkan catatan di MyVendors tabel dengan NULL BusinessEntity_id . Apa yang akan kita lakukan?
Gambar 11:Kumpulan hasil menjadi kosong ketika NULL BusinessEntity_id dimasukkan ke MyVendors
Ke mana perginya semua data?
Anda lihat, TIDAK operator meniadakan IN predikat. Jadi, TIDAK BENAR sekarang akan menjadi SALAH . Tapi BUKAN NULL tidak diketahui. Hal itu menyebabkan filter membuang baris yang TIDAK DIKETAHUI, dan inilah pelakunya.
Untuk memastikan hal ini tidak terjadi pada Anda:
- Entah membuat kolom tabel melarang NULL jika data tidak seharusnya seperti itu.
- Atau tambahkan nama_kolom BUKAN NULL ke DI MANA . Anda ayat. Dalam kasus kami, subquery adalah sebagai berikut:
SELECT b.Name, b.ListPrice, a.BusinessEntityID
FROM Purchasing.ProductVendor a
INNER JOIN Production.Product b on a.ProductID = b.ProductID
WHERE a.BusinessEntityID NOT IN (SELECT c.BusinessEntity_id
FROM Purchasing.MyVendors c
WHERE c.BusinessEntity_id IS NOT NULL)
Bawa Pulang
Kami telah berbicara cukup banyak tentang subkueri, dan saatnya telah tiba untuk memberikan kesimpulan utama dari posting ini dalam bentuk daftar ringkasan:
Sebuah subkueri:
- adalah kueri dalam kueri.
- diapit dalam tanda kurung.
- dapat menggantikan ekspresi di mana saja.
- dapat digunakan di PILIH , MASUKKAN , PERBARUI , HAPUS, atau pernyataan T-SQL lainnya.
- mungkin mandiri atau berkorelasi.
- mengeluarkan nilai tunggal, ganda, atau tabel.
- berfungsi pada operator perbandingan seperti =, <>,>, <,>=, <=dan operator logika seperti IN /TIDAK DI dan ADA /TIDAK ADA .
- tidak jahat atau jahat. Performanya bisa lebih baik atau lebih buruk daripada JOIN s tergantung pada situasi. Jadi ikuti saran saya dan selalu periksa rencana eksekusi.
- dapat memiliki perilaku buruk di NULL s saat digunakan dengan TIDAK DI , dan saat kolom tidak secara eksplisit diidentifikasi dengan tabel atau tabel alias.
Kenali beberapa referensi tambahan untuk kesenangan membaca Anda:
- Discussion of Subqueries from Microsoft.
- IN (Transact-SQL)
- EXISTS (Transact-SQL)
- ALL (Transact-SQL)
- SOME | ANY (Transact-SQL)
- Comparison Operators