T-SQL 查询基础

T-SQL 查询基础

T-SQL 查询基础

2.1 T-SQL 语言规范与 SELECT 逻辑执行顺序

T-SQL(Transact-SQL)是微软对 ANSI SQL 标准的扩展实现,在标准 SQL 基础上增加了流程控制、事务处理、存储过程等编程能力。理解 SELECT 语句的逻辑执行顺序是写出高效 SQL 的基础。

2.1.1 SELECT 逻辑执行顺序

SQL 的书写顺序与实际执行顺序并不相同:

书写顺序                    逻辑执行顺序
──────────                 ──────────────
SELECT      ←(6)       1. FROM(含 JOIN)
FROM        ←(1)       2. ON
JOIN        ←(2)       3. WHERE
ON          ←(3)       4. GROUP BY
WHERE       ←(4)       5. HAVING
GROUP BY    ←(5)       6. SELECT
HAVING      ←(6)       7. DISTINCT
ORDER BY    ←(8)       8. ORDER BY
TOP / OFFSET←(9)       9. TOP / OFFSET...FETCH

这个顺序解释了很多常见问题: - SELECT 别名不能在 WHERE 中使用(WHERE 在 SELECT 之前执行) - 聚合函数不能在 WHERE 中使用(需用 HAVING) - ORDER BY 可以引用 SELECT 别名(ORDER BY 最后执行)

-- 演示逻辑执行顺序带来的规则
-- 以下查询会报错:WHERE 不能引用 SELECT 别名
-- SELECT ProductName, UnitPrice * 1.1 AS AdjustedPrice
-- FROM Products
-- WHERE AdjustedPrice > 100;  -- 错误!

-- 正确写法:在 WHERE 中重写表达式,或用子查询/CTE
SELECT ProductName, UnitPrice * 1.1 AS AdjustedPrice
FROM Products
WHERE UnitPrice * 1.1 > 100;  -- OK

-- 或用 CTE
WITH Priced AS (
    SELECT ProductName, UnitPrice * 1.1 AS AdjustedPrice
    FROM Products
)
SELECT * FROM Priced WHERE AdjustedPrice > 100;

2.1.2 T-SQL 基本数据类型

-- 创建演示用数据库
CREATE DATABASE SqlServerTutorial
COLLATE Chinese_PRC_CI_AS;
GO

USE SqlServerTutorial;
GO

-- 数值类型演示
CREATE TABLE #DataTypesDemo (
    -- 整数类型
    col_tinyint    TINYINT,         -- 0~255, 1字节
    col_smallint   SMALLINT,        -- -32768~32767, 2字节
    col_int        INT,             -- ~±21亿, 4字节
    col_bigint     BIGINT,          -- ~±922亿亿, 8字节
    -- 精确小数
    col_decimal    DECIMAL(18, 4),  -- 推荐用于金额
    col_numeric    NUMERIC(10, 2),  -- 与 DECIMAL 等价
    col_money      MONEY,           -- 4位小数,8字节,但有精度问题(不推荐)
    -- 近似小数
    col_float      FLOAT,           -- 64位IEEE 754
    col_real       REAL,            -- 32位IEEE 754
    -- 字符串
    col_char       CHAR(10),        -- 定长,不足补空格
    col_varchar    VARCHAR(200),    -- 变长,<=8000字节
    col_nchar      NCHAR(10),       -- Unicode定长
    col_nvarchar   NVARCHAR(400),   -- Unicode变长(常用于中文)
    col_nvarcharmax NVARCHAR(MAX),  -- Unicode,最多2GB
    -- 日期时间
    col_date       DATE,            -- 仅日期,3字节
    col_time       TIME(7),         -- 仅时间,精度到100纳秒
    col_datetime   DATETIME,        -- 日期+时间,精度3.33ms
    col_datetime2  DATETIME2(7),    -- 日期+时间,精度100ns(推荐)
    col_datetimeoffset DATETIMEOFFSET(7), -- 含时区
    col_smalldatetime SMALLDATETIME,-- 精度1分钟
    -- 其他
    col_bit        BIT,             -- 0/1/NULL,相当于布尔
    col_uniqueidentifier UNIQUEIDENTIFIER, -- GUID,16字节
    col_varbinary  VARBINARY(MAX),  -- 二进制数据
    col_xml        XML              -- XML数据
);

