Firebird如何选择匹配集合中所有项目的ID

时间:2014-09-05 16:05:30

标签: sql firebird firebird2.1

我正在使用Firebird 2.1。

有一张表:IDs, Labels

同一ID可以有多个标签:

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

我们说我有一套标签,即:( Apple,Pear,Peach)。

如何编写单个选择以返回在给定集合中包含所有标签的所有ID?我希望在用逗号分隔的字符串中指定集合,例如:(' Apple',' Pear',' Peach') - >这应该返回ID = 10.

谢谢!

3 个答案:

答案 0 :(得分: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 :(得分:2)

据我所知,我发布了更简单版本的piclrow的回答。我已经在我的Firebird上测试了这个版本2.5,但是OP(史蒂夫)已经在2.1上对它进行了测试,它也能正常工作。

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

此解决方案与pilcrow相同的缺点...您需要知道要查找的值数,因为HAVING =条件必须与WHERE IN条件匹配。在这方面,Ed的答案更灵活,因为它会拆分连接值字符串参数并计算值。所以你只需要更改一个参数,而不是我和pilcrow使用的两个条件。

OTOH,如果关注效率,我宁愿认为(但我绝对不确定),Firebird引擎可能不像我建议的那样优化Ed的CTE方法。 Firebird非常擅长优化查询,但是如果能够以这种方式使用CTE,我现在就不会这样做。但是,只需在(id,label)上设置索引即可优化WHERE + GROUP BY + HAVING。

总之,如果您的案例中涉及执行时间,那么您可能需要一些解释计划来查看正在发生的事情,无论您选择哪种解决方案;)

答案 2 :(得分:1)

如果可以创建将从主要选择调用的帮助程序存储过程,请考虑以下内容。

Helper存储过程接受带分隔符的分隔字符串,并为每个分隔字符串返回一行

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