Pole SQL SELECT WHERE zawiera słowa


562

Potrzebuję wybierz, który zwróci wyniki takie jak to:

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

Potrzebuję wszystkich wyników, tzn. Obejmuje to ciągi zawierające „słowo2 słowo3 słowo1” lub „słowo1 słowo3 słowo2” lub dowolną inną kombinację tych trzech.

Wszystkie słowa muszą znaleźć się w wyniku.

Odpowiedzi:


843

Raczej powolna, ale działająca metoda na dodanie dowolnego słowa:

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

Jeśli potrzebujesz wszystkich słów, użyj tego:

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

Jeśli chcesz czegoś szybciej, musisz zajrzeć do wyszukiwania pełnotekstowego, a jest to bardzo specyficzne dla każdego typu bazy danych.


3
+ 1 Zgadzam się, że jest wolniejszy, ale można go złagodzić za pomocą dobrego indeksowania
Preet Sangha

12
@PreetSangha Indeksowanie, gdy szukasz LIKE zaczynającej się od wieloznacznej karty? Pokaż mi jak!
Popnoodles,

1
W PostgreSQL 9.1 i nowszych wersjach możesz utworzyć indeks trigram, który może indeksować takie wyszukiwania .
mvp

2
@AquaAlex: twoja instrukcja nie powiedzie się, jeśli tekst ma word3 word2 word1.
mvp

3
Kolejny minus tego podejścia: „% word%” znajdzie również „słowa”, „krzyżówkę” i „miecz” (tylko jako przykład). Musiałbym wykonać kolumnę1 LIKE „słowo” LUB kolumnę1 LIKE ”słowo%” LUB kolumnę1 LIKE „% słowo” LUB kolumnę1 LIKE „słowo”, aby znaleźć dokładnie pasujące słowa - i nadal nie udałoby się to w przypadku wpisów, w których słowa nie są po prostu oddzielone spacjami.
BlaM

81

Zauważ, że jeśli używasz LIKEdo ustalenia, czy ciąg jest podciągiem innego ciągu, musisz uciec od pasujących do wzorca znaków w szukanym ciągu.

Jeśli Twój dialekt SQL obsługuje CHARINDEX, o wiele łatwiej jest go użyć:

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

Należy również pamiętać, że to i metoda w zaakceptowanej odpowiedzi dotyczą tylko dopasowywania podciągów zamiast dopasowywania słów. Na przykład ciąg 'word1word2word3'nadal będzie pasował.


1
Wydaje się to o wiele łatwiejsze, jeśli wyszukiwane hasło jest zmienną, niż trzeba dodawać znaki „%” przed rozpoczęciem wyszukiwania
ShaneBlake

4
W serwerach i silnikach Microsoft SQL powinniśmy InStr()zamiast tego użyćCHARINDEX
23 W

6
@ 23W Nie ma InStr w MS SQL
Romano Zumbé

19

Funkcjonować

 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
 )

Pytanie

 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
Znakomity! Jak zacząć uczyć się o tej funkcji, Sir? co to jest sztuk i czy możesz mi powiedzieć pseudokod o tej linii? SUBSTRING (@str, start, CASE WHEN stop> 0 THEN stop - start ELSE 512 END) AS Data
Khaneddy2013

