Есть ли в SQL комбинация «LIKE» и «IN»?

260

В 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 здесь, но мне интересно, возможно ли это в любой СУБД вообще.

  • 0
    Вы должны делать и любить или: AND (что-то LIKE "% thing%" или что-то LIKE "% thing%" или что-то LIKE "% thing% ')
  • 0
    Хотелось бы, чтобы у нас были Teradata like any / like all : stackoverflow.com/questions/40475982/sql-like-any-vs-like-all . (Для сведения, это было запрошено на форуме Oracle Community Ideas community.oracle.com/ideas/11592 )
Теги:
sql-server
tsql
plsql

22 ответа

173
Лучший ответ

В SQL нет комбинации LIKE и IN, а тем более в TSQL (SQL Server) или PLSQL (Oracle). Частично это объясняется тем, что рекомендуется использовать полнотекстовый поиск (FTS).

Обе реализации Oracle и SQL Server FTS поддерживают ключевое слово CONTAINS, но синтаксис все еще немного отличается:

Oracle:

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

SQL Server:

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

Ссылка:

  • 10
    Привет, с Oracle, вам нужно построить незашифрованные индексы для столбцов, к которым вы хотите применить оператор «CONTAINS». В зависимости от объема ваших данных это может быть довольно долго.
  • 12
    С SQL Server (по крайней мере, версия 2008) комментарий @Pilooz также применим, вам нужно создавать полнотекстовые индексы.
55

Если вы хотите сделать выражение легко читаемым, вы можете использовать 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. Таким образом, примеры были просто для удовольствия.

  • 2
    +1 хорошая иллюстрация использования REGEXP в 10г. Мне любопытно, если бы производительность действительно была намного хуже. Оба требуют полного сканирования таблицы и / или индекса, не так ли?
  • 11
    Правда. Но регулярные выражения сжигают процессор как сумасшедший, а не как ввод-вывод. Если оно хуже и насколько оно хуже, зависит от того, насколько велик ваш список выражений и индексирован ли столбец или нет. Это всего лишь предупреждение, так что оригинальный постер не удивляется, когда начинает его реализовывать.
45

вы застряли с

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

если вы не заселяете таблицу temp (включая дикие карты с данными) и присоединяетесь так:

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)
  • 0
    Хорошо, это сработало бы, но это не входит в мое намерение сделать оператор SQL более читабельным :)
  • 10
    в SQL вы идете для использования индекса и производительности. Используйте отступы и имена только для удобства чтения SQL, когда вы вносите другие изменения для удобства чтения, вы рискуете изменить план выполнения (что влияет на использование индекса и производительность). Если вы не будете осторожны, вы можете легко изменить мгновенно выполняющийся запрос на очень медленный, сделав тривиальные изменения.
Показать ещё 6 комментариев
18

В PostgreSQL есть форма ANY или ALL:

WHERE col LIKE ANY( subselect )

или

WHERE col LIKE ALL( subselect )

где подзаголовок возвращает ровно один столбец данных.

  • 1
    Являются ли LIKE ANY и LIKE ALL общими для всех диалектов SQL, то есть частью основного языка, или специфичными для диалекта?
  • 1
    @ AssadEbrahim, нет, они конкретные. Oracle имеет = ANY или <> ALL но работает только в SQL, а не в PLSQL, например.
Показать ещё 2 комментария
10

Я бы предложил использовать функцию пользователя TableValue, если вы хотите инкапсулировать методы Inner Join или temp table, показанные выше. Это позволит ему читать более четко.

После использования функции split, определенной по адресу: 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   Bass
2   Pike
7   Angler
8   Walleye
  • 0
    Этот вывод выглядит намного лучше. Спасибо
  • 1
    Строка будет продублирована, если она соответствует многим условиям одновременно.
7

Другое решение должно работать на любой СУБД:

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)
  • 2
    Это, пожалуй, лучший кусок информации, которую я нашел на SO за достаточно долгое время. Я бы проголосовал за это 1000 раз, если бы мог. Отличный трюк!
  • 0
    Но это хуже, чем набор операторов ИЛИ
