Безумный курсор (SQL Server) заставляет программу зависать и вылетать - PullRequest
0 голосов
/ 29 августа 2018

Я разработал программу бронирования авиабилетов. У меня есть (абсурдное) условие в триггере, который должен выполняться, если он заполнен.

Моя проблема заключается в том, что когда я вызываю свою хранимую процедуру для бронирования рейса для клиента, моя программа зависает и вылетает из-за задержки.

Я знаю, что эта проблема возникла из-за моего триггера. Не могли бы вы понять, что застряло?

Если вам нужно больше деталей (таблицы, хранимая процедура, код), не стесняйтесь, сообщите мне! (Извините за французские комментарии: p)

ALTER TRIGGER [dbo].[Tr_Check60j]
ON [dbo].[Reservation]
FOR INSERT
AS
BEGIN
    DECLARE @IdVolInsere INT, @DateVol DATETIME, @IdVolExistant INT, @IdClient INT

    SELECT @IdVolInsere = v.VOL_Id 
    FROM Vol v
    JOIN Planning AS p ON p.PLA_Vol_Id = v.VOL_Id
    JOIN inserted AS i ON p.PLA_Id = i.RES_Pla_Id
    WHERE v.VOL_Id = p.PLA_Vol_Id
      AND p.PLA_Id = i.RES_Pla_Id

    SELECT @IdClient = i.RES_Client_Id
    FROM inserted i

    DECLARE @DateVolExistant DATETIME, @DateVolInsere DATETIME;
    DECLARE @IdVilleDepartExistant INT, @IdVilleArriveeExistant INT;
    DECLARE @IdVilleDepartInseree INT, @IdVilleArriveeInseree INT;

        -- Sélectionne l'id des villes du vol inséré
    SELECT @IdVilleDepartInseree = v.VOL_Vil_Depart_Id FROM Vol v WHERE v.VOL_Id = @IdVolInsere
    SELECT @IdVilleArriveeInseree = v.VOL_Vil_Arrivee_Id FROM Vol v WHERE v.VOL_Id = @IdVolInsere

    SELECT @DateVolInsere = p.PLA_Date
    FROM Planning p
    JOIN inserted AS i ON i.RES_Pla_Id = p.PLA_Id
    WHERE i.RES_Pla_Id = p.PLA_Id

    -- Curseur qui compare chaque vol du client existant avec le vol inséré pour vérifier si le vol existant
    -- est un vol retour, si oui, les 2 vols ont-ils plus de 60 jours entre eux? 
    -- Si oui, alors il faut vérifier s'il existe un vol réservé entre ces 2 vols --> S'il y en a un: ERREUR
    DECLARE CR_Check_Vols_Par_Id_Client CURSOR FOR 
        SELECT v.VOL_Id
        FROM Vol v
        JOIN Planning AS p ON v.VOL_Id = p.PLA_Vol_Id
        JOIN Reservation AS r ON p.PLA_Id = r.RES_Pla_Id
        WHERE r.RES_Client_Id = @IdClient

    OPEN CR_Check_Vols_Par_Id_Client

    FETCH CR_Check_Vols_Par_Id_Client INTO @IdVolExistant

    WHILE @@FETCH_STATUS = 0
    BEGIN
        -- Sélectionne l'id des villes aller (mêmes villes) du dernier vol existant à moins de 60j
        SELECT @IdVilleDepartExistant = v.VOL_Vil_Depart_Id FROM Vol v WHERE v.VOL_Id = @IdVolExistant
        SELECT @IdVilleArriveeExistant = v.VOL_Vil_Arrivee_Id FROM Vol v WHERE v.VOL_Id = @IdVolExistant

            -- Vérifie s'il existe un vol aller pour le vol inséré
        IF (@IdVilleDepartInseree = @IdVilleArriveeExistant) AND (@IdVilleArriveeInseree = @IdVilleDepartExistant)
        BEGIN

            SELECT @DateVolExistant = p.PLA_Date
            FROM Planning p
            JOIN Reservation AS r
            ON r.RES_Pla_Id = p.PLA_Id
            JOIN Vol AS v
            ON p.PLA_Vol_Id = @IdVolExistant
            JOIN inserted AS i
            ON r.RES_Id = i.RES_Id
            WHERE r.RES_Client_Id = @IdClient

            -- Vérifie si le vol inséré est à une date de moins de 60 jours du vol retour existant
            IF ( (DATEDIFF(DAY, @DateVolExistant , @DateVolInsere)) < 60  )
            BEGIN

            DECLARE @CheckVolEntre INT
                -- Sélectionne un vol existant entre le vol aller inséré et le vol retour existant
                SELECT @CheckVolEntre = v.VOL_Id
                FROM Vol v
                JOIN Planning AS p
                ON v.VOL_Id = p.PLA_Vol_Id
                JOIN Reservation AS r
                ON p.PLA_Id = r.RES_Pla_Id
                WHERE r.RES_Client_Id = @IdClient AND v.VOL_Id BETWEEN @IdVolExistant AND @IdVolInsere
                -- Vérifie s'il existe un vol existant entre le vol aller inséré et le vol retour existant
                    IF (@CheckVolEntre != NULL)
                    BEGIN
                        RAISERROR('Réservation impossible', 1, 601)
                        ROLLBACK TRANSACTION
                        STOP
                END
            END
        END
    END
    CLOSE CR_Check_Vols_Par_Id_Client
    DEALLOCATE CR_Check_Vols_Par_Id_Client
