正如 Larnu 所建议的,这里真正的解决方法是修复设计。您应该回到所有者那里并提醒他们数据库是用于存储关系数据的;如果您将多个“事实”塞入单个列中,那么您不妨使用平面文件。例外情况是,如果您为应用程序存储一个逗号分隔的列表,并且只有应用程序负责组装和分解该集合。
无论如何,鉴于您可能对此感到困惑(假设ColumnA 限制为 128 个字符):
CREATE TABLE dbo.BadDesign
(
ColumnA nvarchar(128),
ColumnB nvarchar(max)
);
INSERT dbo.BadDesign(ColumnA, ColumnB) VALUES
(N'[a]', N'[a;b;c;d]'), -- only match
(N'[p]', N'[q;r;s]'),
(N'[h]', N'[hi;j;k]');
您可以在this db<>fiddle看到依次演示的以下解决方案:
嵌套替换
在过去(SQL Server 2017)之前,我们会执行嵌套的REPLACE() 调用来去掉方括号并用分隔符替换字符串的每一端:
-- All versions
SELECT ColumnA, ColumnB
FROM dbo.BadDesign
WHERE REPLACE(REPLACE(ColumnB, N'[',N';'),N']', N';')
LIKE N'%' + REPLACE(REPLACE(ColumnA ,N'[',N';'),N']', N';') + N'%';
毛,但结果:
| ColumnA |
ColumnB |
| [a] |
[a;b;c;d] |
我们不能在 SQL Server 2017 之前的版本上使用 TRIM(),但我在下面解释了为什么我们不想在现代版本上使用该功能。
OpenJson
在 2016+ 中,我们可以在对字符串进行一些操作后使用OPENJSON。在这里,我使用了 PARSENAME() 技巧,只有在 ColumnA this db<>fiddle 中展示了其他解决方法:
SELECT b.ColumnA, b.ColumnB
FROM dbo.BadDesign AS b
CROSS APPLY OPENJSON(REPLACE(REPLACE(REPLACE(
b.ColumnB, N'[', N'["'), N']', N'"]"'), N';', N'","')) AS j
WHERE j.value = PARSENAME(b.ColumnA, 1);
结果:
| ColumnA |
ColumnB |
| [a] |
[a;b;c;d] |
翻译
在 SQL Server 2017 中,使用TRANSLATE() 可以稍微少一些:
-- SQL Server 2017+
SELECT ColumnA, ColumnB
FROM dbo.BadDesign
WHERE TRANSLATE(ColumnB, N'[]',N';;')
LIKE N'%' + TRANSLATE(ColumnA, N'[]',N';;') + N'%';
| ColumnA |
ColumnB |
| [a] |
[a;b;c;d] |
我们不想在这里使用TRIM(),因为我们不只是想删除封闭的方括号;我们需要分隔符,因此无论 B 在字符串中的哪个位置,我们都可以始终将 A 与 B 进行比较。无需替换或翻译周围的分隔符,we could get inaccurate results if the match is at the beginning or end of the multi-value string。
分割函数
或者,您可以在 SQL Server 2016+ 上创建此函数(或者在早期版本中不使用 STRING_SPLIT() 的更混乱的函数 - 正如 Smor 指出的那样,搜索会出现数百个):
CREATE FUNCTION dbo.SplitAndClean(@s nvarchar(max))
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT value
FROM STRING_SPLIT
(
-- if 2016:
REPLACE(REPLACE(@s, N'[',N';'),N']', N';'),
-- if 2017+, TRANSLATE() is slightly cleaner:
/* TRANSLATE(@s, N'[]',N';;'), */
N';'
)
WHERE value > N''
);
那么你可以说:
SELECT bd.ColumnA, bd.ColumnB
FROM dbo.BadDesign AS bd
CROSS APPLY dbo.SplitAndClean(bd.ColumnA) AS a
CROSS APPLY dbo.SplitAndClean(bd.ColumnB) AS b
WHERE a.value = b.value;
| ColumnA |
ColumnB |
| [a] |
[a;b;c;d] |
但最后……
...这些都是掩盖不良设计的粗俗“解决方案”,您真的应该让他们重新考虑他们如何使用数据库。
我知道许多商店不能只切换到passing sets between the app and the database using TVPs,因为一些客户提供商和 ORM 还没有十多年的时间赶上这趟火车。如果不能使用 TVP 或者无法更改 app,至少应该考虑截取 app 传递过来的逗号分隔列表,并使用SPLIT_STRING() 之类的方式将其拆分。然后,您可以以关系方式存储值,让数据库执行数据库的设计任务,而不受应用程序限制的束缚。