Le champ SQL SELECT WHERE contient des mots


562

J'ai besoin d'une sélection qui retournerait des résultats comme celui-ci:

SELECT * FROM MyTable WHERE Column1 CONTAINS 'word1 word2 word3'

Et j'ai besoin de tous les résultats, c'est-à-dire que cela inclut des chaînes avec «mot2 mot3 mot1» ou «mot1 mot3 mot2» ou toute autre combinaison des trois.

Tous les mots doivent être dans le résultat.

Réponses:


844

Plutôt lent, mais méthode de travail pour inclure l' un des mots:

SELECT * FROM mytable
WHERE column1 LIKE '%word1%'
   OR column1 LIKE '%word2%'
   OR column1 LIKE '%word3%'

Si vous avez besoin que tous les mots soient présents, utilisez ceci:

SELECT * FROM mytable
WHERE column1 LIKE '%word1%'
  AND column1 LIKE '%word2%'
  AND column1 LIKE '%word3%'

Si vous voulez quelque chose de plus rapide, vous devez rechercher la recherche en texte intégral, ce qui est très spécifique pour chaque type de base de données.


3
+ 1 Je suis d'accord que c'est plus lent mais cela peut être atténué avec une bonne indexation
Preet Sangha

12
@PreetSangha Indexation lorsque vous recherchez LIKE en commençant par un caractère générique? Veuillez me montrer comment!
Popnoodles

1
Dans PostgreSQL 9.1 et versions ultérieures, vous pouvez créer un index de trigrammes qui peut indexer de telles recherches .
mvp

2
@AquaAlex: votre déclaration échouera si le texte a word3 word2 word1.
mvp

