Trường SQL SELECT WHERE chứa các từ


562

Tôi cần một lựa chọn sẽ trả về kết quả như thế này:

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

Và tôi cần tất cả các kết quả, tức là bao gồm các chuỗi với 'word2 word3 word1' hoặc 'word1 word3 word2' hoặc bất kỳ kết hợp nào khác của ba.

Tất cả các từ cần phải có trong kết quả.

Câu trả lời:


843

Khá chậm, nhưng phương pháp làm việc để bao gồm bất kỳ từ nào:

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

Nếu bạn cần tất cả các từ để có mặt, sử dụng này:

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

Nếu bạn muốn một cái gì đó nhanh hơn, bạn cần xem xét tìm kiếm toàn văn bản và điều này rất cụ thể cho từng loại cơ sở dữ liệu.


3
+ 1 Tôi đồng ý rằng nó chậm hơn nhưng có thể được giảm nhẹ bằng cách lập chỉ mục tốt
Preet Sangha

12
@PreetSangha Lập chỉ mục khi bạn đang tìm kiếm THÍCH bắt đầu bằng thẻ hoang dã? Xin hãy chỉ cho tôi cách làm!
Popnoodles

1
Trong PostgreQuery 9.1 trở lên, bạn có thể tạo chỉ mục bát quái có thể lập chỉ mục các tìm kiếm đó .
mvp

2
@AquaAlex: tuyên bố của bạn sẽ thất bại nếu văn bản có word3 word2 word1.
mvp

3
Một nhược điểm khác của phương pháp này: '% word%' cũng sẽ tìm thấy 'từ', 'ô chữ' và 'thanh kiếm' (chỉ là một ví dụ). Tôi phải thực hiện một cột1 THÍCH 'từ' HOẶC cột1 THÍCH 'từ%' HOẶC cột1 THÍCH '% từ' HOẶC cột1 THÍCH 'từ' để chỉ tìm từ khớp chính xác - và nó vẫn thất bại cho các mục không có từ chỉ cách nhau với không gian.
BlaM

81

Lưu ý rằng nếu bạn sử dụng LIKEđể xác định xem một chuỗi có phải là một chuỗi con của một chuỗi khác hay không, bạn phải thoát khỏi các ký tự khớp mẫu trong chuỗi tìm kiếm của bạn.

Nếu phương ngữ SQL của bạn hỗ trợ CHARINDEX, thay vào đó, việc sử dụng nó sẽ dễ dàng hơn rất nhiều:

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

Ngoài ra, xin lưu ý rằng điều này và phương thức trong câu trả lời được chấp nhận chỉ bao gồm kết hợp chuỗi con thay vì khớp từ. Vì vậy, ví dụ, chuỗi 'word1word2word3'vẫn sẽ khớp.


1
Điều này có vẻ dễ dàng hơn nhiều nếu cụm từ tìm kiếm của bạn là một biến thay vì phải thêm ký tự '%' trước khi tìm kiếm
ShaneBlake

4
Trong các máy chủ và công cụ Microsoft SQL, chúng ta nên sử dụng InStr()thay thếCHARINDEX
23W

6
@ 23W Không có InStr trong MS SQL
Romano Zumbé

19

Chức năng

 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
 )

Truy vấn

 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
Tuyệt vời! Làm thế nào để bắt đầu tìm hiểu về chức năng này, thưa ông? Miếng là gì? và bạn có thể cho tôi biết mã giả về dòng này? Substring (@str, bắt đầu, CASE KHI dừng> 0 THEN ngừng - bắt đầu ELSE 512 END) AS dữ liệu
Khaneddy2013

2
Động thái này thật không thể tin được, tôi thực sự rất JEALOUS :( _____________________________________________________________________________________ INNER THAM GIA + F2.Data + '%'
Ahmad Alkaraki

13

Thay vì SELECT * FROM MyTable WHERE Column1 CONTAINS 'word1 word2 word3', thêm Và ở giữa những từ đó như:

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

để biết chi tiết, xem tại đây https://msdn.microsoft.com/en-us/l Library / ms187787.aspx

CẬP NHẬT

Để chọn cụm từ, sử dụng dấu ngoặc kép như:

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

ps trước tiên bạn phải cho phép Full Text Search trên bàn trước khi sử dụng chứa từ khóa. để biết thêm chi tiết, xem tại đây https://docs.microsoft.com/en-us/sql/relational-database/search/get-started-with-full-text-search


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

Thay đổi ORđể ANDdựa trên chỉnh sửa cho câu hỏi.


Tôi cần tất cả các từ được chứa trong kết quả trong bất kỳ kết hợp nào
Mario M

4

Nếu bạn đang sử dụng Cơ sở dữ liệu Oracle thì bạn có thể đạt được điều này bằng cách sử dụng chứa truy vấn. Chứa các truy vấn nhanh hơn truy vấn như.

Nếu bạn cần tất cả các từ

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

Nếu bạn cần bất kỳ từ nào

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

Chứa chỉ số của loại TIẾP THEO trên cột của bạn.

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

1
@downvoters Một bình luận được đánh giá cao cho biết những gì sai với câu trả lời. Truy vấn tương tự này đang chạy trong giải pháp doanh nghiệp của chúng tôi hơn 1000 lần mỗi ngày, mà không có bất kỳ vấn đề nào :)
mirmdasif

