Как удалить в определенном порядке

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

Когда я хочу выполнить такую ​​очистку (код С#):

do
{
    commandRows = context.Database.ExecuteSqlCommand("DELETE top(5000) from IssuerRequests WHERE discriminator='IssuerRequest' and Issuer_ID = @Issuer_ID and ExpireDate < @LimitDate",
        new SqlParameter("@Issuer_ID", customer.ID), new SqlParameter("@LimitDate", LimitDate));
    AffectedRows += commandRows;
}
while (commandRows >= 5000);

Появляется исключение:

Инструкция DELETE конфликтовала с ограничением SAME TABLE REFERENCE «FK_dbo.IssuerRequests_dbo.IssuerRequests_LinkedRequestForMultichannel_ID». Конфликт произошел в базе данных «xxx», таблица «dbo.IssuerRequests», столбец «LinkedRequestForMultichannel_ID». Заявление было прекращено.

Эта ошибка является нормальной, поскольку если мы попытаемся удалить строку, на которую ссылается другая, система не позволит этого сделать.

Поэтому я управляю сценарием для очистки вручную:

USE [Database]

DECLARE @DaysAfterExpiring INT
DECLARE @Issuer_ID INT
DECLARE @DateAfterExpiring DATETIME

SET @DaysAfterExpiring = 35
SET @DateAfterExpiring = DATEADD(day, -(@DaysAfterExpiring), GETUTCDATE())

SELECT @Issuer_ID = [ID] FROM IssuerDescs WHERE [DisplayName] LIKE '%toto%'

DECLARE @MoreRowsToDelete BIT
SET @MoreRowsToDelete = 1

-- With the relationship
WHILE @MoreRowsToDelete = 1
BEGIN
    DELETE TOP (1000) FROM IssuerRequests WHERE discriminator='IssuerRequest' AND Issuer_ID = @Issuer_ID AND [ExpireDate] < @DateAfterExpiring  AND LinkedRequestForMultiChannel_ID IS NOT NULL
    IF NOT EXISTS (SELECT TOP 1 * FROM IssuerRequests WHERE discriminator='IssuerRequest' AND Issuer_ID = @Issuer_ID AND [ExpireDate] < @DateAfterExpiring  AND LinkedRequestForMultiChannel_ID IS NOT NULL)
    BEGIN
        SET @MoreRowsToDelete = 1
    END
END

-- Without the relationship
SET @MoreRowsToDelete = 1
WHILE @MoreRowsToDelete = 1
BEGIN
    DELETE TOP (1000) FROM IssuerRequests WHERE discriminator='IssuerRequest' AND Issuer_ID = @Issuer_ID AND [ExpireDate] < @DateAfterExpiring  AND LinkedRequestForMultiChannel_ID IS NULL
    IF NOT EXISTS (SELECT TOP 1 * FROM IssuerRequests WHERE discriminator='IssuerRequest' AND Issuer_ID = @Issuer_ID AND [ExpireDate] < @DateAfterExpiring  AND LinkedRequestForMultiChannel_ID IS NULL)
    BEGIN
        SET @MoreRowsToDelete = 1
    END
END

Знаете ли вы, можно ли избежать двойных циклов и удалить 5000 строк за одно действие, не делая различий?

Я думал об удалении «ORDER BY» с помощью CTE, но не уверен в результате.

Я попробую это возможное решение:

USE [database]

DECLARE @DaysAfterExpiring INT
DECLARE @Issuer_ID INT
DECLARE @DateAfterExpiring DATETIME

SET @DaysAfterExpiring = 35
SET @DateAfterExpiring = DATEADD(day, -(@DaysAfterExpiring), GETUTCDATE())

SELECT @Issuer_ID = [ID] FROM IssuerDescs WHERE [DisplayName] LIKE '%toto%'

DECLARE @MoreRowsToDelete BIT
SET @MoreRowsToDelete = 1

-- With the relationship first then without the relationship
WHILE @MoreRowsToDelete = 1
BEGIN
    DELETE T FROM (SELECT TOP (1000) * FROM IssuerRequests WHERE discriminator='IssuerRequest' AND Issuer_ID = @Issuer_ID AND [ExpireDate] < @DateAfterExpiring ORDER BY ISNULL(LinkedRequestForMultiChannel_ID, '') DESC) AS T
    IF NOT EXISTS (SELECT TOP 1 * FROM IssuerRequests WHERE discriminator='IssuerRequest' AND Issuer_ID = @Issuer_ID AND [ExpireDate] < @DateAfterExpiring)
    BEGIN
        SET @MoreRowsToDelete = 0
    END
END

один из вариантов, если вам разрешено это делать, - это УДАЛИТЬ FK перед DELETE, а затем RECREATE и снова после этого.

Geezer 21.02.2024 09:44

Какое тебе дело? В двойной петле нет ничего плохого. Но, как вы говорите, вы можете упорядочить выражение по регистру.

Dale K 21.02.2024 09:47

Это в производственной среде. Полагаю, если я удалю ограничение, это коснется всей системы, а не только контекста сценария?

BaptX 21.02.2024 09:48

«Я думал об «ORDER BY» при удалении с помощью CTE, но не уверен в результате». - попробуй и увидишь?

Dale K 21.02.2024 09:48

У меня нет проблем с двойной петлей. Просто для моей культуры найти другой более короткий путь :)

BaptX 21.02.2024 09:48
stackoverflow.com/questions/13508280/…
Dale K 21.02.2024 09:50

Я добавил возможное решение в свой пост. Я пока не могу пробовать, но как вы думаете, это сработает?

BaptX 21.02.2024 10:07

Почему вы спрашиваете нас? Попробуйте это на своем промежуточном сервере и убедитесь сами?

Dale K 21.02.2024 10:08

Что вы пытаетесь сделать в первую очередь? Сколько строк вы планируете удалить? Конечно, есть более простые способы сделать это. DELETE TOP — более медленный способ удаления большого количества записей (но не такой медленный, как удаление по одной). Если вы хотите удалить исторические записи, возможно, самый быстрый вариант — использовать секционирование и удалить или переместить старые разделы в другие таблицы. Другой вариант — использовать иерархический запрос SELECT для получения идентификаторов следующих 500–1000 корней, а затем запустить DELETE .. WHERE ID in (1,6,89,...).

Panagiotis Kanavos 21.02.2024 10:15

В запросе SELECT вы можете установить порядок от дочерних элементов к корням или порядок по уровням по убыванию. Таким образом, при пакетной обработке идентификаторов на клиенте не будет пропущенных ссылок. Вы также можете сохранить идентификаторы вместе с уровнем во временной таблице или табличной переменной, а также написать запрос, который удаляет пакеты в порядке убывания уровня.

Panagiotis Kanavos 21.02.2024 10:22

Однажды у меня возникла проблема с поддержкой клиента, вызванная каскадом триггеров удаления с использованием курсоров, запускающих триггеры с курсорами... Система стала невероятно медленной. Будьте осторожны с этим.

shawnt00 23.02.2024 19:01
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
0
11
105
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете использовать рекурсивный CTE, чтобы удалить их все за один раз.

Пока все дочерние строки удаляются одновременно, они удаляются в том же операторе, что и дочерние строки. В ORDER BY нет необходимости, так как сервер может определить, что родительский и дочерний элементы удаляются одновременно.

WITH cte AS (
    SELECT
      ir.ID,
      ir.LinkedRequestForMultiChannel_ID
    FROM IssuerRequests ir
    JOIN IssuerDescs id ON id.ID = ir.Issuer_ID
    WHERE id.DisplayName LIKE '%toto%'
      AND ir.discriminator = 'IssuerRequest'
      AND ir.ExpireDate < @DateAfterExpiring

    UNION ALL

    SELECT
      ir.ID,
      ir.LinkedRequestForMultiChannel_ID
    FROM IssuerRequests ir
    JOIN cte ON cte.LinkedRequestForMultiChannel_ID = ir.ID  -- is this the correct join?
)
DELETE ir
FROM cte
JOIN IssuerRequests ir ON ir.ID = cte.ID;

Если вы хотите объединить их в пакет, то это сложнее, так как вам нужно сначала убедиться, что все более удаленные уровни удалены.

WHILE 1=1
BEGIN

WITH cte AS (
    SELECT
      ir.ID,
      ir.LinkedRequestForMultiChannel_ID,
      1 AS Level
    FROM IssuerRequests ir
    JOIN IssuerDescs id ON id.ID = ir.Issuer_ID
    WHERE id.DisplayName LIKE '%toto%'
      AND ir.discriminator = 'IssuerRequest'
      AND ir.ExpireDate < @DateAfterExpiring

    UNION ALL

    SELECT
      ir.ID,
      ir.LinkedRequestForMultiChannel_ID
      cte.Level + 1
    FROM IssuerRequests ir
    JOIN cte ON cte.LinkedRequestForMultiChannel_ID = ir.ID  -- is this the correct join?
)
DELETE ir
FROM (
    SELECT TOP (4500) *
    FROM cte
    ORDER BY
      cte.Level DESC
) cte
JOIN IssuerRequests ir ON ir.ID = cte.ID;

IF @@ROWCOUNT = 0
    BREAK;

WAITFOR DELAY '00:00:01';

END;

Хотя я согласен с этим ответом, возможно, ОП хочет на самом деле группировать DELETE.

siggemannen 21.02.2024 16:04

@siggemannen - если вы добавите представление о том, как далеко от корня находится данный дочерний элемент (что просто - я обычно добавляю столбец 0 as [level] в базовом случае и cte.[level] + 1 в рекурсивном случае), вы можете группировать эти удаления таким образом способ сделать их от листа до корня дерева. И я думаю, что если вы это сделаете, вы гарантированно не столкнетесь с какими-либо проблемами с самоссылкой FK, даже если удаление затронет неоднородные уровни.

Ben Thul 21.02.2024 17:05

@BenThul да, если сохранить элемент cte в #t-таблицу с уровнем, то можно затем выполнить цикл while @@rowcount > 0, который без проблем удаляет 5000 верхних порядков по уровню

siggemannen 21.02.2024 17:11

@siggemannen Смотрите правки, вам нужно отслеживать уровень и сначала сортировать по самому дальнему.

Charlieface 21.02.2024 17:13

@siggemannen - Я не думаю, что вам нужно сохранять результаты (как показывает редактирование Charlieface). Однако это компромисс: если вычисление иерархии обходится дорого, вы будете делать это при каждом прохождении цикла. Конечно, есть оговорка: иерархия будет уменьшаться с каждой итерацией, поэтому, вероятно, она станет менее затратной. Как всегда, тестируйте и смотрите.

Ben Thul 21.02.2024 18:29
Ответ принят как подходящий

Пример в конце моего исходного поста работает так, как он присутствовал. Потому что скрипт сначала удалит все строки с любой ссылкой FK, а затем строки без ссылки FK. Я использую пакет из 1000 строк, чтобы избежать блокировки таблицы.

Таким образом, решение состоит в том, чтобы использовать CTE или конкретный запрос после предложения FROM (как комментарии, это тот же результат).

Другие вопросы по теме