拆分可变长度的字符串,可变分隔符

Split string of variable length, variable delimiters

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

我有一个长度可变的字符串,分隔符有时可以是两个破折号,有时可以只是一个。假设在我的 table 中,我要分解的数据存储在这样的单个列中:

+ -----------------------------------------+
| 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 上找到的示例似乎比这更简单。

有点破解,但它有效:

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;

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

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:

location | category         | sub_category   
:------- | :--------------- | :--------------
Zoo      | Animal           | Lions          
Zoo      | Personnel        | null           
Zoo      | Operating Costs  | Power / Cooling

您还可以使用字符串拆分器。这是适用于您的版本的优秀版本。 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

这将 return:

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

您用 [sql-server-2017] 标记了它。这意味着,您可以使用 JSON-support(这是在 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 子句一起用于 return 每个片段的位置和固定类型。