【问题标题】:Is there a combination of "LIKE" and "IN" in SQL?SQL中是否有“LIKE”和“IN”的组合?
【发布时间】:2011-03-02 04:36:42
【问题描述】:

在 SQL 中,我(遗憾地)经常不得不使用“LIKE”条件,因为数据库几乎违反了所有规范化规则。我现在无法改变。但这与问题无关。

此外,我经常使用WHERE something in (1,1,2,3,5,8,13,21) 之类的条件来提高我的 SQL 语句的可读性和灵活性。

有没有什么方法可以在不编写复杂的子选择的情况下将这两个东西结合起来?

我想要像WHERE something LIKE ('bla%', '%foo%', 'batz%') 这样简单的东西,而不是这样:

WHERE something LIKE 'bla%'
OR something LIKE '%foo%'
OR something LIKE 'batz%'

我在这里与 SQl Server 和 Oracle 合作,但我很想知道这在任何 RDBMS 中是否可行。

【问题讨论】:

标签: sql sql-server oracle tsql plsql


【解决方案1】:

SQL 中没有 LIKE 和 IN 的组合,更不用说 TSQL (SQL Server) 或 PLSQL (Oracle)。部分原因是因为全文搜索 (FTS) 是推荐的替代方法。

Oracle 和 SQL Server FTS 实现都支持 CONTAINS 关键字,但语法仍然略有不同:

甲骨文:

WHERE CONTAINS(t.something, 'bla OR foo OR batz', 1) > 0

SQL 服务器:

WHERE CONTAINS(t.something, '"bla*" OR "foo*" OR "batz*"')

您要查询的列必须是全文索引。

参考:

【讨论】:

  • 您好,使用 Oracle,您需要在要应用“CONTAINS”运算符的列上构建明文索引。根据您的数据量,这可能会很长。
  • 对于SQL Server(至少2008版)@Pilooz的注释也适用,你需要建立全文索引。
  • 最大长度为 4000。
【解决方案2】:

如果您想让语句易于阅读,则可以使用 REGEXP_LIKE(从 Oracle 版本 10 开始提供)。

示例表:

SQL> create table mytable (something)
  2  as
  3  select 'blabla' from dual union all
  4  select 'notbla' from dual union all
  5  select 'ofooof' from dual union all
  6  select 'ofofof' from dual union all
  7  select 'batzzz' from dual
  8  /

Table created.

原始语法:

SQL> select something
  2    from mytable
  3   where something like 'bla%'
  4      or something like '%foo%'
  5      or something like 'batz%'
  6  /

SOMETH
------
blabla
ofooof
batzzz

3 rows selected.

还有一个使用 REGEXP_LIKE 的简单查询

SQL> select something
  2    from mytable
  3   where regexp_like (something,'^bla|foo|^batz')
  4  /

SOMETH
------
blabla
ofooof
batzzz

3 rows selected.

但是...

由于性能不太好,我自己不会推荐它。我会坚持使用几个 LIKE 谓词。所以这些例子只是为了好玩。

【讨论】:

  • +1 很好地说明了 10g 中 REGEXP 的使用。不过,我很好奇,如果性能真的会差很多。两者都需要全表和/或索引扫描,不是吗?
  • 是的。但是正则表达式会像疯了一样消耗 CPU,而不是 I/O。如果它更糟以及它有多糟,取决于您的表达式列表有多大以及该列是否被索引等等。这只是一个警告,所以当他开始实施它时,原始发布者不会感到惊讶。
【解决方案3】:

你被困在了

WHERE something LIKE 'bla%'
OR something LIKE '%foo%'
OR something LIKE 'batz%'

除非您填充临时表(在数据中包含通配符)并像这样加入:

FROM YourTable                y
    INNER JOIN YourTempTable  t On y.something LIKE t.something

试试看(使用 SQL Server 语法):

declare @x table (x varchar(10))
declare @y table (y varchar(10))

insert @x values ('abcdefg')
insert @x values ('abc')
insert @x values ('mnop')

insert @y values ('%abc%')
insert @y values ('%b%')

select distinct *
FROM @x x
WHERE x.x LIKE '%abc%' 
   or x.x LIKE '%b%'


select distinct x.*  
FROM @x             x
    INNER JOIN  @y  y On x.x LIKE y.y

输出:

x
----------
abcdefg
abc

(2 row(s) affected)

x
----------
abc
abcdefg

(2 row(s) affected)

