Что не так с синтаксисом CTE в этом запросе сервера Sql? - PullRequest
3 голосов
/ 28 июля 2011

Может кто-нибудь объяснить, почему 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: Вот версия «Exists» (Примечание, я думаю, что выборв части обработки ошибок не будет работать правильно с более чем одной вставленной записью):

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 ]

2 голосов
/ 28 июля 2011

Единственное, что я могу понять, это то, что оператор " Когда CTE используется в операторе, являющемся частью пакета, перед оператором должна стоять точка с запятой. " (Transact SQL Ссылка) означает, что CTE нельзя использовать в операторе IF.

Кстати, у вас есть две другие ошибки: 1) inserted псевдо-таблица не включена в первый подзапрос, даже если вы ссылаетесь на нее в предложении were. 2) Ваш триггер предполагает, что одна строка вставляется или обновляется. Возможно, что будет несколько повторяющихся имен, но raiserror сообщит только об одном из них.

РЕДАКТИРОВАТЬ И избегать (select count(*) ...) > при выполнении exists (select * ....) Существующие могут остановиться в первом ряду.

РЕДАКТИРОВАТЬ 2 Дерьмо. SQL Server запускает по умолчанию после триггеров. Поэтому строка, в которой вы проверяете существование, уже существует в таблице при срабатывании триггера:

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 голос
/ 28 июля 2011

Не похоже, что оператор 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 работает, но я не смог получитьэто в данном случае.

1 голос
/ 28 июля 2011

Не думаю, что вы можете использовать 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...