3
Un autre inconvénient de cette approche: «% word%» trouvera également «mots», «mots croisés» et «épée» (à titre d'exemple). Je devrais faire une colonne1 COMME 'mot' OU colonne1 COMME 'mot%' OU colonne1 COMME '% mot' OU colonne1 COMME 'mot' pour trouver juste les correspondances exactes des mots - et cela échouerait toujours pour les entrées où les mots ne sont pas juste séparé par des espaces.
BlaM

81

Notez que si vous utilisez LIKEpour déterminer si une chaîne est une sous-chaîne d'une autre chaîne, vous devez échapper les caractères de correspondance de modèle dans votre chaîne de recherche.

Si votre dialecte SQL le prend en charge CHARINDEX, il est beaucoup plus facile de l'utiliser à la place:

SELECT * FROM MyTable
WHERE CHARINDEX('word1', Column1) > 0
  AND CHARINDEX('word2', Column1) > 0
  AND CHARINDEX('word3', Column1) > 0

En outre, veuillez garder à l'esprit que cela et la méthode dans la réponse acceptée ne couvrent que la correspondance de sous-chaîne plutôt que la correspondance de mots. Ainsi, par exemple, la chaîne 'word1word2word3'correspondrait toujours.


1
Cela semble beaucoup plus facile si votre terme de recherche est une variable plutôt que d'avoir à ajouter les caractères '%' avant de chercher
ShaneBlake

4
Dans les serveurs et moteurs Microsoft SQL, nous devrions utiliser à la InStr()placeCHARINDEX
23W

6
@ 23W Il n'y a pas d'InStr dans MS SQL
Romano Zumbé

19

Une fonction

 CREATE FUNCTION [dbo].[fnSplit] ( @sep CHAR(1), @str VARCHAR(512) )
 RETURNS TABLE AS
 RETURN (
           WITH Pieces(pn, start, stop) AS (
           SELECT 1, 1, CHARINDEX(@sep, @str)
           UNION ALL
           SELECT pn + 1, stop + 1, CHARINDEX(@sep, @str, stop + 1)
           FROM Pieces
           WHERE stop > 0
      )

      SELECT
           pn AS Id,
           SUBSTRING(@str, start, CASE WHEN stop > 0 THEN stop - start ELSE 512 END) AS Data
      FROM
           Pieces
 )

Requete

 DECLARE @FilterTable TABLE (Data VARCHAR(512))

 INSERT INTO @FilterTable (Data)
 SELECT DISTINCT S.Data
 FROM fnSplit(' ', 'word1 word2 word3') S -- Contains words

 SELECT DISTINCT
      T.*
 FROM
      MyTable T
      INNER JOIN @FilterTable F1 ON T.Column1 LIKE '%' + F1.Data + '%'
      LEFT JOIN @FilterTable F2 ON T.Column1 NOT LIKE '%' + F2.Data + '%'
 WHERE
      F2.Data IS NULL

2
Exellent! Comment commencer à en apprendre davantage sur cette fonction, monsieur? qu'est-ce que Pieces? et pouvez-vous me dire le pseudocode de cette ligne? SUBSTRING (@str, start, CASE WHEN stop> 0 THEN stop - start ELSE 512 END) AS Data
Khaneddy2013

2
Ce mouvement était incroyable, je suis vraiment JEALOUS :( _______________________________________________________________________________________ INNER JOIN (@FilterTable F1 ON T.Column1 LIKE '%' + F1.Data + '%' LEFT JOIN (@FilterTable F2 ON T.Column1 NOT LIKE '%' + F2.Data + '%'
Ahmad Alkaraki

13

Au lieu de SELECT * FROM MyTable WHERE Column1 CONTAINS 'word1 word2 word3', ajoutez Et entre ces mots comme:

SELECT * FROM MyTable WHERE Column1 CONTAINS 'word1 And word2 And word3'

pour plus de détails, voir ici https://msdn.microsoft.com/en-us/library/ms187787.aspx

MISE À JOUR

Pour sélectionner des phrases, utilisez des guillemets doubles comme:

SELECT * FROM MyTable WHERE Column1 CONTAINS '"Phrase one" And word2 And "Phrase Two"'

ps vous devez d'abord activer la recherche en texte intégral sur la table avant d'utiliser contient le mot-clé. pour plus de détails, voir ici https://docs.microsoft.com/en-us/sql/relational-databases/search/get-started-with-full-text-search


8
SELECT * FROM MyTable WHERE 
Column1 LIKE '%word1%'
AND Column1 LIKE '%word2%'
AND Column1 LIKE  '%word3%'

Modifié ORen ANDfonction de la modification de la question.


J'ai besoin que tous les mots soient contenus dans le résultat dans n'importe quelle combinaison
Mario M

4

Si vous utilisez Oracle Database, vous pouvez y parvenir à l'aide de la requête contient . Contient des requêtes plus rapides qu'une requête similaire.

Si vous avez besoin de tous les mots

SELECT * FROM MyTable WHERE CONTAINS(Column1,'word1 and word2 and word3', 1) > 0

Si vous avez besoin de l'un des mots

SELECT * FROM MyTable WHERE CONTAINS(Column1,'word1 or word2 or word3', 1) > 0

Contient l'index de besoin de type CONTEXT sur votre colonne.

CREATE INDEX SEARCH_IDX ON MyTable(Column) INDEXTYPE IS CTXSYS.CONTEXT

1
@downvoters Un commentaire est apprécié pour dire ce qui ne va pas avec la réponse. Cette même requête s'exécute dans notre solution d'entreprise plus de 1000 fois par jour, sans aucun problème :)
mirmdasif

2
OP ne spécifie pas quelle base de données utilise et tout le monde a supposé qu'il s'agit de Sql Server. Mais puisque vous avez spécifié Oracle dans votre réponse, je ne comprends pas les downvoters.
EAmez

4

Si vous voulez juste trouver un match.

SELECT * FROM MyTable WHERE INSTR('word1 word2 word3',Column1)<>0

Serveur SQL :

CHARINDEX(Column1, 'word1 word2 word3', 1)<>0

Pour obtenir une correspondance exacte. L'exemple (';a;ab;ac;',';b;')n'obtiendra pas de correspondance.

SELECT * FROM MyTable WHERE INSTR(';word1;word2;word3;',';'||Column1||';')<>0

1
'INSTR' n'est pas un nom de fonction intégrée reconnu. Dans mon SQL Server.
Durgesh Pandey

0

essayez d'utiliser la «recherche tesarus» dans l'index de texte intégral dans MS SQL Server. C'est bien mieux que d'utiliser "%" dans la recherche si vous avez des millions d'enregistrements. tesarus a une petite quantité de mémoire que les autres. essayez de rechercher ces fonctions :)


0

la meilleure façon est de créer un index de texte intégral sur une colonne de la table et d'utiliser contain au lieu de LIKE

SELECT * FROM MyTable WHERE 
contains(Column1 , N'word1' )
AND contains(Column1 , N'word2' )
AND contains(Column1 , N'word3' )

0

pourquoi ne pas utiliser "in" à la place?

Select *
from table
where columnname in (word1, word2, word3)

2
Parce que ça ne marche pas. L'avez-vous essayé?
mvp

2
Je crois que cela ne retournera que des correspondances exactes.
Murray

1
J'ai également mal compris la question d'origine: ils ne veulent pas trouver une correspondance exacte, mais un mot faisant partie d'une chaîne (éventuellement) plus grande. Pour le cas plus simple de "correspondance exacte", cela fonctionne à condition que les mots soient entre guillemets simples (cf. SQLfiddle )
sc28

0

L'un des moyens les plus simples d'atteindre ce qui est mentionné dans la question est d'utiliser CONTAINS avec NEAR ou '~'. Par exemple, les requêtes suivantes nous donneraient toutes les colonnes qui incluent spécifiquement word1, word2 et word3.

SELECT * FROM MyTable WHERE CONTAINS(Column1, 'word1 NEAR word2 NEAR word3')

SELECT * FROM MyTable WHERE CONTAINS(Column1, 'word1 ~ word2 ~ word3')

De plus, CONTAINSTABLE renvoie un classement pour chaque document en fonction de la proximité de "mot1", "mot2" et "mot3". Par exemple, si un document contient la phrase «Le mot1 est le mot2 et le mot3», son classement serait élevé car les termes sont plus proches les uns des autres que dans d'autres documents.

Une autre chose que je voudrais ajouter est que nous pouvons également utiliser proximite_term pour trouver des colonnes où les mots sont à l'intérieur d'une distance spécifique entre eux à l'intérieur de l'expression de colonne.


0

Idéalement, cela devrait être fait à l'aide de la recherche en texte intégral du serveur SQL si vous utilisez. Cependant, si vous ne pouvez pas faire fonctionner cela sur votre base de données pour une raison quelconque, voici une solution exigeante en performances: -

-- table to search in
CREATE TABLE dbo.myTable
    (
    myTableId int NOT NULL IDENTITY (1, 1),
    code varchar(200) NOT NULL, 
    description varchar(200) NOT NULL -- this column contains the values we are going to search in 
    )  ON [PRIMARY]
GO

-- function to split space separated search string into individual words
CREATE FUNCTION [dbo].[fnSplit] (@StringInput nvarchar(max),
@Delimiter nvarchar(1))
RETURNS @OutputTable TABLE (
  id nvarchar(1000)
)
AS
BEGIN
  DECLARE @String nvarchar(100);

  WHILE LEN(@StringInput) > 0
  BEGIN
    SET @String = LEFT(@StringInput, ISNULL(NULLIF(CHARINDEX(@Delimiter, @StringInput) - 1, -1),
    LEN(@StringInput)));
    SET @StringInput = SUBSTRING(@StringInput, ISNULL(NULLIF(CHARINDEX
    (
    @Delimiter, @StringInput
    ),
    0
    ), LEN
    (
    @StringInput)
    )
    + 1, LEN(@StringInput));

    INSERT INTO @OutputTable (id)
      VALUES (@String);
  END;

  RETURN;
END;
GO

-- this is the search script which can be optionally converted to a stored procedure /function


declare @search varchar(max) = 'infection upper acute genito'; -- enter your search string here
-- the searched string above should give rows containing the following
-- infection in upper side with acute genitointestinal tract
-- acute infection in upper teeth
-- acute genitointestinal pain

if (len(trim(@search)) = 0) -- if search string is empty, just return records ordered alphabetically
begin
 select 1 as Priority ,myTableid, code, Description from myTable order by Description 
 return;
end

declare @splitTable Table(
wordRank int Identity(1,1), -- individual words are assinged priority order (in order of occurence/position)
word varchar(200)
)
declare @nonWordTable Table( -- table to trim out auxiliary verbs, prepositions etc. from the search
id varchar(200)
)

insert into @nonWordTable values
('of'),
('with'),
('at'),
('in'),
('for'),
('on'),
('by'),
('like'),
('up'),
('off'),
('near'),
('is'),
('are'),
(','),
(':'),
(';')

insert into @splitTable
select id from dbo.fnSplit(@search,' '); -- this function gives you a table with rows containing all the space separated words of the search like in this e.g., the output will be -
--  id
-------------
-- infection
-- upper
-- acute
-- genito

delete s from @splitTable s join @nonWordTable n  on s.word = n.id; -- trimming out non-words here
declare @countOfSearchStrings int = (select count(word) from @splitTable);  -- count of space separated words for search
declare @highestPriority int = POWER(@countOfSearchStrings,3);

with plainMatches as
(
select myTableid, @highestPriority as Priority from myTable where Description like @search  -- exact matches have highest priority
union                                      
select myTableid, @highestPriority-1 as Priority from myTable where Description like  @search + '%'  -- then with something at the end
union                                      
select myTableid, @highestPriority-2 as Priority from myTable where Description like '%' + @search -- then with something at the beginning
union                                      
select myTableid, @highestPriority-3 as Priority from myTable where Description like '%' + @search + '%' -- then if the word falls somewhere in between
),
splitWordMatches as( -- give each searched word a rank based on its position in the searched string
                     -- and calculate its char index in the field to search
select myTable.myTableid, (@countOfSearchStrings - s.wordRank) as Priority, s.word,
wordIndex = CHARINDEX(s.word, myTable.Description)  from myTable join @splitTable s on myTable.Description like '%'+ s.word + '%'
-- and not exists(select myTableid from plainMatches p where p.myTableId = myTable.myTableId) -- need not look into myTables that have already been found in plainmatches as they are highest ranked
                                                                              -- this one takes a long time though, so commenting it, will have no impact on the result
),
matchingRowsWithAllWords as (
 select myTableid, count(myTableid) as myTableCount from splitWordMatches group by(myTableid) having count(myTableid) = @countOfSearchStrings
)
, -- trim off the CTE here if you don't care about the ordering of words to be considered for priority
wordIndexRatings as( -- reverse the char indexes retrived above so that words occuring earlier have higher weightage
                     -- and then normalize them to sequential values
select s.myTableid, Priority, word, ROW_NUMBER() over (partition by s.myTableid order by wordindex desc) as comparativeWordIndex 
from splitWordMatches s join matchingRowsWithAllWords m on s.myTableId = m.myTableId
)
,
wordIndexSequenceRatings as ( -- need to do this to ensure that if the same set of words from search string is found in two rows,
                              -- their sequence in the field value is taken into account for higher priority
    select w.myTableid, w.word, (w.Priority + w.comparativeWordIndex + coalesce(sequncedPriority ,0)) as Priority
    from wordIndexRatings w left join 
    (
     select w1.myTableid, w1.priority, w1.word, w1.comparativeWordIndex, count(w1.myTableid) as sequncedPriority
     from wordIndexRatings w1 join wordIndexRatings w2 on w1.myTableId = w2.myTableId and w1.Priority > w2.Priority and w1.comparativeWordIndex>w2.comparativeWordIndex
     group by w1.myTableid, w1.priority,w1.word, w1.comparativeWordIndex
    ) 
    sequencedPriority on w.myTableId = sequencedPriority.myTableId and w.Priority = sequencedPriority.Priority
),
prioritizedSplitWordMatches as ( -- this calculates the cumulative priority for a field value
select  w1.myTableId, sum(w1.Priority) as OverallPriority from wordIndexSequenceRatings w1 join wordIndexSequenceRatings w2 on w1.myTableId =  w2.myTableId 
where w1.word <> w2.word group by w1.myTableid 
),
completeSet as (
select myTableid, priority from plainMatches -- get plain matches which should be highest ranked
union
select myTableid, OverallPriority as priority from prioritizedSplitWordMatches -- get ranked split word matches (which are ordered based on word rank in search string and sequence)
),
maximizedCompleteSet as( -- set the priority of a field value = maximum priority for that field value
select myTableid, max(priority) as Priority  from completeSet group by myTableId
)
select priority, myTable.myTableid , code, Description from maximizedCompleteSet m join myTable  on m.myTableId = myTable.myTableId 
order by Priority desc, Description -- order by priority desc to get highest rated items on top
--offset 0 rows fetch next 50 rows only -- optional paging

-2
SELECT * FROM MyTable WHERE Column1 Like "*word*"

Cela affichera tous les enregistrements column1contenant une valeur partielle word.


-2
DECLARE @SearchStr nvarchar(100)
SET @SearchStr = ' '



CREATE TABLE #Results (ColumnName nvarchar(370), ColumnValue nvarchar(3630))

SET NOCOUNT ON

DECLARE @TableName nvarchar(256), @ColumnName nvarchar(128), @SearchStr2 nvarchar(110)
SET  @TableName = ''
SET @SearchStr2 = QUOTENAME('%' + @SearchStr + '%','''')

WHILE @TableName IS NOT NULL

BEGIN
    SET @ColumnName = ''
    SET @TableName = 
    (
        SELECT MIN(QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME))
        FROM     INFORMATION_SCHEMA.TABLES
        WHERE         TABLE_TYPE = 'BASE TABLE'
            AND    QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME) > @TableName
            AND    OBJECTPROPERTY(
                    OBJECT_ID(
                        QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME)
                         ), 'IsMSShipped'
                           ) = 0
    )

    WHILE (@TableName IS NOT NULL) AND (@ColumnName IS NOT NULL)

    BEGIN
        SET @ColumnName =
        (
            SELECT MIN(QUOTENAME(COLUMN_NAME))
            FROM     INFORMATION_SCHEMA.COLUMNS
            WHERE         TABLE_SCHEMA    = PARSENAME(@TableName, 2)
                AND    TABLE_NAME    = PARSENAME(@TableName, 1)
                AND    DATA_TYPE IN ('char', 'varchar', 'nchar', 'nvarchar', 'int', 'decimal')
                AND    QUOTENAME(COLUMN_NAME) > @ColumnName
        )

        IF @ColumnName IS NOT NULL

        BEGIN
            INSERT INTO #Results
            EXEC
            (
                'SELECT ''' + @TableName + '.' + @ColumnName + ''', LEFT(' + @ColumnName + ', 3630) FROM ' + @TableName + ' (NOLOCK) ' +
                ' WHERE ' + @ColumnName + ' LIKE ' + @SearchStr2
            )
        END
    END   
END

SELECT ColumnName, ColumnValue FROM #Results

DROP TABLE #Results

2
Merci pour cet extrait de code, qui pourrait fournir une aide immédiate limitée. Une explication appropriée améliorerait considérablement sa valeur à long terme en montrant pourquoi c'est une bonne solution au problème, et la rendrait plus utile aux futurs lecteurs avec d'autres questions similaires. Veuillez modifier votre réponse pour ajouter des explications, y compris les hypothèses que vous avez faites.
Mogsdad

-5
select * from table where name regexp '^word[1-3]$'

ou

select * from table where name in ('word1','word2','word3')

3
"Regexp" est-il du SQL standard?
Peter Mortensen

2
Pour la deuxième requête, le mot ne doit-il pas être cité?
Peter Mortensen

1
Ce code semble vérifier si la colonne est égale à l' un des trois mots. La question est de vérifier si la colonne contient tous les trois mots.
Sam

7
Hiya, cela pourrait bien résoudre le problème ... mais ce serait bien si vous pouviez modifier votre réponse et fournir une petite explication sur comment et pourquoi cela fonctionne :) N'oubliez pas - il y a des tas de débutants sur le débordement de la pile, et ils pourraient apprendre une chose ou deux de votre expertise - ce qui est évident pour vous pourrait ne pas l'être pour eux.
Taryn East
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.