繁体   English   中英

子查询如何引用它之外的表?

[英]How can a sub-query refer to a table outside it?

我试图了解 JOIN 中的子查询如何引用上层查询中的字段。

车辆表存储了公司使用的车辆的当前信息; 所有车辆历史都存储在一个名为 vehicle_aud 的表中,其结构与车辆表完全相同,但还包括对另一个表的引用,称为修订,该表存储有关谁、何时、为什么等对车辆进行更改的信息主表。

为了完成对车辆的最后一个操作,使用了一个非常简单的 JOIN,如下所示:

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,这怎么可能? 我的意思是,在什么情况下子查询可以引用子查询之外的表的字段?

使用分析函数是一种方法:

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);

上述查询将返回每组共享相同vehicles.id revision_id值的具有最大revision_id值的所有记录。

我不确定,这是否真的能回答你的问题......

简而言之:任何一种 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

“标量 MAX”的计划在 27(!) 行上使用表扫描,减少到 9。与 APPLY 相同的方法具有相同的计划。 引擎足够聪明,可以看到这不需要完全成熟的结果集。 附带说明:您可以将 MaxVal 用作查询中的变量,非常有帮助...

在这个小测试中,子查询中TOP 1的计划是最昂贵的。 开头和上面一样(表扫描27行,减少到9行),但是要加一个排序操作。 APPLY 的变化大致相同。

TOP 1 WITH TIES需要 9 行 t2 并对它们进行排序。 以下操作是针对 9 行进行的。 再进行一次排序并减少到 TOP 行。

在这种情况下,第一个是最快的 - 到目前为止。

但是在(您的)现实中,实际行为将取决于现有索引、统计信息和实际行数。 此外,您还有一个额外的级别(多一张桌子)。 查询越复杂,优化器就越难找到最佳计划。

结论

如果性能很重要,那么就与您的马匹赛跑并进行测量。 如果性能不是那么重要,请使用更易于阅读、理解和维护的查询。

这是您的第一个查询:

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;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM