2009-08-03 26 views
6

Próbuję selektywnie usuwać rekordy z tabeli programu SQL Server 2005 bez przechodzenia przez kursor. Tabela może zawierać wiele rekordów (czasami> 500 000), więc zapętlenie jest zbyt wolne.Usuwanie rekordów z tabeli programu SQL Server bez kursora

danych:

ID, UnitID, Day, Interval, Amount 

1 100  10 21  9.345 

2 100  10 22  9.367 

3 200  11 21  4.150 

4 300  11 21  4.350 

5 300  11 22  4.734 

6 300  11 23  5.106 

7 400  13 21  10.257 

8 400  13 22  10.428 

Key jest: ID, UnitID, Dzień, Interval.

W tym przykładzie chcę usunąć zapisy 2, 5 i 8 - sąsiadują z istniejącym rekordem (na podstawie klucza).

Uwaga: rekord 6 nie zostanie usunięty, ponieważ po zniknięciu 5 nie będzie już przylegał.

Czy proszę o zbyt wiele?

+1

Nie jestem pewien, ale zespół nie rozumie „sąsiedztwo” z tego co pamiętam z teorii mnogości. Może to wymagać użycia kursora. –

+1

Jak zdecydujesz, które wiersze usunąć? Jakie są kryteria, w oparciu o jakie pola (pola)? –

+0

Wygląda na "porządek sekwencyjny" oparty na kluczu. –

Odpowiedz

1

Nie sądzę, że to, o co prosisz, jest możliwe, ale możesz być blisko. Wydaje się, można prawie to zrobić poprzez znalezienie zapisów z samosprzężenie tak:

SELECT t1.id 
FROM 
    table t1 JOIN table t2 ON (
    t1.unitid = t2.unitid AND 
    t1.day = t2.day AND 
    t1.interval = t2.interval - 1 
) 

ale problemem jest to, że znajdziemy id = 6, jak również. Jeśli jednak utworzysz tabelę tymczasową z tych danych, może ona być o wiele mniejsza niż oryginalne dane, a tym samym szybciej skanować za pomocą kursora (w celu rozwiązania problemu id = 6). Następnie można wykonać DELETE FROM table WHERE id IN (SELECT id FROM tmp_table), aby zabić wiersze.

Może istnieć sposób naprawienia problemu ID = 6 bez kursora, ale jeśli tak, to go nie widzę.

+1

Jeśli już umieściłeś oryginał wybierz do zmiennej tabeli, następnie możesz użyć WHILE zamiast kursora, aby naprawić problem id = 6. –

0

Istnieje WHILE statement, który jest alternatywą dla kursora. To w połączeniu z table variables może pozwolić ci zrobić to samo w granicach wydajności, z którym jesteś w porządku.

0
DECLARE @Table TABLE (ID INT, UnitID INT, [Day] INT, Interval INT, Amount FLOAT) 

INSERT INTO @Table VALUES (1, 100, 10, 21, 9.345) 
INSERT INTO @Table VALUES (2, 100, 10, 22, 9.367) 
INSERT INTO @Table VALUES (3, 200, 11, 21, 4.150) 
INSERT INTO @Table VALUES (4, 300, 11, 21, 4.350) 
INSERT INTO @Table VALUES (5, 300, 11, 22, 4.734) 
INSERT INTO @Table VALUES (6, 300, 11, 23, 5.106) 
INSERT INTO @Table VALUES (7, 400, 13, 21, 10.257) 
INSERT INTO @Table VALUES (8, 400, 13, 22, 10.428) 

DELETE FROM @Table 
WHERE ID IN (
    SELECT t1.ID 
    FROM @Table t1 
     INNER JOIN @Table t2 
      ON t2.UnitID = t1.UnitID 
       AND t2.Day = t1.Day 
       AND t2.Interval = t1.Interval - 1 
     LEFT OUTER JOIN @Table t3 
      ON t3.UnitID = t2.UnitID 
       AND t3.Day = t2.Day 
       AND t3.Interval = t2.Interval - 1 
    WHERE t3.ID IS NULL) 

SELECT * FROM @Table 
+0

Spowoduje to usunięcie tylko pierwszej sąsiedniej wartości.Jeśli mamy '21',' 22', '23' i' 24', to usunie '22', ale opuści' 24'. – Quassnoi

+0

Masz rację, zrób to, gdy istnieje (wybierz ...) usuń z ... –

4

Zobacz te artykuły w moim blogu o szczegóły wykonania:


Głównym pomysłem na poniższym zapytania jest to, że powinniśmy usunąć wszystkie nawet rzędy z ciągłych przedziałów interwałów.

Oznacza to, że jeśli na dany (unitId, Day) mamy następujące intervals:

1 
2 
3 
4 
6 
7 
8 
9 

mamy dwa ciągłych zakresach:

1 
2 
3 
4 

i

6 
7 
8 
9 

i powinniśmy usuń każdy równy wiersz:

1 
2 -- delete 
3 
4 -- delete 

i

6 
7 -- delete 
8 
9 -- delete 

, dzięki czemu otrzymujemy:

1 
3 
6 
8 

Zauważ, że "nawet wiersze" oznacza "ROW_NUMBER() s nawet za zasięgiem" tu, a nie „nawet wartości interval ".

Oto zapytanie:

DECLARE @Table TABLE (ID INT, UnitID INT, [Day] INT, Interval INT, Amount FLOAT) 

