O campo SQL SELECT WHERE contém palavras


562

Eu preciso de um select que retorne resultados como este:

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

E eu preciso de todos os resultados, ou seja, isso inclui seqüências de caracteres com 'word2 word3 word1' ou 'word1 word3 word2' ou qualquer outra combinação dos três.

Todas as palavras precisam estar no resultado.

Respostas:


844

Método bastante lento, mas funcional, para incluir qualquer uma das palavras:

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

Se você precisar que todas as palavras estejam presentes, use o seguinte:

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

Se você quiser algo mais rápido, precisará procurar na pesquisa de texto completo, e isso é muito específico para cada tipo de banco de dados.


3
+ 1 Concordo que é mais lento, mas ele pode ser mitigado com boa indexação
Preet Sangha

12
@PreetSangha Indexando quando você está procurando COMO começar com um curinga? Por favor me mostre como!
Popnoodles

1
No PostgreSQL 9.1 e posterior, você pode criar um índice trigrama que pode indexar essas pesquisas .
Mvp #

2
@ AquaAlex: sua declaração falhará se houver texto word3 word2 word1.
Mvp

3
Outra desvantagem dessa abordagem: '% word%' também encontrará 'words', 'crosswordpuzzle' e 'sword' (apenas como exemplo). Eu precisaria fazer uma coluna1 LIKE 'word' OU coluna1 LIKE 'word%' OU coluna1 LIKE '% word' OR column1 LIKE 'word' para encontrar apenas as correspondências exatas das palavras - e ainda falharia nas entradas em que as palavras não são apenas separados por espaços.
BlaM

81

Observe que, se você LIKEdeterminar para determinar se uma sequência é uma subcadeia de caracteres de outra sequência, deverá escapar do padrão que corresponde aos caracteres na sequência de caracteres de pesquisa.

Se o seu dialeto SQL suportar CHARINDEX, é muito mais fácil usá-lo:

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

Além disso, lembre-se de que esse e o método na resposta aceita cobrem apenas a correspondência de substring em vez da correspondência de palavras. Então, por exemplo, a string 'word1word2word3'ainda corresponderia.


1
Isso parece muito mais fácil se o seu termo de pesquisa é uma variável ao invés de ter que adicionar o '%' caracteres antes de pesquisar
ShaneBlake

4
Nos servidores e mecanismos Microsoft SQL, devemos usar em InStr()vez dissoCHARINDEX
23W

6
@ 23W Não há InStr no MS SQL
Romano Zumbé

19

Função

 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
 )

Inquerir

 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! Como começar a aprender sobre essa função, senhor? o que é peças? e você pode me dizer pseudocódigo sobre essa linha? SUBSTRING (@str, start, CASE WHEN stop> 0 THEN stop - start ELSE 512 END) AS Data
Khaneddy2013

