SQL SELECT WHERE поле содержит слова


562

Мне нужно выбрать, который будет возвращать результаты, как это:

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

И мне нужны все результаты, т. Е. Сюда входят строки с «word2 word3 word1» или «word1 word3 word2» или любой другой комбинацией из трех.

Все слова должны быть в результате.

Ответы:


844

Довольно медленный, но рабочий метод для включения любого из слов:

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

Если вам нужны все слова, используйте это:

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

Если вы хотите что-то быстрее, вам нужно изучить полнотекстовый поиск, и это очень специфично для каждого типа базы данных.


3
+1 Я согласен, что это медленнее, но его можно смягчить с помощью хорошей индексации
Преет Сангха

12
@PreetSangha Индексирование, когда вы ищете LIKE, начиная с символа подстановки? Пожалуйста, покажи мне, как!
Popnoodles

1
В PostgreSQL 9.1 и более поздних версиях вы можете создать индекс триграмм, который может индексировать такие поиски .
mvp

2
@ AquaAlex: ваше утверждение не удастся, если текст имеет word3 word2 word1.
mvp

3
Другой недостаток этого подхода: «% word%» также найдет «слова», «кроссворд» и «меч» (просто в качестве примера). Мне нужно было бы сделать column1 LIKE «слово» ИЛИ column1 LIKE «слово%» ИЛИ column1 LIKE «% word» ИЛИ column1 LIKE «слово», чтобы просто найти точные совпадения слов - и все равно не получится для записей, где слова не являются просто разделены пробелами.
BlaM

81

Обратите внимание, что если вы используете, LIKEчтобы определить, является ли строка подстрокой другой строки, вы должны экранировать символы, соответствующие шаблону в вашей строке поиска.

Если ваш диалект SQL поддерживает CHARINDEX, его гораздо проще использовать:

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

Кроме того, имейте в виду, что этот и метод в принятом ответе охватывают только сопоставление подстрок, а не сопоставление слов. Так, например, строка 'word1word2word3'все равно будет соответствовать.


1
Это кажется намного проще, если ваш поисковый термин является переменной, а не добавлением символов «%» перед поиском
ShaneBlake

4
В серверах и механизмах Microsoft SQL мы должны использовать InStr()вместо этогоCHARINDEX
23W

6
@ 23W В MS SQL нет InStr
Романо Зумбе

19

функция

 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
 )

запрос

 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! Как начать изучать эту функцию, сэр? что такое кусочки? а можете ли вы рассказать мне псевдокод об этой строке? SUBSTRING (@str, запуск, CASE, когда остановка> 0 THEN остановка - запуск ELSE 512 END) AS Данные
Khaneddy2013

2
Этот шаг был невероятным, я действительно ревнив :( _______________________________________________________________________________________ INNER JOIN (@FilterTable F1 ON T.Column1 LIKE '%' + F1.Data + '%' LEFT JOIN (@FilterTable F2 ON T.Column1 НЕ НРАВИТСЯ '%' + F2.Data + '%'
Ахмад Алкараки

13

Вместо того SELECT * FROM MyTable WHERE Column1 CONTAINS 'word1 word2 word3', чтобы добавить А между этими словами, как:

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

подробности см. здесь https://msdn.microsoft.com/en-us/library/ms187787.aspx.

ОБНОВИТЬ

Для выбора фраз используйте двойные кавычки, такие как:

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

ps вы должны сначала включить полнотекстовый поиск по таблице, прежде чем использовать содержит ключевое слово. Подробнее см. здесь 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%'

Изменено ORна ANDоснове редактирования вопроса.


Мне нужно, чтобы все слова содержались в результате в любой комбинации
Mario M

4

Если вы используете Oracle Database , то вы можете добиться этого с помощью содержит запрос. Содержит запросы быстрее, чем запрос.

Если вам нужны все слова

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

Если вам нужно любое из слов

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

Содержит индекс необходимости типа КОНТЕКСТ на вашем столбце.

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

1
@ downvoters Комментарий приветствуется, сообщая, что не так с ответом. Этот же запрос выполняется в нашем корпоративном решении более 1000 раз в день, без каких-либо проблем :)
mirmdasif

2
OP не указывает, какая база данных используется, и все предположили, что это Sql Server. Но так как вы указали Oracle в своем ответе, я не понимаю downvoters.
EAmez

4

Если вы просто хотите найти совпадение.

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

SQL Server:

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

Чтобы получить точное совпадение. Пример (';a;ab;ac;',';b;')не получит совпадение.

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

1
INSTR не является распознанным именем встроенной функции. В моем SQL Server.
Дургеш Пандей

0

попробуйте использовать «поиск по tesarus» в полнотекстовом индексе в MS SQL Server. Это гораздо лучше, чем использовать «%» в поиске, если у вас есть миллионы записей. Tesarus имеют небольшое потребление памяти, чем другие. попробуйте поискать эту функцию :)


0

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

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

0

почему бы не использовать вместо "в"?

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

2
Потому что это не работает. Вы действительно пробовали это?
MVP

2
Я верю, что это вернет только точные совпадения.
Мюррей

1
Я также неправильно понял исходный вопрос: они не хотят найти точное совпадение, но слово является частью (возможно) большей строки. Для более простого случая «точного соответствия» это работает при условии, что слова заключены в одинарные кавычки (см. SQLfiddle )
sc28

0

Один из самых простых способов добиться того, что упомянуто в вопросе, - использовать CONTAINS с NEAR или '~'. Например, следующие запросы дадут нам все столбцы, которые конкретно включают в себя word1, word2 и word3.

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

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

Кроме того, CONTAINSTABLE возвращает рейтинг для каждого документа на основе близости слов «word1», «word2» и «word3». Например, если документ содержит предложение «Слово1 - это слово2 и слово3», его рейтинг будет высоким, поскольку термины ближе друг к другу, чем в других документах.

Еще одна вещь, которую я хотел бы добавить, - это то, что мы также можем использовать бесконтактный_терм, чтобы найти столбцы, в которых слова находятся на определенном расстоянии между ними внутри фразы столбца.


0

В идеале это должно быть сделано с помощью полнотекстового поиска сервера SQL при использовании. Однако, если вы по какой-то причине не можете работать с вашей БД, вот решение, требующее высокой производительности:

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

Это отобразит все записи, где column1содержится частичное значение 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
Спасибо за этот фрагмент кода, который может оказать некоторую ограниченную, немедленную помощь. Надлежащее объяснение было бы значительно улучшить свою долгосрочную ценность , показывая , почему это хорошее решение проблемы, и сделает его более полезным для читателей будущих с другими подобными вопросами. Пожалуйста, измените свой ответ, чтобы добавить некоторые объяснения, в том числе предположения, которые вы сделали.
Могсдад

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

или

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

3
Является ли регулярное выражение стандартным SQL?
Питер Мортенсен

2
Для второго запроса, не должно ли слово быть в кавычках?
Питер Мортенсен

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

7
Привет, это вполне может решить проблему ... но было бы хорошо, если бы вы могли отредактировать свой ответ и дать небольшое объяснение о том, как и почему он работает :) Не забывайте - на переполнении стека есть куча новичков, и они могут выучить одну или две вещи из вашего опыта - то, что для вас очевидно, может быть не так для них.
Тарын Ист
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.