-- 类型转换:CAST vs CONVERT
SELECT
    CAST(3.14159 AS DECIMAL(10,2))          AS CastDemo,
    CONVERT(VARCHAR(20), GETDATE(), 120)    AS ConvertDate_ISO,     -- 2024-01-15 10:30:00
    CONVERT(VARCHAR(10), GETDATE(), 23)     AS ConvertDate_YYYYMMDD, -- 2024-01-15
    CONVERT(VARCHAR(8),  GETDATE(), 112)    AS ConvertDate_Compact,  -- 20240115
    TRY_CAST('abc' AS INT)                  AS SafeCast_NULL,        -- NULL(不抛错)
    TRY_CONVERT(INT, '123.45')              AS SafeConvert_NULL;     -- NULL

2.1.3 条件表达式:CASE / IIF / COALESCE / NULLIF

-- CASE 表达式(两种语法)
SELECT OrderID,
       -- 搜索型(推荐,更灵活)
       CASE
           WHEN TotalAmount >= 10000 THEN '大单'
           WHEN TotalAmount >= 1000  THEN '中单'
           WHEN TotalAmount >= 100   THEN '小单'
           ELSE '微单'
       END AS OrderCategory,
       -- 简单型
       CASE Status
           WHEN 1 THEN '待付款'
           WHEN 2 THEN '待发货'
           WHEN 3 THEN '已发货'
           WHEN 4 THEN '已完成'
           ELSE '未知'
       END AS StatusDesc,
       -- IIF(简化的二值选择,SQL Server 2012+)
       IIF(IsVIP = 1, '会员', '普通') AS MemberType,
       -- COALESCE(返回第一个非NULL值)
       COALESCE(Remark, Description, '无备注') AS FinalRemark,
       -- NULLIF(两值相等返回NULL,防止除零)
       SalesAmount / NULLIF(Quantity, 0) AS UnitPrice
FROM Orders;

2.2 内置函数大全

2.2.1 字符串函数

-- ============ 字符串处理函数 ============
DECLARE @str NVARCHAR(100) = N'  Hello, SQL Server 2022!  ';

SELECT
    -- 长度与截取
    LEN(@str)                        AS Len_NoTrailing,     -- 不含尾部空格
    DATALENGTH(@str)                 AS DataLength_Bytes,   -- 实际字节数
    LEFT(@str, 5)                    AS Left5,
    RIGHT(@str, 5)                   AS Right5,
    SUBSTRING(@str, 3, 10)           AS Sub_3_10,
    -- 去除空白
    LTRIM(@str)                      AS LTrimmed,
    RTRIM(@str)                      AS RTrimmed,
    TRIM(@str)                       AS Trimmed,            -- SQL Server 2017+
    TRIM('  ' FROM @str)             AS TrimCustom,
    -- 大小写
    UPPER(@str)                      AS Upper_,
    LOWER(@str)                      AS Lower_,
    -- 查找与替换
    CHARINDEX('SQL', @str)           AS CharIndex_,         -- 返回位置
    CHARINDEX('SQL', @str, 10)       AS CharIndex_From10,   -- 从第10位开始找
    REPLACE(@str, 'SQL', 'My SQL')   AS Replaced,
    -- 填充
    CONCAT('Name:', N'张三')         AS Concat_,
    CONCAT_WS(', ', 'A','B','C')     AS ConcatWS_,          -- SQL Server 2017+
    FORMAT(12345.678, 'N2', 'zh-CN') AS FormatNum,           -- 12,345.68
    STRING_SPLIT('a,b,c,d', ',')     AS StringSplit_Ref,     -- 返回表(需 FROM 子句)
    -- 字符串反转
    REVERSE(@str)                    AS Reversed,
    -- 重复
    REPLICATE('*', 10)               AS Repeated,
    -- 填充至固定宽度
    RIGHT('000000' + CAST(42 AS VARCHAR), 6) AS PaddedLeft;  -- 000042

-- STRING_AGG 替代 FOR XML PATH 聚合字符串(SQL Server 2017+)
SELECT
    CategoryID,
    STRING_AGG(ProductName, ', ')
        WITHIN GROUP (ORDER BY ProductName) AS Products
FROM Products
GROUP BY CategoryID;

-- PATINDEX:使用通配符查找(比 CHARINDEX 更灵活)
SELECT PATINDEX('%[0-9]%', 'ABC123DEF'); -- 返回 4

-- STRING_SPLIT 实际用法(需 FROM/CROSS APPLY)
SELECT value
FROM STRING_SPLIT('北京,上海,广州,深圳', ',');

2.2.2 数值与数学函数

