Как подзапрос может ссылаться на таблицу за ее пределами? - PullRequest
0 голосов
/ 25 марта 2020

Я пытаюсь понять, как подзапрос в JOIN может ссылаться на поле в верхнем запросе.

Таблица транспортных средств хранит текущую информацию об транспортных средствах, используемых в компании; вся история транспортного средства хранится в таблице с именем Vehicles_aud, структура которой полностью идентична таблице транспортных средств, но также содержит ссылку на другую таблицу, называемую Revisions, в которой хранится информация о том, кто, когда, почему и т. д. c. внес изменения в основную таблицу.

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

SELECT *
FROM vehicles v
    JOIN vehicles_aud vu ON vu.id=v.id AND vu.revision_id=(
        SELECT max(revision_id)
        from vehicles_aud
        WHERE id=v.id
    )
    JOIN revisions r ON r.id=vu.revision_id

Пожалуйста, не обращайте внимания на звездочку в разделе SELECT: я уверен, что указание каких-либо реальных полей здесь не имеет большого смысла для моего вопроса ниже. Чтобы быть точным, этот запрос также может быть переписан следующим образом для лучшего понимания:

SELECT *
FROM vehicles v
    CROSS APPLY (
        SELECT TOP 1 *
        FROM vehicles_aud
        WHERE id=v.id
        ORDER BY id DESC
    ) vu
    JOIN revisions r ON r.id=vu.revision_id

Во втором примере JOIN не применим.

Я предполагаю подзапрос в первом примере следует использовать оператор CROSS APPLY, поскольку он ссылается на поле id в таблице транспортных средств вне подзапроса, но IRL-запрос с JOIN, как описано выше, работает хорошо. И я сомневаюсь, как это могло быть когда-либо возможно без CROSS APPLY? Как, я имею в виду, в каких случаях и при каких обстоятельствах подзапрос может ссылаться на поля таблицы вне подзапроса?

Ответы [ 3 ]

0 голосов
/ 25 марта 2020

Я не уверен, если это действительно ответит на ваш вопрос ...

Короче говоря: любой тип JOIN создаст два набора результатов и сопоставит их с заданным условием, в то время как любой тип APPLY вызовет операцию строка за строкой . Если APPLY возвращает более одной строки, набор результатов добавляется (аналогично JOIN), в то время как с результатами одной строки движок просто добавляет столбцы.

Реальность будет намного более сложный.

Движок очень умен и выберет лучший план после проверки статистики, индексов, существующих планов и так далее. Весьма вероятно, что реальный план, который вы получаете, не соответствует ожиданиям. И вполне вероятно, что план, который вы получаете, может быть одинаковым для, казалось бы, разных запросов.

Попробуйте выполнить следующее с включенным «включить фактические планы»:

USE master;
GO
CREATE DATABASE testPlan;
GO
USE testPlan;
GO

CREATE TABLE t1 (ID INT IDENTITY CONSTRAINT pk PRIMARY KEY, SomeValue VARCHAR(100));
INSERT INTO t1 VALUES('MaxVal will be 100'),('MaxVal will be 200'),('MaxVal will be 300');
GO

CREATE TABLE t2(fkID INT CONSTRAINT fk FOREIGN KEY REFERENCES t1(ID),TheValue INT);
INSERT INTO t2 VALUES(1,1),(1,2),(1,100)
                    ,(2,1),(2,2),(2,200)
                    ,(3,1),(3,2),(3,300);
GO

--a scalar computation using MAX()
SELECT *
      ,(SELECT MAX(t2.TheValue) FROM t2 WHERE t1.ID=t2.fkID) AS MaxVal
FROM t1

--the same as above, but with APPLY
SELECT *
FROM t1
CROSS APPLY(SELECT MAX(t2.TheValue) FROM t2 WHERE t1.ID=t2.fkID) A(MaxVal)

--Now we pick the TOP 1 after an ORDER BY
SELECT *
      ,(SELECT TOP 1 t2.TheValue FROM t2 WHERE t1.ID=t2.fkID ORDER BY t2.TheValue DESC) AS MaxVal
