SQL中是否有“ LIKE”和“ IN”的组合?


340

在SQL I中,(不幸的)我经常不得不使用“ 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中实现。


1
您必须做并且喜欢或:AND(类似'%thing%'的事物或类似'%thing%'的事物或类似'%thing%'的事物)
Cosmic Hawk

我希望我们有Teradata的like any/ like allstackoverflow.com/questions/40475982/sql-like-any-vs-like-all。(作为记录,在Oracle Community Ideas论坛community.oracle.com/ideas/11592上要求提供此信息)
William Robertson

Answers:


196

在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 Server:

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

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

参考:


11
嗨,对于Oracle,您需要在要应用“ CONTAINS”运算符的列上构建纯文本索引。根据您的数据量,这可能会很长。
Pierre-Gilles Levallois

18
对于SQL Server(至少是2008版),@ Pilooz的注释同样适用,您需要构建全文本索引。
Marcel

最大长度为4000
ᴍᴀᴛᴛʙᴀᴋᴇʀ

59

如果要使语句易于阅读,则可以使用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谓词。因此,这些示例只是为了好玩。


4
+1个10克REGEXP用法的精美插图。不过,我很好奇,如果性能真的会变差很多。两者都需要全表扫描和/或索引扫描,不是吗?
DCookie 2010年

12
真正。但是正则表达式会疯狂地烧毁CPU,而不是I / O。如果情况更糟,情况更糟,则取决于表达式列表的大小以及该列是否已索引以及其他情况。这只是一个警告,因此原始海报发布者在开始实施时不会感到惊讶。
Rob van Wijk 2010年

49

你被困在

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语句更易于阅读的预期方向:)
selfawaresoup 2010年

10
在SQL中,您会使用索引的用法和性能。仅对SQL可读性使用缩进和命名,当对可读性进行其他修改时,仅冒险更改执行计划(这会影响索引使用和性能)。如果您不小心,则可以通过微不足道的更改轻松地将即时运行的查询更改为非常慢的查询。
KM。

这个答案的第一句话是关键-(最多?)基于SQL的系统和语言不支持您想要的东西,而不是没有实现变通方法。(在SQL Server中,全文本索引会有所帮助吗?)
Philip Kelley 2010年

@Philip Kelley,SQL Server的全文本索引可以做吗LIKE 'bla%' ,在OP的示例代码中是哪个?还是只能进行LIKE '%bla%'搜索?
KM。

老实说,我不知道,我从未使用过FT索引。我把它作为产品中已经包括的可能解决方法的样本。对于他正在做的事情(A或B或C),我怀疑它没有做,非常有信心确定这需要花费很多精力,并且知道它不在他最初问题的范围内(确实SQL本机执行)。
菲利普·凯利2010年

20

对于PostgreSQL,存在ANYALL形式:

WHERE col LIKE ANY( subselect )

要么

WHERE col LIKE ALL( subselect )

其中,subselect恰好返回一列数据。


1
是所有SQL方言(即核心语言的一部分)还是特定于一种方言LIKE ANYLIKE ALL通用?
阿萨德·易卜拉欣

1
@AssadEbrahim,不,它们是特定的。Oracle具有= ANY<> ALL仅适用于SQL,而不适用于PLSQL。
Benoit 2015年

我认为这是标准语法(但不是很多DBMS已实施)
ypercubeᵀᴹ


13

另一个解决方案,可以在任何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)

1
但这比一组OR语句更丑陋
Fandango68 '18

1
@ Fandango68,但是select的并集可以替换为其他模式源,例如表,视图等
。– mik

10

如果您想封装上面显示的Inner Join或temp表技术,我建议使用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)名称)

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角膜白斑

1
如果同时符合多个条件,则行将重复。
mik

7

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

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

如果同时符合多个条件,则行将重复。
mik '18

7

改用内部联接:

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

1
好吧,这正是我想要避免的。虽然有效。
selfawaresoup 2010年

为什么要避免这种解决方案?它的工作速度与公认的解决方案一样快,并且用途广泛。
Phil Factor

