2014-09-05 2 views
6

Я использую Firebird 2.1.Firebird как выбрать идентификаторы, соответствующие всем элементам в наборе

Существует таблица: IDs, Labels

Там может быть несколько меток для одной и той же ID:

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

Допустим, у меня есть набор наклеек, т.е. .: (яблоко, груша, персик) ,

Как написать одиночный выбор для возврата всех идентификаторов, имеющих все метки, связанные с заданным набором? Предпочтительно, я хотел бы указать набор в строке, разделенной запятыми, например: («Apple», «Pear», «Peach») -> это должно вернуться ID = 10.

Спасибо!

ответ

2

Как я уже сказал, я отправляю свою более простую версию ответа piclrow. Я тестировал это на своем Firebird, который является версией 2.5, но OP (Steve) протестировал его на 2.1, и он также работает.

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

Это решение имеет тот же недостаток, что и знак абзаца в ... Вы должны знать, сколько значений вы ищете, как HAVING = условие должно соответствовать где в состоянии. В этом отношении ответ Эд более гибкий, так как он разбивает параметр конкатенированной строки значений и подсчитывает значения. Таким образом, вам просто нужно изменить один параметр, вместо двух условий, которые я и пиккруу используют.

OTOH, если эффективность вызывает беспокойство, я бы предпочел (но я абсолютно не уверен), что подход EdE от Ed может быть менее оптимизирован с помощью двигателя Firebird, чем тот, который я предлагаю. Firebird очень хорош в оптимизации запросов, но я действительно не сейчас, если это возможно, когда вы используете CTE таким образом. Но WHERE + GROUP BY + HAVING следует оптимизировать, просто имея индекс на (id, label).

В заключение, если время выполнения вызывает беспокойство в вашем случае, то вы, вероятно, потребуются некоторые объяснить планы увидеть, что происходит, в зависимости от решения вы выбрали;)

+0

В вашем запросе (или пиккрау) нет CTE (или табличного выражения) –

+0

Этот комментарий был отнесен к ответу Эд, который является приятным и гибким, но ** использует ** CTE. Я сделаю это более ясным.Спасибо – Frazz

+0

Работает также с FB2.1. Я отвечу на это как на ответ, так как это самый простой запрос. Благодаря! – Steve

2

Это проще всего разбить строку в коде, а затем запрос

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

Что делать, если (идентификатор, метка) не является уникальным? Я бы добавил DISTINCT в подзапрос ... на всякий случай;) – Frazz

+0

@Frazz, да, спасибо и получил его. – pilcrow

+0

Я не использовал Firebird через некоторое время, и я не использовал его для выполнения этого типа запроса. Не может ли это быть сделано без SUBSELECT в FireBird? Я имею в виду ... используя HAVING вместо WHERE во внешнем select? – Frazz

1

Если это приемлемо для создания хранимой процедуры помощника, который будет вызываться из первичных выберите, затем рассмотрит следующее.

Процедура Помощника хранится принимает в строке с разделителями вместе с разделителем и возвращает строку для каждой строки с разделителями

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 

Ниже приведен код для вызова, я использую Execute блока, чтобы продемонстрировать, проходя в разделителях строка

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 

 Смежные вопросы

  • Нет связанных вопросов^_^