web-dev-qa-db-fra.com

Existe-il une combinaison de "LIKE" et "IN" dans SQL?

Malheureusement, en SQL, je dois souvent utiliser des conditions "LIKE" en raison de bases de données qui violent presque toutes les règles de normalisation. Je ne peux pas changer ça maintenant. Mais ce n'est pas pertinent à la question.

De plus, j’utilise souvent des conditions telles que WHERE something in (1,1,2,3,5,8,13,21) pour une meilleure lisibilité et flexibilité de mes instructions SQL.

Est-il possible de combiner ces deux choses sans écrire des sous-sélections compliquées?

Je veux quelque chose d'aussi simple que WHERE something LIKE ('bla%', '%foo%', 'batz%') au lieu de ceci:

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

Je travaille avec SQl Server et Oracle ici, mais je suis intéressé si cela est possible dans n'importe quel SGBDR.

280
selfawaresoup

Il n'y a pas de combinaison de LIKE & IN dans SQL, encore moins dans TSQL (SQL Server) ou PLSQL (Oracle). Cela est en partie dû au fait que la recherche en texte intégral est la solution recommandée.

Les implémentations Oracle et SQL Server FTS prennent en charge le mot clé CONTAINS, mais la syntaxe est toujours légèrement différente:

Oracle:

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

Serveur SQL:

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

Référence:

167
OMG Ponies