2
Ten ruch był niesamowity ,, Naprawdę JEALOUS :( _______________________________________________________________________________________ DOŁĄCZ DO WEWNĘTRZNEGO (@FilterTable F1 ON T.Column1 LIKE '%' + F1.Data + '%' LEFT JOIN (@FilterTable F2 ON T.Column1 NOT LIKE '%' ' + F2.Data + '%'
Ahmad Alkaraki

13

Zamiast SELECT * FROM MyTable WHERE Column1 CONTAINS 'word1 word2 word3', dodaj I pomiędzy tymi słowami, takimi jak:

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

Szczegółowe informacje można znaleźć tutaj https://msdn.microsoft.com/en-us/library/ms187787.aspx

AKTUALIZACJA

Aby wybrać frazy, użyj podwójnych cudzysłowów, takich jak:

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

ps musisz najpierw włączyć wyszukiwanie pełnotekstowe w tabeli, zanim użyjesz słowa kluczowego. Aby uzyskać więcej informacji, zobacz tutaj 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%'

Zmieniono na ORna ANDpodstawie edycji na pytanie.


Potrzebuję zawrzeć wszystkie słowa w wyniku w dowolnej kombinacji
Mario M

4

Jeśli korzystasz z bazy danych Oracle, możesz to osiągnąć za pomocą zapytania zawierającego . Zawiera zapytania są szybsze niż podobne zapytania.

Jeśli potrzebujesz wszystkich słów

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

Jeśli potrzebujesz któregoś ze słów

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

Zawiera potrzebny indeks typu CONTEXT w kolumnie.

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

1
@downvoters Doceniany jest komentarz mówiący, co jest nie tak z odpowiedzią. To samo zapytanie jest uruchamiane w naszym rozwiązaniu dla przedsiębiorstw ponad 1000 razy dziennie, bez żadnych problemów :)
mirmdasif

2
OP nie określa, której bazy danych używa i wszyscy przyjęli, że jest to Sql Server. Ale ponieważ w odpowiedzi podałeś Oracle, nie rozumiem downvoters.
EAmez

4

Jeśli chcesz tylko znaleźć dopasowanie.

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

SQL Server:

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

Aby uzyskać dokładne dopasowanie. Przykład (';a;ab;ac;',';b;')nie pasuje.

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

1
„INSTR” nie jest rozpoznawalną nazwą wbudowanej funkcji. W moim SQL Server.
Durgesh Pandey,

0

spróbuj użyć „wyszukiwania tesarus” w indeksie pełnotekstowym w MS SQL Server. Jest to znacznie lepsze niż użycie „%” w wyszukiwaniu, jeśli masz miliony rekordów. tesarus zużywają niewiele pamięci niż inne. spróbuj wyszukać te funkcje :)


0

Najlepszym sposobem jest utworzenie indeksu pełnotekstowego na kolumnie w tabeli i użycie zawierać zamiast LIKE

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

0

dlaczego zamiast tego nie użyć „in”?

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

2
Ponieważ to nie działa. Czy rzeczywiście tego próbowałeś?
mvp

2
Wierzę, że to zwróci tylko dokładne dopasowania.
Murray,

1
Nie rozumiem też oryginalnego pytania: nie chcą znaleźć dokładnego dopasowania, ale słowo będące częścią (być może) większego ciągu. W prostszym przypadku „dokładnego dopasowania” działa to pod warunkiem, że słowa znajdują się między pojedynczymi cudzysłowami (por. SQLfiddle )
sc28,

0

Jednym z najprostszych sposobów na osiągnięcie tego, o czym mowa w pytaniu, jest użycie ZAWIERA z BLISKĄ lub „~”. Na przykład następujące zapytania dałoby nam wszystkie kolumny, które konkretnie zawierają słowo 1, słowo 2 i słowo 3.

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

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

Ponadto CONTAINSTABLE zwraca rangę dla każdego dokumentu na podstawie odległości między „słowem 1”, „słowem 2” i „słowem 3”. Na przykład, jeśli dokument zawiera zdanie „Słowo1 to słowo2 i słowo3”, jego pozycja w rankingu byłaby wysoka, ponieważ terminy są bliżej siebie niż w innych dokumentach.

Inną rzeczą, którą chciałbym dodać, jest to, że możemy również użyć bliskości_termu, aby znaleźć kolumny, w których słowa znajdują się w określonej odległości między nimi w obrębie frazy kolumny.


0

Najlepiej, jeśli jest to możliwe, przy pomocy wyszukiwania pełnotekstowego serwera SQL. Jeśli jednak z jakiegoś powodu nie możesz tego zrobić na swoim DB, oto rozwiązanie wymagające dużej wydajności: -

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

Spowoduje to wyświetlenie wszystkich rekordów column1zawierających częściową wartość 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
Dziękujemy za ten fragment kodu, który może zapewnić pewną ograniczoną, natychmiastową pomoc. Właściwe wyjaśnienie byłoby znacznie poprawić swoją długoterminową wartość pokazując dlaczego jest to dobre rozwiązanie problemu, a byłoby bardziej użyteczne dla czytelników przyszłości z innymi, podobnymi pytaniami. Proszę edytować swoją odpowiedź dodać kilka wyjaśnień, w tym założeń już wykonanych.
Mogsdad

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

lub

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

3
Czy „regexp” to standardowy SQL?
Peter Mortensen

2
W przypadku drugiego zapytania nie należy cytować tego słowa?
Peter Mortensen

1
Ten kod wydaje się sprawdzać, czy kolumna jest równa jednemu z trzech słów. Pytanie dotyczy sprawdzenia, czy kolumna zawiera wszystkie trzy słowa.
Sam

7
Cześć, to może rozwiązać problem ... ale byłoby dobrze, gdybyś mógł edytować swoją odpowiedź i podać krótkie wyjaśnienie, w jaki sposób i dlaczego to działa :) Nie zapominaj - na przepełnieniu stosu jest mnóstwo nowych graczy, i mogą nauczyć się czegoś z twojej wiedzy - to, co dla ciebie oczywiste, może nie być dla nich takie.
Taryn East
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.