INSERT INTO @Table VALUES (1, 100, 10, 21, 9.345) 
INSERT INTO @Table VALUES (2, 100, 10, 22, 9.345) 
INSERT INTO @Table VALUES (3, 200, 11, 21, 9.345) 
INSERT INTO @Table VALUES (4, 300, 11, 21, 9.345) 
INSERT INTO @Table VALUES (5, 300, 11, 22, 9.345) 
INSERT INTO @Table VALUES (6, 300, 11, 23, 9.345) 
INSERT INTO @Table VALUES (7, 400, 13, 21, 9.345) 
INSERT INTO @Table VALUES (8, 400, 13, 22, 9.345) 
INSERT INTO @Table VALUES (9, 400, 13, 23, 9.345) 
INSERT INTO @Table VALUES (10, 400, 13, 24, 9.345) 
INSERT INTO @Table VALUES (11, 400, 13, 26, 9.345) 
INSERT INTO @Table VALUES (12, 400, 13, 27, 9.345) 
INSERT INTO @Table VALUES (13, 400, 13, 28, 9.345) 
INSERT INTO @Table VALUES (14, 400, 13, 29, 9.345) 

;WITH rows AS 
     (
     SELECT *, 
       ROW_NUMBER() OVER 
       (
       PARTITION BY 
         (
         SELECT TOP 1 qi.id AS mint 
         FROM @Table qi 
         WHERE qi.unitid = qo.unitid 
           AND qi.[day] = qo.[day] 
           AND qi.interval <= qo.interval 
           AND NOT EXISTS 
           (
           SELECT NULL 
           FROM @Table t 
           WHERE t.unitid = qi.unitid 
             AND t.[day] = qi.day 
             AND t.interval = qi.interval - 1 
           ) 
         ORDER BY 
           qi.interval DESC 
         ) 
       ORDER BY interval 
       ) AS rnm 
     FROM @Table qo 
     ) 
DELETE 
FROM rows 
WHERE rnm % 2 = 0 

SELECT * 
FROM @table 

Aktualizacja:

Oto bardziej efektywne zapytania:

DECLARE @Table TABLE (ID INT, UnitID INT, [Day] INT, Interval INT, Amount FLOAT) 

INSERT INTO @Table VALUES (1, 100, 10, 21, 9.345) 
INSERT INTO @Table VALUES (2, 100, 10, 22, 9.345) 
INSERT INTO @Table VALUES (3, 200, 11, 21, 9.345) 
INSERT INTO @Table VALUES (4, 300, 11, 21, 9.345) 
INSERT INTO @Table VALUES (5, 300, 11, 22, 9.345) 
INSERT INTO @Table VALUES (6, 300, 11, 23, 9.345) 
INSERT INTO @Table VALUES (7, 400, 13, 21, 9.345) 
INSERT INTO @Table VALUES (8, 400, 13, 22, 9.345) 
INSERT INTO @Table VALUES (9, 400, 13, 23, 9.345) 
INSERT INTO @Table VALUES (10, 400, 13, 24, 9.345) 
INSERT INTO @Table VALUES (11, 400, 13, 26, 9.345) 
INSERT INTO @Table VALUES (12, 400, 13, 27, 9.345) 
INSERT INTO @Table VALUES (13, 400, 13, 28, 9.345) 
INSERT INTO @Table VALUES (14, 400, 13, 29, 9.345) 

;WITH source AS 
     (
     SELECT *, ROW_NUMBER() OVER (PARTITION BY unitid, day ORDER BY interval) rn 
     FROM @Table 
     ), 
     rows AS 
     (
     SELECT *, ROW_NUMBER() OVER (PARTITION BY unitid, day, interval - rn ORDER BY interval) AS rnm 
     FROM source 
     ) 
DELETE 
FROM rows 
WHERE rnm % 2 = 0 

SELECT * 
FROM @table 
+0

Jeśli twój ciągły zakres interwałów wynosi 2, 3, 4, 5 - Musisz usunąć kursy, a nie wszystkie. – Matt

+0

@Matt: Musisz usunąć nawet rzędy, nawet wartości. val '3' będzie miał równe' ROW_NUMBER() 'z' 2' w twoim przykładzie. – Quassnoi

+0

Rozumiem. Wygląda na to, że Z/ROW_NUMBER jest sposobem, aby przejść do tego – Matt

0

Lieven jest tak blisko - pracował dla zestawu testowego, ale jeśli Dodaję jeszcze kilka rekordów, za którymi trochę tęsknię.

Nie możemy użyć żadnych nieparzystych/parzystych kryteriów - nie mamy pojęcia, jak dane spadają.

Dodaj te dane i spróbuj jeszcze raz:

INSERT @Table VALUES (9, 100,  10, 23,  9.345) 

INSERT @Table VALUES (10, 100,  10, 24,  9.367) 

INSERT @Table VALUES (11, 100,  10, 25,  4.150) 

INSERT @Table VALUES (12, 100,  10, 26,  4.350) 

INSERT @Table VALUES (13, 300,  11, 25,  4.734) 

INSERT @Table VALUES (14, 300,  11, 26,  5.106) 

INSERT @Table VALUES (15, 300,  11, 27,  10.257) 

INSERT @Table VALUES (16, 300,  11, 29,  10.428) 
+0

@Ian: ponowione, pozostawia wiersze '9',' 11', '13',' 15' i '16', usuwa" 10 "," 12 " i '14'. Czy tego nie chcesz? – Quassnoi

Powiązane problemy