有没有办法通过使用逗号作为分隔符来选择 SQL 查询中字符串的自定义部分?

Is there a way to select custom part of string in a SQL query by using commas as separators?

提问人:Glen Owen 提问时间:11/13/2023 最后编辑:halferGlen Owen 更新时间:11/14/2023 访问量:89

问:

问题:

我正在尝试从多个表(表 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]

问题(局限性):

该逻辑工作正常,但仅当存在 [ ] 时,当前数据肯定是这种情况,但是如果有新数据,则此逻辑将失败。

例如:如果我们有以下字符串“需要一段时间,收到的文档 - 未选中,杂项”,则由于字符串周围缺少 [ ],逻辑失败。

我怎样才能克服这个限制?

sql-server azure 子字符串 charindex

评论

0赞 siggemannen 11/13/2023
代码中的斜杠看起来不对,你能修复它吗
0赞 Alan Schofield 11/13/2023
您使用的是哪个版本的 SQL Server?

答:

-1赞 Yitzhak Khabinsky 11/13/2023 #1

请尝试以下基于通过 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
缺少文档 - 个人 缺少文档 - 个人
其他 其他
转介给决策者团队 转介给决策者团队
目标日期错误 目标日期错误
0赞 Alan Schofield 11/13/2023 #2

在此示例中,我添加了您的“问题”数据

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

评论

0赞 T N 11/13/2023
建议的调整:为避免重复的子表达式,可用于计算一次中间值,并根据需要多次引用它们。请参阅此 db<>fiddle,其中显示了两种形式。CROSS APPLY
0赞 Alan Schofield 11/13/2023
同意,使用这种方法要干净得多。
0赞 Glen Owen 11/14/2023
这个解决方案效果很好,可以得到我需要的结果。非常感谢MS SQL Server上的帮助。这是否也适用于其他平台(如 Azure),或者这是否仅特定于 SQL Server?
0赞 T N 11/13/2023 #3

您可以在子选择中使用并筛选 。添加首先检查语法并去掉括号的逻辑,最终会得到以下内容: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 以获取工作演示(包含一些额外的测试用例)。

评论

0赞 Glen Owen 11/14/2023
感谢您为我指出STRING_SPLIT方向,这在应用于 MS SQL Server 时对我的情况很有魅力。但是,由于某种原因,我无法在其他 SQL 上复制它,例如 AWS,因为它无法识别 ESCAPE () 函数。为了克服这个问题,我使用了 2 步过程,即创建一个临时表,其中包含没有 [ ** ]** 的数据,然后将其与 STRING_SPLIT () 一起使用。由于字符限制,我将在下面将其作为单独的答案发布。分享想法,让我知道这是否可以即兴创作?
0赞 Glen Owen 11/14/2023 #4

在第一步中,我过滤掉 [ ] 并将字符串保存在新的临时表中。

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”前有空格

任何帮助都是值得赞赏的。