END

Заранее спасибо!

Jon

Ответы [ 2 ]

0 голосов
/ 30 августа 2018

Прежде всего, даже если у вашего триггера не было тайм-аута, он почти наверняка не делал то, что вы хотите. Большая часть этого сводится к вопиющему игнорированию возможно нескольких возвращаемых значений в некоторых операторах.
Я также немного обеспокоен тем, к каким условиям, похоже, приближаются, потому что похоже, что я не мог сделать что-то вроде планирования рейса на выходные, если бы у меня была короткая командировка на следующей неделе! Возможно, вы захотите проверить следующий рейс, но я не знаю достаточно, чтобы гарантировать это.

Тем не менее, я думаю (основываясь на некоторых предположениях о ваших данных и намерениях, но вам нужно будет проверить мою работу), вы можете сделать свой триггер похожим на:

ALTER TRIGGER [dbo].[Tr_Check60j]
ON [dbo].[Reservation]
FOR INSERT -- You could probably do INSTEAD OF and perform the insert here
AS
BEGIN

    SELECT 'Existing Flight'
    FROM Inserted
    JOIN Planning Inserted_planning
      ON Inserted_planning.pla_id = Inserted.res_pla_id
    JOIN Vol Inserted_vol
      ON Inserted_vol.vol_id = Inserted_planning.pla_vol_id
    WHERE EXISTS (
          SELECT 'Existing Flight'    
          FROM Vol Return_vol
          JOIN Planning Return_planning
            ON Return_planning.pla_vol_Id = return_vol.vol_id
               AND Return_planning.pla_date >= DATEADD(day, -60, CAST(Inserted_planning.pla_date AS DATE))
               AND Return_planning.pla_id != Inserted_planning.pla_id
          JOIN Return_reservation
            ON Return_reservation.res_pla_id = Return_planning.pla_Id
               AND Return_reservation.res_client_id = Inserted.res_client_id
               AND Return_reservation.res_id != Inserted.res_id
          JOIN Planning Planning_Other
            ON Planning_Other.pla_date >= Return_planning.pla_date
               AND Planning_Other.pla_date < Inserted_planning.pla_date
               AND Planning_other.pla_id != Inserted_planning.pla_id
               AND Planning_other.pla_id != Return_planning.pla_id
          JOIN Reservation Reservation_Other
            ON Reservation_Other.res_pla_id = Planning_Other.pla_id
               AND Reservation_Other.res_client_id = Inserted.res_client_id
               AND Reservation_other.res_id != Inserted.res_id
               AND Reservation_other.res_id != Return_reservation.res_id
          WHERE Return_vol.vol_vil_depart_id = Inserted_vol.vol_vil_arrivee_id
                AND Return_vol.vil_arrivee_id = Inserted_vol.vol_vil_depart_id)


    IF (@@ROWCOUNT > 0)
    BEGIN
        RAISERROR('Réservation impossible', 1, 601)
        ROLLBACK TRANSACTION
        STOP
    END