SELECT
    ABS(-42)              AS Abs_,
    CEILING(3.1)          AS Ceil_,        -- 4
    FLOOR(3.9)            AS Floor_,       -- 3
    ROUND(3.456, 2)       AS Round2,       -- 3.46
    ROUND(3.455, 2, 1)    AS Truncate2,    -- 3.45(第三参数非0=截断)
    POWER(2, 10)          AS Power2_10,    -- 1024
    SQRT(144)             AS Sqrt_,        -- 12
    LOG(100, 10)          AS Log10_,       -- 2
    EXP(1)                AS E_,
    PI()                  AS Pi_,
    RAND()                AS RandomFloat,  -- 0~1随机数
    -- 生成范围随机整数 [min, max]
    CAST(RAND() * (100 - 1) + 1 AS INT) AS Random1to100,
    -- 取模
    17 % 5                AS Modulo;       -- 2

2.2.3 日期时间函数

DECLARE @dt DATETIME2(7) = '2024-06-15 14:30:45.1234567';

SELECT
    -- 获取当前时间
    GETDATE()                     AS GetDate_DateTime,       -- 含时区无关的本地时间
    GETUTCDATE()                  AS GetUTCDate,
    SYSDATETIME()                 AS SysDateTime2,           -- DATETIME2精度
    SYSDATETIMEOFFSET()           AS SysDateTimeOffset,      -- 含时区偏移
    SYSUTCDATETIME()              AS SysUTCDateTime2,
    -- 提取部分
    YEAR(@dt)                     AS Year_,
    MONTH(@dt)                    AS Month_,
    DAY(@dt)                      AS Day_,
    DATEPART(WEEKDAY, @dt)        AS Weekday_,               -- 1=周日
    DATENAME(MONTH, @dt)          AS MonthName_,
    DATEPART(ISO_WEEK, @dt)       AS ISOWeek,
    -- 加减
    DATEADD(DAY, 30, @dt)         AS Add30Days,
    DATEADD(MONTH, -1, @dt)       AS Minus1Month,
    DATEADD(SECOND, 3600, @dt)    AS Add1Hour,
    -- 差值
    DATEDIFF(DAY, '2024-01-01', @dt)   AS DiffDays,
    DATEDIFF(MONTH, '2023-01-01', @dt) AS DiffMonths,
    -- 格式化(推荐 FORMAT 函数)
    FORMAT(@dt, 'yyyy-MM-dd HH:mm:ss', 'zh-CN')  AS Formatted,
    FORMAT(@dt, 'D', 'zh-CN')                     AS LongDate,  -- 2024年6月15日
    -- 截断到日期开始
    CAST(CAST(@dt AS DATE) AS DATETIME2)          AS TruncToDay,
    DATETRUNC(MONTH, @dt)                          AS TruncToMonth, -- SQL Server 2022+
    -- 构造日期
    DATEFROMPARTS(2024, 12, 31)                   AS DateFromParts,
    DATETIME2FROMPARTS(2024,12,31,23,59,59,0,0)   AS DT2FromParts,
    -- 时区转换(SQL Server 2016+)
    @dt AT TIME ZONE 'China Standard Time' AS ChinaTime;

-- 实用场景:获取本月第一天和最后一天
SELECT
    DATETRUNC(MONTH, GETDATE())                             AS FirstDayOfMonth,  -- 2022+
    EOMONTH(GETDATE())                                      AS LastDayOfMonth,
    EOMONTH(GETDATE(), 1)                                   AS LastDayNextMonth,
    -- 兼容旧版本写法
    CAST(CONVERT(VARCHAR(7), GETDATE(), 120) + '-01' AS DATE) AS FirstDayCompat;

2.3 多表关联与子查询

2.3.1 各类 JOIN 语义与性能考量

-- 准备演示数据
CREATE TABLE #Customers (
    CustomerID INT PRIMARY KEY,
    CustomerName NVARCHAR(100),
    CityID INT
);
CREATE TABLE #Cities (
    CityID INT PRIMARY KEY,
    CityName NVARCHAR(50)
);
CREATE TABLE #Orders (
    OrderID INT PRIMARY KEY,
    CustomerID INT,
    Amount DECIMAL(10,2),
    OrderDate DATE
);

INSERT INTO #Customers VALUES
(1, N'张三', 1), (2, N'李四', 2), (3, N'王五', NULL), (4, N'赵六', 99);
INSERT INTO #Cities VALUES (1, N'北京'), (2, N'上海'), (3, N'广州');
INSERT INTO #Orders VALUES
(101, 1, 500.00, '2024-01-10'),
(102, 1, 300.00, '2024-02-15'),
(103, 2, 800.00, '2024-01-20'),
(104, 5, 200.00, '2024-03-01');  -- CustomerID=5 不存在