2
Essa mudança foi incrível. Estou realmente invejoso :( _______________________________________________________________________________________ INNER JOIN (@FilterTable F1 ON T.Column1 LIKE '%' + F1.Data + '%' LEFT JOIN (@FilterTable F2 ON T.Column1 NOT LIKE '%' + F2.Data + '%'
Ahmad Alkaraki 28/07

13

Em vez de SELECT * FROM MyTable WHERE Column1 CONTAINS 'word1 word2 word3', adicione E entre essas palavras como:

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

para obter detalhes, consulte aqui https://msdn.microsoft.com/en-us/library/ms187787.aspx

ATUALIZAR

Para selecionar frases, use aspas duplas como:

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

ps, você deve primeiro ativar a Pesquisa de texto completo na tabela antes de usar o contains keyword. para obter mais detalhes, consulte aqui 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%'

Alterado ORpara com ANDbase na edição da pergunta.


Eu preciso que todas as palavras estejam contidas no resultado em qualquer combinação #
M Mario M

4

Se você estiver usando o Oracle Database , poderá conseguir isso usando a consulta contém . Contém consultas mais rápidas do que as consultas semelhantes.

Se você precisar de todas as palavras

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

Se você precisar de alguma das palavras

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

Contém um índice necessário do tipo CONTEXT na sua coluna.

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

1
@ downvoters Um comentário é apreciado dizendo o que há de errado com a resposta. Esta mesma consulta está sendo executado em nossa solução de empresa mais de 1000 vezes por dia, sem quaisquer problemas :)
mirmdasif

2
O OP não especifica qual banco de dados está usando e todos assumiram que é o Sql Server. Mas desde que você especificou o Oracle em sua resposta, não entendo os que recusam.
EAmez 01/02/19

4

Se você só quer encontrar uma correspondência.

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

Servidor SQL :

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

Para obter a correspondência exata. O exemplo (';a;ab;ac;',';b;')não será compatível.

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

1
'INSTR' não é um nome de função incorporado reconhecido. No meu SQL Server.
Durgesh Pandey

0

tente usar a "pesquisa de tesarus" no índice de texto completo no MS SQL Server. Isso é muito melhor do que usar "%" na pesquisa se você tiver milhões de registros. tesarus tem uma pequena quantidade de consumo de memória do que os outros. tente pesquisar essas funções :)


0

A melhor maneira é criar um índice de texto completo em uma coluna da tabela e usar o include em vez do LIKE

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

0

por que não usar "in"?

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

2
Porque isso não funciona. Você realmente tentou?
mvp

2
Acredito que isso retornará apenas correspondências exatas.
Murray

1
Eu também entendi mal a pergunta original: eles não querem encontrar uma correspondência exata, mas uma palavra fazendo parte de uma sequência (possivelmente) maior. Para a "exata correspondência de" caso mais simples, isso funciona desde que as palavras são entre aspas simples (cf. SQLfiddle )
SC28

0

Uma das maneiras mais fáceis de obter o que é mencionado na pergunta é usando CONTAINS com NEAR ou '~'. Por exemplo, as consultas a seguir nos forneceriam todas as colunas que incluem especificamente word1, word2 e word3.

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

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

Além disso, CONTAINSTABLE retorna uma classificação para cada documento com base na proximidade de "palavra1", "palavra2" e "palavra3". Por exemplo, se um documento contiver a frase "A palavra1 é palavra2 e palavra3", sua classificação será alta porque os termos estão mais próximos um do outro do que em outros documentos.

Outra coisa que gostaria de acrescentar é que também podemos usar o proximidade_termo para encontrar colunas onde as palavras estão dentro de uma distância específica entre elas na frase da coluna.


0

Idealmente, isso deve ser feito com a ajuda da pesquisa de texto completo do servidor sql, se estiver sendo usada. No entanto, se você não conseguir fazer isso funcionar no seu banco de dados por algum motivo, aqui está uma solução com alto desempenho:

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

Isso exibirá todos os registros em que column1contém um valor parcial 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
Obrigado por este trecho de código, que pode fornecer ajuda imediata e limitada. Uma explicação adequada melhoraria bastante seu valor a longo prazo , mostrando por que essa é uma boa solução para o problema e a tornaria mais útil para futuros leitores com outras perguntas semelhantes. Por favor edite sua resposta para adicionar alguma explicação, incluindo as suposições que você fez.
Mogdad 5/03

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

ou

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

3
O "regexp" é SQL padrão?
Peter Mortensen

2
Para a segunda consulta, a palavra não deve ser citada?
Peter Mortensen

1
Esse código parece verificar se a coluna é igual a uma das três palavras. A questão é verificar se a coluna contém todas as três palavras.
Sam

7
Olá, isso pode muito bem resolver o problema ... mas seria bom se você pudesse editar sua resposta e fornecer uma pequena explicação sobre como e por que funciona :) Não se esqueça - existem muitos novatos no Stack overflow, e eles podem aprender uma coisa ou duas com sua experiência - o que é óbvio para você pode não ser o mesmo para eles.
Taryn East
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.