複数の列でどのようにSELECT DISTINCTを行う(または行うことができる)のですか?


415

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

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

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

しかし、私の脳はそれより遠くに行くと痛いです。

回答:


436
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
 )

117
このクエリは正しく、現在1年間受け入れられていますが、非常に非効率であり、不必要です。これは使わないでください。私は別の回答で代替案といくつかの説明を提供しました。
Erwin Brandstetter 2012

1
TからDISTINCT A、B、C、SELECTされていない正確には、B、CでSELECTなどのTグループから、B、Cを同じことを?
famargar

8
単純なケースでは@famargarですが、意味的には意味が異なり、より大きなクエリを作成するときのステップで実行できることも異なります。さらに、テクノロジーフォーラムの人々は、物事について非常に知識が豊富であることがよくあります。私は、この文脈での投稿にイタチの単語を追加すると便利な場合が多いと思います。
Joel Coehoorn 2017年

344

これまでの回答をまとめ、整理して改善すると、次の優れたクエリに到達します。

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 Fiddle

行を識別する一意のキー

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標準に従って等しいと比較されないため、一意のインデックスなど、ほとんどどこでも渡します。見る:

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

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


16
いい答えです。私はsqlサーバーの男なので、IN()チェック付きのタプルを使用するという最初の提案は私には起こりません。存在しないという提案は、通常、SQL Serverでの内部結合と同じ実行プランになります。
Joel Coehoorn、2012

2
いいね。説明は答えの価値を大幅に高めます。計画がPostgresやSQLServerとどのように比較されるかを確認するために、Oracleでいくつかのテストを実行したくなります。
Peter

2
@alairock:どこで手に入れたの?Postgresの場合、そのが当てはまります。すべての行を数える間、count(*)より効率的ですcount(<expression>)。やってみなよ。Postgresは、集約関数のこのバリアントをより高速に実装しています。多分あなたは他のいくつかのRDBMSとPostgresを混同していますか?
Erwin Brandstetter

6
@alairock:私はたまたまそのページの共著者であり、そのようなことは何も言わない。
Erwin Brandstetter 2016年

2
@ErwinBrandstetter、あなたはいつもスタック全体であなたの答えをそのようにしています。あなたは何年にもわたって想像を絶するほどの方法で助けてきました。この例については、問題を解決するためのいくつかの異なる方法を知っていましたが、誰かが可能性間の効率をテストしたことを確認したかったのです。ありがとうございました。
WebWanderer

24

クエリの問題は、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つしかない場合は、列の値を返す関数を使用することが重要です。


1

1つの列「GrondOfLucht」から個別の値を選択したいのですが、「sortering」列に指定された順序で並べ替える必要があります。を使用して1つの列だけの個別の値を取得することはできません

Select distinct GrondOfLucht,sortering
from CorWijzeVanAanleg
order by sortering

また、「sortering」列も提供します。「GrondOfLucht」と「sortering」は一意ではないため、結果はすべての行になります。

GROUPを使用して、「sorting」で指定された順序で「GrondOfLucht」のレコードを選択します

SELECT        GrondOfLucht
FROM            dbo.CorWijzeVanAanleg
GROUP BY GrondOfLucht, sortering
ORDER BY MIN(sortering)

これは基本的に受け入れられた答えが何をするかを説明しますが、例としてそのような名前を使用しないことをお勧めします(少なくともそれらを翻訳します)。PS:オランダ人であっても、すべてのプロジェクトで常にすべて英語で名前を付けることをお勧めします。
Kerwin

0

次のように、DBMSが複数の列を持つDISTINCTをサポートしていない場合:

select distinct(col1, col2) from table

一般に、複数選択は次のように安全に実行できます。

select distinct * from (select col1, col2 from table ) as x

これはほとんどのDBMSで機能し、グループ化機能を回避しているため、ソリューションごとのグループ化よりも高速であることが期待されています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.