2016-04-20 2 views
3

У меня проблемы с вычислением коррелированных подзапросов. Пример использования коррелировала подзапрос в SELECT, так что GROUP BY не требуется:Оценка коррелированного подзапроса в SQL

Рассмотрим соотношения:

Movies : Title, Director Length 
Schedule : Theatre, Title 

У меня есть следующий запрос

SELECT S.Theater, MAX(M.Length) 
FROM Movies M JOIN Schedule S ON M.Title=S.Title 
GROUP BY S.Theater 

который получает самый длинный фильм что каждый театр играет. Это тот же запрос без использования GROUP BY:

SELECT DISTINCT S.theater, 
    (SELECT MAX(M.Length) 
    FROM Movies M 
    WHERE M.Title=S.Title) 
FROM Schedule S 

, но я не понимаю, как это работает.

Буду признателен, если кто-нибудь может дать мне пример того, как оцениваются коррелированные подзапросы.

Спасибо :)

ответ

2

Концептуально ...

Чтобы понять это, сначала проигнорируйте бит о коррелированном подзапросе.

Рассмотрим порядок операций для заявления, как это:

SELECT t.foo FROM mytable t 

MySQL готовит пустой результирующий набор. Строки в наборе результатов будут состоять из одного столбца, потому что в списке SELECT есть одно выражение. Строка извлекается из таблицы. MySQL помещает строку в набор результатов, используя значение из столбца foo из строки mytable, назначая его столбцу foo в наборе результатов. Выполните следующую строку, повторите этот же процесс, пока не будет больше строк для извлечения из таблицы.

Довольно легкий материал. Но медведь со мной.

Рассмотрим это утверждение:

SELECT t.foo AS fi, 'bar' AS fo FROM mytable t 

MySQL процесса, который таким же образом. Подготовьте пустой набор результатов. Строки в наборе результатов будут иметь две колонки. В первом столбце указано имя fi (потому что мы присвоили имя fi псевдонимом). Второй столбец в строках набора результатов будет называться fo, потому что (снова) мы назначили псевдоним.

Теперь мы протравливаем строку из таблицы mytable и вставляем строку в набор результатов. Значение столбца foo переместится в имя столбца fi, а строка литерала «bar» переходит в столбец с именем fo. Продолжайте получать строки и вставлять строки в набор результатов, пока не будет больше строк для извлечения.

Не слишком сложно.

Далее рассмотрим это утверждение, которое выглядит немного сложнее:

SELECT t.foo AS fi, (SELECT 'bar') AS fo FROM mytable t 

То же самое происходит снова. Пустой набор результатов. Строки имеют два столбца, имя fi и fo.

Извлеките строку из таблицы mytable и вставьте строку в набор результатов. Значение foo переходит в столбец fi (как и раньше). Это то, где он становится сложным ... для второго столбца в наборе результатов MySQL выполняет запрос внутри parens. В этом случае это довольно простой запрос, мы можем легко проверить, что он возвращает. Возьмите результат из этого запроса и назначьте его столбцу fo и вставьте строку в набор результатов.

Еще со мной?

SELECT t.foo AS fi, (SELECT q.tip FROM bartab q LIMIT 1) AS fo FROM mytable 

Это начинает выглядеть более сложным. Но на самом деле это не так уж и много. То же самое происходит снова. Подготовьте пустой набор результатов. Строки будут иметь два столбца, одно имя fi, другое имя fo. Извлеките строку из таблицы. Получите значение из столбца foo и назначьте его столбцу fi в строке результата. Для столбца fo выполните запрос и назначьте результат из запроса в столбец fo. Вставьте строку результатов в набор результатов. Извлеките еще одну строку из таблицы mytable, повторите процесс.

Здесь мы должны остановиться и заметить что-то. MySQL придирчив к этому запросу в списке SELECT. Действительно очень придирчивый. У MySQL есть ограничения на это. Запрос должен возвращать ровно один столбец. И он не может вернуть более одной строки.

В этом последнем примере для строки, вставленной в набор результатов, MySQL ищет одно значение для назначения столбцу fo. Когда мы думаем об этом таким образом, имеет смысл, что запрос не может вернуть более одного столбца ... что бы MySQL сделал со значением из второго столбца? И имеет смысл, что мы не хотим возвращать больше одной строки ... что бы MySQL сделал с несколькими строками?

