web-dev-qa-db-ja.com

セットごとに少なくとも1行が条件を満たす行を選択します

次の表があります。

create table test (
  company_id integer not null, 
  client_id integer not null, 
  client_status text,
  unique (company_id, client_id)
);

insert into test values
  (1, 1, 'y'),    -- company1

  (2, 2, null),   -- company2

  (3, 3, 'n'),    -- company3

  (4, 4, 'y'),    -- company4
  (4, 5, 'n'),

  (5, 6, null),   -- company5
  (5, 7, 'n')
;

基本的に、5つの異なる会社があり、それぞれに1つ以上のクライアントがあり、各クライアントにはステータス「y」または「n」があります(nullの場合もあります)。

私がしなければならないことは、ステータスが「n」ではない(「y」またはnull)クライアントが少なくとも1つあるすべての企業のすべてのペア(company_id, client_id)を選択することです。上記の例のデータの場合、出力は次のようになります。

company_id;client_id
1;1
2;2
4;4
4;5
5;6
5;7

ウィンドウ関数で何かを試しましたが、すべてのクライアントの数とSTATUS = 'n'を使用してクライアントの数を比較する方法がわかりません。

select company_id,
count(*) over (partition by company_id) as all_clients_count
from test
-- where all_clients_count != ... ?

私はこれを行う方法を理解しましたが、それが正しい方法であるかどうかはわかりません:

select sub.company_id, unnest(sub.client_ids)
from (
  select company_id, array_agg(client_id) as client_ids
  from test
  group by company_id
  having count(*) != count( (case when client_status = 'n' then 1 else null end) )
) sub
7
user606521

基本的にあなたは式を探しています:

client_status IS DISTINCT FROM 'n'

client_status列は実際にはデータ型 boolean である必要があり、textではなく、より単純な式を使用できます。

client_status IS NOT FALSE

このマニュアルの詳細は 比較演算子 の章にあります。


実際のテーブルに UNIQUEまたはPK制約 があるとすると、次のようになります。

CREATE TABLE test (
  company_id    integer NOT NULL, 
  client_id     integer NOT NULL, 
  client_status boolean,
  PRIMARY KEY (company_id, client_id)
);

クエリ

これらはすべて同じです(あなたが尋ねたとおり)。これは、データの配布に依存します。

SELECT company_id, client_id
FROM   test t
WHERE  EXISTS (
   SELECT 1 FROM test
   WHERE  company_id = t.company_id
   AND    client_status IS NOT FALSE
   );

または:

SELECT company_id, client_id
FROM   test t
JOIN  (
   SELECT company_id
   FROM   test t
   GROUP  BY 1
   HAVING bool_or(client_status IS NOT FALSE)
   ) c USING (company_id);

または:

SELECT company_id, client_id
FROM   test t
JOIN  (
   SELECT DISTINCT company_id, client_status 
   FROM   test t
   ORDER  BY company_id, client_status DESC
   ) c USING (company_id)
WHERE  c.client_status IS NOT FALSE;

ブール値はFALSE-> TRUE-> NULLを昇順で並べ替えます。したがって、FALSEは降順で最後になります。他に利用可能なany値がある場合、その値が最初に選択されます...

追加されたPKは、これらのクエリに役立つインデックスで実装されます。さらに高速にしたい場合は、クエリ1に部分インデックスを追加します。

CREATE INDEX test_special_idx ON test (company_id, client_id)
WHERE  client_status IS NOT FALSE;

あなたもウィンドウ関数を使用できますが、それは遅くなります。 first_value()の例:

SELECT company_id, client_id
FROM  (
   SELECT company_id, client_id
        , first_value(client_status) OVER (PARTITION BY company_id
                                           ORDER BY client_status DESC) AS stat
   FROM   test t
   ) sub
WHERE stat IS NOT FALSE;

company_idあたりの行のlotsの場合、これらの手法のいずれかが高速である可能性があります。

5

私はあなたを誤解したかもしれませんが、私は次のようなものを想像します:

 select * 
 from test x 
 where exists ( 
     select 1 
     from test y 
     where x.company_id = y.company_id 
       and coalesce(client_status, 'y') <> 'n'
 );

働くでしょう。合体はnullを 'y'にマッピングするために使用されますが、 'n'と異なるものは何でもする必要があります

OLAP関数を使用すると、「結合」を節約できます。

select company_id, client_id 
from (
    select x.*
         , count(nullif(coalesce(client_status,'y'),'n')) 
               over (partition by company_id) as cnt 
    from test x
) 
where cnt > 0;

ここではnull-> 'y'と 'n'-> nullをマッピングします。 count(x)は、xがnullでない行をカウントするため、client_status <> 'n'の行をカウントします。 OLAP関数を使用してGROUP BYを回避しました。つまり、テーブルを1回参照するだけで済みます。

2
Lennart

これは少し簡略化できると思います:

select company_id 
from test 
group by company_id 
having count(*) filter (where client_status!='n' or client_status is null) > 0;
1
Károly Nagy

以下の標準SQLクエリは機能するはずです

select
  company_id,
  client_id
from test
where client_status!='n' or client_status is null;
0
Sahap Asci