END

<ч /> Хорошо, вот анализ / изменение рассуждений. Давайте разберем это по частям.

Прежде всего, ваши запросы перед курсором могут быть объединены и упрощены:

-- For clarity, put DECLAREs on their own lines
DECLARE @IdVolInsere INT;
DECLARE @IdClient INT;
DECLARE @DateVolInsere DATETIME;
DECLARE @IdVilleDepartInseree INT;
DECLARE @IdVilleArriveeInseree INT;

-- ....I'm against prefixing columns with table names, especially _shortened_ table names.
-- Except maybe for id columns.
SELECT @IdVolInsere = Vol.vol_id, 
       @IdClient = Inserted.res_client_Id,
       @IdVilleDepartInseree = Vol.vol_vil_depart_id,
       @IdVilleArriveeInseree = Vol.vol_vil_arrivee_id,
       @DateVolInsere = Planning.pla_Date
FROM Vol -- Single-character aliases make it hard to track tables.
JOIN Planning 
  ON Planning.pla_vol_Id = vol.vol_Id
JOIN Inserted
  ON Inserted.res_pla_id = Planning.pla_id

... теперь, когда все прояснено, давайте (очистим и) взглянем на курсор и другие утверждения:

DECLARE CR_Check_Vols_Par_Id_Client CURSOR FOR 
SELECT Vol.vol_Id
FROM Vol
JOIN Planning
  ON Planning.pla_vol_Id = Vol.vol_id 
JOIN Reservation
  ON Reservation.res_pla_id = Planning.pla_Id
     AND Reservation.res_client_id = @IdClient

... Хорошо, проверяем любой Vol.vol_id, который соответствует нашему идентификатору клиента. За исключением того, что это прямой AFTER триггер, мы собираемся получить строку, которую мы только что вставили! Мы, вероятно, не очень этого хотим!

Ну, мы пока проигнорируем это и предположим, что нашему конечному состоянию не нужно беспокоиться об этом. Давайте просто удалим объявление курсора, которое превращает это в набор:

SELECT -- dunno what we need yet
FROM Vol
JOIN Planning
  ON Planning.pla_vol_Id = Vol.vol_id 
JOIN Reservation
  ON Reservation.res_pla_id = Planning.pla_Id
     AND Reservation.res_client_id = @IdClient

... следующие следующие строки:

    -- Sélectionne l'id des villes aller (mêmes villes) du dernier vol existant à moins de 60j
    SELECT @IdVilleDepartExistant = v.VOL_Vil_Depart_Id FROM Vol v WHERE v.VOL_Id = @IdVolExistant
    SELECT @IdVilleArriveeExistant = v.VOL_Vil_Arrivee_Id FROM Vol v WHERE v.VOL_Id = @IdVolExistant

    -- Vérifie s'il existe un vol aller pour le vol inséré
    IF (@IdVilleDepartInseree = @IdVilleArriveeExistant) AND (@IdVilleArriveeInseree = @IdVilleDepartExistant)

... скажите мне, что мы хотим ограничить наш набор на основе данных из исходного запроса. Это обрезает набор только до этих совпадающих строк. Давайте сделаем это:

SELECT -- dunno what we need yet
FROM Vol
JOIN Planning
  ON Planning.pla_vol_Id = Vol.vol_id 
JOIN Reservation
  ON Reservation.res_pla_id = Planning.pla_Id
     AND Reservation.res_client_id = @IdClient
WHERE Vol.vol_vil_depart_id = @IdVilleArriveeInseree
      AND Vol.vil_arrivee_id = @IdVilleDepartInseree

... следующий запрос получает данные для простого условия:

IF ( (DATEDIFF(DAY, @DateVolExistant , @DateVolInsere)) < 60  )

... но опять же, у нас уже есть эти данные, и мы можем добавить их к нашему запросу:

