Чому запити розбираються таким чином, що забороняє використання псевдонімів стовпців у більшості пропозицій?


16

Намагаючись написати запит, я виявив (важкий шлях), що SQL Server розбирає WHEREs у запиті задовго до розбору SELECTs під час виконання запиту.

Документи MSDN кажуть, що загальний порядок логічного розбору такий, що SELECT аналізується майже останнім (таким чином, виникає помилка "немає такого об'єкта [псевдонім]" при спробі використання псевдоніму стовпців в інших пунктах). Була навіть пропозиція дозволити використання псевдонімів де завгодно, що було збито командою Microsoft, посилаючись на проблеми дотримання стандартів ANSI (що говорить про те, що така поведінка є частиною стандарту ANSI).

Як програміст (не DBA) я вважав цю поведінку дещо заплутаною, оскільки мені здається, що вона значною мірою перемагає мету мати псевдоніми стовпців (або, принаймні, псевдоніми стовпців можна зробити значно більш потужними, якби вони були проаналізовано раніше у виконанні запиту), оскільки єдине місце, де ви можете фактично використовувати псевдоніми, знаходиться в ORDER BY. Як програмісту, здається, що йому не вистачає величезної можливості зробити запити більш потужними, зручними та сухими.

Здається, це настільки кричуще питання, що, безперечно, виникають інші причини, щоб вирішити, що псевдоніми стовпців не повинні бути дозволені ні в чому іншому, крім SELECT і ORDER BY, але які це причини?

Відповіді:


19

Підсумок

Немає жодної логічної причини, що цього не вдалося зробити, але користь невелика, і є деякі підводні камені, які можуть виявитись не відразу.

Результати досліджень

Я провів кілька досліджень і знайшов хорошу інформацію. Нижче наводиться пряма цитата надійного первинного джерела (який хоче залишатись анонімним) на 2012-08-09 17:49 GMT:

Коли SQL був вперше винайдений, у нього не було псевдонімів SELECT. Це був серйозний недолік, який було виправлено, коли мова була стандартизована ANSI приблизно в 1986 році.

Мова повинна була бути "непроцедурною" - іншими словами, описати потрібні вам дані, не вказуючи, як їх знайти. Отже, наскільки я знаю, немає жодної причини, по якій реалізація SQL не могла проаналізувати весь запит перед його обробкою, і дозволить визначати псевдоніми де-небудь і використовувати їх всюди. Наприклад, я не бачу жодної причини, чому наступний запит не повинен бути дійсним:

select name, salary + bonus as pay
from employee
where pay > 100000

Хоча я вважаю, що це розумний запит, деякі системи, що базуються на SQL, можуть ввести обмеження на використання псевдонімів з якоїсь причини, пов'язаної з реалізацією. Я не здивований, коли чує, що SQL Server це робить.

Мене цікавлять подальші дослідження стандарту SQL-86, і чому сучасні СУБД не підтримують повторне використання псевдоніму, але ще не встигли зайнятися цим. Для початку я не знаю, де взяти документацію або як з’ясувати, хто саме склав комітет. Хтось може допомогти? Я також хотів би дізнатися більше про оригінальний продукт Sybase, з якого походить SQL Server.

З цього дослідження та деякої подальшої думки я підозрюю, що використання псевдонімів в інших статтях, хоча це цілком можливо, просто ніколи не було таким пріоритетним для виробників СУБД порівняно з іншими мовними особливостями. Оскільки це не стільки перешкода, тому що легко обійдений автором запитів, прикладання зусиль над іншими досягненнями не є оптимальним. Крім того, це було б власником, оскільки воно, очевидно, не є частиною стандарту SQL (хоча я чекаю, щоб дізнатися про це напевне), і, таким чином, було б незначне поліпшення, порушуючи сумісність SQL між СУБД. Для порівняння CROSS APPLY(що насправді є не що інше, як похідна таблиця із зовнішніми посиланнями) - це величезна зміна, яка в той час як патентований пропонує неймовірну виразну силу, яку не легко виконати іншими способами.

Проблеми з використанням псевдонімів скрізь

Якщо ви можете додати елементи SELECT до пункту WHERE, ви можете не тільки підірвати складність запиту (і, таким чином, складність пошуку хорошого плану виконання), можна придумати абсолютно нелогічний матеріал. Спробуйте:

SELECT X + 5 Y FROM MyTable WHERE Y = X

Що робити, якщо в MyTable вже є стовпець Y, на який посилається пункт WHERE? Рішення полягає у використанні CTE або отриманої таблиці, яка в більшості випадків не повинна коштувати додатково, але досягає того ж кінцевого кінцевого результату. CTE та похідні таблиці принаймні примушують вирішити неоднозначність, дозволяючи псевдонім використовувати лише один раз.