FROM t1

--and again the same with APPLY
SELECT *
FROM t1
CROSS APPLY(SELECT TOP 1 t2.TheValue FROM t2 WHERE t1.ID=t2.fkID ORDER BY t2.TheValue DESC) A(MaxVal)

--Tim's approach using the very slick TOP 1 WITH TIES approach
SELECT TOP 1 WITH TIES *
FROM t1 INNER JOIN t2 ON t1.ID=t2.fkID
ORDER BY ROW_NUMBER() OVER(PARTITION BY t1.ID ORDER BY t2.TheValue DESC);

GO
USE master;
GO
--carefull with real data!
--DROP DATABASE testPlan;
GO

План для «scalar MAX» использует сканирование таблицы на 27 (!) строках, уменьшенных до 9. Тот же подход с APPLY имеет тот же план. Двигатель достаточно умен, чтобы видеть, что для этого не понадобится полный набор результатов. Примечание: вы можете использовать MaxVal как переменную в запросе, очень полезно ...

План с TOP 1 в подзапросе является самым дорогим в этом крошечном тесте. Он начинается с того же, что и выше (сканирование таблицы с 27 строками, уменьшено до 9), но должен добавить операцию сортировки. Вариант с APPLY примерно такой же.

Подход с TOP 1 WITH TIES занимает 9 строк t2 и сортирует их. Следующая операция выполняется против 9 строк. Еще одна сортировка и приведение к ТОП-строкам.

В этом случае первый самый быстрый - на сегодняшний день.

Но в (вашей) реальности фактическое поведение будет зависеть от существующих индексов, статистика и фактическое количество строк. Кроме того, у вас есть один дополнительный уровень (еще одна таблица) между ними. Чем сложнее запрос, тем сложнее оптимизатору будет найти лучший план.

Заключение

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

0 голосов
/ 25 марта 2020

Это ваш первый запрос:

SELECT *
FROM vehicles v JOIN
     vehicles_aud va
     ON va.id = v.id AND
        va.revision_id = (SELECT MAX(va2.revision_id)
                          FROM vehicles_aud va2
                          WHERE va2.id = v.id
--------------------------------^
                         ) JOIN
     revisions r
     ON r.id = va.revision_id;

Я предполагаю, что ваш вопрос касается этого пункта. Это условие корреляции в коррелированном подзапросе . Использование псевдонимов таблиц проясняет, что происходит.

Логически, происходит то, что для каждой строки во внешнем запросе внутренний запрос выполняется с отдельным значением для va.id Как вы, кажется, знаете, он вытягивает самое последнее значение revision_id.

Некоторые люди имеют неестественный уклон против коррелированных подзапросов, полагая, что база данных фактически циклически перебирает все строки. Помните, SQL является описательным языком. Хотя это описывает то, что делает обработка, это не то, что на самом деле происходит в целом. В частности, коррелированные подзапросы могут быть наиболее эффективным механизмом при некоторых обстоятельствах.

Более "разговорный" способ написания запроса будет использовать оконные функции:

SELECT *
FROM vehicles v JOIN
     (SELECT va.*,
             ROW_NUMBER() OVER (PARTITION BY va.id ORDER BY va2.revision_id DESC) as seqnum
      FROM vehicles_aud va
     ) va
     ON va.id = v.id AND
        va.seqnum = 1 JOIN
     revisions r
     ON r.id = va.revision_id;
0 голосов
/ 25 марта 2020

Использование функций analyti c - это один из способов go здесь:

SELECT TOP 1 WITH TIES *
FROM vehicles v
INNER JOIN vehicles_aud vu ON vu.id = v.id
INNER JOIN revisions r ON r.id = vu.revision_id
ORDER BY ROW_NUMBER() OVER (PARTITION BY v.id ORDER BY vu.revision_id DESC);

Приведенный выше запрос вернет все записи, имеющие максимальное значение revision_id на группу записей, совместно использующих vehicles.id значение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...