2014-09-05 13 views
6

Sto usando Firebird 2.1.Firebird come selezionare gli ID che corrispondono a tutti gli elementi di un set

Ci

è una tabella: IDs, Labels

Non ci può essere più etichette per lo stesso ID:

10 Peach 
10 Pear 
10 Apple 
11 Apple 
12 Pear 
13 Peach 
13 Apple 

Diciamo che ho una serie di etichette, cioè .: (mela, pera, pesca) .

Come posso scrivere una singola selezione per restituire tutti gli ID che hanno tutte le etichette associate in un determinato set? Preferibilmente, desidero specificare il set in una stringa separata da virgole, come: ('Apple', 'Pera', 'Pesca') -> questo dovrebbe restituire ID = 10.

Grazie!

risposta

2

Come richiesto, sto postando la mia versione più semplice della risposta di Piclrow. Ho provato questo sul mio Firebird, che è la versione 2.5, ma l'OP (Steve) lo ha testato su 2.1 e funziona altrettanto bene.

SELECT id 
FROM table 
WHERE label IN ('Apple', 'Pear', 'Peach') 
GROUP BY id 
HAVING COUNT(DISTINCT label)=3 

Questa soluzione ha lo stesso svantaggio Pilcrow di ... è necessario sapere quanti valori si sta cercando, come il HAVING = condizione deve corrispondere al DOVE IN condizione. In questo senso, la risposta di Ed è più flessibile, in quanto divide il parametro di stringa del valore concatenato e conta i valori. Quindi devi solo cambiare un parametro, invece delle 2 condizioni che uso io e pilcrow.

OTOH, se l'efficienza è preoccupante, preferirei pensare (ma non sono assolutamente sicuro) che l'approccio CTE di Ed potrebbe essere meno ottimizzato dal motore Firebird di quello che suggerisco. Firebird è molto bravo nell'ottimizzare le query, ma in realtà non lo so se è in grado di farlo quando usi CTE in questo modo. Ma WHERE + GROUP BY + HAVING dovrebbe essere ottimizzato semplicemente avendo un indice su (id, label).

In conclusione, se i tempi di esecuzione sono fonte di preoccupazione nel tuo caso, allora probabilmente bisogno di alcuni Spiegare i piani per vedere ciò che sta accadendo, a seconda di quale soluzione scelta;)

+0

Non c'è CTE ("espressione di tabella comune") nella query (o pilcrow) –

+0

Questo commento è stato riferito alla risposta di Ed, che è carina e flessibile, ma ** fa ** usa CTE. Lo renderò più chiaro.Grazie – Frazz

+0

anche con FB2.1. Prenderò questa risposta poiché questa è la domanda più semplice. Grazie! – Steve

2

E 'più facile per dividere la stringa in codice e quindi eseguire una query

SQL> select ID 
CON> from (select ID, count(DISTINCT LABEL) as N_LABELS 
CON>   from T 
CON>   where LABEL in ('Apple', 'Pear', 'Peach') 
CON>   group by 1) D 
CON> where D.N_LABELS >= 3; -- We know a priori we have 3 LABELs 

      ID 
============ 
      10 
+1

E se (id, etichetta) non è unica? Aggiungerei un DISTINCT nella sottoselezione ... nel caso;) – Frazz

+0

@Frazz, sì, grazie e capito. – pilcrow

+0

Non ho usato Firebird da un po ', e non l'ho usato per fare questo tipo di query. Non è possibile farlo senza il SUBSELECT in FireBird? Voglio dire ... usare un HAVING al posto di WHERE nella selezione esterna? – Frazz

1

Se è accettabile per creare una stored procedure di supporto che verrà chiamato dal primario selezionare quindi prendere in considerazione quanto segue.

La procedura Helper stored prende in una stringa delimitata insieme con il delimitatore e restituisce una riga per ogni stringa delimitata

CREATE OR ALTER PROCEDURE SPLIT_BY_DELIMTER (
    WHOLESTRING VARCHAR(10000), 
    SEPARATOR VARCHAR(10)) 
RETURNS (
    ROWID INTEGER, 
    DATA VARCHAR(10000)) 
AS 
DECLARE VARIABLE I INTEGER; 
BEGIN 
    I = 1; 
    WHILE (POSITION(:SEPARATOR IN WHOLESTRING) > 0) DO 
    BEGIN 
     ROWID = I; 
     DATA = TRIM(SUBSTRING(WHOLESTRING FROM 1 FOR POSITION(TRIM(SEPARATOR) IN WHOLESTRING) - 1));   
     SUSPEND;  
     I = I + 1; 
     WHOLESTRING = TRIM(SUBSTRING(WHOLESTRING FROM POSITION(TRIM(SEPARATOR) IN WHOLESTRING) + 1)); 
    END 
    IF (CHAR_LENGTH(WHOLESTRING) > 0) THEN 
    BEGIN 
     ROWID = I; 
     DATA = WHOLESTRING; 
     SUSPEND; 
    END 
END 

seguito è il codice per chiamare, sto usando Esegui il blocco per dimostrare che passa nel delimitato stringa

EXECUTE BLOCK 
RETURNS (
    LABEL_ID INTEGER) 
AS 
DECLARE VARIABLE PARAMETERS VARCHAR(50); 
BEGIN 
    PARAMETERS = 'Apple,Peach,Pear'; 

    FOR WITH CTE 
    AS (SELECT ROWID, 
      DATA 
     FROM SPLIT_BY_DELIMITER(:PARAMETERS, ',')) 
    SELECT ID 
    FROM TABLE1 
    WHERE LABELS IN (SELECT DATA 
        FROM CTE) 
    GROUP BY ID 
    HAVING COUNT(*) = (SELECT COUNT(*) 
        FROM CTE) 
    INTO :LABEL_ID 
    DO 
    SUSPEND; 
END 
Problemi correlati