SELECT -- dunno what we need yet
FROM Vol
JOIN Planning
  ON Planning.pla_vol_Id = Vol.vol_id
     AND Planning.pla_date < DATEADD(day, 60, CAST(@DateVolInsere AS DATE))
JOIN Reservation
  ON Reservation.res_pla_id = Planning.pla_Id
     AND Reservation.res_client_id = @IdClient
WHERE Vol.vol_vil_depart_id = @IdVilleArriveeInseree
      AND Vol.vil_arrivee_id = @IdVilleDepartInseree

... К сожалению, основное условие следующего раздела, скорее всего, бессмыслица:

v.VOL_Id BETWEEN @IdVolExistant AND @IdVolInsere

... это потому, что он предполагает неключевые отношения о ключе. Если это автоматически генерируемые суррогатные ключи, это определенно не соответствует действительности. Наиболее вероятные схемы ключей для естественного ключа вряд ли будут полезны во всех случаях. Гораздо лучше использовать атрибутивные данные, которые для этого типа запроса обычно являются датой. Я думаю, мы можем использовать Planning.pla_date для этого:

SELECT -- dunno what we need yet
FROM Vol
JOIN Planning
  ON Planning.pla_vol_Id = Vol.vol_id
     AND Planning.pla_date >= DATEADD(day, -60, CAST(@DateVolInsere AS DATE))
JOIN Reservation
  ON Reservation.res_pla_id = Planning.pla_Id
     AND Reservation.res_client_id = @IdClient
JOIN Planning Planning_Other
  ON Planning_Other.pla_date >= Planning.pla_date
     AND Planning_Other.pla_date < @DateVolInsere
     AND Planning_Other.pla_id != Planning.pla_id
JOIN Reservation Reservation_Other
  ON Reservation_Other.res_pla_id = Planning_Other.pla_id
     AND Reservation_Other.res.client_id = @IdClient
     AND Reservation_Other.res_id != Reservation.res_id
WHERE Vol.vol_vil_depart_id = @IdVilleArriveeInseree
      AND Vol.vil_arrivee_id = @IdVilleDepartInseree

... и в этот момент мы наблюдаем, что на самом деле нам нет дела до каких-либо данных, а только от того, существует ли такая строка. К счастью, мы можем превратить это в обычное предложение EXISTS.

0 голосов
/ 30 августа 2018

ЭТО ТОЛЬКО ЗНАЧИТ БЫТЬ ВРЕМЕННЫМ ВРЕМЕННЫМ ОБРАЩЕНИЕМ. Как указано в моем комментарии, вам необходимо удалить курсора из вашего триггера.

Возможно, у вас проблемы с блокировкой. Попробуйте обходной путь ниже и посмотрите, поможет ли это. Я также рассмотрел несколько вопросов кодирования. Как указано в предыдущем комментарии, вы не можете проверить NOT NULL с ! = , это должно быть IS NOT NULL или IS NULL.