Крім того, використання псевдонімів у пункті FROM має видатний сенс. Ви не можете цього зробити:

SELECT
   T3.ID + (SELECT Min(Interval) FROM Intervals WHERE IntName = 'T') CalcID
FROM
   Table1 T
   INNER JOIN Table2 T2
      ON T2.ID = CalcID
   INNER JOIN Table3 T3
      ON T2.ID = T3.ID

Це кругле посилання (в тому сенсі, що T2 таємно посилається на значення з T3, до того, як ця таблиця була представлена ​​у списку СПОЛУЧЕННЯ), і чорт важко помітити. Як щодо цього:

INSERT dbo.FinalTransaction
SELECT
   newid() FinalTransactionGUID,
   'GUID is: ' + Convert(varchar(50), FinalTransactionGUID) TextGUID,
   T.*
FROM
   dbo.MyTable T

На скільки ви хочете зробити ставку на те, що функцію newid () буде внесено в план виконання двічі, абсолютно несподівано зробивши два стовпці різними значеннями? Як щодо того, коли вищезазначений запит використовується N рівнів глибоко в CTE або похідних таблицях. Я гарантую, що проблема є гіршою, ніж ви можете собі уявити. Вже є серйозні проблеми з неузгодженістю з приводу того, коли речі оцінюються лише один раз або в який момент плану запитів, і Microsoft заявила, що це не виправитьдеякі з них тому, що вони належним чином виражають алгебру запитів - якщо ви отримаєте несподівані результати, розбийте запит на частини. Дозволити ланцюгові посилання, виявити кругові посилання через потенційно дуже довгі такі ланцюги - це досить складні проблеми. Введіть паралелізм, і у вас виник кошмар.

Примітка: Використання псевдоніму WHERE або GROUP BY не буде впливати на проблеми з такими функціями, як newid () або rand ().

Спосіб SQL Server для створення виразів для багаторазового використання

CROSS APPLY / OUTER APPLY - це один із способів у SQL Server для створення виразів, які можна використовувати будь-де в запиті (тільки не раніше у пункті ВІД):

SELECT
   X.CalcID
FROM
   Table1 T
   INNER JOIN Table3 T3
      ON T.ID = T3.ID
   CROSS APPLY (
      SELECT
         T3.ID + (SELECT Min(Interval) FROM Intervals WHERE IntName = 'T') CalcID
   ) X
   INNER JOIN Table2 T2
      ON T2.ID = X.CalcID

Це робить дві речі:

  1. Робить усі вирази в CROSS APPLY отримати "простір імен" (псевдонім таблиці, тут, X) і бути унікальним у цьому просторі імен.
  2. Повсюдно стає очевидним не лише те, що CalcID походить від X, але й робить очевидним, чому ви не можете використовувати нічого з X при приєднанні до таблиць T1 і T3, оскільки X ще не представлено.

Я насправді дуже люблю CROSS APPLY. Це стало моїм вірним другом, і я ним постійно користуюся. Потрібна часткова UNPIVOT (що вимагатиме PIVOT / UNPIVOT або UNPIVOT / PIVOT з використанням нативного синтаксису)? Зроблено з CROSS APPLY. Потрібно розрахункове значення, яке буде повторно використане багато разів Зроблено. Потрібно жорстко виконувати порядок виконання дзвінків через пов'язаний сервер? Зроблено - з кричущим покращенням швидкості. Вам потрібен лише один тип рядків, розділених на 2 ряди або з додатковими умовами? Зроблено.

Отже, щонайменше, у СУБД SQL Server 2005 та новіших версій у вас більше не виникає причин для скарг: CROSS APPLY - це те, як ви ДУХИТЕ так, як вам хочеться.


14

Я не можу сказати точні причини, але я скажу вам, що існують способи обходу повторюваних виразів, наприклад, використовуючи CTE, підзапити, отримані таблиці тощо, щоб уникнути повторення.

Якщо ви показуєте запит з повторним виразом, ми, ймовірно, можемо показати вам, як переписати його, щоб вираз було вказано лише один раз. Однак це лише зменшує складність у написанні / читанні запиту, навряд чи багато що змінить щодо ефективності. SQL Server, як правило, досить добре розпізнає, що вирази повторюються, і він не буде виконувати цю роботу двічі. Є винятки, які йдуть іншим шляхом, але про ефективність ви повинні турбуватися лише тоді, коли фактично спостерігаєте, як це відбувається. Я підозрюю, що більшість повторених виразів, які ви пишете, дійсно згортаються лише в одній операції в плані.