Si vous voulez que votre déclaration soit facilement lisible, vous pouvez utiliser REGEXP_LIKE (disponible à partir d'Oracle version 10).

Un exemple de tableau:

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.

La syntaxe originale:

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.

Et une requête simple avec REGEXP_LIKE

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

SOMETH
------
blabla
ofooof
batzzz

3 rows selected.

MAIS ...

Je ne le recommanderais pas moi-même en raison de la performance médiocre. Je resterais avec plusieurs prédicats LIKE. Donc, les exemples étaient juste pour le plaisir.

55
Rob van Wijk

vous êtes coincé avec le 

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

sauf si vous remplissez une table temporaire (incluez les caractères génériques dans les données) et rejoignez comme ceci

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

essayez-le (en utilisant la syntaxe 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

SORTIE:

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

(2 row(s) affected)

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

(2 row(s) affected)
43
KM.

Avec PostgreSQL, il existe la forme ANY ou ALL:

WHERE col LIKE ANY( subselect )

ou

WHERE col LIKE ALL( subselect )

où la sous-sélection renvoie exactement une colonne de données.

18
Benoit

Je suggérerais d'utiliser une fonction utilisateur TableValue si vous souhaitez encapsuler les techniques de jointure interne ou de table temporaire ci-dessus. Cela lui permettrait de lire un peu plus clairement.

Après avoir utilisé la fonction split définie à: http://www.logiclabz.com/sql-server/split-function-in-sql-server-to-break-comma-separated-strings-into-table.aspx

on peut écrire ce qui suit en se basant sur une table que j'ai créée et appelée "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.

Les sorties

 1 Basse 
 2 Brochet 
 7 Pêcheur 
 8 Doré 
10
Famous Nerd

Une autre solution devrait fonctionner sur n’importe quel SGBDR:

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)
9
mik

Une approche serait de stocker les conditions dans une table temporaire (ou une variable de table dans SQL Server) et de les rejoindre comme ceci:

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

Utilisez plutôt une jointure interne:

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
7
A-K

tu peux même essayer ça

Une fonction

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

Question

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

J'ai une solution simple, qui fonctionne dans postgresql au moins, en utilisant like any suivi de la liste des regex. Voici un exemple, cherchant à identifier certains antibiotiques dans une liste:

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

Je me demandais aussi quelque chose comme ça. Je viens de tester en utilisant une combinaison de SUBSTRING et IN et c'est une solution efficace pour ce genre de problème. Essayez la requête ci-dessous:

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

Dans Oracle , vous pouvez utiliser une collection de la manière suivante:

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

Ici, j'ai utilisé un type de collection prédéfini ku$_vcnt, mais vous pouvez en déclarer votre propre comme ceci:

CREATE TYPE my_collection AS TABLE OF VARCHAR2(4000);
2
mik

Pour SQL Server, vous pouvez recourir à SQL dynamique.

La plupart du temps, dans de telles situations, le paramètre de la clause IN est basé sur certaines données de la base de données.

L'exemple ci-dessous est un peu "forcé", mais cela peut correspondre à divers cas réels trouvés dans des bases de données héritées. 

Supposons que vous ayez une table Personnes où les noms de personnes sont stockés dans un seul champ Nom de la personne comme Prénom + + '' + Nom. Vous devez sélectionner toutes les personnes dans une liste de prénoms, stockées dans le champ NameToSelect dans le tableau NamesToSelect , ainsi que certains critères supplémentaires (filtrés par sexe, date de naissance, etc.)

Vous pouvez le faire comme suit

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

_ {Je travaille avec SQl Server et Oracle ici mais je suis intéressé si cela est possible dans n'importe quel SGBDR.

Teradata supporte LIKE ALL/ANY syntax:

ALL chaque chaîne de la liste.
ANY toute chaîne de la liste.

┌──────────────────────────────┬────────────────────────────────────┐
│      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
Lukasz Szozda

J'ai peut-être une solution à cela, même si cela ne fonctionne que dans SQL Server 2008, à ma connaissance. J'ai découvert que vous pouvez utiliser le constructeur de lignes décrit dans https://stackoverflow.com/a/7285095/894974 pour joindre une table "fictive" à l'aide d'une clause like ..__ est, regardez:

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

Tous les utilisateurs avec une adresse e-mail similaire à celle fournie dans la liste en seront résultés . J'espère que cela servira à tout le monde. Le problème me dérangeait depuis un moment.

2
Sander

Cela fonctionne pour les valeurs séparées par des virgules

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

Evalue à:

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

Si vous souhaitez utiliser des index, vous devez omettre le premier caractère '%'.

1
David F Mayer

Dans Oracle RBDMS, vous pouvez obtenir ce comportement en utilisant REGEXP_LIKE function.

Le code suivant testera si la chaîne trois est présente dans l'expression de liste un | deux | trois | quatre | cinq (dans lequel le symbole de tuyau "_ ​​|" signifie une opération logique OR). 

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

RESULT
---------------------------------
Success !!!

1 row selected.

L'expression précédente est équivalente à:

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

Alors ça va réussir.

D'autre part, le test suivant échouera.

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

no rows selected

Plusieurs fonctions liées aux expressions régulières (REGEXP_ *) sont disponibles dans Oracle depuis la version 10g. Si vous êtes un développeur Oracle et que vous êtes intéressé par ce sujet, cela devrait être un bon début Utilisation d'expressions régulières avec Oracle Database .

1
jackattack

Si vous utilisez MySQL, la recherche en texte intégral est la plus proche:

Recherche en texte intégral, Documentation MySQL

1
chris

Pas de réponse comme ça:

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

Dans Oracle, pas de problème.

0
Hong Van Vit

Peut-être pensez-vous que la combinaison ressemble à ceci:

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

Si vous avez défini l'index de texte intégral pour votre table cible, vous pouvez utiliser cette alternative:

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

Depuis 2016, SQL Server inclut une STRING_SPLITfonction . J'utilise SQL Server v17.4 et cela a fonctionné pour moi:

DECLARE @dashboard nvarchar(50)
SET @dashboard = 'P1%,P7%'

SELECT * from Project p
JOIN STRING_SPLIT(@dashboard, ',') AS sp ON p.ProjectNumber LIKE sp.value
0
Mark

Dans Teradata, vous pouvez utiliser LIKE ANY ('%ABC%','%PQR%','%XYZ%'). Ci-dessous un exemple qui a produit les mêmes résultats pour moi

--===========
--  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
Piyush Verma