El campo SQL SELECT WHERE contiene palabras

562

Necesito una selección que arroje resultados como este:

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

Y necesito todos los resultados, es decir, esto incluye cadenas con 'word2 word3 word1' o 'word1 word3 word2' o cualquier otra combinación de los tres.

Todas las palabras deben estar en el resultado.

Mario M
fuente

Respuestas:

844

Más bien lento, pero método de trabajo para incluir cualquiera de las palabras:

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

Si necesita que todas las palabras estén presentes, use esto:

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

Si desea algo más rápido, necesita buscar texto completo, y esto es muy específico para cada tipo de base de datos.

mvp
fuente
3
+ 1 Estoy de acuerdo en que es más lento, pero se puede mitigar con una buena indexación
Preet Sangha
12
@PreetSangha Indexing cuando busca LIKE comenzando con un comodín? ¡Por favor muéstrame cómo!
Popnoodles
1
En PostgreSQL 9.1 y versiones posteriores, puede crear un índice de trigrama que puede indexar dichas búsquedas .
mvp
2
@AquaAlex: su declaración fallará si el texto lo ha hecho word3 word2 word1.
mvp
3
Otro inconveniente de este enfoque: '% word%' también encontrará 'palabras', 'crucigrama' y 'espada' (solo como ejemplo). Tendría que hacer una columna1 ME GUSTA 'palabra' O columna1 ME GUSTA 'palabra%' O columna1 ME GUSTA '% palabra' O columna1 ME GUSTA 'palabra' para encontrar coincidencias exactas de palabras, y aún fallaría para las entradas donde las palabras no son solo separado con espacios.
BlaM
81

Tenga en cuenta que si usa LIKEpara determinar si una cadena es una subcadena de otra cadena, debe escapar de los caracteres coincidentes de patrón en su cadena de búsqueda.

Si su dialecto SQL es compatible CHARINDEX, es mucho más fácil usarlo:

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

Además, tenga en cuenta que esto y el método en la respuesta aceptada solo cubren la coincidencia de subcadenas en lugar de la coincidencia de palabras. Entonces, por ejemplo, la cadena 'word1word2word3'aún coincidiría.

Sam
fuente
1
Esto parece mucho más fácil si su término de búsqueda es una variable en lugar de tener que agregar los caracteres '%' antes de buscar
ShaneBlake
44
En los servidores y motores de Microsoft SQL deberíamos usar InStr()en su lugarCHARINDEX
23W
66
@ 23W No hay InStr en MS SQL
Romano Zumbé
19

Función

 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
 )

Consulta

 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
