【问题标题】:SQL SELECT WHERE field contains wordsSQL SELECT WHERE 字段包含单词
【发布时间】:2012-12-26 18:49:39
【问题描述】:

我需要一个可以返回如下结果的选择:

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

我需要所有结果,即这包括带有 'word2 word3 word1' 或 'word1 word3 word2' 或三者的任何其他组合的字符串。

所有单词都需要在结果中。

【问题讨论】:

    标签: sql select


    【解决方案1】:

    相当慢,但包含任何字词的工作方法:

    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%'
    

    如果您想要更快的速度,您需要研究全文搜索,这对于每种数据库类型都非常具体。

    【讨论】:

    • + 1 我同意它的速度较慢,但​​可以通过良好的索引来缓解
    • @PreetSangha 在搜索以通配符开头的 LIKE 时编制索引?请告诉我怎么做!
    • 在 PostgreSQL 9.1 及更高版本中,您可以创建 can index such searches 的三元索引。
    • @AquaAlex:如果文本中有word3 word2 word1,您的语句将失败。
    • 这种方法的另一个缺点:'%word%' 也会找到 'words'、'crosswordpuzzle' 和 'sword'(仅作为示例)。我必须做一个 column1 LIKE 'word' OR column1 LIKE 'word %' OR column1 LIKE '% word' OR column1 LIKE ' word ' 才能找到完全匹配的单词 - 对于单词不匹配的条目,它仍然会失败只是用空格隔开。
    【解决方案2】:
    SELECT * FROM MyTable WHERE 
    Column1 LIKE '%word1%'
    AND Column1 LIKE '%word2%'
    AND Column1 LIKE  '%word3%'
    

    根据对问题的编辑将OR 更改为AND

    【讨论】:

    • 我需要所有单词以任意组合包含在结果中
    【解决方案3】:
    select * from table where name regexp '^word[1-3]$'
    

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

    【讨论】:

    • “regexp”是标准 SQL 吗?
    • 第二个查询,不应该引用这个词吗?
    • 这段代码似乎检查列是否等于三个词之一。问题是关于检查列 contains all 的三个词。
    • 嗨,这可能很好地解决问题......但如果你能编辑你的答案并提供一些关于它如何工作以及为什么工作的解释会很好: ) 不要忘记 - Stack Overflow 上有很多新手,他们可以从你的专业知识中学到一两件事 - 对你来说显而易见的事情对他们来说可能不是。
    【解决方案4】:

    请注意,如果您使用LIKE 来确定一个字符串是否是另一个字符串的子字符串,则必须对搜索字符串中的模式匹配字符进行转义。

    如果您的 SQL 方言支持CHARINDEX,那么使用它会容易得多:

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

    另外,请记住,这个和接受的答案中的方法仅涵盖子字符串匹配而不是单词匹配。因此,例如,字符串 'word1word2word3' 仍然会匹配。

    【讨论】:

    • 如果您的搜索词是一个变量而不是在搜索之前添加 '%' 字符,这似乎更容易
    • 在 Microsoft SQL 服务器和引擎中,我们应该使用 InStr() 而不是 CHARINDEX
    • @23W MS SQL 中没有 InStr
    • @ShaneBlake 而不是将 % 添加到变量中,只需将其添加到搜索中 '%'+var+'%' 是的,它更简洁而且非常丑陋,但可能比更改变量更好价值。
    【解决方案5】:

    功能

     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
    

    【讨论】:

    • 优秀!先生,如何开始学习这个功能?什么是碎片?你能告诉我关于这一行的伪代码吗? SUBSTRING(@str, start, CASE WHEN > 0 THEN stop - start ELSE 512 END) AS Data
    • 这一举动令人难以置信,我真的很嫉妒:( _________________________________________________________________________________ INNER JOIN (@FilterTable F1 ON T.Column1 LIKE '%' + F1.Data + '%' LEFT JOIN (@FilterTable F2 ON T.Column1 NOT LIKE '%' + F2.Data + '%'
    【解决方案6】:

    如果您使用的是 Oracle 数据库,那么您可以使用 contains 查询来实现此目的。包含查询比类似查询更快。

    如果你需要所有的话

    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
    

    在您的列中包含 CONTEXT 类型的需要索引。

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

    【讨论】:

    • @downvoters 评论表示答案有什么问题。同样的查询每天在我们的企业解决方案中运行超过 1000 次,没有任何问题:)
    • OP 没有指定使用哪个数据库,大家都认为是 Sql Server。但由于您在回复中指定了 Oracle,我不理解反对者。
    【解决方案7】:

    如果你只是想找到一个匹配。

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

    SQL 服务器:

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

    获得完全匹配。示例 (';a;ab;ac;',';b;') 将无法匹配。

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

    【讨论】:

    • 'INSTR' 不是可识别的内置函数名称。在我的 SQL Server 中。
    【解决方案8】:

    而不是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"'
    

    p.s.在使用包含关键字之前,您必须先在表格上启用全文搜索。更多详情,请看这里https://docs.microsoft.com/en-us/sql/relational-databases/search/get-started-with-full-text-search

    【讨论】:

      【解决方案9】:
      SELECT * FROM MyTable WHERE Column1 Like "*word*"
      

      这将显示column1 的部分值包含word 的所有记录。

      【讨论】:

        【解决方案10】:

        尝试在 MS SQL Server 的全文索引中使用“tesarus 搜索”。如果您有数百万条记录,这比在搜索中使用“%”要好得多。 tesarus 的内存消耗比其他的少。 尝试搜索此功能:)

        【讨论】:

          【解决方案11】:

          最好的方法是对表中的列进行全文索引 并使用包含而不是喜欢

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

          【讨论】:

            【解决方案12】:

            为什么不改用“in”呢?

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

            【讨论】:

            • 因为它不起作用。你真的试过了吗?
            • 我相信这只会返回完全匹配。
            • 我也误解了原来的问题:他们不想找到一个完全匹配的词,而是一个词是(可能)更大的字符串的一部分。对于更简单的“精确匹配”情况,只要单词在单引号之间(参见SQLfiddle
            【解决方案13】:
            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
            

            【讨论】:

            • 感谢您提供此代码 sn-p,它可能会提供一些有限的即时帮助。 proper explanation would greatly improve its long-term value 通过展示为什么这是一个很好的解决问题的方法,并将使其对未来有其他类似问题的读者更有用。请edit您的回答添加一些解释,包括您所做的假设。
            【解决方案14】:

            实现问题中提到的最简单的方法之一是将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”的接近度返回每个文档的排名。例如,如果一个文档包含句子“The word1 is word2 and word3”,那么它的排名就会很高,因为这些词比其他文档中的词更接近。

            我想补充的另一件事是,我们还可以使用proximity_term 来查找单词在列短语内的特定距离内的列。

            【讨论】:

            • 很好的答案,但请注意,如果表格或视图没有全文索引,这将不起作用。 Contains() 会抛出错误:Cannot use a CONTAINS or FREETEXT predicate on table or indexed view 'TABLENAME' because it is not full-text indexed.
            【解决方案15】:

            如果使用的话,最好借助 sql server 全文搜索来完成。 但是,如果由于某种原因您无法在您的数据库上工作,这里有一个性能密集型解决方案:-

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

            【讨论】:

              猜你喜欢
              • 2018-03-03
              • 2014-12-17
              • 1970-01-01
              • 1970-01-01
              • 2013-01-01
              • 2012-12-26
              • 2014-10-11
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多