ALTER TRIGGER [dbo].[Tr_Check60j]
ON [dbo].[Reservation]
FOR INSERT
AS
BEGIN

    DECLARE @IdVolInsere INT, @DateVol DATETIME, @IdVolExistant INT, @IdClient INT

    SELECT 
        @IdVolInsere = v.VOL_Id 
    FROM Vol v WITH ( NOLOCK )
    JOIN Planning AS p WITH ( NOLOCK )
        ON p.PLA_Vol_Id = v.VOL_Id
    JOIN inserted AS i 
        ON p.PLA_Id = i.RES_Pla_Id
    WHERE 
        v.VOL_Id = p.PLA_Vol_Id
        AND p.PLA_Id = i.RES_Pla_Id

    SELECT @IdClient = i.RES_Client_Id FROM inserted i;

    DECLARE @DateVolExistant DATETIME, @DateVolInsere DATETIME;
    DECLARE @IdVilleDepartExistant INT, @IdVilleArriveeExistant INT;
    DECLARE @IdVilleDepartInseree INT, @IdVilleArriveeInseree INT;

        -- Sélectionne l'id des villes du vol inséré
    SELECT 
        @IdVilleDepartInseree = v.VOL_Vil_Depart_Id 
        , @IdVilleArriveeInseree = v.VOL_Vil_Arrivee_Id
    FROM Vol v WITH ( NOLOCK )
    WHERE 
        v.VOL_Id = @IdVolInsere;

    SELECT 
        @DateVolInsere = p.PLA_Date
    FROM Planning p WITH ( NOLOCK )
    JOIN inserted AS i 
        ON i.RES_Pla_Id = p.PLA_Id
    WHERE 
        i.RES_Pla_Id = p.PLA_Id

    -- Curseur qui compare chaque vol du client existant avec le vol inséré pour vérifier si le vol existant
    -- est un vol retour, si oui, les 2 vols ont-ils plus de 60 jours entre eux? 
    -- Si oui, alors il faut vérifier s'il existe un vol réservé entre ces 2 vols --> S'il y en a un: ERREUR
    DECLARE CR_Check_Vols_Par_Id_Client CURSOR FOR 
    SELECT 
        v.VOL_Id 
    FROM Vol v WITH ( NOLOCK )
    JOIN Planning AS p WITH ( NOLOCK ) 
        ON v.VOL_Id = p.PLA_Vol_Id
    JOIN Reservation AS r WITH ( NOLOCK ) 
        ON p.PLA_Id = r.RES_Pla_Id
    WHERE 
        r.RES_Client_Id = @IdClient

    OPEN CR_Check_Vols_Par_Id_Client

    FETCH CR_Check_Vols_Par_Id_Client INTO @IdVolExistant

    WHILE @@FETCH_STATUS = 0
    BEGIN

        -- Sélectionne l'id des villes aller (mêmes villes) du dernier vol existant à moins de 60j
        SELECT 
            @IdVilleDepartExistant = v.VOL_Vil_Depart_Id 
            , @IdVilleArriveeExistant = v.VOL_Vil_Arrivee_Id
        FROM Vol v WITH ( NOLOCK ) 
        WHERE 
            v.VOL_Id = @IdVolExistant;

            -- Vérifie s'il existe un vol aller pour le vol inséré
        IF (@IdVilleDepartInseree = @IdVilleArriveeExistant) AND (@IdVilleArriveeInseree = @IdVilleDepartExistant)
        BEGIN

            SELECT 
                @DateVolExistant = p.PLA_Date
            FROM Planning p WITH ( NOLOCK )
            JOIN Reservation AS r WITH ( NOLOCK )
                ON r.RES_Pla_Id = p.PLA_Id
            JOIN Vol AS v WITH ( NOLOCK )
                ON p.PLA_Vol_Id = @IdVolExistant
            JOIN inserted AS i
                ON r.RES_Id = i.RES_Id
            WHERE 
                r.RES_Client_Id = @IdClient;

            -- Vérifie si le vol inséré est à une date de moins de 60 jours du vol retour existant
            IF ( (DATEDIFF(DAY, @DateVolExistant , @DateVolInsere)) < 60  )
            BEGIN

            DECLARE @CheckVolEntre INT
                -- Sélectionne un vol existant entre le vol aller inséré et le vol retour existant
                SELECT 
                    @CheckVolEntre = v.VOL_Id
                FROM Vol v WITH ( NOLOCK )
                JOIN Planning AS p WITH ( NOLOCK )
                    ON v.VOL_Id = p.PLA_Vol_Id
                JOIN Reservation AS r WITH ( NOLOCK )
                    ON p.PLA_Id = r.RES_Pla_Id
                WHERE 
                    r.RES_Client_Id = @IdClient 
                    AND v.VOL_Id BETWEEN @IdVolExistant AND @IdVolInsere;

                -- Vérifie s'il existe un vol existant entre le vol aller inséré et le vol retour existant
                IF (@CheckVolEntre IS NOT NULL)
                BEGIN
                    RAISERROR('Réservation impossible', 1, 601)
                    ROLLBACK TRANSACTION
                    STOP
                END

            END

        END

    END

    CLOSE CR_Check_Vols_Par_Id_Client
    DEALLOCATE CR_Check_Vols_Par_Id_Client

END
...