-- INNER JOIN:只返回两侧均匹配的行
SELECT c.CustomerName, ci.CityName, o.Amount
FROM   #Customers c
INNER JOIN #Cities ci   ON c.CityID = ci.CityID
INNER JOIN #Orders o    ON c.CustomerID = o.CustomerID;

-- LEFT JOIN:保留左表全部行
SELECT c.CustomerName,
       ci.CityName,
       ISNULL(SUM(o.Amount), 0) AS TotalAmount
FROM   #Customers c
LEFT JOIN #Cities ci   ON c.CityID = ci.CityID
LEFT JOIN #Orders o    ON c.CustomerID = o.CustomerID
GROUP BY c.CustomerName, ci.CityName;

-- RIGHT JOIN(不常用,可用 LEFT JOIN 改写)

-- FULL OUTER JOIN:保留两侧所有行
SELECT c.CustomerName, o.OrderID, o.Amount
FROM   #Customers c
FULL OUTER JOIN #Orders o ON c.CustomerID = o.CustomerID;

-- CROSS JOIN:笛卡尔积(慎用!)
SELECT c.CustomerName, ci.CityName AS PotentialCity
FROM   #Customers c
CROSS JOIN #Cities ci;  -- 4×3=12行

-- SELF JOIN:自连接(查找组织层级等)
CREATE TABLE #Employees (
    EmpID INT PRIMARY KEY, EmpName NVARCHAR(50), ManagerID INT
);
INSERT INTO #Employees VALUES
(1,N'王总',NULL),(2,N'张经理',1),(3,N'李主管',2),(4,N'陈员工',3);

SELECT e.EmpName AS Employee, m.EmpName AS Manager
FROM   #Employees e
LEFT JOIN #Employees m ON e.ManagerID = m.EmpID;

2.3.2 APPLY 运算符:侧向关联

APPLY 是 SQL Server 特有的强大运算符,允许右侧表表达式引用左侧表的列。

-- CROSS APPLY vs OUTER APPLY(类似 INNER JOIN vs LEFT JOIN)

-- 场景:取每个客户最近3笔订单
SELECT c.CustomerName,
       o.OrderID,
       o.Amount,
       o.OrderDate
FROM   #Customers c
CROSS APPLY (
    SELECT TOP 3 OrderID, Amount, OrderDate
    FROM   #Orders
    WHERE  CustomerID = c.CustomerID   -- 引用外层 c.CustomerID
    ORDER BY OrderDate DESC
) o;

-- OUTER APPLY:没有订单的客户也保留
SELECT c.CustomerName,
       o.OrderID,
       o.Amount
FROM   #Customers c
OUTER APPLY (
    SELECT TOP 1 OrderID, Amount
    FROM   #Orders
    WHERE  CustomerID = c.CustomerID
    ORDER BY Amount DESC
) o;

-- APPLY 配合表值函数(TVF)
CREATE FUNCTION dbo.fn_GetOrderStats (@CustomerID INT)
RETURNS TABLE AS RETURN (
    SELECT
        COUNT(*)         AS OrderCount,
        SUM(Amount)      AS TotalAmount,
        MAX(OrderDate)   AS LastOrderDate
    FROM Orders
    WHERE CustomerID = @CustomerID
);

SELECT c.CustomerName, s.OrderCount, s.TotalAmount, s.LastOrderDate
FROM   Customers c
CROSS APPLY dbo.fn_GetOrderStats(c.CustomerID) s;

2.3.3 子查询:标量、行集、EXISTS

-- 标量子查询(返回单值)
SELECT OrderID, Amount,
       (SELECT AVG(Amount) FROM Orders) AS AvgAmount,
       Amount - (SELECT AVG(Amount) FROM Orders) AS DiffFromAvg
FROM Orders;

-- IN 子查询
SELECT CustomerName
FROM Customers
WHERE CustomerID IN (
    SELECT DISTINCT CustomerID
    FROM Orders
    WHERE Amount > 500
);

-- EXISTS 子查询(比 IN 通常更高效,尤其子查询返回大量行时)
SELECT c.CustomerName
FROM Customers c
WHERE EXISTS (
    SELECT 1
    FROM Orders o
    WHERE o.CustomerID = c.CustomerID
      AND o.Amount > 500
);