Показать ещё 2 комментария
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
    Ну, это именно то, чего я хотел бы избежать. Хотя это работает.
  • 0
    Зачем избегать этого решения? Он работает так же быстро, как принятое решение, и столь же универсален.
Показать ещё 1 комментарий
7

Один из подходов - хранить условия в таблице temp (или табличной переменной в SQL Server) и присоединяться к такому:

SELECT t.SomeField
FROM YourTable t
   JOIN #TempTableWithConditions c ON t.something LIKE c.ConditionValue
  • 0
    Строка будет продублирована, если она соответствует многим условиям одновременно.
4

u может даже попробовать это

Функция

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

У меня есть простое решение, которое работает в postgresql, используя like any, за которым следует список регулярных выражений. Вот пример, рассматривающий выявление некоторых антибиотиков в списке:

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

Я работаю с SQl Server и Oracle здесь, но мне интересно, возможно ли это в любой СУБД вообще.

Teradata поддерживает LIKE 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%'                    │
└──────────────────────────────┴────────────────────────────────────┘
2

Мне тоже было интересно, что-то в этом роде. Я просто тестировал, используя комбинацию SUBSTRING и IN, и это эффективное решение для такого рода проблем. Попробуйте выполнить следующий запрос:

Select * from TB_YOUR T1 Where SUBSTRING(T1.Something, 1,3) IN ('bla', 'foo', 'batz')
  • 1
    одна проблема с этим подходом - вы теряете возможность использовать индекс для t1.something, если он существует.
  • 1
    Спасибо, это работает как шарм для меня
2

Для Sql Server вы можете обратиться к Dynamic SQL.

В большинстве случаев в таких ситуациях у вас есть параметр предложения IN на основе некоторых данных из базы данных.

Пример ниже немного "принудительный", но это может соответствовать различным реальным случаям, найденным в старых базах данных.

Предположим, что у вас есть таблица Лица, где имена людей хранятся в одном поле Имя_пользователя как FirstName + '' + LastName. Вам нужно выбрать всех людей из списка первых имен, сохраненных в поле NameToSelect в таблице NamesToSelect, а также некоторые дополнительные критерии (например, отфильтрованные по полу, дате рождения и т.д.),

Вы можете сделать это следующим образом

-- @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/questions/1564956/how-can-i-select-from-list-of-values-in-sql-server, чтобы присоединиться к таблице "вымышленный", используя аналогичное предложение. Это звучит сложнее, чем есть, посмотрите:

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

Это приведет к тому, что все пользователи будут иметь адрес электронной почты, подобный тем, которые указаны в списке. Надеюсь, что это полезно для всех. Эта проблема беспокоила меня некоторое время.

  • 1
    Это интересно. Однако имейте в виду, что это следует использовать только для небольшой таблицы, поскольку оператор like не может использовать индексы. Вот почему полнотекстовый поиск, хотя его сложнее изначально настроить, является лучшим выбором, если у вас много данных.
1

Нет ответа:

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

В оракуле нет проблем.

  • 0
    Не работает, но +1 за попытку
1

В Oracle RBDMS вы можете добиться этого, используя REGEXP_LIKE.

Следующий код проверяет, присутствует ли строка три в выражении списка один | два | три | четыре | пять (в котором труба " |" означает ИЛИ логическую операцию).

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

Существует несколько функций, связанных с регулярными выражениями (REGEXP_ *), доступными в Oracle с версии 10g. Если вы разработчик Oracle и интересуетесь этой темой, это должно быть хорошим началом Использование регулярных выражений с Oracle Database.

1

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

Если вы используете MySQL, ближе всего вы можете получить полнотекстовый поиск:

Полнотекстовый поиск, Документация MySQL

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

Начиная с 2016 года, SQL Server включает в себя STRING_SPLIT функцию. Я использую 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
-4

сделайте это

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

или

WHERE something + '%' in (select col from table where ....)
  • 0
    Как это будет работать? LHS - это строка с%, поэтому этот% не является подстановочным знаком

Ещё вопросы

Сообщество Overcoder
Наверх
Меню