2
OP không chỉ định cơ sở dữ liệu nào đang sử dụng và mọi người đã cho rằng đó là Sql Server. Nhưng vì bạn đã chỉ định Oracle trong phản hồi của bạn nên tôi không hiểu về downvoters.
EAmez

4

Nếu bạn chỉ muốn tìm một trận đấu.

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

Máy chủ SQL:

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

Để có được kết hợp chính xác. Ví dụ (';a;ab;ac;',';b;')sẽ không có được một trận đấu.

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

1
'INSTR' không phải là tên hàm tích hợp được công nhận. Trong máy chủ SQL của tôi.
Durgesh Pandey

0

hãy thử sử dụng "tìm kiếm tesarus" trong chỉ mục toàn văn bản trong MS SQL Server. Điều này tốt hơn nhiều so với việc sử dụng "%" trong tìm kiếm nếu bạn có hàng triệu bản ghi. tesarus có một lượng tiêu thụ bộ nhớ nhỏ hơn những người khác. cố gắng tìm kiếm các chức năng này :)


0

cách tốt nhất là tạo chỉ mục toàn văn bản trên một cột trong bảng và sử dụng chứa thay vì THÍCH

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

0

Tại sao không sử dụng "trong" thay thế?

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

2
Bởi vì nó không hoạt động. Bạn đã thực sự thử nó chưa?
mvp

2
Tôi tin rằng điều này sẽ chỉ trả lại trận đấu chính xác.
Murray

1
Tôi cũng đã hiểu nhầm câu hỏi ban đầu: họ không muốn tìm một kết hợp chính xác, nhưng một từ là một phần của chuỗi lớn hơn (có thể). Đối với trường hợp "khớp chính xác" đơn giản hơn, tác phẩm này cung cấp các từ nằm giữa các dấu ngoặc đơn (xem SQLfiddle )
sc28

0

Một trong những cách dễ nhất để đạt được những gì được đề cập trong câu hỏi là sử dụng CONTAIN với NEAR hoặc '~'. Ví dụ: các truy vấn sau đây sẽ cung cấp cho chúng tôi tất cả các cột cụ thể bao gồm word1, word2 và word3.

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

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

Ngoài ra, CONTAINSTABLE trả về thứ hạng cho mỗi tài liệu dựa trên mức độ gần của "word1", "word2" và "word3". Ví dụ: nếu một tài liệu có chứa câu "Word1 là word2 và word3" thì thứ hạng của nó sẽ cao vì các thuật ngữ gần nhau hơn so với các tài liệu khác.

Một điều khác mà tôi muốn thêm là chúng ta cũng có thể sử dụng lân cận để tìm các cột trong đó các từ nằm trong một khoảng cách cụ thể giữa chúng trong cụm từ cột.


0

Điều này lý tưởng nên được thực hiện với sự trợ giúp của máy chủ sql tìm kiếm toàn văn nếu sử dụng. Tuy nhiên, nếu bạn không thể khiến nó hoạt động trên DB của mình vì một số lý do, đây là một giải pháp chuyên sâu về hiệu năng: -

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

Điều này sẽ hiển thị tất cả các hồ sơ, nơi column1có một giá trị phần chứa 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
Cảm ơn bạn vì đoạn mã này, có thể cung cấp một số trợ giúp hạn chế, ngay lập tức. Một lời giải thích phù hợp sẽ cải thiện đáng kể giá trị lâu dài của nó bằng cách chỉ ra lý do tại sao đây là một giải pháp tốt cho vấn đề và sẽ giúp nó hữu ích hơn cho những người đọc tương lai với những câu hỏi tương tự khác. Vui lòng chỉnh sửa câu trả lời của bạn để thêm một số giải thích, bao gồm các giả định bạn đã thực hiện.
Mogsdad

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

hoặc là

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

3
Là SQL "regrec" tiêu chuẩn?
Peter Mortensen

2
Đối với truy vấn thứ hai, không nên trích dẫn từ này?
Peter Mortensen

1
Mã này dường như kiểm tra xem cột có bằng một trong ba từ không. Câu hỏi là về việc kiểm tra xem cột có chứa tất cả ba từ không.
Sam

7
Hiya, điều này có thể giải quyết vấn đề tốt ... nhưng sẽ tốt nếu bạn có thể chỉnh sửa câu trả lời của mình và cung cấp một lời giải thích nhỏ về cách thức và lý do hoạt động :) Đừng quên - có rất nhiều người mới trên Stack tràn, và họ có thể học được một hoặc hai điều từ chuyên môn của bạn - điều hiển nhiên đối với bạn có thể không đúng với họ.
Taryn Đông
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.