2

次のフィールドを持つテーブルを作成しました。

Record:
Id                 int Primary Key, Auto Increment
ForeignId          int
IsDuplicateRecord bit NULL

次に、いくつかのデータを挿入しました。

INSERT INTO Record (ForeignId)
VALUES (5), (5), (1), (2), (3)

その後、次の更新ステートメントを実行しました ( http://archive.msdn.microsoft.com/SQLExamples/Wiki/View.aspx?title=DuplicateRowsにあります)。

UPDATE Record
SET IsDuplicateRecord = 1
WHERE Id IN (
    SELECT MAX(Id)
    FROM Record
    GROUP BY ForeignId
    HAVING COUNT(*) > 1
)

これまでのところ、クエリは 1 つの行に影響し、テーブルは次のようになります。

Id ForeignId IsDuplicateRecord
0  5         NULL
1  5         1
2  1         NULL
3  2         NULL
4  3         NULL

一瞬、すべてうまくいくと思ったので、うれしかったです。しかし、外の雲のように暗い疑いが頭をよぎりました。

INSERT INTO Record (ForeignId)
VALUES (1), (1)

上記のクエリを再度実行すると、次の結果が得られました。

Id  ForeignId  IsDuplicateRecord
0   0          NULL
1   5          1
2   1          NULL
3   2          NULL
4   3          NULL
5   1          NULL
6   1          1

そこで、StackOverflow にアクセスして、ID 5 の行にある IsDuplicatedRecord フィールドが 1 に更新されなかった理由を誰が説明できるか見てみようと思いました。あなたがそうですか?

4

3 に答える 3

5

実行した SQL は、最後の重複のみを重複としてマークするためです。代わりにこれを試してください:

UPDATE Record
SET IsDuplicateRecord = 1
WHERE Id NOT IN (
    SELECT MIN(Id)
    FROM Record
    GROUP BY ForeignId
)

これは、必要に応じて、それぞれの 2 回目以降の発生をForeignId重複としてマークします。

于 2012-07-12T16:09:23.797 に答える
1
UPDATE Record uu
SET IsDuplicateRecord = 1
   -- if there exists a record with the same foreignid
   -- but a lower id
   -- this (uu) is a duplicate
WHERE EXISTS (
    SELECT *
    FROM Record ex 
    WHERE ex.ForeignId = uu.ForeignId
    AND ex.Id < uu.Id
    );

このサブクエリと @DavidM のサブクエリには微妙な(しかし失礼な)違いがあります。 ForeignId を持つすべてのタプルの isDuplicateRecord フラグは NULL です。(ForeignId は FK であると思われるため、NULL 可能である可能性があります)EXISTS (...)NOT IN (...)NOT IN

null 非許容の ForeignId の場合、2 つのバージョンは基本的に同じです。

UPDATE: @MartinSmith が指摘したように、一部の実装では、FROM 句のない UPDATE ... WHERE が好きではありません。自己結合ダミーを使用できます。(最初のクエリも通常に更新しました)

-- DROP SCHEMA tmp CASCADE;
-- CREATE SCHEMA tmp ;
-- SET search_path='tmp';

DROP TABLE zrecord CASCADE;
CREATE TABLE zrecord
        ( id SERIAL NOT NULL PRIMARY KEY
        , foreign_id INTEGER -- REFERENCES zrecord(id)
        , is_duplicate boolean DEFAULT False
        );
SELECT * FROM zrecord;

INSERT INTO zrecord(foreign_id) VALUES(NULL),(1),(NULL),(1),(NULL),(2),(NULL);

SELECT * FROM zrecord;

EXPLAIN ANALYZE
UPDATE zrecord uu
SET is_duplicate = True
        --
        -- This selfjoin is needed if UPDATE ... WHERE needs a FROM TABLE
        --
FROM zrecord dum
WHERE  dum.id = uu.id
AND EXISTS (
    SELECT *
    FROM zrecord ex
    WHERE ex.foreign_id = uu.foreign_id
    AND ex.Id < uu.Id
    );

SELECT * FROM zrecord;

UPDATE2: PARTITION BY は、IN 句と同じ nullability 問題に悩まされているため、次のように思われます。

WITH zcte AS (
    SELECT *
    , row_number() OVER (PARTITION BY foreign_id ORDER BY id) AS rn
    FROM   zrecord
    )
SELECT * FROM zcte;

結果: (更新前の元のテストセット)

 id | foreign_id | is_duplicate | rn 
----+------------+--------------+----
  2 |          1 | f            |  1
  4 |          1 | t            |  2
  6 |          2 | f            |  1
  1 |            | f            |  1
  3 |            | f            |  2
  5 |            | f            |  3
  7 |            | f            |  4
于 2012-07-12T17:47:10.100 に答える
0

これは、他の 2 つの回答のいずれよりも推定コストが低くなります。

;WITH CTE
     AS (SELECT *,
                Row_number() OVER (PARTITION BY ForeignId ORDER BY Id) AS RN
         FROM   Record)
UPDATE CTE
SET    IsDuplicateRecord = 1
WHERE  RN > 1 

実行計画

予定

于 2012-07-15T12:05:38.910 に答える