SQL Server 2005 中 CTE 的评估

发布于 2024-08-26 07:26:12 字数 1473 浏览 7 评论 0原文

我有一个关于 MS SQL 如何评估 CTE 内的函数的问题。几次搜索没有找到与此问题相关的任何结果,但如果这是常识并且我只是落后于潮流,我深表歉意。这不是第一次:-)

这个查询是我实际正在做的事情的简化(并且显然不太动态)版本,但它确实展示了我遇到的问题。它看起来像这样:

CREATE TABLE #EmployeePool(EmployeeID int, EmployeeRank int);

INSERT INTO #EmployeePool(EmployeeID, EmployeeRank)
  SELECT 42, 1
  UNION ALL 
  SELECT 43, 2;

DECLARE @NumEmployees int;
SELECT @NumEmployees  = COUNT(*) FROM #EmployeePool;

WITH RandomizedCustomers AS (
  SELECT CAST(c.Criteria AS int) AS CustomerID,
         dbo.fnUtil_Random(@NumEmployees) AS RandomRank
    FROM dbo.fnUtil_ParseCriteria(@CustomerIDs, 'int') c)
SELECT rc.CustomerID,
       ep.EmployeeID
  FROM RandomizedCustomers rc
  JOIN #EmployeePool ep ON ep.EmployeeRank = rc.RandomRank;

DROP TABLE #EmployeePool;

对于上述的所有执行,可以假设以下情况:

  • dbo.fnUtil_Random() 的结果始终是一个大于零且小于或等于的 int 值传入的参数。由于上面使用值为 2 的 @NumEmployees 调用该函数,因此该函数的计算结果始终为 1 或 2。

  • dbo.fnUtil_ParseCriteria(@CustomerIDs, 'int') 的结果生成一个单列单行表,其中包含基本类型为 'int' 的 sql_variant,该表具有值 219935。

鉴于上述假设,上面表达式的结果应该始终生成一个包含一条记录(CustomerID 和 EmployeeID)的两列表格(无论如何,对我来说)是有道理的。 CustomerID 应始终为 int 值 219935,EmployeeID 应为 42 或 43。

但是,情况并非总是如此。有时我会得到预期的单曲记录。有时我会得到两条记录(每个 EmployeeID 一条),还有一些时候我没有得到任何记录。但是,如果我用真正的临时表替换 RandomizedCustomers CTE,问题就会完全消失。

每当我认为我对这种行为有一个解释时,结果证明它没有意义或不可能,所以我实际上无法解释为什么会发生这种情况。由于当我用临时表替换 CTE 时不会发生问题,因此我只能假设它与在连接到该 CTE 期间评估 CTE 内的函数有关。你们有什么理论吗?

I have a question about how MS SQL evaluates functions inside CTEs. A couple of searches didn't turn up any results related to this issue, but I apologize if this is common knowledge and I'm just behind the curve. It wouldn't be the first time :-)

This query is a simplified (and obviously less dynamic) version of what I'm actually doing, but it does exhibit the problem I'm experiencing. It looks like this:

CREATE TABLE #EmployeePool(EmployeeID int, EmployeeRank int);

INSERT INTO #EmployeePool(EmployeeID, EmployeeRank)
  SELECT 42, 1
  UNION ALL 
  SELECT 43, 2;

DECLARE @NumEmployees int;
SELECT @NumEmployees  = COUNT(*) FROM #EmployeePool;

WITH RandomizedCustomers AS (
  SELECT CAST(c.Criteria AS int) AS CustomerID,
         dbo.fnUtil_Random(@NumEmployees) AS RandomRank
    FROM dbo.fnUtil_ParseCriteria(@CustomerIDs, 'int') c)
SELECT rc.CustomerID,
       ep.EmployeeID
  FROM RandomizedCustomers rc
  JOIN #EmployeePool ep ON ep.EmployeeRank = rc.RandomRank;

DROP TABLE #EmployeePool;

The following can be assumed about all executions of the above:

  • The result of dbo.fnUtil_Random() is always an int value greater than zero and less than or equal to the argument passed in. Since it's being called above with @NumEmployees which has the value 2, this function always evaluates to 1 or 2.

  • The result of dbo.fnUtil_ParseCriteria(@CustomerIDs, 'int') produces a one-column, one-row table that contains a sql_variant with a base type of 'int' that has the value 219935.

Given the above assumptions, it makes sense (to me, anyway) that the result of the expression above should always produce a two-column table containing one record - CustomerID and an EmployeeID. The CustomerID should always be the int value 219935, and the EmployeeID should be either 42 or 43.

However, this is not always the case. Sometimes I get the expected single record. Other times I get two records (one for each EmployeeID), and still others I get no records. However, if I replace the RandomizedCustomers CTE with a true temp table, the problem vanishes completely.

Every time I think I have an explanation for this behavior, it turns out to not make sense or be impossible, so I literally cannot explain why this would happen. Since the problem does not happen when I replace the CTE with a temp table, I can only assume it has something to do with the functions inside CTEs are evaluated during joins to that CTE. Do any of you have any theories?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

著墨染雨君画夕 2024-09-02 07:26:12

SQL Server 的优化器可以自由决定是否重新评估CTE

例如,此查询:

WITH    q AS
        (
        SELECT  NEWID() AS n
        )
SELECT  *
FROM    q
UNION ALL
SELECT  *
FROM    q

将生成两个不同的 NEWID(),但是,如果您使用缓存的 XML 计划将 CTE 包装到Eager Spool 操作,记录将是相同的。

SQL Server's optimizer is free to decide whether to reevaluate a CTE or not.

For instance, this query:

WITH    q AS
        (
        SELECT  NEWID() AS n
        )
SELECT  *
FROM    q
UNION ALL
SELECT  *
FROM    q

will produce two different NEWID()'s, however, if you use cached XML plan to wrap the CTE into an Eager Spool operation, the records will be same.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文