【讨论】:

  • 好的,这可行,但它不符合我使 SQL 语句更易于阅读的预期方向 :)
  • 在 SQL 中,您关注索引的使用和性能。仅对 SQL 可读性使用缩进和命名,当您为可读性进行其他修改时,只有您冒着更改执行计划的风险(这会影响索引的使用和性能)。如果您不小心,您可以通过进行微不足道的更改轻松地将即时运行的查询更改为非常慢的查询。
  • 这个答案的第一个陈述是关键——(大多数?)基于 SQL 的系统和语言不支持你想要的,而不是没有实施变通方法。 (在 SQL Server 中,全文索引有帮助吗?)
  • @Philip Kelley,SQL Server 的全文索引可以在 OP 的示例代码中执行 LIKE 'bla%' 吗?还是只能搜索LIKE '%bla%'
  • 我真的不知道,我从未使用过 FT 索引。我把它作为产品中已经包含的可能解决方法的样本扔进去了。对于他正在做的事情(A 或 B 或 C),我怀疑它没有这样做,我相当有信心确定这一点需要付出很多努力,并且知道它在他的原始问题的范围(SQL 是否本机执行)。
【解决方案4】:

对于 PostgreSQL,有 ANY or ALL form:

WHERE col LIKE ANY( subselect )

WHERE col LIKE ALL( subselect )

子选择只返回一列数据。

【讨论】:

  • LIKE ANYLIKE ALL 是所有 SQL 方言所共有的,即核心语言的一部分,还是特定于方言?
  • @AssadEbrahim,不,它们是具体的。 Oracle 有 = ANY<> ALL,但它只适用于 SQL,例如,不适用于 PLSQL。
  • 我认为这是标准语法(但没有多少 DBMS 实现过)
【解决方案5】:

另一种解决方案,应该适用于任何 RDBMS:

WHERE EXISTS (SELECT 1
                FROM (SELECT 'bla%' pattern FROM dual UNION ALL
                      SELECT '%foo%'        FROM dual UNION ALL
                      SELECT 'batz%'        FROM dual)
               WHERE something LIKE pattern)

内部选择可以通过这种方式替换为其他模式来源,例如表格(或视图):

WHERE EXISTS (SELECT 1
                FROM table_of_patterns t
               WHERE something LIKE t.pattern)

table_of_patterns 应至少包含一列pattern,并且可以这样填充:

INSERT INTO table_of_patterns(pattern) VALUES ('bla%');
INSERT INTO table_of_patterns(pattern) VALUES ('%foo%');
INSERT INTO table_of_patterns(pattern) VALUES ('batz%');

【讨论】:

  • 但它比一组 OR 语句更丑
  • @Fandango68,但是选择的并集可以替换为其他模式来源,如表格、视图等。
【解决方案6】:

如果您想封装上面显示的内连接或临时表技术,我建议使用 TableValue 用户函数。这样可以让它读得更清楚一些。

使用定义于:http://www.logiclabz.com/sql-server/split-function-in-sql-server-to-break-comma-separated-strings-into-table.aspx的分割函数后

我们可以根据我创建的名为“Fish”的表(int id, varchar(50) Name)编写以下内容

SELECT Fish.* from Fish 
    JOIN dbo.Split('%ass,%e%',',') as Splits 
    on Name like Splits.items  //items is the name of the output column from the split function.

输出

1 贝司 2 派克 7 垂钓者 8 大眼

【讨论】:

  • 如果同时匹配多个条件,会复制一行。
【解决方案7】:

我在这里与 SQl Server 和 Oracle 合作,但我很感兴趣这是否可以在任何 RDBMS 中实现。

Teradata 支持LIKE ALL/ANY 语法:

ALL 列表中的每个字符串。
ANY 列表中的任何字符串。

┌──────────────────────────────┬────────────────────────────────────┐
│      THIS expression …       │ IS equivalent to this expression … │
├──────────────────────────────┼────────────────────────────────────┤
│ x LIKE ALL ('A%','%B','%C%') │ x LIKE 'A%'                        │
│                              │ AND x LIKE '%B'                    │
│                              │ AND x LIKE '%C%'                   │
│                              │                                    │
│ x LIKE ANY ('A%','%B','%C%') │ x LIKE 'A%'                        │
│                              │ OR x LIKE '%B'                     │
│                              │ OR x LIKE '%C%'                    │
└──────────────────────────────┴────────────────────────────────────┘

编辑:

jOOQ 3.12.0 版支持该语法:

Add synthetic [NOT] LIKE ANY and [NOT] LIKE ALL operators

很多时候,SQL 用户希望能够组合 LIKE 和 IN 谓词,如下所示:

