Как ANY может стать неоднозначным
Как подразумевалось выше, ANY не полностью однозначен. Если мы создаем запрос, чтобы выбрать заказчиков, которые имеют больший рейтинг чем любой заказчик в Риме, мы можем получить вывод, который несколько отличался бы от того, что мы ожидали (как показано в Рисунке 13.5):
SELECT *
FROM Customers
WHERE rating > ANY (SELECT rating
FROM Customers
WHERE city = 'Rome');
В английском языке, способ которым мы обычно склонны интерпретировать оценку "больше чем любой (где city = 'Rome')", должен вам сообщить, что это значение оценки должно быть выше, чем значение оценки в каждом случае, где значение city = 'Rome'. Однако это не так, в случае ANY, используемом в SQL. ANY оценивает как верно, если подзапрос находит любое значение, которое делает условие верным.
=============== SQL Execution Log ============
| SELECT * |
| FROM Customers |
| WHERE rating > ANY |
| (SELECT rating |
| FROM Customers |
| WHERE city = 'Rome'); |
| ============================================= |
| cnum cname city rating snum |
| ----- -------- ---- ------ ------ |
| 2002 Giovanni Rome 200 1003 |
| 2003 Liu San Jose 200 1002 |
| 2004 Grass Berlin 300 1002 |
| 2008 Cisneros San Jose 300 1007 |
===============================================
Рисунок 13.5. Как оператор "больше чем" (>) интерпретируется ANY.
Если мы оценим ANY способом, использующим грамматику английского языка, то только заказчики с оценкой 300 будут превышать Giovanni, который находится в Риме и имеет оценку 200. Однако, подзапрос ANY также находит Periera в Риме с оценкой 100. Так как все заказчики с оценкой 200 были выше этой, они будут выбраны, даже если имелся другой заказчик из Рима (Giovanni), чья оценка не была выше (фактически, то, что один из выбранных заказчиков также находится в Риме, несущественно). Так как подзапрос произвел, по крайней мере, одно значение, которое сделает предикат верным в отношении этих строк, строки были выбраны.
Чтобы дать другой пример, предположим, что мы должны были выбирать все Заказы сумм приобретений, которые были больше чем, по крайней мере, один из Заказов на 6-е Октября:
SELECT *
FROM Orders
WHERE amt > ANY (SELECT amt
FROM Orders
WHERE odate = 10/06/1990);
Вывод для этого запроса показывается в Рисунке 13.6.
=============== SQL Execution Log ==============
| SELECT * |
| FROM Orders |
| WHERE amt > ANY |
| (SELECT amt |
| FROM Orders |
| WHERE odate = 10/06/1990); |
| =============================================== |
| onum amt odate cnum snum |
| ----- -------- ---------- ----- ------ |
| 3002 1900.10 10/03/1990 2007 1004 |
| 3005 5160.45 10/03/1990 2003 1002 |
| 3009 1713.23 10/04/1990 2002 1003 |
| 3008 4723.00 10/05/1990 2006 1001 |
| 3011 9891.88 10/06/1990 2006 1001 |
=================================================
Рисунок 13.6. Выбранное значение больше чем любое (ANY) на 6-е Октября.
Даже если самая высокая сумма приобретений в таблице (9891.88) — имелась на 6-е Октября, предыдущая строка имеет более высокое значение суммы, чем другая строка на 6-е Октября, которая имела значение суммы = 1309.95. Если мы введем реляционный оператор ">=" вместо просто ">", эта строка будет также выбрана, потому что она равна самой себе.
Конечно, вы можете использовать ANY с другой SQL техникой, например с техникой объединения. Этот запрос будет находить все Заказы со значением суммы меньшей, чем значение любой суммы для заказчика в San Jose. (вывод показывается в Рисунке 13.7):
SELECT *
FROM Orders
WHERE amt < ANY (SELECT amt
FROM Orders A, Customers b
WHERE a.cnum = b.cnum
AND b.city = 'San Jose');
Даже если наименьший Заказ в таблице был для заказчика из San Jose, то был второй наибольший; следовательно почти все строки будут выбраны. Простой способ запомнить, что < ANYзначение меньшее чем наибольшее выбранное значение, а > ANYзначение большее чем наименьшее выбранное значение.
=============== SQL Execution Log ==============
| WHERE amt > ANY |
| (SELECT amt |
| FROM Orders a, Customers b |
| WHERE a.cnum = b.cnum |
| AND b.city = 'San Jose'); |
| =============================================== |
| onum amt odate cnum snum |
| ----- -------- ---------- ----- ------ |
| 3001 18.69 10/03/1990 2008 1007 |
| 3003 767.10 10/03/1990 2001 1001 |
| 3002 1900.10 10/03/1990 2007 1004 |
| 3006 1098.10 10/03/1990 2008 1007 |
| 3009 1713.23 10/04/1990 2002 1003 |
| 3007 75.10 10/04/1990 2004 1002 |
| 3008 4723.00 10/05/1990 2006 1001 |
| 3010 1309.88 10/06/1990 2004 1002 |
=================================================
Рисунок 13.7. Использование ANY с объединением.
Фактически, вышеуказанные команды весьма похожи на следующее — (вывод показан на Рисунке 13.8):
SELECT *
FROM Orders
WHERE amt < (SELECT MAX (amt)
FROM Orders a, Customers b
WHERE a.cnum = b.cnum AND b.city = 'San Jose');
=============== SQL Execution Log ==============
| WHERE amt < |
| (SELECT MAX (amt) |
| FROM Orders a, Customers b |
| WHERE a.cnum = b.cnum |
| AND b.city = 'San Jose'); |
| =============================================== |
| onum amt odate cnum snum |
| ----- -------- ---------- ----- ------ |
| 3002 1900.10 10/03/1990 2007 1004 |
| 3005 5160.45 10/03/1990 2003 1002 |
| 3009 1713.23 10/04/1990 2002 1003 |
| 3008 4723.00 10/05/1990 2006 1001 |
| 3011 9891.88 10/06/1990 2006 1001 |
=================================================
Рисунок 13.8. Использование агрегатной функции вместо ANY.
Специальный оператор ALL
С помощью ALL, предикат является верным, если каждое значение выбранное подзапросом удовлетворяет условию в предикате внешнего запроса. Если мы хотим пересмотреть наш предыдущий пример, чтобы вывести только тех заказчиков, чьи оценки, фактически, выше, чем у каждого заказчика в Риме, мы можем ввести следующее чтобы произвести вывод, показанный в Рисунке 13.9:
SELECT *
FROM Customers
WHERE rating > ALL (SELECT rating
FROM Customers
WHERE city = 'Rome');
=============== SQL Execution Log ============
| SELECT * |
| FROM Customers |
| WHERE rating > ALL |
| (SELECT rating |
| FROM Customers |
| WHERE city = 'Rome'); |
| ============================================= |
| cnum cname city rating snum |
| ----- -------- ---- ------ ------ |
| 2004 Grass Berlin 300 1002 |
| 2008 Cisneros San Jose 300 1007 |
===============================================
Рисунок 13.9. Использование оператора ALL.
Этот оператор проверяет значения оценки всех заказчиков в Риме. Затем он находит заказчиков с оценкой большей, чем у любого из заказчиков в Риме. Самая высокая оценка в Риме — у Giovanni (200). Следовательно, выбираются только значения выше этих 200.
Как и в случае с ANY, мы можем использовать EXISTS для производства альтернативной формулировки такого же запроса — (вывод показан на Рисунке 13.10):
SELECT *
FROM Customers outer
WHERE NOT EXISTS (SELECT *
FROM Customers inner
WHERE outer.rating <= inner.rating
AND inner.city = 'Rome');
=============== SQL Execution Log ============
| SELECT * |
| FROM Customers outer |
| WHERE NOT EXISTS |
| (SELECT * |
| FROM Customers inner |
| WHERE outer rating <= inner.rating |
| AND inner.city = 'Rome'); |
| ============================================= |
| cnum cname city rating snum |
| ----- -------- ---- ------ ------ |
| 2004 Grass Berlin 300 1002 |
| 2008 Cisneros San Jose 300 1007 |
===============================================
Рисунок 13.10. Использование EXISTS в качестве альтернативы к ALL.
Равенства и неравенства
ALL используется в основном с неравенствами, а не с равенствами, так как значение может быть "равным для всех" результатом подзапроса только если все результаты, фактически, идентичны. Посмотрите следующий запрос:
SELECT *
FROM Customers
WHERE rating = ALL (SELECT rating
FROM Customers
WHERE city = 'San Jose');
Эта команда допустима, но с этими данными мы не получим никакого вывода. Только в единственном случае вывод будет выдан этим запросом — если все значения оценки в San Jose окажутся идентичными. В этом случае, можно сказать следующее:
SELECT *
FROM Customers
WHERE rating = (SELECT DISTINCT rating
FROM Customers
WHERE city = 'San Jose');
Основное различие в том, что эта последняя команда должна потерпеть неудачу, если подзапрос выведет много значений, в то время как вариант с ALL просто не даст никакого вывода. В общем, не самая удачная идея использовать запросы, которые работают только в определенных ситуациях, подобно этой. Так как ваша база данных будет постоянно меняться, это неудачный способ, чтобы узнать о ее содержании.
Однако, ALL может более эффективно использоваться с неравенствами, то есть с оператором "<>". Но учтите, что сказанное в SQL что — значение, которое не равняется всем результатам подзапроса, — будет отличаться от того же, но сказанного с учетом грамматики aнглийского языка.
Очевидно, если подзапрос возвращает много различных значений, как это обычно бывает, ни одно отдельное значение не может быть равно им всем в обычном смысле. В SQL, выражение <> ALL — в действительности соответствует "не равен любому" результату подзапроса. Другими словами, предикат верен, если данное значение не найдено среди результатов подзапроса. Следовательно, наш предыдущий пример противоположен по смыслу этому примеру (с выводом показанным в Рисунке 13.11):
SELECT *
FROM Customers
WHERE rating <> ALL (SELECT rating
FROM Customers
WHERE city = 'San Jose');
=============== SQL Execution Log ============
| SELECT * |
| FROM Customers |
| WHERE rating <> ALL |
| (SELECT rating |
| FROM Customers |
| WHERE city = 'San Jose'); |
| ============================================= |
| cnum cname city rating snum |
| ----- -------- ---- ------ ------ |
| 2001 Hoffman London 100 1001 |
| 2006 Clemens London 100 1001 |
| 2007 Pereira Rome 100 1004 |
===============================================
Рисунок 13.11. Использование ALL с <>.
Вышеупомянутый подзапрос выбирает все оценки для города San Jose. Он выводит набор из двух значений: 200 (для Liu) и 300 (для Cisneros). Затем, основной запрос, выбирает все строки, с оценкой, не совпадающей ни с одной из них — другими словами все строки с оценкой 100. Вы можете сформулировать тот же самый запрос, используя оператор NOT IN:
SELECT*
FROM Customers
WHERE rating NOT IN (SELECT rating
FROM Customers
WHERE city = 'San Jose');
Вы могли бы также использовать оператор ANY:
SELECT *
FROM Customers
WHERE NOT rating = ANY (SELECT rating
FROM Customers
WHERE city = 'San Jose');
Вывод будет одинаков для всех трех условий.