-- NOT EXISTS(查找没有订单的客户)
SELECT c.CustomerName
FROM Customers c
WHERE NOT EXISTS (
    SELECT 1 FROM Orders o WHERE o.CustomerID = c.CustomerID
);

-- 相关子查询(Correlated Subquery,每行执行一次,注意性能)
SELECT o1.OrderID,
       o1.Amount,
       (SELECT SUM(o2.Amount)
        FROM Orders o2
        WHERE o2.CustomerID = o1.CustomerID
          AND o2.OrderDate <= o1.OrderDate) AS RunningTotal
FROM Orders o1
ORDER BY o1.CustomerID, o1.OrderDate;
-- 注意:上面的累计求和建议改用窗口函数 SUM() OVER(...) 提升性能

2.4 数据增删改与 MERGE

2.4.1 INSERT 的多种形式

-- 单行 INSERT
INSERT INTO Products (ProductName, Price, StockQty, CategoryID)
VALUES (N'Surface Pro 11', 9999.00, 100, 1);

-- 多行 INSERT(单语句,性能好)
INSERT INTO Products (ProductName, Price, StockQty, CategoryID)
VALUES
    (N'Surface Laptop 6',  8999.00, 50, 1),
    (N'Surface Studio 2+', 25999.00, 20, 1),
    (N'Xbox Series X',     3699.00, 200, 2);

-- INSERT...SELECT(从其他表复制数据)
INSERT INTO ProductsArchive
SELECT *, GETDATE() AS ArchivedAt
FROM   Products
WHERE  StockQty = 0;

-- SELECT INTO(创建新表并插入,生产环境慎用,会锁 tempdb 分配)
SELECT *
INTO   #TempProducts
FROM   Products
WHERE  CategoryID = 1;

-- INSERT 配合 OUTPUT(获取插入后的自增ID或计算值)
DECLARE @InsertedRows TABLE (
    ProductID INT, ProductName NVARCHAR(100), InsertedAt DATETIME2
);

INSERT INTO Products (ProductName, Price, StockQty, CategoryID)
OUTPUT inserted.ProductID, inserted.ProductName, SYSDATETIME()
INTO   @InsertedRows
VALUES (N'HoloLens 2', 29999.00, 10, 3);

SELECT * FROM @InsertedRows;

2.4.2 UPDATE 与 DELETE 最佳实践

-- 基本 UPDATE
UPDATE Products
SET    Price = Price * 1.1,
       UpdatedAt = GETDATE()
WHERE  CategoryID = 1
  AND  StockQty > 0;

-- UPDATE 联表(FROM 子句)
UPDATE p
SET    p.Price = p.Price * d.DiscountRate
FROM   Products p
JOIN   Discounts d ON p.CategoryID = d.CategoryID
WHERE  d.EffectiveDate <= GETDATE();

-- UPDATE 配合 OUTPUT(同时获取新旧值)
UPDATE Products
SET    Price = Price * 0.9
OUTPUT deleted.ProductID,
       deleted.Price AS OldPrice,
       inserted.Price AS NewPrice
WHERE  StockQty > 100;

-- DELETE 基本用法
DELETE FROM OrderItems
WHERE  OrderID IN (
    SELECT OrderID FROM Orders WHERE Status = 'Cancelled'
);

-- 大量删除时分批处理(避免长事务锁表)
DECLARE @BatchSize INT = 5000;
DECLARE @RowsDeleted INT = 1;

WHILE @RowsDeleted > 0
BEGIN
    DELETE TOP (@BatchSize)
    FROM   OrderItems
    WHERE  CreatedDate < DATEADD(YEAR, -3, GETDATE());
    
    SET @RowsDeleted = @@ROWCOUNT;
    
    -- 稍微等待,让其他事务有机会执行
    IF @RowsDeleted > 0 WAITFOR DELAY '00:00:00.1';
END;

-- TRUNCATE TABLE(DDL操作,速度极快,但无法带 WHERE,会重置标识列)
TRUNCATE TABLE TempStagingTable;

2.4.3 MERGE:Upsert 的标准写法

MERGE 是 SQL Server 2008+ 引入的合并语句,可以在一条语句中完成 INSERT / UPDATE / DELETE。

-- 场景:将 ProductsStaging 的数据合并到 Products
-- 如果产品已存在则更新,不存在则插入,源中没有的目标产品则删除

