---
title: "FROM、CTE和APPLY"
date: "2026-06"
category: "随笔"
tags: ["随笔"]
description: "FROM 的主表一般就是左表，而 LEFT JOIN本质上就是取左表+两表交集的部分，剩下的右表未交集部分则直接为null；而INNER JOIN则是直接取交集，两表都有的才返回..."
source: "https://enriquemark.com/zh-hans/posts/FROM%E3%80%81CTE%E5%92%8CAPPLY"
---

FROM 的主表一般就是左表，而 `LEFT JOIN`本质上就是取左表+两表交集的部分，剩下的右表未交集部分则直接为null；而`INNER JOIN`则是直接取交集，两表都有的才返回。

CTE 是个很有意思的操作，它跟子查询不一样的点是，其本身可以先定义再使用，更符合人类可读的形式——从外向内。而子查询就跟变成里面的嵌套if一样，你得从内向外的取梳理逻辑：看到一个子查询的时候得先看里面，再去看外面倒推这里在干嘛。子查询可以理解为现场使用的逻辑，而执行上来说确实如此，CTE则是预先声明的逻辑变量。在没被调用之前并不会执行，只有真的被调用时才会运作。而从执行器的角度来看，它跟子查询没什么太大差别，性能不会更好，但也不会更坏。

```SQL
-- CTE 写法：逻辑清晰，先定义，后使用
WITH DeptAvg AS (
    SELECT DepartmentID, AVG(Salary) AS AvgSalary 
    FROM Employees 
    GROUP BY DepartmentID
),
第二个 AS (SELECT ...)
SELECT * 
FROM DeptAvg    -- 像使用普通表一样使用这个 CTE
WHERE AvgSalary > 50000;

```

OUTER APPLY 也是一个有意思的操作。这是一个SQL Server特有的操作，其他数据库里可能叫JOIN LATERAL之类的。类比的话，就像是带了关联查询的JOIN。一般情况下，对于后者来说，它内部是无法引用外部其他表的字段来进行关联的——但是，OUTER APPLY就可以：

```SQL
SELECT 
    A.列名, 
    B.列名
FROM 左表 AS A
OUTER APPLY (
    -- 这里是可以直接引用 A.列名 的子查询或表值函数
    SELECT TOP 1 * 
    FROM 右表 AS R 
    WHERE R.关联键 = A.关联键 
    ORDER BY R.排序键 DESC
) AS B;

```

JOIN就没办法这么玩儿。如果我需要在JOIN里面进行一次子查询，那么这个子查询就只能是完全独立的，不可能还引用左表的字段来进行匹配。OUTER APPLY 在这里对应的是 LEFT JOIN，左表里面没匹配到的会返回 NULL，而 CROSS APPLY 则是 INNER JOIN，只会返回左右两表的交集。

这个看上去其实蛮好用的，但它可能会存在性能损失。因为这种关联匹配本质上是对左表的每行进行遍历，也自然存在经典的遍历问题——如果左表数据量极其庞大，那右边的循环就会爆炸。解决此问题的方法就是建立索引，如果右表有非常良好的索引，那么循环爆炸就会消失。执行器不再需要每次遍历全表来进行匹配，只需要精确匹配到指定项即可。但是，这种情况下遍历问题仍然存在，即便右表的匹配可以压缩到精确的几行，但如果左表几百上千万行，那就算是从N对N优化到N对1的逐行匹配，性能损失仍然不小。

因此在性能优先的场合下，这个最好还是优化成别的性能写法，但在合适的情况下，甚至是大多数中小场合下，倒是没有这些担忧。毕竟比起硬写JOIN写到理解和维护困难，轻微的性能损失完全是可接受的。比如我需要获取子表的TOP N，那这种能关联外部的APPLY写法，就比直接写JOIN要好读的多。
