Използвате ли SQL подзаявки или избягвате да ги използвате?
Да кажем, че главният служител по кредити и събирания ви моли да посочите имената на хората, техните неплатени салда на месец и текущото текущо салдо и иска да импортирате този масив от данни в Excel. Целта е да се анализират данните и да се изготви предложение, което улеснява плащанията, за да смекчи последиците от пандемията COVID19.
Избирате ли да използвате заявка и вложена подзаявка или присъединяване? Какво решение ще вземете?
SQL подзаявки – какви са те?
Преди да се потопим дълбоко в синтаксиса, въздействието върху производителността и предупрежденията, защо първо не дефинирате подзаявка?
Най-просто казано, подзаявката е заявка в заявка. Докато заявка, която въплъщава подзаявка, е външната заявка, ние наричаме подзаявка вътрешна заявка или вътрешен избор. И скоби обхващат подзаявка, подобна на структурата по-долу:
SELECT
col1
,col2
,(subquery) as col3
FROM table1
[JOIN table2 ON table1.col1 = table2.col2]
WHERE col1 <operator> (subquery)
Ще разгледаме следните точки в тази публикация:
- Синтаксис на подзаявка на SQL в зависимост от различните типове подзаявки и оператори.
- Кога и в какъв вид изрази може да се използва подзаявка.
- Влияние върху производителността спрямо JOIN .
- Общи предупреждения при използване на SQL подзаявки.
Както е обичайно, ние предоставяме примери и илюстрации, за да подобрим разбирането. Но имайте предвид, че основният фокус на тази публикация е върху подзаявките в SQL Server.
Сега, нека да започнем.
Направете SQL подзаявки, които са самостоятелни или корелирани
От една страна, подзаявките се категоризират въз основа на тяхната зависимост от външната заявка.
Нека опиша какво е самостоятелна подзаявка.
Самостоятелните подзаявки (или понякога наричани некорелирани или прости подзаявки) са независими от таблиците във външната заявка. Позволете ми да илюстрирам това:
-- 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)
Както е показано в горния код, подзаявката (оградена в скоби по-долу) няма препратки към нито една колона във външната заявка. Освен това можете да маркирате подзаявката в SQL Server Management Studio и да я изпълните, без да получавате грешки по време на изпълнение.
Което от своя страна води до по-лесно отстраняване на грешки на самостоятелни подзаявки.
Следващото нещо, което трябва да вземете предвид, са корелираните подзаявки. В сравнение със самостоятелния си аналог, този има поне една колона, която се препраща от външната заявка. За да изясня, ще дам пример:
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)
Бяхте ли достатъчно внимателни, за да забележите препратката към BusinessEntityID от Лицето маса? Браво!
След като колона от външната заявка бъде посочена в подзаявката, тя се превръща в корелирана подзаявка. Още един момент, който трябва да имате предвид:ако маркирате подзаявка и я изпълните, ще възникне грешка.
И да, напълно сте прав:това прави корелираните подзаявки доста по-трудни за отстраняване на грешки.
За да направите възможно отстраняването на грешки, следвайте тези стъпки:
- изолирайте подзаявката.
- заменете препратката към външната заявка с постоянна стойност.
Изолирането на подзаявката за отстраняване на грешки ще я направи да изглежда така:
SELECT [SalesQuota]
FROM Sales.SalesPersonQuotaHistory spq
WHERE spq.BusinessEntityID = <constant value>
Сега, нека се поразровим малко по-дълбоко в изхода на подзаявките.
Направете SQL подзаявки с 3 възможни върнати стойности
Е, първо, нека помислим какви върнати стойности можем да очакваме от SQL подзаявки.
Всъщност има 3 възможни резултата:
- Единична стойност
- Множество стойности
- Цели таблици
Единична стойност
Нека започнем с изход с една стойност. Този тип подзаявка може да се появи навсякъде във външната заявка, където се очаква израз, като WHERE клауза.
-- 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)
Когато използвате MAX () извличате една стойност. Точно това се случи с нашата подзаявка по-горе. Използвайки равното (= ) операторът казва на SQL Server, че очаквате една стойност. Друго нещо:ако подзаявката връща множество стойности, използвайки равни (= ) получавате грешка, подобен на този по-долу:
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.
Множество стойности
След това разглеждаме многозначния изход. Този вид подзаявка връща списък със стойности с една колона. Освен това оператори като IN иНЕ В ще очаква една или повече стойности.
-- 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')
Стойности на цяла таблица
И не на последно място, защо да не се задълбочите в изходите на цялата таблица.
-- 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]
Забелязали ли сте ОТ клауза?
Вместо да използва таблица, той използва подзаявка. Това се нарича производна таблица или подзаявка за таблица.
И сега, нека ви представя някои основни правила, когато използвате този вид заявка:
- Всички колони в подзаявката трябва да имат уникални имена. Подобно на физическата таблица, извлечената таблица трябва да има уникални имена на колони.
- ПОРЪЧАЙТЕ ОТ не е разрешено освен ако ТОП също е уточнено. Това е така, защото извлечената таблица представлява релационна таблица, в която редовете нямат дефиниран ред.
В този случай извлечената таблица има предимствата на физическата таблица. Ето защо в нашия пример можем да използваме COUNT () в една от колоните на извлечената таблица.
Това е всичко по отношение на изходите на подзаявка. Но преди да стигнем по-нататък, може би сте забелязали, че логиката зад примера за множество стойности и други също може да се направи с помощта на 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'
Всъщност изходът ще бъде същият. Но кой от тях се представя по-добре?
Преди да влезем в това, позволете ми да ви кажа, че посветих раздел на тази гореща тема. Ще го разгледаме с пълни планове за изпълнение и ще разгледаме илюстрациите.
Така че потърпете за момент. Нека обсъдим друг начин за поставяне на подзаявките.
Други изявления, където можете да използвате SQL подзаявки
Досега използвахме SQL подзаявки за SELECT изявления. И работата е, че можете да се насладите на предимствата на подзаявките на INSERT , АКТУАЛИЗИРАНЕ и ИЗТРИВАНЕ изрази или във всеки T-SQL израз, който образува израз.
И така, нека да разгледаме поредица от още няколко примера.
Използване на SQL подзаявки в изявления UPDATE
Достатъчно просто е да включите подзаявки в АКТУАЛИЗИРАНЕ изявления. Защо не проверите този пример?
-- 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
Видяхте ли какво направихме там?
Работата е там, че можете да поставите подзаявки в КЪДЕ клауза за АКТУАЛИЗИРАНЕ изявление.
Тъй като го нямаме в примера, можете също да използвате подзаявка за SET клауза като SET колона =(подзаявка) . Но бъдете предупредени:трябва да изведе една стойност, защото в противен случай възниква грешка.
Какво да правим след това?
Използване на SQL подзаявки в инструкции INSERT
Както вече знаете, можете да вмъквате записи в таблица с помощта на SELECT изявление. Сигурен съм, че имате представа каква ще бъде структурата на подзаявката, но нека демонстрираме това с пример:
-- 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)
И така, какво гледаме тук?
- Първата подзаявка извлича последната ставка на заплата на служител, преди да добави допълнителните 10.
- Втората подзаявка получава последния запис на заплатата на служителя.
- Накрая, резултатът от SELECT се вмъква в EmployeePayHistory маса.
В други изрази на T-SQL
Освен SELECT ,ВМЪКНЕТЕ , АКТУАЛИЗИРАНЕ и ИЗТРИВАНЕ , можете също да използвате SQL подзаявки в следното:
Декларации на променливи или оператори SET в съхранени процедури и функции
Позволете ми да изясня, използвайки този пример:
DECLARE @maxTransId int = (SELECT MAX(TransactionID)
FROM Production.TransactionHistory)
Като алтернатива можете да направите това по следния начин:
DECLARE @maxTransId int
SET @maxTransId = (SELECT MAX(TransactionID)
FROM Production.TransactionHistory)
В условни изрази
Защо не надникнете в този пример:
IF EXISTS(SELECT [Name] FROM sys.tables where [Name] = 'MyVendors')
BEGIN
DROP TABLE MyVendors
END
Освен това можем да го направим така:
IF (SELECT count(*) FROM MyVendors) > 0
BEGIN
-- insert code here
END
Направете SQL подзаявки със сравнение или логически оператори
Досега виждахме равните (= ) оператор и оператор IN. Но има още много за изследване.
Използване на оператори за сравнение
Когато оператор за сравнение като =, <,>, <>,>=или <=се използва с подзаявка, подзаявката трябва да върне една стойност. Освен това възниква грешка, ако подзаявката върне множество стойности.
Примерът по-долу ще генерира грешка по време на изпълнение.
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)
Знаете ли какво не е наред в горния код?
На първо място, кодът използва оператора equals (=) с подзаявката. Освен това подзаявката връща списък с начални дати.
За да отстраните проблема, накарайте подзаявката да използва функция като MAX () в колоната с начална дата, за да върне една стойност.
Използване на логически оператори
Използване на EXISTS или NOT EXISTS
СЪЩЕСТВУВА връща TRUE ако подзаявката връща някакви редове. В противен случай връща FALSE . Междувременно с помощта на НЕ СЪЩЕСТВУВА ще върне TRUE ако няма редове и FALSE , иначе.
Помислете за примера по-долу:
IF EXISTS(SELECT name FROM sys.tables where name = 'Token')
BEGIN
DROP TABLE Token
END
Първо, позволете ми да обясня. Кодът по-горе ще премахне маркера на таблицата, ако е намерен в sys.tables , което означава, че съществува в базата данни. Друг момент:препратката към името на колоната е без значение.
Защо е така?
Оказва се, че двигателят на базата данни трябва само да получи поне 1 ред, използвайки EXISTS . В нашия пример, ако подзаявката върне ред, таблицата ще бъде отпаднала. От друга страна, ако подзаявката не върне нито един ред, следващите оператори няма да бъдат изпълнени.
Така загрижеността наСЪЩЕСТВУВА е само редове и без колони.
Освен това СЪЩЕСТВУВА използва логика с две стойности:TRUE или FALSE . Няма случаи, в които да върне NULL . Същото се случва, когато отричате СЪЩЕСТВУВА като използвате НЕ .
Използване на IN или NOT IN
Подзаявка, въведена с IN или НЕ В ще върне списък с нула или повече стойности. И за разлика отСЪЩЕСТВУВА , се изисква валидна колона с подходящия тип данни.
Позволете ми да изясня това с друг пример:
-- 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
Както можете да видите от горния код, и двете IN иНЕ В се въвеждат оператори. И в двата случая редовете ще бъдат върнати. Всеки ред във външната заявка ще бъде съпоставен с резултата от всяка подзаявка, за да се получи продукт, който е под ръка, и продукт, който не е от доставчик 1676.
Вложение на SQL подзаявки
Можете да вложите подзаявки дори до 32 нива. Независимо от това, тази възможност зависи от наличната памет на сървъра и сложността на други изрази в заявката.
Какво мислите за това?
Според моя опит не си спомням да съм гнездил до 4. Рядко използвам 2 или 3 нива. Но това съм само аз и моите изисквания.
Какво ще кажете за добър пример, за да разберете това:
-- 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))
Както можем да видим в този пример, влагането достигна 2 нива.
Влоши ли са SQL подзаявките за производителност?
Накратко:да и не. С други думи, зависи.
И не забравяйте, че това е в контекста на SQL Server.
Като начало, много T-SQL изрази, които използват подзаявки, могат алтернативно да бъдат пренаписани с помощта на JOIN с. И производителността и за двете обикновено е една и съща. Въпреки това, има конкретни случаи, когато присъединяването е по-бързо. И има случаи, когато подзаявката работи по-бързо.
Пример 1
Нека разгледаме пример за подзаявка. Преди да ги изпълните, натиснете Control-M или активирайте Включване на действителен план за изпълнение от лентата с инструменти на SQL Server Management Studio.
USE [AdventureWorks]
GO
SELECT Name
FROM Production.Product
WHERE ListPrice = SELECT ListPrice
FROM Production.Product
WHERE Name = 'Touring End Caps')
Като алтернатива заявката по-горе може да бъде пренаписана с помощта на присъединяване, което дава същия резултат.
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'
В крайна сметка резултатът и за двете заявки е 200 реда.
Освен това можете да проверите плана за изпълнение и за двата оператора.
Фигура 1:План за изпълнение с помощта на подзаявка
Фигура 2:План за изпълнение с помощта на присъединяване
Какво мислиш? На практика ли са еднакви? С изключение на действителното изминало време на всеки възел, всичко останало е по същество същото.
Но ето още един начин да го сравните освен визуалните разлики. Предлагам да използвате Сравнете Showplan .
За да го изпълните, изпълнете следните стъпки:
- Щракнете с десния бутон върху плана за изпълнение на израза, като използвате подзаявката.
- Изберете Запазване на плана за изпълнение като .
- Наименувайте файла subquery-execution-plan.sqlplan .
- Отидете до плана за изпълнение на израза, като използвате присъединяване и щракнете с десния бутон върху него.
- Изберете Сравни Showplan .
- Изберете името на файла, което сте запазили в #3.
Сега вижте това за повече информация относно Сравнете Showplan .
Трябва да можете да видите нещо подобно на това:
Фигура 3:Сравнете Showplan за използване на присъединяване и използване на подзаявка
Забележете приликите:
- Прогнозните редове и разходите са еднакви.
- QueryPlanHash също е същото, което означава, че имат подобни планове за изпълнение.
Независимо от това, обърнете внимание на разликите:
- Размерът на плана за кеш паметта е по-голям чрез обединяването, отколкото при използване на подзаявката
- Компилирането на процесора и времето (в ms), включително паметта в KB, използвани за анализиране, свързване и оптимизиране на плана за изпълнение, е по-високо при използване на присъединяването, отколкото при използване на подзаявката.
- Времето на процесора и изминалото време (в ms) за изпълнение на плана са малко по-високи, като се използва присъединяването спрямо подзаявката
В този пример подзаявката е тик по-бърза от присъединяването, въпреки че получените редове са еднакви.
Пример 2
В предишния пример използвахме само една таблица. В примера, който следва, ще използваме 3 различни таблици.
Нека да направим това:
-- 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'
И двете заявки извеждат едни и същи 3806 реда.
След това нека разгледаме техните планове за изпълнение:
Фигура 4:План за изпълнение за втория ни пример с помощта на подзаявка
Фигура 5:План за изпълнение за втория ни пример с използване на присъединяване
Можете ли да видите 2-та плана за изпълнение и да откриете разлика между тях? На пръв поглед изглеждат еднакви.
Но по-внимателен преглед с Сравнете Showplan разкрива какво наистина има вътре.
Фигура 6:Подробности за Compare Showplan за втория пример
Нека започнем с анализ на няколко прилики:
- Розовото подчертаване в плана за изпълнение разкрива подобни операции и за двете заявки. Тъй като вътрешната заявка използва присъединяване вместо вложени подзаявки, това е съвсем разбираемо.
- Прогнозните разходи за оператор и поддърво са еднакви.
След това нека да разгледаме разликите:
- Първо, компилацията отне повече време, когато използвахме съединения. Можете да проверите това в Compile CPU и Compile Time. Въпреки това, заявката с подзаявка отне по-висока компилационна памет в KB.
- Тогава QueryPlanHash и на двете заявки е различен, което означава, че имат различен план за изпълнение.
- На последно място, изминалото време и времето на процесора за изпълнение на плана са по-бързи с помощта на присъединяването отколкото да използвате подзаявка.
Подзаявка срещу присъединяване на извлечение за ефективност
Вероятно ще се сблъскате с твърде много други проблеми, свързани със заявката, които могат да бъдат решени чрез използване на присъединяване или подзаявка.
Но изводът е, че подзаявката не е по своята същност лоша в сравнение с присъединяванията. И няма практическо правило, че в конкретна ситуация присъединяването е по-добро от подзаявка или обратното.
Така че, за да сте сигурни, че имате най-добрия избор, проверете плановете за изпълнение. Целта на това е да се разбере как SQL Server ще обработи конкретна заявка.
Въпреки това, ако решите да използвате подзаявка, имайте предвид, че може да възникнат проблеми, които ще тестват уменията ви.
Общи предупреждения при използването на SQL подзаявки
Има 2 често срещани проблема, които могат да накарат вашите заявки да се държат диво, когато използвате SQL подзаявки.
Болката от разделителната способност на имената на колони
Този проблем въвежда логически грешки във вашите заявки и те може да са много трудни за намиране. Един пример може допълнително да изясни този проблем.
Нека започнем със създаване на таблица за демонстрационни цели и попълване с данни.
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
Сега, когато таблицата е настроена, нека задействаме някои подзаявки, използвайки я. Но преди да изпълните заявката по-долу, не забравяйте, че идентификаторите на доставчици, които използвахме от предишния код, започват с „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)
Горният код работи без грешки, както можете да видите по-долу. Както и да е, обърнете внимание на списъка с BusinessEntityID .
Фигура 7:BusinessEntityIDs на набора от резултати не са в съответствие със записите на таблицата MyVendors
Не вмъкнахме ли данни с BusinessEntityID започвайки с '14'? Тогава какво има? Всъщност можем да видим BusinessEntityID които започват с '15' и '16'. Откъде дойдоха тези?
Всъщност заявката изброява всички данни от ProductVendor таблица.
В този случай може да си помислите, че псевдоним ще разреши този проблем, така че да се отнася до MyVendors таблица точно като тази по-долу:
Фигура 8:Добавянето на псевдоним към BusinessEntityID води до грешка
Само дето сега истинският проблем се появи поради грешка по време на изпълнение.
Проверете Моите доставчици таблица отново и ще видите, че вместо BusinessEntityID , името на колоната трябва да бъде BusinessEntity_id (с долна черта).
По този начин използването на правилното име на колона най-накрая ще реши този проблем, както можете да видите по-долу:
Фигура 9:Промяната на подзаявката с правилното име на колона реши проблема
Както можете да видите по-горе, вече можем да наблюдаваме BusinessEntityID започвайки с „14“ точно както очаквахме по-рано.
Но може да се чудите: защо, по дяволите, SQL Server изобщо позволи успешното изпълнение на заявката?
Ето кикера:Разделителната способност на имената на колони без псевдоним работи в контекста на подзаявката от самата себе си към външната заявка. Ето защо препратката към BusinessEntityID вътре в подзаявката не предизвика грешка, защото е намерена извън подзаявката – в ProductVendor таблица.
С други думи, SQL Server търси колоната без псевдоним BusinessEntityID в MyVendors маса. Тъй като го няма, погледна отвън и го намери в ProductVendor маса. Лудо, нали?
Може да кажете, че това е грешка в SQL Server, но всъщност това е по замисъл в SQL стандарта и Microsoft го последва.
Добре, това е ясно, не можем да направим нищо за стандарта, но как да избегнем грешка?
- Първо, добавете префикс към имената на колоните с името на таблицата или използвайте псевдоним. С други думи, избягвайте имена на таблици без префикс или без псевдоним.
- Второ, имайте последователно именуване на колоните. Избягвайте да имате и двата BusinessEntityID и BusinessEntity_id , например.
Звучи добре? Да, това внася малко разум в ситуацията.
Но това не е краят.
Луди NULL числа
Както споменах, има още за покриване. T-SQL използва логика с 3 стойности поради поддръжката на NULL . И NULL може почти да ни побърка, когато използваме SQL подзаявки с NOT IN .
Нека започна с представянето на този пример:
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)
Резултатът от заявката ни води до списък с продукти, които не са в MyVendors таблица., както се вижда по-долу:
Фигура 10:Резултатът от примерната заявка с използване на NOT IN
Да предположим, че някой неволно е вмъкнал запис в MyVendors таблица с NULL BusinessEntity_id . Какво ще правим по въпроса?
Фигура 11:Наборът от резултати става празен, когато в MyVendors се вмъкне NULL BusinessEntity_id
Къде отидоха всички данни?
Виждате, че НЕ оператор отхвърли IN предикат. Така че, НЕ ВЯРНО сега ще стане FALSE . Но НЕ NULL е НЕИЗВЕСТНО. Това накара филтъра да изхвърли редовете, които са НЕИЗВЕСТНИ, и това е виновникът.
За да сте сигурни, че това няма да ви се случи:
- Или направете колоната на таблицата да забранява NULL ако данните не трябва да са такива.
- Или добавете име на колона НЕ Е NULL до вашето КЪДЕ клауза. В нашия случай подзаявката е както следва:
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)
Вземане за вкъщи
Говорихме доста за подзаявките и дойде време да предоставим основните изводи на тази публикация под формата на обобщен списък:
Подзаявка:
- е заявка в заявка.
- е затворен в скоби.
- може да заменя израз навсякъде.
- може да се използва в SELECT ,ВМЪКНЕТЕ , АКТУАЛИЗИРАНЕ , ИЗТРИВАНЕ, или други T-SQL изрази.
- може да бъде самостоятелен или свързан.
- извежда единични, множествени или таблични стойности.
- работи с оператори за сравнение като =, <>,>, <,>=, <=и логически оператори като IN /НЕ В иСЪЩЕСТВУВА /НЕ СЪЩЕСТВУВА .
- не е лошо или зло. Може да работи по-добре или по-лошо от JOIN в зависимост от ситуацията. Така че послушайте съвета ми и винаги проверявайте плановете за изпълнение.
- може да има лошо поведение при NULL s, когато се използва с NOT IN и когато колона не е изрично идентифицирана с псевдоним на таблица или таблица.
Get familiarized with several additional references for your reading pleasure:
- Discussion of Subqueries from Microsoft.
- IN (Transact-SQL)
- EXISTS (Transact-SQL)
- ALL (Transact-SQL)
- SOME | ANY (Transact-SQL)
- Comparison Operators