Я использую последнюю версию SQL Server и имею следующую проблему. Учитывая приведенную ниже таблицу, требование довольно просто состоит в том, чтобы удалить «завершающие» записи в каждом _category
разделе, в котором есть _value = 0
. В этом контексте трейлинг означает, что когда записи расположены в порядке _date
, любая серия или непрерывный блок записей с _value = 0
в конце списка должны быть удалены. Записи с _value = 0
, у которых есть последующие записи в разделе с некоторым ненулевым значением, должны остаться.
create table #x (_id int identity, _category int, _date date, _value int)
insert into #x values (1, '2022-10-01', 12)
insert into #x values (1, '2022-10-03', 0)
insert into #x values (1, '2022-10-04', 10)
insert into #x values (1, '2022-10-06', 11)
insert into #x values (1, '2022-10-07', 10)
insert into #x values (2, '2022-10-01', 1)
insert into #x values (2, '2022-10-02', 0)
insert into #x values (2, '2022-10-05', 19)
insert into #x values (2, '2022-10-10', 18)
insert into #x values (2, '2022-10-12', 0)
insert into #x values (2, '2022-10-13', 0)
insert into #x values (2, '2022-10-15', 0)
insert into #x values (3, '2022-10-02', 10)
insert into #x values (3, '2022-10-03', 0)
insert into #x values (3, '2022-10-05', 0)
insert into #x values (3, '2022-10-06', 12)
insert into #x values (3, '2022-10-08', 0)
Я вижу несколько способов сделать это. Путь грубой силы состоит в том, чтобы пропустить записи через курсор в порядке дат и получить идентификатор любой записи, где _value = 0
, и посмотреть, сохраняется ли он, пока категория не изменится. Я стараюсь избегать T-SQL, если могу сделать это в запросе.
С этой целью я подумал, что могу применить некоторые трюки с пробелами и островами и сделать что-то с оконными функциями. Я чувствую, что может быть способ использовать last_value()
для этого, но пока я вижу, что это полезно только для определения разделов, которые имеют критерии, а не для того, чтобы помочь мне получить идентификаторы записей для удаления.
Желаемый результат — удаление записей 10, 11, 12 и 17.
Цените любую помощь.
Я не уверен, что ваше требование требует подхода с пробелами и островами. Простая логика существования должна работать.
SELECT _id, _catrgory, _date, _value
FROM #x x1
WHERE _value <> 0 OR
EXISTS (
SELECT 1
FROM #x x2
WHERE x2._category = x1._category AND
x2._date > x1._date AND
x2._value <> 0
);
Это хорошо. Будет интересно сравнить планы между вашим подходом и предложенным @forpas.
Используя общие табличные выражения, вы можете использовать:
WITH CTE_NumberedRows AS (
SELECT *, rn = ROW_NUMBER() OVER(PARTITION BY _category ORDER BY _date)
FROM #x
),
CTE_Keepers AS (
SELECT _category, rnLastKeeper = MAX(rn)
FROM CTE_NumberedRows
WHERE _value <> 0
GROUP BY _category
)
DELETE NR
FROM CTE_NumberedRows NR
LEFT JOIN CTE_Keepers K
ON K._category = NR._category
WHERE NR.rn > ISNULL(K.rnLastKeeper, 0)
См. эту скрипку db<> для рабочей демонстрации.
Обновлено: мой исходный пост не обрабатывал крайний случай с нулевым значением. Это было исправлено выше вместе с некоторыми изменениями в именах. (Оригинал все еще можно найти здесь.
Сообщение Тима Бигелейзена может быть более простым подходом.
На самом деле я предпочитаю оконный подход везде, где это возможно. Я просто не мог придумать ваш запрос :-)
Почему вы предпочитаете оконную функцию @timbiegeleisen? Ваш ответ очень ясен и прост?
Подход с использованием оконных функций может быть более эффективным.
Предполагая, что все _value
s больше или равны 0
, вы можете использовать оконную функцию MAX()
в обновляемом CTE
:
WITH cte AS (
SELECT *,
MAX(_value) OVER (
PARTITION BY _category
ORDER BY _date
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
) max
FROM #x
)
DELETE FROM cte
WHERE max = 0;
Если есть отрицательные _value
, используйте MAX(ABS(_value))
вместо MAX(_value)
.
Смотрите демо.
Никаких отрицательных значений, и этот подход больше всего похож на тот, который я пытался выкинуть из головы. У меня нет привычки использовать предложение фрейма, которого мне не хватало, я думаю. Спасибо.
См. этот db<.fiddle для демонстрации, включая оператор удаления, который инвертирует условие
WHERE
выше.