3
@PhilFactor此解决方案可以创建重复的行。
Jakub Kania

5

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

Teradata支持LIKE ALL / ANY语法:

列表中的所有字符串。
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支持以下语法:

添加综合[NOT]像任何人和[NOT]像所有运算符

很多时候,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可以立即支持这种综合谓词。


PostgreSQL LIKE/ILIKE ANY (ARRAY[])

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

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

db <> fiddle演示


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')

4

你甚至可以尝试这个

功能

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+'%';

4

我有一个简单的解决方案,至少在postgresql中工作,使用like any正则表达式列表。这是一个示例,旨在确定列表中的某些抗生素:

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

3

我也想知道这样的事情。我只是用的组合测试SUBSTRING,并IN和它是这类问题的有效解决方案。请尝试以下查询:

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

1
一个问题这种方法是你失去了,如果它的存在是为了对t1.something使用索引的能力..
鞋带

1
这将永远找不到“ batz”
mik

3

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);

2

对于Sql Server,您可以求助于Dynamic SQL。

在大多数情况下,您会基于数据库中的某些数据使用IN子句的参数。

下面的示例有点“强制”,但这可以匹配遗留数据库中发现的各种实际情况。

假设你有表的人在那里的人的名字都存储在一个单一的领域PERSONNAME为姓+“” +姓氏。您需要选择从第一名称列表,存储在现场的所有人员NameToSelectNamesToSelect,再加上一些额外的标准(如过滤性别,出生日期等)

您可以按照以下步骤进行操作

-- @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

2

尽管据我所知它只能在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+'%' 

这将导致所有用户都拥有类似于列表中提供的电子邮件地址。希望对所有人有用。问题困扰了我一段时间。


1
那很有意思。但是,请注意,这仅应在小型表上使用,因为like语句不能使用索引。因此,如果您有大量数据,则虽然很难进行初始设置,但全文搜索是更好的选择。
HLGEM

2

从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


1

这适用于逗号分隔的值

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%')

如果要使用索引,则必须省略第一个'%'字符。


1

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

下面的代码将测试列表表达式one |中是否存在字符串3 | | | (其中的管道“ | ”符号表示“或”逻辑运算)。

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开发人员并且对本主题感兴趣,那么这对于将正则表达式与Oracle数据库一起使用应该是一个很好的开始。


1

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

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*"')

谢谢。这应该是IMO接受的答案。并非每个人都有定义的全文索引(无论如何),您的第一个建议就像一个魅力。您甚至可以将通配符放在临时表值本身中,而不是在LIKE上进行串联。
愚人节

0

没有这样的答案:

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

在oracle中没问题。


0

在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%')

0

我知道这已经很晚了,但是我也遇到了类似的情况。我需要一个“ Like In”运算符来存储一组存储过程,该存储过程接受许多参数,然后使用这些参数来聚合来自多个RDBMS系统的数据,因此,RDBMS特定的技巧将不起作用,但是该存储过程和任何功能它将在MS SQL Server上运行,因此我们可以使用T-SQL来为每个RDBMS生成完整的SQL语句,但是输出必须与RDBMS完全无关。

这就是我目前想将带分隔符的字符串(例如存储过程中的参数)转换为SQL块的想法。我称其为“喜欢”的“地衣”。得到它?

