SQL 服务器:使用字符串的所有单词作为单独的 LIKE 参数(并且所有单词都应该匹配)

SQL Server: use all the words of a string as separate LIKE parameters (and all words should match)

我有一个包含一定数量单词的字符串(它可能从 1 到很多不等),我需要找到 table 的记录,其中包含所有这些单词,顺序不限。

例如,假设我的输入字符串是 'yellow blue red' 并且我有一个包含以下记录的 table:

1 yellow brown white
2 red blue yellow
3 black blue red

查询应该return记录2.

我知道基本的方法应该是这样的:

select * from mytable where colors like '%yellow%' and colors like '%blue%' and colors like '%red%'

但是我无法弄清楚如何将字符串的单词变成单独的类似参数。

我有这段代码将字符串的单词拆分为 table,但现在我卡住了:

DECLARE @mystring varchar(max) = 'yellow blue red';
DECLARE @terms TABLE (term varchar(max));
INSERT INTO @terms
SELECT Split.a.value('.', 'NVARCHAR(MAX)') term FROM (SELECT CAST('<X>'+REPLACE(@mystring, ' ', '</X><X>')+'</X>' AS XML) AS String) AS A CROSS APPLY String.nodes('/X') AS Split(a)
SELECT * FROM @terms

有什么想法吗?

首先,将 XML 垃圾放入函数中:

CREATE FUNCTION dbo.SplitThem
(
   @List       NVARCHAR(MAX),
   @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
   RETURN ( SELECT Item = y.i.value(N'(./text())[1]', N'nvarchar(4000)')
      FROM ( SELECT x = CONVERT(XML, '<i>' 
          + REPLACE(@List, @Delimiter, '</i><i>') 
          + '</i>').query('.')
      ) AS a CROSS APPLY x.nodes('i') AS y(i));

现在您可以提取 table 中的单词,将它们连接到输入字符串中的单词,并丢弃任何不具有相同计数的单词:

DECLARE @mystring varchar(max) = 'red yellow blue';

;WITH src AS 
(
  SELECT t.id, t.colors, fc = f.c, tc = COUNT(t.id)
  FROM dbo.mytable AS t
  CROSS APPLY dbo.SplitThem(t.colors, ' ') AS s
  INNER JOIN (SELECT Item, c = COUNT(*) OVER()
    FROM dbo.SplitThem(@mystring, ' ')) AS f
  ON s.Item = f.Item
  GROUP BY t.id, t.colors, f.c
)
SELECT * FROM src
WHERE fc = tc;

输出:

id colors fc tc
2 red blue yellow 3 3

这忽略了任何一方重复的可能性,并忽略了更大的首要问题,即这是存储事物集的最不理想的方式。你有一个关系数据库,使用它!你肯定不认为这个问题上的标签作为文字字符串存储在某处

string sql-server-2012 sql-like

当然不是,这些 question:tag 关系存储在关系 table 中。拆弦是为了鸟儿和那些有各种CPU和空闲时间的人。

如果您将分隔列表存储在单个列中,那么您确实需要将其规范化为单独的 table。

但假设您实际上只想进行多次 free-form LIKE 比较,您可以针对值列表进行比较:

select *
from mytable t
where not exists (select 1
    from (values
        ('%yellow%'),
        ('%blue%'),
        ('%red%')
    ) v(search)
    where t.colors not like v.search
);

理想情况下,您应该将这些值作为 Table 值参数传递,然后将其放入查询中

select *
from mytable t
where not exists (select 1
    from @tmp v
    where t.colors not like v.search
);

如果您想模拟 OR 语义而不是 AND,请将 not exists 更改为 exists 并将 not like 更改为 like