Wählen Sie Zeilen aus, für die mindestens eine Zeile pro Satz eine Bedingung erfüllt

7

Ich habe folgende Tabelle:

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')
;

Grundsätzlich gibt es 5 verschiedene Unternehmen, jedes hat einen oder mehrere Kunden und jeder Kunde hat den Status: 'y' oder 'n' (kann auch null sein).

Was ich tun muss, ist, alle Paare (company_id, client_id)für alle Unternehmen auszuwählen , für die es mindestens einen Kunden gibt, dessen Status nicht 'n' ('y' oder null) ist. Für die obigen Beispieldaten sollte die Ausgabe also sein:

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

Ich habe etwas mit Fensterfunktionen versucht, kann aber nicht herausfinden, wie die Anzahl ALLER Clients mit der Anzahl der Clients verglichen werden kann STATUS = 'n'.

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

Ich habe herausgefunden, wie das geht, bin mir aber nicht sicher, ob es der richtige Weg ist:

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
user606521
quelle
Ihre Anforderungen sind unklar. Müssen Sie die Companyids und Clientids oder nur die Companyids auswählen?
Andrew Brennan
Sie haben Recht, Entschuldigung, ich habe die Frage aktualisiert (erwartetes Ergebnis). Wenn mindestens ein Kunde in der Firma den Status null oder status! = 'N' hat, möchte ich diese Firma mit allen Kunden zusammenbringen, auch mit status = 'n'. Mit anderen Worten, ich möchte alle Unternehmen (zusammen mit Kunden) herausfiltern, für die alle Kunden den Status = 'n' haben.
user606521
Wichtiges fehlendes Detail: Sie sind in Ihrer Tabellendefinition nicht UNIQUEeingeschränkt (company_id, client_id). Ihre Testdaten lassen jedoch den Eindruck entstehen (company_id, client_id), dass sie tatsächlich einzigartig sind. Ist es? Wenn nicht, benötigen Sie * alle * qualifizierenden Zeilen oder jede Kombination von company_idund client_idnur einmal ?
Erwin Brandstetter
Ja, Sie haben Recht, (company_id, client_id) ist eindeutig.
user606521
@ErwinBrandstetter Entschuldigung, ich habe letztendlich meine (letzte fragliche) Lösung verwendet, da sie im Moment für mich ganz gut funktioniert. Wie auch immer, ich werde Ihre Antwort akzeptieren, da ich ziemlich sicher bin, dass sie korrekt und vollständig ist :).
user606521

Antworten:

5

Grundsätzlich suchen Sie nach dem Ausdruck:

client_status IS DISTINCT FROM 'n'

Die Spalte client_statussollte eigentlich vom Datentyp sein boolean, nicht text, was den einfacheren Ausdruck ermöglichen würde:

client_status IS NOT FALSE

Das Handbuch enthält Details im Kapitel Vergleichsoperatoren .


Angenommen, Ihr tatsächlicher Tisch hat eine UNIQUEoder eine PKEinschränkung , kommen wir zu:

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

Abfragen

Alle diese tun dasselbe (was Sie gefragt haben), was am schnellsten von der Datenverteilung abhängt:

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

Oder:

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

Oder:

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;

Boolesche Werte sortieren FALSE-> TRUE-> NULLin aufsteigender Sortierreihenfolge. Also FALSEkommt zuletzt in absteigender Reihenfolge. Wenn ein anderer Wert verfügbar ist, wird dieser zuerst ausgewählt ...

Die hinzugefügte PK wird mit einem nützlichen Index für diese Abfragen implementiert. Wenn Sie dennoch schneller möchten, fügen Sie einen Teilindex für Abfrage 1 hinzu:

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

Sie könnten auch Fensterfunktionen verwenden, aber das wäre langsamer. Beispiel mit 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;

Bei vielen Zeilen pro company_idZeile kann eine dieser Techniken dennoch schneller sein:

Erwin Brandstetter
quelle
2

Ich habe dich vielleicht missverstanden, aber ich stelle mir so etwas vor wie:

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

wird funktionieren. Koaleszenz wird verwendet, um 'y' null zuzuordnen, aber alles andere als 'n' sollte reichen

Die Verwendung einer OLAP-Funktion kann uns einen "Join" ersparen:

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;

Hier ordnen wir null -> 'y' und 'n' -> null zu. Da count (x) Zeilen zählt, in denen x nicht null ist, zählen wir Zeilen, in denen client_status <> 'n' ist. Ich habe eine OLAP-Funktion verwendet, um GROUP BY zu vermeiden, was bedeutet, dass wir die Tabelle nur einmal referenzieren müssen.

Lennart
quelle
1

Ich denke, das kann ein bisschen vereinfacht werden:

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

Eine Standard-SQL-Abfrage unten sollte funktionieren

select
  company_id,
  client_id
from test
where client_status!='n' or client_status is null;
Sahap Asci
quelle
Entschuldigung, meine Schuld, ich habe die Frage (erwartetes Ergebnis) nicht genau geschrieben: Wenn mindestens ein Kunde im Unternehmen den Status null oder status hat! = 'N', dann möchte ich dieses Unternehmen mit allen Kunden zusammenbringen, auch mit status = ' n '.
user606521