地衣

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =======================================================================
-- Lichen - Scalar Valued Function
-- Returns nvarchar(512) of "LIKE IN" results.  See further documentation.
-- CREATOR: Norman David Cooke
-- CREATED: 2020-02-05
-- UPDATED:
-- =======================================================================
CREATE OR ALTER FUNCTION Lichen 
(
    -- Add the parameters for the function here
    @leadingAnd bit = 1,
    @delimiter nchar(1) = ';',
    @colIdentifier nvarchar(64),
    @argString nvarchar(256)
)
RETURNS nvarchar(512)
AS
BEGIN
    -- Declare the return variable here
    DECLARE @result nvarchar(512)

    -- set delimiter to detect (add more here to detect a delimiter if one isn't provided)
    DECLARE @delimit nchar(1) = ';'
    IF NOT @delimiter = @delimit 
        SET @delimit = @delimiter


    -- check to see if we have any delimiters in the input pattern
    IF CHARINDEX(@delimit, @argString) > 1  -- check for the like in delimiter
    BEGIN  -- begin 'like in' branch having found a delimiter
        -- set up a table variable and string_split the provided pattern into it.
        DECLARE @lichenTable TABLE ([id] [int] IDENTITY(1,1) NOT NULL, line NVARCHAR(32))
        INSERT INTO @lichenTable SELECT * FROM STRING_SPLIT(@argString, ';')

        -- setup loop iterators and determine how many rows were inserted into lichen table
        DECLARE @loopCount int = 1
        DECLARE @lineCount int 
        SELECT @lineCount = COUNT(*) from @lichenTable

        -- select the temp table (to see whats inside for debug)
        --select * from @lichenTable

        -- BEGIN AND wrapper block for 'LIKE IN' if bit is set
        IF @leadingAnd = 1
            SET @result = ' AND ('
        ELSE
            SET @result = ' ('

        -- loop through temp table to build multiple "LIKE 'x' OR" blocks inside the outer AND wrapper block
        WHILE ((@loopCount IS NOT NULL) AND (@loopCount <= @lineCount))
        BEGIN -- begin loop through @lichenTable
            IF (@loopcount = 1) -- the first loop does not get the OR in front
                SELECT @result = CONCAT(@result, ' ', @colIdentifier, ' LIKE ''', line, '''') FROM @lichenTable WHERE id = @loopCount
            ELSE  -- but all subsequent loops do
                SELECT @result = CONCAT(@result, ' OR ', @colIdentifier, ' LIKE ''', line, '''') FROM @lichenTable WHERE id = @loopCount
            SET @loopcount = @loopCount + 1     -- increment loop
        END -- end loop through @lichenTable

        -- set final parens after lichenTable loop
        SET @result = CONCAT(@result, ' )')
    END  -- end 'like in' branch having found a delimiter
    ELSE -- no delimiter was provided
    BEGIN   -- begin "no delimiter found" branch
        IF @leadingAnd = 1 
            SET @result = CONCAT(' AND ', @colIdentifier, ' LIKE ''' + @argString + '''')
        ELSE
            SET @result = CONCAT(' ', @colIdentifier, ' LIKE ''' + @argString + '''')
    END     -- end "no delimiter found" branch

    -- Return the result of the function
    RETURN @result
END  -- end lichen function

GO

可能已经计划了定界符检测,但目前它默认为分号,因此您可以将其default放在其中。这可能有错误。该@leadingAnd参数只是一个位值,用于确定是否要在块的前面放置一个前置的“ AND”,以便与其他WHERE子句添加项很好地配合。

用法示例(在argString中带有定界符)

SELECT [dbo].[Lichen] (
   default        -- @leadingAND, bit, default: 1
  ,default        -- @delimiter, nchar(1), default: ';'
  ,'foo.bar'      -- @colIdentifier, nvarchar(64), this is the column identifier
  ,'01%;02%;%03%' -- @argString, nvarchar(256), this is the input string to parse "LIKE IN" from
)
GO

将返回一个nvarchar(512),其中包含:

 AND ( foo.bar LIKE '01%' OR foo.bar LIKE '02%' OR foo.bar LIKE '%03%' ) 

如果输入不包含定界符,它将也跳过该块:

用法示例(argString中没有定界符)

SELECT [dbo].[Lichen] (
   default        -- @leadingAND, bit, default: 1
  ,default        -- @delimiter, nchar(1), default: ';'
  ,'foo.bar'      -- @colIdentifier, nvarchar(64), this is the column identifier
  ,'01%'          -- @argString, nvarchar(256), this is the input string to parse "LIKE IN" from
)
GO

将返回一个nvarchar(512),其中包含:

 AND foo.bar LIKE '01%'

我将继续对此进行研究,因此,如果我忽略了某些内容(显而易见或其他明显的内容),请随时发表评论或寻求帮助。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.