Все, що сказано, я також повторю частину своєї відповіді з цього питання:

/dba/19762/why-is-the-select-clause-listed-first


Ось пояснення Джо Челко про те, як запит обробляється відповідно до стандарту (я вкрав це з моєї власної статті aspfaq.com , яка вкрала цитату, ймовірно, з публікації групи новин Celko):

Ось як SELECT працює в SQL ... принаймні теоретично. Справжні продукти оптимізують речі, коли зможуть.

Почніть з пункту ВІД і створіть робочий стіл з усіх з'єднань, об'єднань, перехресть та будь-якого іншого конструктора таблиці. Опція AS дозволяє вказати ім’я цієї робочої таблиці, яку потім доведеться використовувати для решти запиту, що містить.

Перейдіть до пункту WHERE і видаліть рядки, які не відповідають критеріям; тобто не перевіряйте на ІСТИНУ (відхиліть НЕЗНАЧЕНО та ЛІЖНЕ). Заява WHERE застосовується до роботи в пункті FROM.

Перейдіть до додаткового пункту GROUP BY, складіть групи та зменшіть кожну групу до одного рядка, замінивши оригінальну робочу таблицю новою згрупованою таблицею. Рядки згрупованої таблиці повинні бути характеристиками групи: (1) стовпець групування (2) статистика щодо групи (тобто сукупних функцій) (3) функція або (4) вираз, складений із цих трьох елементів.

Перейдіть до додаткового пункту HAVING і застосуйте його до згрупованого робочого столу; якщо не було пункту GROUP BY, розглядайте всю таблицю як одну групу.

Перейдіть до пункту SELECT і побудуйте вирази зі списку. Це означає, що скалярні підзапити, виклики функцій та вирази в SELECT виконуються після того, як будуть виконані всі інші пропозиції. Оператор AS може також давати ім'я виразам у списку SELECT. Ці нові імена з’являються усі відразу, але після виконання пункту WHERE; Ви не можете використовувати їх у списку SELECT або в цьому ключі WHERE.

Вкладені вирази запитів відповідають звичайним правилам визначення, яких ви очікували від структури структури мови, наприклад C, Pascal, Algol тощо. А саме, найпотаємніші запити можуть посилатись на стовпці та таблиці в запитах, у яких вони містяться.

Це означає, що SELECT не може мати більше стовпців, ніж GROUP BY; але це, безумовно, може мати менше стовпців.

Тепер Celko був одним з головних учасників попередніх версій стандартів. Я не знаю, чи збираєтесь ви коли-небудь отримати остаточну відповідь на WHY?питання, крім спекуляцій. Я здогадуюсь, що перелічення фактичної операції спочатку робить дуже просто для аналізатора точно знати, який тип операції буде. Уявіть, що приєднання до 20 таблиць може стати SELECTабо UPDATEабо DELETE, і пам’ятайте, що код для цих двигунів був спочатку записаний ще в часи, коли синтаксичний аналіз був досить дорогим.

Зауважте, що якщо стандарт SQL продиктований FROMпершим, постачальники, можливо, самостійно вирішили проаналізувати граматику в іншому порядку, тому все ще не має сенсу очікувати, що порядок пропозицій, як написано, повністю підпорядковується порядку обробки 100% час.

Те саме стосується таких речей CASE. На цьому сайті ми бачили сценарії , наприклад, коли раніше вважали міфом, який CASEзавжди обробляється в порядку та коротких замиканнях, хибними. І це поширюється і на інші поширені переконання, такі як SQL Server, який оцінює приєднання в порядку, в якому вони були написані, клавіші короткого замикання WHEREзліва направо або обробка CTE один раз або в певному порядку, навіть якщо на них посилаються кілька разів. Продукти вільно оптимізують, наскільки вони вважають за потрібне, навіть якщо це не відображає, як саме ви заявили, що запит повинен працювати декларативно.


2
Також зауважте, що можливість використовувати чи не використовувати псевдоніми в різних частинах запиту застосовується аналізатором, а не оптимізатором чи механізмом виконання. Те, як двигун реально виконує запит, не обов'язково відображає обмеження, які впливають на синтаксис.
Аарон Бертран

2

В Entity SQL ви можете використовувати псевдоніми виразів в інших місцях запиту в деяких ситуаціях:

select k1, count(t.a), sum(t.a)
from T as t
group by t.b + t.c as k1

Зауважте, що тут Ви ОБОВ'ЯЗКОВО визначите вираз у GROUP BYпункті, щоб використовувати його в SELECTпункті.

Очевидно, можна дозволити деякий подібний вираз псевдоніму як повторного використання в SQL-запитах.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.