Eduardo Cuomo
fuente
2
Excelente! ¿Cómo comenzar a aprender sobre esta función, señor? ¿Qué son las piezas? y me puedes decir pseudocódigo sobre esta línea? SUBSTRING (@str, start, CASE WHEN stop> 0 THEN stop - start ELSE 512 END) AS Data
Khaneddy2013
2
Este movimiento fue increíble, estoy realmente celoso :( _______________________________________________________________________________________ INNER JOIN (@FilterTable F1 ON T.Column1 LIKE '%' + F1.Data + '%' LEFT JOIN (@FilterTable F2 ON T.Column1 NOT LIKE '%' + F2.Data + '%'
Ahmad Alkaraki
13

En lugar de SELECT * FROM MyTable WHERE Column1 CONTAINS 'word1 word2 word3', agregue Y entre esas palabras como:

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

para más detalles, consulte aquí https://msdn.microsoft.com/en-us/library/ms187787.aspx

ACTUALIZAR

Para seleccionar frases, use comillas dobles como:

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

PD : primero debe habilitar la búsqueda de texto completo en la tabla antes de usar la palabra clave contiene. para más detalles, vea aquí https://docs.microsoft.com/en-us/sql/relational-databases/search/get-started-with-full-text-search

hecho un desastre
fuente
8
SELECT * FROM MyTable WHERE 
Column1 LIKE '%word1%'
AND Column1 LIKE '%word2%'
AND Column1 LIKE  '%word3%'

Cambiado ORa ANDbasado en editar a pregunta.

Jon Crowell
fuente
Necesito que todas las palabras estén contenidas en el resultado en cualquier combinación
Mario M
4

Si está utilizando la base de datos Oracle , puede lograr esto utilizando la consulta contiene . Contiene consultas son más rápidas que como consulta.

Si necesitas todas las palabras

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

Si necesitas alguna de las palabras

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

Contiene el índice de necesidad de tipo CONTEXTO en su columna.

CREATE INDEX SEARCH_IDX ON MyTable(Column) INDEXTYPE IS CTXSYS.CONTEXT
mirmdasif
fuente
1
@downvoters Se agradece un comentario que dice qué está mal con la respuesta. Esta misma consulta se ejecuta en nuestra solución empresarial más de 1000 veces al día, sin ningún problema :)
mirmdasif
2
OP no especifica qué base de datos está utilizando y todos han asumido que se trata de SQL Server. Pero como ha especificado Oracle en su respuesta, no entiendo a los votantes negativos.
EAmez
4

Si solo quieres encontrar una coincidencia.

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

Servidor SQL :

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

Para obtener la coincidencia exacta. El ejemplo (';a;ab;ac;',';b;')no obtendrá una coincidencia.

SELECT * FROM MyTable WHERE INSTR(';word1;word2;word3;',';'||Column1||';')<>0
Joshua Balan
fuente
1
'INSTR' no es un nombre de función incorporado reconocido. En mi servidor SQL.
Durgesh Pandey
0

intente utilizar la "búsqueda de tesarus" en el índice de texto completo en MS SQL Server. Esto es mucho mejor que usar "%" en la búsqueda si tiene millones de registros. Tesarus tiene una pequeña cantidad de consumo de memoria que los demás. intenta buscar estas funciones :)

Daryl Arenas
fuente
0

la mejor manera es hacer un índice de texto completo en una columna de la tabla y usar contener en lugar de LIKE

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

¿por qué no usar "in" en su lugar?

Select *
from table
where columnname in (word1, word2, word3)
Michael Angerbauer
fuente
2
Porque no funciona ¿Realmente lo has intentado?
mvp
2
Creo que esto solo devolverá coincidencias exactas.
Murray
1
También entendí mal la pregunta original: no quieren encontrar una coincidencia exacta, pero una palabra es parte de una (posiblemente) cadena más grande. Para el caso más simple de "coincidencia exacta", esto funciona siempre que las palabras estén entre comillas simples (cf. SQLfiddle )
sc28
0

Una de las formas más fáciles de lograr lo que se menciona en la pregunta es usar CONTAINS con NEAR o '~'. Por ejemplo, las siguientes consultas nos darían todas las columnas que incluyen específicamente word1, word2 y word3.

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

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

Además, CONTAINSTABLE devuelve un rango para cada documento en función de la proximidad de "word1", "word2" y "word3". Por ejemplo, si un documento contiene la oración, "La palabra1 es palabra2 y palabra3", su clasificación sería alta porque los términos están más cercanos entre sí que en otros documentos.

Otra cosa que me gustaría agregar es que también podemos usar proximidad_term para encontrar columnas donde las palabras están dentro de una distancia específica entre ellas dentro de la frase de la columna.

Anastasios Selmanis
fuente
0

Idealmente, esto debería hacerse con la ayuda de la búsqueda de texto completo del servidor sql si se usa. Sin embargo, si no puede hacer que eso funcione en su base de datos por alguna razón, aquí hay una solución de alto rendimiento:

-- 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
JBelfort
fuente
-2
SELECT * FROM MyTable WHERE Column1 Like "*word*"

Esto mostrará todos los registros donde column1contiene un valor parcial word.

Jino
fuente
-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
usuario2274887
fuente
2
Gracias por este fragmento de código, que puede proporcionar una ayuda limitada e inmediata. Una explicación adecuada mejoraría en gran medida su valor a largo plazo al mostrar por qué esta es una buena solución al problema y lo haría más útil para futuros lectores con otras preguntas similares. Por favor, editar su respuesta a añadir un poco de explicación, incluyendo los supuestos realizados.
Mogsdad
-5
select * from table where name regexp '^word[1-3]$'

o

select * from table where name in ('word1','word2','word3')
vidyadhar
fuente
3
¿Es "regexp" el SQL estándar?
Peter Mortensen
2
Para la segunda consulta, ¿no debería citarse la palabra?
Peter Mortensen
1
Este código parece verificar si la columna es igual a una de las tres palabras. La pregunta es acerca de la comprobación si la columna contiene todas de las tres palabras.
Sam
77
Hola, esto puede resolver el problema ... pero sería bueno si pudieras editar tu respuesta y proporcionar una pequeña explicación sobre cómo y por qué funciona :) No lo olvides: hay un montón de novatos en el desbordamiento de Stack, y podrían aprender una o dos cosas de su experiencia: lo que es obvio para usted podría no serlo para ellos.
Taryn East