【问题标题】:Split string of variable length, variable delimiters拆分可变长度的字符串,可变分隔符
【发布时间】:2020-03-11 00:25:53
【问题描述】:

阅读here提出的问题,但我的问题有点复杂。

我有一个长度可变的字符串,分隔符有时可以是两个破折号,有时也可以只有一个。假设在我的表中,我想要分解的数据存储在一个单独的列中,如下所示:

+ -----------------------------------------+
| Category                                 |
+------------------------------------------+
| Zoo - Animals - Lions                    |
| Zoo - Personnel                          |
| Zoo - Operating Costs - Power / Cooling  |
+------------------------------------------+

但我想将该单列中的数据字符串输出到三个单独的列中,如下所示:

+----------+--------------------+-----------------+
| Location | Category           | Sub-Category    |
+----------+--------------------+-----------------+
| Zoo      | Animals            | Lions           |
| Zoo      | Personnel          |                 |
| Zoo      | Operating Costs    | Power / Cooling |
+----------+--------------------+-----------------+

希望得到一些指导,因为我在 Google 上找到的示例似乎比这更简单。

【问题讨论】:

  • 位置 - 类别 - 子类别总是顺序吗?
  • 以哪种语言输出它们?
  • @squillman 是的,它将始终按此顺序排列。总会有一个位置和类别,但子类别是可选的,因此有时可以为 NULL。此外,“动物园”只是地点的一个例子;位置可以是可变长度,最多 255 个字符。与类别和子类别相同。
  • @JQSOFT 是SQL查询的结果,表中的列名是Category。
  • 您的数据库在什么兼容级别下运行?

标签: sql-server string tsql sql-server-2017


【解决方案1】:

您还可以使用字符串拆分器。这是一个适用于您的版本的优秀版本。 DelimitedSplit8K

现在我们需要一些样本数据。

declare @Something table
(
    Category varchar(100)
)

insert @Something values
('Zoo - Animals - Lions')
, ('Zoo - Personnel')
, ('Zoo - Operating Costs - Power / Cooling')

现在我们有了一个函数和示例数据,这个代码非常漂亮和整洁。

select s.Category
    , Location = max(case when x.ItemNumber = 1 then Item end)
    , Category = max(case when x.ItemNumber = 2 then Item end)
    , SubCategory = max(case when x.ItemNumber = 3 then Item end)
from @Something s
cross apply dbo.DelimitedSplit8K(s.Category, '-') x
group by s.Category

这将返回:

Category                                |Location|Category       |SubCategory
Zoo - Animals - Lions                   |Zoo     |Animals        |Lions
Zoo - Operating Costs - Power / Cooling |Zoo     |Operating Costs|Power / Cooling
Zoo - Personnel                         |Zoo     |Personnel      |NULL

【讨论】:

  • 不幸的是,这似乎不适用于我的数据库,可能是因为它是 2012 年,尽管它说的是 2017 年。DBA 基于公司治理进行控制。
  • 您阅读链接的文章了吗?您需要具有 DelimitedSplit8K 功能。是的,它在 2012 年运行得很好,可以追溯到 sql server 2005,当时他们首次引入 ctes 和表值函数。
  • 幸运的是,我能够创建该功能,而且效果很好!非常感谢。
【解决方案2】:

这是一个仅使用字符串函数的解决方案:

select 
    left(
        category, 
        charindex('-', category) - 2
    ) location,
    substring(
        category, 
        charindex('-', category) + 2, 
        len(category) - charindex('-', category, charindex('-', category) + 1)
    ) category,
    case when charindex('-', category, charindex('-', category) + 1) > 0 
        then right(category, charindex('-', reverse(category)) - 2) 
    end sub_category
from t

Demo on DB Fiddle

位置 |类别 |子类别 :------- | :--------------- | :-------------- 动物园 |动物 |狮子 动物园 |人事 | 动物园 |运营成本 |电源/冷却

【讨论】:

  • 谢谢,这是最有可能走的路线,但我得到了一些不一致的结果。有时数据会很好地拆分,有些则不会,我不知道为什么。
【解决方案3】:

您已使用 [sql-server-2017] 标记此内容。这意味着,您可以使用 JSON 支持(这是在 v2016 中引入的)。

目前 JSON 是最佳的内置位置和类型安全字符串拆分方法:

一个模型,用来模拟你的问题

DECLARE @mockup TABLE (ID INT IDENTITY, Category VARCHAR(MAX))

INSERT INTO @mockup (Category)
VALUES ('Zoo - Animals - Lions')
      ,('Zoo - Personnel')
      ,('Zoo - Operating Costs - Power / Cooling');

--查询

SELECT t.ID
      ,A.[Location] 
      ,A.Category 
      ,A.subCategory 
FROM @mockup t
CROSS APPLY OPENJSON(CONCAT('[["',REPLACE(t.Category,'-','","'),'"]]')) 
WITH ([Location] VARCHAR(MAX) '$[0]'
     ,Category VARCHAR(MAX) '$[1]'
     ,SubCategory VARCHAR(MAX) '$[2]') A;

结果(可能需要一些TRIM()ing)

ID  Location    Category            subCategory
1   Zoo         Animals             Lions
2   Zoo         Personnel           NULL
3   Zoo         Operating Costs     Power / Cooling

简而言之:

我们使用一些简单的字符串操作将您的字符串转换为 JSON 数组:

a b c    => [["a","b","c"]]

现在我们可以使用OPENJSON()WITH 子句以固定类型返回每个片段的位置。

【讨论】:

  • 是的,我在 2017 年标记了它,可能不应该将兼容性级别设置为 110。
【解决方案4】:

有点破解,但它有效:

DECLARE @t TABLE (Category VARCHAR(255))

INSERT @t (Category)
VALUES ('Zoo - Animals - Lions'),('Zoo - Personnel'),('Zoo - Operating Costs - Power / Cooling')

;WITH split_vals AS (
    SELECT Category AS Cat,TRIM(Value) AS Value,ROW_NUMBER() OVER (PARTITION BY Category ORDER BY Category) AS RowNum
    FROM @t
    CROSS APPLY STRING_SPLIT(Category,'-')
), cols AS (
    SELECT
        Cat,
        CASE WHEN RowNum = 1 THEN Value END AS Location,
        CASE WHEN RowNum = 2 THEN Value END AS Category,
        CASE WHEN RowNum = 3 THEN Value END AS [Sub-Category]
    FROM split_vals
)
SELECT STRING_AGG(Location, '') AS Location,
       STRING_AGG(Category, '') AS Category,
       STRING_AGG([Sub-Category], '') AS [Sub-Category]
FROM cols
GROUP BY Cat;

【讨论】:

  • 得到这个:'TRIM' 不是一个公认的内置函数名。 “STRING_AGG”不是可识别的内置函数名称。
  • 您使用的是 SQL 2017?您的数据库设置为什么兼容性级别?
  • 110...我无法控制,我在公司环境中。
  • Oy... 110 是 SQL 2012 兼容性。你应该和你的 DBA 聊一聊。
  • @squillman STRING_SPLIT() 不保证以与传入片段相同的顺序返回片段。这可能适用于所有内部测试,通过任何验证并且仍然在生产中中断(绝对愚蠢的错误,这让你发疯)。我刚刚使用 JSON 放置了一个带有位置和类型安全拆分器的答案(但这也不适用于兼容性 110 :-()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-17
  • 1970-01-01
相关资源
最近更新 更多