CTE 的奇怪行为

Strange behavior of CTE

我刚刚回答了这个问题:Generate scripts with new ids (also for dependencies)

我的第一次尝试是这样的:

DECLARE @Form1 UNIQUEIDENTIFIER=NEWID();
DECLARE @Form2 UNIQUEIDENTIFIER=NEWID();

DECLARE @tblForms TABLE(id UNIQUEIDENTIFIER,FormName VARCHAR(100));
INSERT INTO @tblForms VALUES(@Form1,'test1'),(@Form2,'test2');

DECLARE @tblFields TABLE(id UNIQUEIDENTIFIER,FormId UNIQUEIDENTIFIER,FieldName VARCHAR(100));
INSERT INTO @tblFields VALUES(NEWID(),@Form1,'test1.1'),(NEWID(),@Form1,'test1.2'),(NEWID(),@Form1,'test1.3')
                            ,(NEWID(),@Form2,'test2.1'),(NEWID(),@Form2,'test2.2'),(NEWID(),@Form2,'test2.3');

--These are the originalIDs
SELECT frms.id,frms.FormName
      ,flds.id,flds.FieldName
FROM @tblForms AS frms
INNER JOIN @tblFields AS flds ON frms.id=flds.FormId ;                           

--The same with new ids
WITH FormsWithNewID AS
(
    SELECT NEWID() AS myNewFormID
          ,*
    FROM @tblForms            
)
SELECT frms.myNewFormID, frms.id,frms.FormName
      ,NEWID() AS myNewFieldID,flds.FieldName
FROM FormsWithNewID AS frms
INNER JOIN @tblFields AS flds ON frms.id=flds.FormId  

第二个 select 应该提供 - 至少我是这么认为的 - "myNewFormID" 中的两个值,每个值 3 次...但它会产生 6 个不同的值。这意味着 CTE 的 "NEWID()" 是针对最终结果集的每一行完成的。我错过了什么?

NEWID() returns每次执行一个值。每当您使用它时,您都会获得新的价值 例如,

select top 5 newid()
from sys.tables
order by newid()

您将看不到它们的排序依据,因为所选字段生成的值与排序依据字段的值不同

你对CTE的理解是错误的。它们不仅仅是一个充满查询结果的 table 变量 - 相反,它们本身就是一个查询。请注意,CTE 可以递归使用 - 这对于 table 变量来说是一个很好的景象 :)

来自 MSDN:

A common table expression (CTE) can be thought of as a temporary result set that is defined within the execution scope of a single SELECT, INSERT, UPDATE, DELETE, or CREATE VIEW statement. A CTE is similar to a derived table in that it is not stored as an object and lasts only for the duration of the query. Unlike a derived table, a CTE can be self-referencing and can be referenced multiple times in the same query.

的"can be thought"有点骗人——当然可以这么想,但是不是结果集。当您仅使用纯函数时,您看不到这种表现,但正如您所注意到的,newId 不是 而非 纯函数。实际上,它更像是一个命名子查询 - 在您的示例中,如果您直接将查询从 CTE 移动到 from 子句,您将得到相同的结果。

为了进一步说明这一点,您可以在 CTE 上向查询添加另一个联接:

WITH FormsWithNewID AS
(
    SELECT NEWID() AS myNewFormID
          ,*
    FROM @tblForms            
)
SELECT frms.myNewFormID, frms.id,frms.FormName
      ,NEWID() AS myNewFieldID,flds.FieldName,
      frms2.myNewFormID
FROM FormsWithNewID AS frms
INNER JOIN @tblFields AS flds ON frms.id=flds.FormId  
left join FormsWithNewID as frms2 on frms.id = frms2.id

您会看到 frms2.myNewFormID 包含 不同的 myNewFormIDs

记住这一点——当你只对不变的数据使用纯函数时,你只能将 CTE 视为结果集;换句话说,如果在可序列化事务隔离级别中执行相同的查询两次将产生相同的结果集。