外部 WHERE 子句如何影响嵌套查询的执行方式?
How does the outer WHERE clause affect the way nested query is executed?
假设我有 table 行
b | a
-----------
17 7000
17 0
18 6000
18 0
19 5000
19 2500
我想获得一个函数的正值:(a1 - a2) \ (b2 - b1)
对于具有不同 b 的 行 的笛卡尔积中的所有元素。 (如果您有兴趣,这将导致 行 y1 = b1*x + a1
和 y2 = b2*x + a2
的交叉点)
我为此写了 query1
SELECT temp.point FROM
(SELECT DISTINCT ((l1.a - l2.a) / (l2.b - l1.b)) AS point
FROM lines AS l1
CROSS JOIN lines AS l2
WHERE l1.b != l2.b
) AS temp
WHERE temp.point > 0
它抛出 "division by zero" 错误。我在没有 WHERE 子句的情况下尝试了相同的查询 (query2),它工作正常
SELECT temp.point FROM
(SELECT DISTINCT ((l1.a - l2.a) / (l2.b - l1.b)) AS point
FROM lines AS l1
CROSS JOIN lines AS l2
WHERE l1.b != l2.b
) AS temp
以及定义的SQL函数的变体(query3)
CREATE FUNCTION get_point(@a1 DECIMAL(18, 4), @a2 DECIMAL(18, 4), @b1 INT, @b2 INT)
RETURNS DECIMAL(18, 4)
WITH EXECUTE AS CALLER
AS
BEGIN
RETURN (SELECT (@a1 - @a2) / (@b2 - @b1))
END
GO
SELECT temp.point FROM
(SELECT DISTINCT dbo.get_point(l1.a, l2.a, l1.b, l2.b) AS point
FROM lines AS l1
CROSS JOIN lines AS l2
WHERE l1.b != l2.b
) AS temp
WHERE temp.point > 0
我有一个直觉假设,即外部 SELECT 不应该影响嵌套 SELECT 的执行方式 (或者至少不应该破坏它).即使这不是真的,也无法解释为什么 query3 有效而 query1 无效。
谁能解释一下这背后的原理?那将不胜感激。
如果您想保证查询始终有效,您需要将计算包装在类似 case
语句
中
case when l2.b - l1.b = 0
then null
else (l1.a - l2.a) / (l2.b - l1.b)
end
从技术上讲,优化器可以完全自由地按照它期望更有效的顺序评估条件。优化器可以自由地评估 where
子句之前的除法,该子句过滤掉除数为 0 的行。它也可以自由地首先评估 where
子句。您的不同查询具有不同的查询计划,这会导致不同的行为。
但实际上,即使特定查询今天可能有一个 "good" 查询计划,也不能保证优化器不会在一天、一个月或一年内决定改变计划查询计划会引发除以 0 的错误。我想你可以决定使用一堆提示/计划指南来强制使用具有特定行为的特定计划。但这往往是那种事后会在后肢咬你的东西。将计算包装在 case
中(或以其他方式防止除以 0 错误)将更安全,也更容易向下一个开发人员解释。
假设我有 table 行
b | a
-----------
17 7000
17 0
18 6000
18 0
19 5000
19 2500
我想获得一个函数的正值:(a1 - a2) \ (b2 - b1)
对于具有不同 b 的 行 的笛卡尔积中的所有元素。 (如果您有兴趣,这将导致 行 y1 = b1*x + a1
和 y2 = b2*x + a2
的交叉点)
我为此写了 query1
SELECT temp.point FROM
(SELECT DISTINCT ((l1.a - l2.a) / (l2.b - l1.b)) AS point
FROM lines AS l1
CROSS JOIN lines AS l2
WHERE l1.b != l2.b
) AS temp
WHERE temp.point > 0
它抛出 "division by zero" 错误。我在没有 WHERE 子句的情况下尝试了相同的查询 (query2),它工作正常
SELECT temp.point FROM
(SELECT DISTINCT ((l1.a - l2.a) / (l2.b - l1.b)) AS point
FROM lines AS l1
CROSS JOIN lines AS l2
WHERE l1.b != l2.b
) AS temp
以及定义的SQL函数的变体(query3)
CREATE FUNCTION get_point(@a1 DECIMAL(18, 4), @a2 DECIMAL(18, 4), @b1 INT, @b2 INT)
RETURNS DECIMAL(18, 4)
WITH EXECUTE AS CALLER
AS
BEGIN
RETURN (SELECT (@a1 - @a2) / (@b2 - @b1))
END
GO
SELECT temp.point FROM
(SELECT DISTINCT dbo.get_point(l1.a, l2.a, l1.b, l2.b) AS point
FROM lines AS l1
CROSS JOIN lines AS l2
WHERE l1.b != l2.b
) AS temp
WHERE temp.point > 0
我有一个直觉假设,即外部 SELECT 不应该影响嵌套 SELECT 的执行方式 (或者至少不应该破坏它).即使这不是真的,也无法解释为什么 query3 有效而 query1 无效。
谁能解释一下这背后的原理?那将不胜感激。
如果您想保证查询始终有效,您需要将计算包装在类似 case
语句
case when l2.b - l1.b = 0
then null
else (l1.a - l2.a) / (l2.b - l1.b)
end
从技术上讲,优化器可以完全自由地按照它期望更有效的顺序评估条件。优化器可以自由地评估 where
子句之前的除法,该子句过滤掉除数为 0 的行。它也可以自由地首先评估 where
子句。您的不同查询具有不同的查询计划,这会导致不同的行为。
但实际上,即使特定查询今天可能有一个 "good" 查询计划,也不能保证优化器不会在一天、一个月或一年内决定改变计划查询计划会引发除以 0 的错误。我想你可以决定使用一堆提示/计划指南来强制使用具有特定行为的特定计划。但这往往是那种事后会在后肢咬你的东西。将计算包装在 case
中(或以其他方式防止除以 0 错误)将更安全,也更容易向下一个开发人员解释。