这个Sql Server查询中的CTE语法有什么问题?

时间:2011-07-28 14:46:11

标签: sql sql-server tsql

有人可以解释为什么Sql Server抱怨围绕“WITH”子句的语法吗?

感谢您的帮助。

CREATE TABLE TestTable1 (
    Id int not null,
    Version int not null constraint d_Ver default (0),
    [Name] nvarchar(50) not null,

    CONSTRAINT pk_TestTable1 PRIMARY KEY (Id, Version)
);
GO


CREATE TRIGGER trg_iu_UniqueActiveName
ON [dbo].[TestTable1]
AFTER INSERT, UPDATE
AS
IF(UPDATE([Name]))
BEGIN
    IF(
        ( 
            WITH MaxVers AS
                (SELECT Id, Max(Version) AS MaxVersion 
                FROM [dbo].[TestTable1]
                GROUP BY Id)
            SELECT Count(1) 
            FROM [dbo].[TestTable1] t
                INNER JOIN MaxVers ON t.Id = MaxVers.Id AND t.Version = MaxVers.MaxVersion
            WHERE t.[Name] = inserted.[Name]
        )
        > 0
    )
    BEGIN
        DECLARE @name nvarchar(50)
        SELECT @name = [Name] FROM inserted;
        RAISERROR('The name "%s" is already in use.', 16, 1, @name);
    END
END;
GO

编辑2: 对于任何好奇的人来说,这是CTE版本,其中包含以下所有优秀评论。我想我会切换到子查询方法,以便我可以按照建议使用“EXISTS”。

CREATE TRIGGER trg_iu_UniqueActiveName
ON [dbo].[TestTable1]
AFTER INSERT, UPDATE
AS
IF(UPDATE([Name]))
BEGIN
    DECLARE @cnt [int];
    WITH MaxVers AS
        (SELECT Id, Max(Version) AS MaxVersion 
        FROM [dbo].[TestTable1]
        GROUP BY Id)
        SELECT @cnt = COUNT(1) 
        FROM [dbo].[TestTable1] t
            INNER JOIN MaxVers ON t.Id = MaxVers.Id AND t.Version = MaxVers.MaxVersion
            INNER JOIN [inserted] i ON t.[Id] = MaxVers.[Id]
        WHERE t.[Name] = i.[Name] AND NOT [t].[Id] = [i].[Id] ;
    IF( @cnt > 0)
    BEGIN
        DECLARE @name nvarchar(50)
        SELECT @name = [Name] FROM inserted;
        RAISERROR('The name "%s" is already in use by an active entity.', 16, 1, @name);

        ROLLBACK TRANSACTION;
    END
END;
GO 

编辑3:这是“存在”版本(注意,我认为错误处理部分中的选择不能与多个插入的记录一起正常工作):

CREATE TRIGGER trg_iu_UniqueActiveName
ON [dbo].[TestTable1]
AFTER INSERT, UPDATE
AS
IF(UPDATE([Name]))
BEGIN

    IF(EXISTS (
        SELECT t.Id 
        FROM [dbo].[TestTable1] t
            INNER JOIN (
                SELECT Id, Max(Version) AS MaxVersion 
                FROM [dbo].[TestTable1]
                GROUP BY Id) maxVer
                ON t.[Id] = [maxVer].[Id] AND [t].[Version] = [maxVer].[MaxVersion]
            INNER JOIN [inserted] i ON t.[Id] = MaxVer.[Id]
            WHERE [t].[Name] = [i].[Name] AND NOT [t].[Id] = [i].[Id]
        ))
    BEGIN
        DECLARE @name nvarchar(50)
        SELECT @name = [Name] FROM inserted;
        RAISERROR('The name "%s" is already in use by an active entity.', 16, 1, @name);

        ROLLBACK TRANSACTION;
    END
END;
GO

3 个答案:

答案 0 :(得分:2)

我唯一能想到的是语句“When a CTE is used in a statement that is part of a batch, the statement before it must be followed by a semicolon.”(Transact SQL Reference)意味着CTE不能在IF语句中使用。

顺便说一句,你还有两个错误:1)inserted伪表不包含在第一个子查询中,即使你在are子句中引用它也是如此。 2)您的触发器假设正在插入或更新单行。可能会有多个重复的名称,但raiserror只会报告其中一个名称。

编辑并在(select count(*) ...) >执行时避免exists (select * ....)存在可以在第一行停止。

编辑2 废话。 SQL Server触发默认为after触发器。因此,当触发器触发时,您检查存在的行已存在于表中:

CREATE TRIGGER trg_iu_UniqueActiveName
ON [dbo].[TestTable1]
AFTER INSERT, UPDATE
AS
IF(UPDATE([Name]))
BEGIN
    IF EXISTS 
        ( 
            SELECT *
            FROM [dbo].[TestTable1] t
                INNER JOIN inserted i on i.[NAME] = t.[NAME]
                INNER JOIN (SELECT Id, Max(Version) AS MaxVersion 
                    FROM [dbo].[TestTable1]
                    GROUP BY Id) MaxVers ON t.Id = MaxVers.Id AND t.Version = MaxVers.MaxVersion
        )
    BEGIN
        DECLARE @name nvarchar(50)
        SELECT @name = [Name] FROM inserted;
        RAISERROR('The name "%s" is already in use.', 16, 1, @name);
    END
END;
GO

insert into testTable1 (name) values ('Hello')

结果:

Msg 50000, Level 16, State 1, Procedure trg_iu_UniqueActiveName, Line 20
The name "Hello" is already in use.

(1 row(s) affected)

另外,raiserror不会执行回滚,因此该行仍然存在。

答案 1 :(得分:1)

我认为您不能将CTE用于内部查询。

使用此方法解决方法:

DECLARE @cnt int;
WITH MaxVers AS
    (SELECT Id, Max(Version) AS MaxVersion 
    FROM [dbo].[TestTable1]
    GROUP BY Id)
SELECT @cnt = Count(1) 
FROM [dbo].[TestTable1] t
    INNER JOIN MaxVers ON t.Id = MaxVers.Id AND t.Version = MaxVers.MaxVersion
WHERE t.[Name] = inserted.[Name];
IF @cnt > 0
BEGIN
    DECLARE @name nvarchar(50)
    SELECT @name = [Name] FROM inserted;
    RAISERROR('The name "%s" is already in use.', 16, 1, @name);
END

答案 2 :(得分:1)

似乎不喜欢WITH内的IF语句。

请尝试使用以下SQL:

SELECT COUNT(1)
FROM TestTable1 t1
WHERE t.Name = (SELECT [Name] FROM inserted)
AND t.Version = (SELECT MAX(Version) FROM TestTable1 t2 WHERE t2.Id = t.Id)

在我看来简单得多。但是,这并不考虑插入表中的多行。将其更改为IN而不是=可能会这样做。

正如其他人所指出的那样,有时会从WITH语句中加入分号,但在这种情况下我无法得到它。