488

2つの列を組み合わせたテーブルからすべての行を取得する必要があります。そのため、同じ日に同じ価格で発生した他の販売がないすべての販売が必要です。日と価格に基づいて一意の販売は、アクティブなステータスに更新されます。

だから私は考えています:

UPDATE sales
SET status = 'ACTIVE'
WHERE id IN (SELECT DISTINCT (saleprice, saledate), id, count(id)
             FROM sales
             HAVING count = 1)

しかし、私の脳はそれ以上に行くのが痛いです。

4

5 に答える 5

513
SELECT DISTINCT a,b,c FROM t

ほぼ同等です :

SELECT a,b,c FROM t GROUP BY a,b,c

GROUP BY構文の方が強力なので、慣れることをお勧めします。

あなたの質問のために、私はそれをこのようにします:

UPDATE sales
SET status='ACTIVE'
WHERE id IN
(
    SELECT id
    FROM sales S
    INNER JOIN
    (
        SELECT saleprice, saledate
        FROM sales
        GROUP BY saleprice, saledate
        HAVING COUNT(*) = 1 
    ) T
    ON S.saleprice=T.saleprice AND s.saledate=T.saledate
 )
于 2008-09-10T15:36:09.417 に答える
376

これまでの回答をまとめ、クリーンアップして改善すると、次の優れたクエリに到達します。

UPDATE sales
SET    status = 'ACTIVE'
WHERE  (saleprice, saledate) IN (
    SELECT saleprice, saledate
    FROM   sales
    GROUP  BY saleprice, saledate
    HAVING count(*) = 1 
    );

これは、それらのいずれよりもはるかに高速です。現在受け入れられている回答のパフォーマンスを 10 ~ 15 倍にします (PostgreSQL 8.4 および 9.1 での私のテストでは)。

しかし、これはまだ最適とは言えません。NOT EXISTSパフォーマンスをさらに向上させるには、(アンチ) セミ結合を使用します。EXISTS標準SQLであり、永遠に存在しており(少なくともPostgreSQL 7.2以降、この質問が尋ねられるずっと前から)、提示された要件に完全に適合しています。

UPDATE sales s
SET    status = 'ACTIVE'
WHERE  NOT EXISTS (
   SELECT FROM sales s1                     -- SELECT list can be empty for EXISTS
   WHERE  s.saleprice = s1.saleprice
   AND    s.saledate  = s1.saledate
   AND    s.id <> s1.id                     -- except for row itself
   )
AND    s.status IS DISTINCT FROM 'ACTIVE';  -- avoid empty updates. see below

db<>fiddle here
古い SQL フィドル

行を識別する一意のキー

テーブルの主キーまたは一意のキーがない場合 (例)、このクエリの目的でidシステム列に置き換えることができます (ただし、他の目的ではできません)。ctid

   AND    s1.ctid <> s.ctid

すべてのテーブルには主キーが必要です。まだお持ちでない場合は追加してください。Postgres 10+serialの列または列をお勧めします。IDENTITY

関連している:

これはどのように高速ですか?

EXISTS反セミ結合のサブクエリは、最初の重複が見つかるとすぐに評価を停止できます (これ以上調べても意味がありません)。重複がほとんどないベーステーブルの場合、これはわずかに効率的です。重複が多いと、これはより効率的になります。

空の更新を除外する

この更新が既にある行の場合status = 'ACTIVE'、何も変更されませんが、新しい行バージョンが全額で挿入されます (小さな例外が適用されます)。通常、これは必要ありません。WHEREこれを回避してさらに高速化するには、上記のような別の条件を追加します。

statusが定義されている場合NOT NULL、次のように簡略化できます。

AND status <> 'ACTIVE';

<>列のデータ型は、演算子をサポートしている必要があります。jsonそうでないタイプもあります。見る:

NULL 処理の微妙な違い

このクエリは ( Joel による現在受け入れられている回答とは異なり) NULL 値を等しいものとして扱いません。次の 2 つの行は(saleprice, saledate)、「別個」と見なされます (ただし、人間の目と同じように見えます)。

(123, NULL)
(123, NULL)

また、NULL 値は SQL 標準に従って同等に比較されないため、一意のインデックスと他のほとんどすべての場所を渡します。見る:

OTOH、GROUP BYDISTINCTまたはDISTINCT ON ()NULL 値を等しいものとして扱います。達成したいことに応じて、適切なクエリ スタイルを使用します。一部またはすべての比較IS NOT DISTINCT FROMの代わりに、このより高速なクエリを引き続き使用して、NULL 比較を等しくすることができます。=もっと:

比較されるすべての列が定義されている場合NOT NULL、不一致の余地はありません。

于 2012-09-28T00:50:06.250 に答える
27

クエリの問題は、GROUP BY 句を使用する場合 (基本的に、distinct を使用して行う)、グループ化または集計関数を使用する列のみを使用できることです。値が異なる可能性があるため、列 ID は使用できません。あなたの場合、HAVING句のために常に1つの値しかありませんが、ほとんどのRDBMSはそれを認識できるほど賢くありません。

ただし、これは機能するはずです(結合は必要ありません):

UPDATE sales
SET status='ACTIVE'
WHERE id IN (
  SELECT MIN(id) FROM sales
  GROUP BY saleprice, saledate
  HAVING COUNT(id) = 1
)

MIN の代わりに MAX または AVG を使用することもできます。一致する行が 1 つしかない場合にのみ、列の値を返す関数を使用することが重要です。

于 2008-09-10T16:17:13.937 に答える