MySQL позволит запросу возвращать нулевые строки. Когда это произойдет, MySQL назначает NULL столбцу fo.

Если у вас есть понимание этого, ваш 95% пути к пониманию коррелированного подзапроса.

Давайте рассмотрим другой пример. Наша единственная строка SQL немного обескуражена, поэтому мы просто добавим некоторые разрывы строк и пробелы, чтобы облегчить нам работу. Дополнительные пробелы и разрывы строк не меняют смысла нашего заявления.

SELECT t.foo AS fi 
    , (SELECT q.tip 
      FROM bartab q 
      WHERE q.col = t.foo 
      ORDER BY q.tip DESC 
      LIMIT 1 
     ) AS fo 
    FROM mytable t 

Хорошо, это выглядит намного сложнее. Но действительно ли это? Это тот же вещь снова. Подготовьте пустой набор результатов. Строки будут иметь два столбца: fi и fo. Извлеките строку из таблицы mytable и получите строку, готовую к вставке в набор результатов. Скопируйте значение из столбца foo и назначьте его столбцу fi. А для столбца fo выполните запрос, возьмите единственное значение, возвращаемое запросом, в столбец fo и вставьте строку в набор результатов. Извлеките следующую строку из таблицы и повторите.

Чтобы объяснить (finall!) Часть о «коррелированных».

Этот запрос мы собираемся запустить, чтобы получить результат для столбца fo. Он содержит ссылку на столбец из внешней таблицы. t.foo. В этом примере, который появляется в предложении WHERE; это не обязательно, оно может появиться в любом месте инструкции.

Что MySQL делает с этим, когда он запускает этот подзапрос, он передает в значение столбца foo в запрос. Если строка мы просто скачиваются из MyTable имеет значение 42 в столбце Foo ... что подзапрос эквивалентно

  SELECT q.tip 
      FROM bartab q 
      WHERE q.col = 42 
      ORDER BY q.tip DESC 
      LIMIT 1 

Но так как мы не переходящая в буквальном значении 42, что мы передача - значения из строки во внешнем запросе, результат, возвращаемый нашим подзапросом, «связан» с строкой, которую мы обрабатываем во внешнем запросе.

Мы можем быть намного сложнее в нашем подзапросе, если мы помним правило о подзапросе в списке SELECT ... оно должно возвращать ровно один столбец и не более одной строки. Он возвращает не более одного значения.

Корреляционные подзапросы могут отображаться в некоторых частях инструкции, отличной от списка SELECT, например, в предложении WHERE. Используется одна и та же общая концепция. Для каждой строки, обработанной внешним запросом, значения столбца (ов) из этой строки: , переданные в в подзапрос. Результат, возвращаемый из подзапроса, равен , относящемуся к, к строке, обрабатываемой во внешнем запросе.


Обсуждение отменяет все этапы перед фактическим исполнением ...анализ синтаксиса в токенах, выполнение проверки синтаксиса (ключевые слова и идентификаторы в нужном месте). Затем, выполняя проверку семантики (существует ли моя таблица, есть ли у пользователя привилегия выбора на ней, существует ли столбец foo в моей таблице). Затем определите план доступа. И при исполнении, получения необходимых блокировок и т. Д. Все, что происходит с каждым выполняемым нами оператором.)

И мы не будем обсуждать виды ужасающих проблем производительности, которые мы можем создать с помощью коррелированных подзапросов. Хотя предыдущее обсуждение должно дать ключ. Поскольку подзапрос выполняется для каждой строки, которую мы помещаем в набор результатов (если он находится в списке SELECT нашего внешнего запроса) или выполняется для , каждая строка, к которой обращается внешний запрос ... if внешний запрос возвращает 40 000 строк, это означает, что наш коррелированный подзапрос будет выполнен 40 000 раз. Поэтому нам лучше убедиться, что подзапрос выполняется быстро. Даже когда он выполняется быстро, мы все равно выполняем его 40 000 раз.

+0

ОЧЕНЬ Хорошее и полное объяснение, спасибо за это! – user3186023

+0

Что произойдет, если коррелированы часть запроса появляется раньше, в выбранном пункте, например: \t 'ВЫБРАТЬ (SELECT q.tip \t ОТ bartab д \t ГДЕ q.col = t.foo \t ORDER BY q.tip DESC \t ПРЕДЕЛ 1 \t) КАК фо, \t \t t.foo КАК фи \t \t оТ туЬаЫе – user3186023

