【问题标题】:Firebird how to select ids that match all items in a setFirebird如何选择与集合中所有项目匹配的ID
【发布时间】:2014-10-30 15:31:01
【问题描述】:

我使用的是 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。

谢谢!

【问题讨论】:

    标签: sql firebird firebird2.1


    【解决方案1】:

    在代码中拆分字符串然后查询是最简单的方法

    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 
    

    【讨论】:

    • 如果 (id, label) 不是唯一的怎么办?我会在子选择中添加一个 DISTINCT ......以防万一;)
    • 好久没用Firebird了,也没有用它来做这种查询。如果没有 FireBird 中的 SUBSELECT,这不能完成吗?我的意思是......在外部选择中使用 HAVING 而不是 WHERE?
    • @Frazz 你能发布你的简单版本吗?
    【解决方案2】:

    如果可以接受创建将从主选择调用的辅助存储过程,请考虑以下事项。

    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
    

    【讨论】:

      【解决方案3】:

      如被问及,我正在发布我更简单的 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
      

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

      OTOH,如果关注效率,我宁愿认为(但我绝对不确定)Ed 的 CTE 方法可能不如我建议的那样被 Firebird 引擎优化。 Firebird 非常擅长优化查询,但是当您以这种方式使用 CTE 时,我现在真的不知道它是否能够这样做。但是 WHERE + GROUP BY + HAVING 应该可以通过简单地在 (id,label) 上设置索引来优化。

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

      【讨论】:

      • 您(或 pilcrow 的)查询中没有 CTE(“公用表表达式”)
      • 该评论引用了 Ed 的回答,这很好且灵活,但 确实 使用 CTE。我会说得更清楚。谢谢
      • 也适用于 FB2.1。我会将此作为答案,因为这是最简单的查询。谢谢!
      猜你喜欢
      • 1970-01-01
      • 2022-09-30
      • 1970-01-01
      • 2013-04-05
      • 1970-01-01
      • 2018-09-23
      • 2016-06-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多