【问题标题】:Multiple columns for the same WHERE IN set同一 WHERE IN 集的多个列
【发布时间】:2017-01-11 18:37:47
【问题描述】:

假设我想找出所有特定人群的父母或子女。

我可以这样做:

SELECT *
FROM people P
WHERE
  P.parent_id IN ('111', 'abc', '42', '1a2b3c') OR
  P.child_id IN  ('111', 'abc', '42', '1a2b3c')

有什么方法可以避免将列表写入两次(如果我要查找更多列,则可以多次写入)?

我正在寻找类似的东西:

(...) WHERE (P.parent_id OR P.child_id) IN ('111', 'abc', '42', '1a2b3c')

我使用的是 Oracle,但也欢迎使用简单的 SQL 解决方案。

【问题讨论】:

    标签: sql oracle


    【解决方案1】:

    试试这个:

    WITH search_ids (id) AS (
              SELECT '111'    FROM dual
    UNION ALL SELECT 'abc'    FROM dual
    UNION ALL SELECT '42'     FROM dual
    UNION ALL SELECT '1a2b3c' FROM dual
    ) 
    SELECT * FROM people P 
    WHERE P.parent_id IN (SELECT id FROM search_ids)
      OR  P.child_id IN (SELECT id FROM search_ids)
    ;
    

    FROM dual 位是 Oracle 特定的。

    玩的开心

    马可

    【讨论】:

    • 我最终使用了这种技术的变体,但创建了一个实际的表。在我的例子中,WHERE IN 的集合非常长,我想放弃我遇到的一些问题是由于这个字符串的长度使得查询文本对于我们的连接器来说太长了。这种方法减少了查询长度并且确实有效。
    • 这不是仍然使用OR 的最佳方法。如果您的查询有点复杂,它的性能可能会大大降低。请参阅我的答案,了解使用 SET 运算符 intersect 执行相同操作的另一种方法。
    【解决方案2】:

    在性能和通用性方面,这种情况最好使用OR以外的其他方法。在 Oracle 中,有 SET 运算符可以为您提供很大帮助。例如,对于您的情况,您的查询可能如下所示:

    select *
    from people p
    where
        exists (
            (
                select p.parent_id from dual
                union all
                select p.child_id from dual
            ) intersect (
                select '111' from dual
                union all
                select 'abc' from dual
                union all
                select '42' from dual
                union all
                select '1a2b3c' from dual
            )
        )
    

    或者使用with子句:

    with people_list (value) as  (
                select '111' from dual
                union all
                select 'abc' from dual
                union all
                select '42' from dual
                union all
                select '1a2b3c' from dual
    ) 
    select * from people p
    where
        exists (
            (
                select p.parent_id from dual
                union all
                select p.child_id from dual
            ) intersect (
                select value from people_list
            )
        )
    

    这种方法的好处:

    1. 在性能方面更好(正如已经提到的,优化器很难消化查询中的ORs,尤其是当它们有点复杂时)。
    2. 它更通用 - 实际上,您可以添加任意数量的列,以检查它们是否在给定的值集中。
    3. 您可以使用架构中的任何现有表,而不是从 dual 中选择。
    4. 此子查询更容易集成到复杂查询中(并且更有效,正如我在第一点中已经提到的)。

    【讨论】:

      【解决方案3】:

      您已经在使用OR,因此查询很难优化。因此,正则表达式是一种选择:

      SELECT *
      FROM people P
      WHERE regexp_like('[' || P.parent_id || ']['  || p.child_id || ']') regexp_like('\[111|abc|42|1a2b3c\]') 
      

      【讨论】:

        【解决方案4】:

        一种方法是使用分层查询(然后它更通用 - 您可以通过 LEVEL 使用不同的截止值):

        select *
        from   people
        connect by level <= 2
               and parent_id = prior child_id
        start with child_id in ( ..... )
        

        最好将“搜索 ID”放在单独的表中,并且 IN 条件为 in (select search_id from helper_table),如另一个答案所示。

        【讨论】:

        • 我喜欢这背后的想法,无论如何这个查询不会按预期工作。它只选择 child_id 列在值集中的那些行。
        【解决方案5】:
        CREATE TABLE #People(parent_id NVARCHAR(50),child_id NVARCHAR(50))
        GO
        INSERT INTO #People
            ( parent_id, child_id )
        VALUES  ( N'111', -- parent_id - nvarchar(50)
              N'321331'  -- child_id - nvarchar(50)
              ),( N'111', -- parent_id - nvarchar(50)
              N'abc'  -- child_id - nvarchar(50)
              ),( N'42', -- parent_id - nvarchar(50)
              N'321331'  -- child_id - nvarchar(50)
              ),( N'111', -- parent_id - nvarchar(50)
              N'1a2b3c'  -- child_id - nvarchar(50)
              ),( N'11dsdfs1', -- parent_id - nvarchar(50)
              N'1a2sdfsdfsb3c'  -- child_id - nvarchar(50)
              )
        ;WITH CTE (Value) AS (
              SELECT '111'    
             UNION SELECT 'abc'   
             UNION SELECT '42'    
             UNION SELECT '1a2b3c' 
          ) 
        SELECT *
        FROM #People p
        WHERE EXISTS(
            (SELECT p.parent_id 
            UNION 
            SELECT p.child_id
            )
            INTERSECT 
            SELECT value
            FROM CTE
        )
        

        【讨论】:

          【解决方案6】:

          您可能可以使用以下内容对它们进行分组[简洁:想法来自SQL multiple columns in IN clause

          WHERE (P.parent_id, P.child_id)  IN (('111','111'), ('abc','abc'),('42','42'), ('1a2b3c','1a2b3c'));
          

          【讨论】:

          • 这不是我所需要的。例如,如果给定的人P.parent_id 在列表中,但P.child_id 不是,则不会选择该人,尽管我确实想选择它.
          • 这个查询解决了一个不同的任务,最好用AND操作符重写。
          猜你喜欢
          • 1970-01-01
          • 2018-10-04
          • 1970-01-01
          • 1970-01-01
          • 2014-02-16
          • 1970-01-01
          • 1970-01-01
          • 2019-06-24
          • 2013-08-20
          相关资源
          最近更新 更多