+0

Т * в этом случае, с той лишь разницей будет порядок столбцов в наборе результатов. Кроме того, нет никакой разницы. Результат будет эквивалентен. НО .. иногда порядок выражений имеет значение. Но это не такой случай.Где порядок выражений имеет значение, когда мы оцениваем выражения, содержащие пользовательские переменные, и присваиваем значения тем же определяемым пользователем переменным. Порядок операций является значительным (Справочное руководство по MySQL предупреждает против использования * неопределенного * поведения с использованием этих переменных, определяемых пользователем.) – spencer7593

2

С концептуальной точки зрения, представьте себе, что база данных будет по каждой строке результата без подзапроса:

SELECT DISTINCT S.Theater, S.Title 
FROM Schedule S 

И затем, для каждого из них, выполнив Подзапрос для вас:

SELECT MAX(M.Length) 
FROM Movies M 
WHERE M.Title = (whatever S.Title was) 

И размещение этого в качестве значения. На самом деле, это не так (концептуально), что отличается от использования функции:

SELECT DISTINCT S.Theater, SUBSTRING(S.Title, 1, 5) 
FROM Schedule S 

Это просто, что эта функция выполняет запрос к другой таблице, вместо этого.

Я действительно говорю концептуально. База данных может оптимизировать коррелированный запрос во что-то большее, чем объединение. Независимо от того, что делает внутренне, важно для производительности, но не важно для понимания концепции.

Но он не может вернуть ожидаемые результаты. Рассмотрим следующие данные (извините sqlfiddle кажется, erroring атм):

CREATE TABLE Movies (
    Title varchar(255), 
    Length int(10) unsigned, 
    PRIMARY KEY (Title) 
); 

CREATE TABLE Schedule (
    Title varchar(255), 
    Theater varchar(255), 
    PRIMARY KEY (Theater, Title) 
); 

INSERT INTO Movies 
VALUES ('Star Wars', 121); 
INSERT INTO Movies 
VALUES ('Minions', 91); 
INSERT INTO Movies 
VALUES ('Up', 96); 

INSERT INTO Schedule 
VALUES ('Star Wars', 'Cinema 8'); 
INSERT INTO Schedule 
VALUES ('Minions', 'Cinema 8'); 
INSERT INTO Schedule 
VALUES ('Up', 'Cinema 8'); 
INSERT INTO Schedule 
VALUES ('Star Wars', 'Cinema 6'); 

А потом этот вопрос:

SELECT DISTINCT 
    S.Theater, 
    (
    SELECT MAX(M.Length) 
    FROM Movies M 
    WHERE M.Title = S.Title 
) AS MaxLength 
FROM Schedule S; 

Вы получите этот результат:

+----------+-----------+ 
| Theater | MaxLength | 
+----------+-----------+ 
| Cinema 6 |  121 | 
| Cinema 8 |  91 | 
| Cinema 8 |  121 | 
| Cinema 8 |  96 | 
+----------+-----------+ 

Как вы можете см., это не замена GROUP BY (и вы все еще можете использовать GROUP BY), она просто запускает подзапрос для каждой строки. DISTINCT удаляет только дубликаты из результата. Это больше не дает «наибольшей длины» для театра, это просто дает каждому уникальное количество фильмов, связанных с названием театра.

PS: Скорее всего, вы можете использовать столбец идентификатора для идентификации фильмов, а не использовать заголовок в соединении. Таким образом, если случайно название фильма должно быть изменено, его нужно изменить только в одном месте, а не во всех списках. Кроме того, быстрее присоединяться к идентификационному номеру, чем к строке.

+0

Hi. Спасибо за ответ. Я буду читать и переваривать это утром. Очень признателен. Когда вы говорите «для каждого из этих ...», вы имеете в виду, что каждый кортеж вернулся из этого запроса? – user3186023

+0

Чтобы добавить, то, что меня смущает, в моем запросе, S.title не выводится во внешнем вопросе, поэтому, когда он сравнивает S.title с M.title во внутреннем запросе, я смущен относительно того, что S. в данном случае речь идет о названии. – user3186023

+0

Да, для каждой строки результата. –

Смежные вопросы