次の表があります。
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
基本的にあなたは式を探しています:
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の場合、これらの手法のいずれかが高速である可能性があります。
私はあなたを誤解したかもしれませんが、私は次のようなものを想像します:
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回参照するだけで済みます。
これは少し簡略化できると思います:
select company_id
from test
group by company_id
having count(*) filter (where client_status!='n' or client_status is null) > 0;
以下の標準SQLクエリは機能するはずです
select
company_id,
client_id
from test
where client_status!='n' or client_status is null;