SELECT *
FROM customer
WHERE last_name [ NOT ] LIKE ANY ('A%', 'E%') [ ESCAPE '!' ]

解决方法是手动将谓词扩展为等价

SELECT *
FROM customer
WHERE last_name LIKE 'A%'
OR last_name LIKE 'E%'

jOOQ 可以开箱即用地支持这种合成谓词。


PostgreSQLLIKE/ILIKE ANY (ARRAY[]):

SELECT *
FROM t
WHERE c LIKE ANY (ARRAY['A%', '%B']);

SELECT *
FROM t
WHERE c LIKE ANY ('{"Do%", "%at"}');

db<>fiddle demo


Snowflake 还支持LIKE ANY/LIKE ALL 匹配:

喜欢任何/所有

允许基于与一个或多个模式的比较对字符串进行区分大小写的匹配。

<subject> LIKE ANY (<pattern1> [, <pattern2> ... ] ) [ ESCAPE <escape_char> ]

例子:

SELECT * 
FROM like_example 
WHERE subject LIKE ANY ('%Jo%oe%','T%e')
-- WHERE subject LIKE ALL ('%Jo%oe%','J%e')

【讨论】:

    【解决方案8】:

    改用内连接:

    SELECT ...
    FROM SomeTable
    JOIN
    (SELECT 'bla%' AS Pattern 
    UNION ALL SELECT '%foo%'
    UNION ALL SELECT 'batz%'
    UNION ALL SELECT 'abc'
    ) AS Patterns
    ON SomeTable.SomeColumn LIKE Patterns.Pattern
    

    【讨论】:

    • 嗯,这正是我想避免的。虽然它有效。
    • 为什么要避免这个解决方案?它的运行速度与公认的解决方案一样快,而且用途广泛。
    • @PhilFactor 此解决方案可以创建重复行。
    【解决方案9】:

    一种方法是将条件存储在临时表(或 SQL Server 中的表变量)中,然后像这样加入:

    SELECT t.SomeField
    FROM YourTable t
       JOIN #TempTableWithConditions c ON t.something LIKE c.ConditionValue
    

    【讨论】:

    • 如果同时匹配多个条件,会复制一行。
    【解决方案10】:

    我有一个简单的解决方案,至少可以在 postgresql 中使用,使用 like any 后跟正则表达式列表。下面是一个示例,旨在确定列表中的一些抗生素:

    select *
    from database.table
    where lower(drug_name) like any ('{%cillin%,%cyclin%,%xacin%,%mycine%,%cephal%}')
    

    【讨论】:

      【解决方案11】:

      你甚至可以试试这个

      功能

      CREATE  FUNCTION [dbo].[fn_Split](@text varchar(8000), @delimiter varchar(20))
      RETURNS @Strings TABLE
      (   
        position int IDENTITY PRIMARY KEY,
        value varchar(8000)  
      )
      AS
      BEGIN
      
      DECLARE @index int
      SET @index = -1
      
      WHILE (LEN(@text) > 0)
        BEGIN 
          SET @index = CHARINDEX(@delimiter , @text) 
          IF (@index = 0) AND (LEN(@text) > 0) 
            BEGIN  
              INSERT INTO @Strings VALUES (@text)
                BREAK 
            END 
          IF (@index > 1) 
            BEGIN  
              INSERT INTO @Strings VALUES (LEFT(@text, @index - 1))  
              SET @text = RIGHT(@text, (LEN(@text) - @index)) 
            END 
          ELSE
            SET @text = RIGHT(@text, (LEN(@text) - @index))
          END
        RETURN
      END
      

      查询

      select * from my_table inner join (select value from fn_split('ABC,MOP',','))
      as split_table on my_table.column_name like '%'+split_table.value+'%';
      

      【讨论】:

        【解决方案12】:

        从 2016 年开始,SQL Server 包含一个 STRING_SPLIT function。我正在使用 SQL Server v17.4,我得到了这个为我工作:

        DECLARE @dashboard nvarchar(50)
        SET @dashboard = 'P1%,P7%'
        
        SELECT * from Project p
        JOIN STRING_SPLIT(@dashboard, ',') AS sp ON p.ProjectNumber LIKE sp.value
        

        【讨论】:

          【解决方案13】:

          我也想知道类似的事情。我刚刚使用SUBSTRINGIN 的组合进行了测试,它是解决此类问题的有效方法。试试下面的查询:

          Select * from TB_YOUR T1 Where SUBSTRING(T1.Something, 1,3) IN ('bla', 'foo', 'batz')
          

          【讨论】:

          • 这种方法的一个问题是您失去了在 t1.something 上使用索引(如果存在)的能力..
          • 这永远找不到'batz'
          【解决方案14】:

          Oracle 中,您可以通过以下方式使用集合:

          WHERE EXISTS (SELECT 1
                          FROM TABLE(ku$_vcnt('bla%', '%foo%', 'batz%'))
                         WHERE something LIKE column_value)
          

          这里我使用了一个预定义的集合类型ku$_vcnt,但是你可以像这样声明你自己的:

          CREATE TYPE my_collection AS TABLE OF VARCHAR2(4000);
          

          【讨论】:

            【解决方案15】:

            对于 Sql Server,您可以使用动态 SQL。

            在这种情况下,大多数情况下,IN 子句的参数都是基于数据库中的一些数据。

            下面的例子有点“强制”,但这可以匹配遗留数据库中的各种真实案例。

            假设您有表 Persons,其中人名存储在单个字段 PersonName 中,即 FirstName + ' ' + LastName。 您需要从名字列表中选择所有人,这些名字存储在表 NamesToSelect 的字段 NameToSelect 中,以及一些附加条件(如按性别、出生日期等过滤)

            你可以这样做

            -- @gender is nchar(1), @birthDate is date 
            
            declare 
              @sql nvarchar(MAX),
              @subWhere nvarchar(MAX)
              @params nvarchar(MAX)
            
            -- prepare the where sub-clause to cover LIKE IN (...)
            -- it will actually generate where clause PersonName Like 'param1%' or PersonName Like 'param2%' or ...   
            set @subWhere = STUFF(
              (
                SELECT ' OR PersonName like ''' + [NameToSelect] + '%''' 
                    FROM [NamesToSelect] t FOR XML PATH('')
              ), 1, 4, '')
            
            -- create the dynamic SQL
            set @sql ='select 
                  PersonName
                  ,Gender
                  ,BirstDate    -- and other field here         
              from [Persons]
              where 
                Gender = @gender
                AND BirthDate = @birthDate
                AND (' + @subWhere + ')'
            
            set @params = ' @gender nchar(1),
              @birthDate Date'     
            
            EXECUTE sp_executesql @sql, @params,    
              @gender,  
              @birthDate
            

            【讨论】:

              【解决方案16】:

              我可能对此有一个解决方案,尽管据我所知它只适用于 SQL Server 2008。我发现您可以使用https://stackoverflow.com/a/7285095/894974 中描述的行构造函数通过like 子句加入“虚构”表。 这听起来更复杂,看:

              SELECT [name]
                ,[userID]
                ,[name]
                ,[town]
                ,[email]
              FROM usr
              join (values ('hotmail'),('gmail'),('live')) as myTable(myColumn) on email like '%'+myTable.myColumn+'%' 
              

              这将导致所有用户都拥有与列表中提供的电子邮件地址相同的电子邮件地址。 希望它对任何人都有用。这个问题困扰了我一段时间。

              【讨论】:

              • 这很有趣。但是,请注意,这应该只用于小表,因为 like 语句不能使用索引。这就是为什么全文搜索虽然最初设置起来更困难,但如果您有大量数据,它是更好的选择。
              【解决方案17】:

              可能你认为组合是这样的:

              SELECT  * 
              FROM    table t INNER JOIN
              (
                SELECT * FROM (VALUES('bla'),('foo'),('batz')) AS list(col)
              ) l ON t.column  LIKE '%'+l.Col+'%'
              

              如果您为目标表定义了全文索引,那么您可以使用此替代方法:

              SELECT  * 
              FROM    table t
              WHERE CONTAINS(t.column, '"bla*" OR "foo*" OR "batz*"')
              

              【讨论】:

              • 谢谢。这应该是国际海事组织接受的答案。不是每个人都有一个明确的全文索引(不管这意味着什么)你的第一个建议就像一个魅力。您甚至可以将通配符放在临时表值本身中,而不是连接在 LIKE 上。
              • 如果有人感兴趣,这里有一个使用 VALUES 时添加附加列的语法示例:SELECT a, b FROM (VALUES (1, 2), (3, 4), (5, 6), (7, 8), (9, 10) ) AS MyTable(a, b);
              【解决方案18】:

              如果您使用的是 MySQL,您可以获得的最接近的是全文搜索:

              Full-Text Search, MySQL Documentation

              【讨论】:

                【解决方案19】:

                这适用于逗号分隔值

                DECLARE @ARC_CHECKNUM VARCHAR(MAX)
                SET @ARC_CHECKNUM = 'ABC,135,MED,ASFSDFSF,AXX'
                SELECT ' AND (a.arc_checknum LIKE ''%' + REPLACE(@arc_checknum,',','%'' OR a.arc_checknum LIKE ''%') + '%'')''
                

                评估为:

                 AND (a.arc_checknum LIKE '%ABC%' OR a.arc_checknum LIKE '%135%' OR a.arc_checknum LIKE '%MED%' OR a.arc_checknum LIKE '%ASFSDFSF%' OR a.arc_checknum LIKE '%AXX%')
                

                如果你希望它使用索引,你必须省略第一个 '%' 字符。

                【讨论】:

                  【解决方案20】:

                  在 Oracle RBDMS 中,您可以使用 REGEXP_LIKE 函数实现此行为。

                  以下代码将测试字符串 three 是否存在于列表表达式 one|two em>|||(其中管道“|”符号表示或逻辑运算)。

                  SELECT 'Success !!!' result
                  FROM dual
                  WHERE REGEXP_LIKE('three', 'one|two|three|four|five');
                  
                  RESULT
                  ---------------------------------
                  Success !!!
                  
                  1 row selected.
                  

                  前面的表达式等价于:

                  three=one OR three=two OR three=three OR three=four OR three=five
                  

                  所以它会成功。

                  另一方面,以下测试将失败。

                  SELECT 'Success !!!' result
                  FROM dual
                  WHERE REGEXP_LIKE('ten', 'one|two|three|four|five');
                  
                  no rows selected
                  

                  从 10g 版本开始,Oracle 中提供了几个与正则表达式 (REGEXP_*) 相关的函数。如果您是 Oracle 开发人员并且对此主题感兴趣,那么这应该是一个好的开始Using Regular Expressions with Oracle Database

                  【讨论】:

                    【解决方案21】:

                    没有这样的答案:

                    SELECT * FROM table WHERE something LIKE ('bla% %foo% batz%')
                    

                    在oracle中没问题。

                    【讨论】:

                      【解决方案22】:

                      在 Teradata 中,您可以使用 LIKE ANY ('%ABC%','%PQR%','%XYZ%')。下面是一个对我产生相同结果的示例

                      --===========
                      --  CHECK ONE
                      --===========
                      SELECT *
                      FROM Random_Table A
                      WHERE (Lower(A.TRAN_1_DSC) LIKE ('%american%express%centurion%bank%')
                      OR Lower(A.TRAN_1_DSC) LIKE ('%bofi%federal%bank%')
                      OR Lower(A.TRAN_1_DSC) LIKE ('%american%express%bank%fsb%'))
                      
                      ;
                      --===========
                      --  CHECK TWO
                      --===========
                      SELECT *
                      FROM Random_Table  A
                      WHERE Lower(A.TRAN_1_DSC) LIKE ANY 
                      ('%american%express%centurion%bank%',
                      '%bofi%federal%bank%',
                      '%american%express%bank%fsb%')
                      

                      【讨论】:

                        【解决方案23】:

                        很抱歉挖了一个旧帖子,但它有很多观点。本周我遇到了类似的问题并想出了这个模式:

                        declare @example table ( sampletext varchar( 50 ) );
                        
                        insert @example values 
                        ( 'The quick brown fox jumped over the lazy dog.' ),
                        ( 'Ask not what your country can do for you.' ),
                        ( 'Cupcakes are the new hotness.' );
                        
                        declare @filter table ( searchtext varchar( 50 ) );
                        
                        insert @filter values
                        ( 'lazy' ),
                        ( 'hotness' ),
                        ( 'cupcakes' );
                        
                        -- Expect to get rows 1 and 3, but no duplication from Cupcakes and Hotness
                        select * 
                        from @example e
                        where exists ( select * from @filter f where e.sampletext like '%' + searchtext + '%' )
                        

                        Exists() 比 IMO 连接好一点,因为它只测试集合中的每条记录,但如果有多个匹配项,则不会导致重复。

                        【讨论】:

                          【解决方案24】:

                          这样做

                          WHERE something + '%' in ('bla', 'foo', 'batz')
                          OR '%' + something + '%' in ('tra', 'la', 'la')
                          

                          WHERE something + '%' in (select col from table where ....)
                          

                          【讨论】:

                          • 这将如何运作? LHS 是一个带有 % 的字符串,因此该 % 不是通配符
                          猜你喜欢
                          • 2021-06-28
                          • 1970-01-01
                          • 2015-12-06
                          • 1970-01-01
                          • 1970-01-01
                          • 2010-12-24
                          • 2013-10-05
                          • 1970-01-01
                          • 2010-09-20
                          相关资源
                          最近更新 更多