提问人:Glen Owen 提问时间:11/13/2023 最后编辑:halferGlen Owen 更新时间:11/14/2023 访问量:89
有没有办法通过使用逗号作为分隔符来选择 SQL 查询中字符串的自定义部分?
Is there a way to select custom part of string in a SQL query by using commas as separators?
问:
问题:
我正在尝试从多个表(表 A 和 B)中进行选择,但这主要是关于我从表 B 中选择的唯一列。此列中的值包含字符串的 Array。
目标是摆脱任何 [],然后查看字符串。字符串有两种主要类型,一种仅包含文本,不以逗号分隔,另一种则具有文本和逗号的一些组合。
需要什么:
对于字符串不包含任何逗号的条件,则只需返回文本值。对于字符串包含单个或多个逗号的其他情况,返回第一个逗号之后的值。如果有超过 2 个或更多逗号,则需要在第一个逗号之后和第二个逗号之前返回字符串。
请查看代码以获取进一步的解释:
DROP TABLE IF EXISTS
\[dbo\].\[multi_string_db\]
GO
CREATE TABLE \[dbo\].\[multi_string_db\] (
multi_string nvarchar (256) NULL
)
INSERT INTO \[dbo\].\[multi_string_db\] ( multi_string)
VALUES ('\[Additional time required, Time requested\]')
, ('\[Additional time required, Document requested\]')
, ('\[Additional time required, Missing documents - Personal, Other\]')
, ('\[Additional time required, Missing documents - Personal\]')
, ('Additional time required')
, ('Document Requested')
, ('Missing FPA/evidence')
, ('Missing documents - Office')
, ('Missing documents - Personal')
, ('Other')
, ('Referred to Decision Maker Team')
, ('Target date error')
预期结果:
Desired_Output |
---|
请求的时间 |
要求提供的文件 |
缺少文档 - 个人 |
缺少文档 - 个人 |
需要额外的时间 |
要求的文件 |
缺少 FPA/证据 |
缺少文档 - Office |
缺少文档 - 个人 |
其他 |
转介给决策者团队 |
目标日期错误 |
到目前为止,我尝试过:
SELECT
LTRIM(RTRIM(
CASE
WHEN CHARINDEX('[', multi_string) > 0 AND CHARINDEX(']', multi_string) > 0
THEN
CASE
WHEN CHARINDEX(',', multi_string) > 0 AND CHARINDEX(',', multi_string, CHARINDEX(',', multi_string) + 1) > 0
THEN SUBSTRING(multi_string, CHARINDEX(',', multi_string) + 1, CHARINDEX(',', multi_string, CHARINDEX(',', multi_string) + 1) - CHARINDEX(',', multi_string) - 1)
WHEN CHARINDEX(',', multi_string) > 0
THEN SUBSTRING(multi_string, CHARINDEX(',', multi_string) + 1, CHARINDEX(']', multi_string) - CHARINDEX(',', multi_string) - 1)
ELSE SUBSTRING(multi_string, CHARINDEX('[', multi_string) + 1, CHARINDEX(']', multi_string) - CHARINDEX('[', multi_string) - 1)
END
ELSE
LTRIM(RTRIM(multi_string)) -- If no brackets, return the original string
END
)) AS Result
FROM dbo.[multi_string_db]
问题(局限性):
该逻辑工作正常,但仅当存在 [ ] 时,当前数据肯定是这种情况,但是如果有新数据,则此逻辑将失败。
例如:如果我们有以下字符串“需要一段时间,收到的文档 - 未选中,杂项”,则由于字符串周围缺少 [ ],逻辑失败。
我怎样才能克服这个限制?
答:
请尝试以下基于通过 XML 和 XQuery 进行标记化的解决方案。它将从 SQL Server 2017 开始工作。
无需通过 endless 、 、 和其他函数进行任何字符串解析。CHARINDEX()
SUBSTRING()
值得注意的几点:
- 第一种是通过XML对字符串进行标记。
CROSS APPLY
- 第二个是计算用逗号分隔的标记字符串中的 # 个标记。
CROSS APPLY
SELECT
子句有一个简单的语句,用于根据令牌计数在标记字符串中检索正确的标记。CASE
SQL算法
-- DDL and sample data population, start
DECLARE @tbl TABLE (ID INT IDENTITY PRIMARY KEY, multi_string NVARCHAR(256));
INSERT INTO @tbl( multi_string) VALUES
('[Additional time required, Time requested]'),
('[Additional time required, Document requested]'),
('[Additional time required, Missing documents - Personal, Other]'),
('[Additional time required, Missing documents - Personal]'),
('Additional time required'),
('Document Requested'),
('Missing FPA/evidence'),
('Missing documents - Office'),
('Missing documents - Personal'),
('Other'),
('Referred to Decision Maker Team'),
('Target date error');
-- DDL and sample data population, end
DECLARE @separator CHAR(1) = ',';
SELECT ID, t.multi_string --, c, t2.token_count -- uncomment to see XML and the token count
, result = CASE
WHEN t2.token_count = 1 THEN TRIM(c.value('(/root/r/text())[1]', 'NVARCHAR(100)'))
WHEN t2.token_count > 1 THEN TRIM(c.value('(/root/r[2]/text())[1]', 'NVARCHAR(100)'))
ELSE 'Alarm! Some edge case.'
END
FROM @tbl AS t
CROSS APPLY (SELECT TRY_CAST('<root><r><![CDATA[' +
REPLACE(TRIM('[]' FROM multi_string), @separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)) AS t1(c)
CROSS APPLY (SELECT c.value('count(/root/r)', 'INT')) AS t2(token_count);
输出
multi_string | 结果 |
---|---|
[需要额外的时间,要求的时间] | 请求的时间 |
[需要额外的时间,要求提供文件] | 要求提供的文件 |
[需要额外时间,缺少文件 - 个人、其他] | 缺少文档 - 个人 |
[需要额外时间,缺少文件 - 个人] | 缺少文档 - 个人 |
需要额外的时间 | 需要额外的时间 |
要求的文件 | 要求的文件 |
缺少 FPA/证据 | 缺少 FPA/证据 |
缺少文档 - Office | 缺少文档 - Office |
缺少文档 - 个人 | 缺少文档 - 个人 |
其他 | 其他 |
转介给决策者团队 | 转介给决策者团队 |
目标日期错误 | 目标日期错误 |
在此示例中,我添加了您的“问题”数据
DROP TABLE IF EXISTS #multi_string_db
CREATE TABLE #multi_string_db (
multi_string nvarchar (256) NULL
)
INSERT INTO #multi_string_db ( multi_string)
VALUES ('[Additional time required, Time requested]')
, ('[Additional time required, Document requested]')
, ('[Additional time required, Missing documents - Personal, Other]')
, ('[Additional time required, Missing documents - Personal]')
, ('Additional time required')
, ('Document Requested')
, ('Missing FPA/evidence')
, ('Missing documents - Office')
, ('Missing documents - Personal')
, ('Other')
, ('Referred to Decision Maker Team')
, ('Target date error')
, ('Some time required, Received documents - Unchecked, Misc')
SELECT
multi_string,
Result =
REPLACE(REPLACE(
LTRIM(RTRIM(
SUBSTRING( multi_string
, CHARINDEX(',', multi_string)+1
, IIF(CHARINDEX(',', multi_string, CHARINDEX(',', multi_string)+1) = 0
, LEN(multi_string) - CHARINDEX(',', multi_string)
, CHARINDEX(',', multi_string, CHARINDEX(',', multi_string)+1) - CHARINDEX(',', multi_string) -1
)
)
))
, '[', '')
, ']', '')
FROM #multi_string_db
如果忽略 and 以 d 开头,则只找到结果所需的部分,则修剪其结果并替换任何剩余的 和 。[
]
[
]
如果您使用的是 SQL Server 2022,则可以使用序号选项(我认为)并提取第 1 个或第 2 个序号。那真的很简单。string_split()
评论
您可以在子选择中使用并筛选 。添加首先检查语法并去掉括号的逻辑,最终会得到以下内容:STRING_SPLIT()
ordinal = 2
[list, ...]
SELECT M.multi_string,
CASE WHEN M.multi_string LIKE '\[%,%\]' ESCAPE '\'
THEN (
SELECT TRIM(S.value)
FROM (SELECT SUBSTRING(M.multi_string, 2, LEN(M.multi_string)-2) AS Trimmed) T
CROSS APPLY STRING_SPLIT(T.Trimmed, ',', 1) S
WHERE S.ordinal = 2
)
ELSE M.multi_string
END AS Result
FROM multi_string_db M
以下变体还处理单值括号列表。在这种情况下,返回第一个(单个)值。
SELECT M.multi_string,
CASE WHEN M.multi_string LIKE '\[%\]' ESCAPE '\'
THEN (
SELECT TOP 1 TRIM(S.value)
FROM (SELECT SUBSTRING(M.multi_string, 2, LEN(M.multi_string)-2) AS Trimmed) T
CROSS APPLY STRING_SPLIT(T.Trimmed, ',', 1) S
WHERE S.ordinal <= 2
ORDER BY S.ordinal DESC
)
ELSE M.multi_string
END AS Result
FROM multi_string_db M
请参阅此 db<>fiddle 以获取工作演示(包含一些额外的测试用例)。
评论
在第一步中,我过滤掉 [ ] 并将字符串保存在新的临时表中。
SELECT
CASE LEFT (cd.multi_string,1)
WHEN '['
THEN SUBSTRING (cd.multi_string, 2, LEN(cd.multi_string) -2)
ELSE cd.multi_string
END AS multi_string
FROM multi_string_db AS cd
然后使用提供的逻辑STRING_SPLIT
SELECT
split_str.value
FROM multi_string_db AS cd
CROSS APPLY String_Split(multi_string, ',') AS split_str
WHERE multi_string IS NOT NULL;
它确实可以在 SQL Server 和 AWS 等其他平台上工作,这很方便,但我相信还有改进的余地。出于某种原因,ESCAPE 不适用于我在 AWS 上的 SQL 版本,并且由于我不确定全局方法,因此我应用了此逻辑。因此,这不是一个简单/干净的方法,如果有更好的方法,将不胜感激。这样做的另一个缺点是,对于任何拆分字符串,它都会给我一个不需要的空白。例如,[需要额外时间,请求文档] 返回“请求的文档”而不是“请求的文档”。其中“D”前有空格
任何帮助都是值得赞赏的。
评论