2014-09-05 13 views
6

Używam Firebird 2.1.Firebirda, jak wybrać identyfikatory pasujące do wszystkich elementów w zestawie

Nie

jest tabela: IDs, Labels

Może istnieć wiele etykiet dla tego samego ID:

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

powiedzmy Mam zestaw etykiet, tj .: (jabłko, gruszka, brzoskwinia) .

Jak mogę napisać pojedynczą opcję, aby zwrócić wszystkie identyfikatory, które mają przypisane wszystkie etykiety w danym zestawie? Najlepiej, bym określił zestaw w łańcuchu oddzielonym przecinkami, jak: ("Apple", "Pear", "Peach") -> powinno to zwrócić ID = 10.

Dzięki!

Odpowiedz

2

Na pytanie, publikuję moją prostszą wersję odpowiedzi piclrowa. Przetestowałem to na moim Firebird, który jest w wersji 2.5, ale OP (Steve) przetestował go na 2.1 i działa również.

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

To rozwiązanie ma tę samą wadę, co pilcrow w ... trzeba wiedzieć, ile wartości szukasz, jak BIORĄC = warunek musi dopasować gdzie w stanie. Pod tym względem odpowiedź Eda jest bardziej elastyczna, ponieważ dzieli parametr łańcucha wartości i zlicza wartości. Musisz więc zmienić tylko jeden parametr, zamiast 2 warunków, które ja i pilcrow używam.

OTOH, jeśli wymuszenie jest niepokojące, wolałbym raczej (ale absolutnie nie jestem pewien), że podejście CTE Eda może być mniej optymalizowane przez silnik Firebird, niż ten, który sugeruję. Firebird jest bardzo dobry w optymalizacji zapytań, ale tak naprawdę nie teraz, jeśli jest w stanie to zrobić, gdy używasz CTE w ten sposób. Ale WHERE + GROUP BY + HAVING powinien być optymalizowany przez proste posiadanie indeksu na (identyfikator, etykieta).

Podsumowując, jeśli czasy wykonania budzą obawy w Twoim przypadku, to prawdopodobnie trzeba Niektórzy tłumaczą plany, aby zobaczyć, co się dzieje, cokolwiek rozwiązanie wybrać;)

+0

W zapytaniu użytkownika (lub pilcrow) nie ma CTE ("common table expression"). –

+0

Ten komentarz został skierowany do odpowiedzi Eda, która jest dobra i elastyczna, ale ** robi ** używa CTE. Sprawię, że będzie jaśniejszy.Dzięki – Frazz

+0

Działa również z FB2.1. Przyjmę to jako odpowiedź, ponieważ jest to najprostsze zapytanie. Dzięki! – Steve

2

Najłatwiej podzielić ciąg w kodzie, a następnie kwerendy

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

Co if (id, etykieta) nie jest wyjątkowy? Dodałbym DISTINCT w podselekcji ... na wszelki wypadek;) – Frazz

+0

@Frazz, tak, dziękuję i dostałem to. – pilcrow

+0

Nie używam Firebirda od jakiegoś czasu i nie użyłem go do zrobienia tego typu zapytania. Czy nie można tego zrobić bez SUBSELECT w FireBird? Mam na myśli ... używanie HAVING zamiast WHERE w zewnętrznym select? – Frazz

1

Jeżeli dopuszczalne jest utworzenie pomocnika procedurę przechowywaną, która będzie wywoływana z głównego wybierz następnie rozważyć następujące.

Procedura Helper przechowywane odbywa się w wyznaczonym ciąg wraz z separatorem i zwraca wiersz dla każdego wyznaczonego ciąg

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 

Poniżej znajduje się kod, aby zadzwonić, używam Execute bloku wykazać przekazując rozdzielany string

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 
Powiązane problemy