MERGE INTO Products AS Target
USING ProductsStaging AS Source
ON    Target.SKU = Source.SKU
-- 匹配:更新
WHEN MATCHED AND (
    Target.Price <> Source.Price OR
    Target.StockQty <> Source.StockQty
) THEN
    UPDATE SET
        Target.Price = Source.Price,
        Target.StockQty = Source.StockQty,
        Target.UpdatedAt = GETDATE()
-- 目标不存在,源存在:插入
WHEN NOT MATCHED BY TARGET THEN
    INSERT (SKU, ProductName, Price, StockQty, CategoryID, CreatedAt)
    VALUES (Source.SKU, Source.ProductName, Source.Price,
            Source.StockQty, Source.CategoryID, GETDATE())
-- 源不存在,目标存在:删除(可选)
WHEN NOT MATCHED BY SOURCE THEN
    DELETE
-- 输出操作结果
OUTPUT $action AS MergeAction,
       deleted.SKU AS OldSKU,
       inserted.SKU AS NewSKU;

-- ⚠️ MERGE 的已知 BUG:
-- 1. 当 Source 中有重复 SKU 时,同一 Target 行可能被多次操作
-- 2. 建议在 USING 子句中先 SELECT DISTINCT,或使用 GROUP BY 去重
MERGE INTO Products AS Target
USING (
    SELECT SKU, MAX(Price) AS Price, MAX(StockQty) AS StockQty
    FROM ProductsStaging
    GROUP BY SKU  -- 确保 Source 唯一
) AS Source
ON Target.SKU = Source.SKU
WHEN MATCHED THEN UPDATE SET Target.Price = Source.Price
WHEN NOT MATCHED THEN INSERT (SKU, Price) VALUES (Source.SKU, Source.Price);

2.5 NULL 值处理

NULL 是 SQL 中最容易犯错的领域,需要专门重视:

-- NULL 的特殊行为
SELECT
    NULL = NULL,              -- NULL(不是 TRUE!)
    NULL <> NULL,             -- NULL
    1 + NULL,                 -- NULL(任何运算遇到NULL结果为NULL)
    CONCAT('a', NULL, 'b'),   -- 'ab'(CONCAT自动忽略NULL)
    'a' + NULL + 'b';         -- NULL(+运算符遇NULL结果为NULL)

-- 正确判断 NULL
SELECT * FROM Customers WHERE Phone IS NULL;
SELECT * FROM Customers WHERE Phone IS NOT NULL;

-- ISNULL vs COALESCE
SELECT
    ISNULL(NULL, '默认值')          AS ISNull_Result,   -- SQL Server特有,只接受2参数
    COALESCE(NULL, NULL, '默认值')  AS Coalesce_Result; -- ANSI标准,接受多参数

-- NULL 在聚合中被忽略(除了 COUNT(*))
SELECT
    COUNT(*)           AS TotalRows,    -- 含NULL行
    COUNT(Phone)       AS NonNullPhone, -- 不含NULL
    AVG(Amount)        AS AvgAmount,    -- 忽略NULL行计算平均
    SUM(Amount)        AS TotalAmount,  -- NULL被忽略,等价于0
    MIN(Phone)         AS MinPhone,     -- 忽略NULL
    MAX(Phone)         AS MaxPhone;

-- NULL 排序(默认排到最后,ASC)
SELECT * FROM Customers ORDER BY Phone ASC;   -- NULL 最后
SELECT * FROM Customers ORDER BY Phone DESC;  -- NULL 最后

-- SQL Server 没有 NULLS FIRST/LAST,用 CASE 模拟
SELECT * FROM Customers
ORDER BY CASE WHEN Phone IS NULL THEN 0 ELSE 1 END, Phone;  -- NULL 排最前

-- 表连接中的 NULL(ON 条件中 NULL=NULL 不成立)
-- 所以 INNER JOIN 会丢失 CityID=NULL 的行

2.6 小结

主题 关键点
执行顺序 FROM→WHERE→GROUP BY→HAVING→SELECT→ORDER BY
类型转换 优先 TRY_CAST/TRY_CONVERT 避免运行时错误
JOIN 明确语义选择;EXISTS 通常比 IN 更高效
APPLY SQL Server 特有,适合行级子查询和 TVF 配合
MERGE 注意重复源数据 BUG,USING 子句需唯一
NULL IS NULL 判断;聚合自动忽略;JOIN 中 NULL 不等价

下一章:高级查询:CTE 与窗口函数

文章版权声明:除非注明,否则均为边学边练网络文章,版权归原作者所有
  • 上一篇:
  • 下一篇: