У меня есть столбец в таблице, который ссылается на одну и ту же таблицу (как отношения родитель-потомок).
Когда я хочу выполнить такую очистку (код С#):
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
Какое тебе дело? В двойной петле нет ничего плохого. Но, как вы говорите, вы можете упорядочить выражение по регистру.
Это в производственной среде. Полагаю, если я удалю ограничение, это коснется всей системы, а не только контекста сценария?
«Я думал об «ORDER BY» при удалении с помощью CTE, но не уверен в результате». - попробуй и увидишь?
У меня нет проблем с двойной петлей. Просто для моей культуры найти другой более короткий путь :)
Я добавил возможное решение в свой пост. Я пока не могу пробовать, но как вы думаете, это сработает?
Почему вы спрашиваете нас? Попробуйте это на своем промежуточном сервере и убедитесь сами?
Что вы пытаетесь сделать в первую очередь? Сколько строк вы планируете удалить? Конечно, есть более простые способы сделать это. DELETE TOP — более медленный способ удаления большого количества записей (но не такой медленный, как удаление по одной). Если вы хотите удалить исторические записи, возможно, самый быстрый вариант — использовать секционирование и удалить или переместить старые разделы в другие таблицы. Другой вариант — использовать иерархический запрос SELECT для получения идентификаторов следующих 500–1000 корней, а затем запустить DELETE .. WHERE ID in (1,6,89,...).
В запросе SELECT вы можете установить порядок от дочерних элементов к корням или порядок по уровням по убыванию. Таким образом, при пакетной обработке идентификаторов на клиенте не будет пропущенных ссылок. Вы также можете сохранить идентификаторы вместе с уровнем во временной таблице или табличной переменной, а также написать запрос, который удаляет пакеты в порядке убывания уровня.
Однажды у меня возникла проблема с поддержкой клиента, вызванная каскадом триггеров удаления с использованием курсоров, запускающих триггеры с курсорами... Система стала невероятно медленной. Будьте осторожны с этим.


Вы можете использовать рекурсивный 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 - если вы добавите представление о том, как далеко от корня находится данный дочерний элемент (что просто - я обычно добавляю столбец 0 as [level] в базовом случае и cte.[level] + 1 в рекурсивном случае), вы можете группировать эти удаления таким образом способ сделать их от листа до корня дерева. И я думаю, что если вы это сделаете, вы гарантированно не столкнетесь с какими-либо проблемами с самоссылкой FK, даже если удаление затронет неоднородные уровни.
@BenThul да, если сохранить элемент cte в #t-таблицу с уровнем, то можно затем выполнить цикл while @@rowcount > 0, который без проблем удаляет 5000 верхних порядков по уровню
@siggemannen Смотрите правки, вам нужно отслеживать уровень и сначала сортировать по самому дальнему.
@siggemannen - Я не думаю, что вам нужно сохранять результаты (как показывает редактирование Charlieface). Однако это компромисс: если вычисление иерархии обходится дорого, вы будете делать это при каждом прохождении цикла. Конечно, есть оговорка: иерархия будет уменьшаться с каждой итерацией, поэтому, вероятно, она станет менее затратной. Как всегда, тестируйте и смотрите.
Пример в конце моего исходного поста работает так, как он присутствовал. Потому что скрипт сначала удалит все строки с любой ссылкой FK, а затем строки без ссылки FK. Я использую пакет из 1000 строк, чтобы избежать блокировки таблицы.
Таким образом, решение состоит в том, чтобы использовать CTE или конкретный запрос после предложения FROM (как комментарии, это тот же результат).
один из вариантов, если вам разрешено это делать, - это УДАЛИТЬ FK перед DELETE, а затем RECREATE и снова после этого.