【问题标题】:Split column value into separate columns based on length根据长度将列值拆分为单独的列
【发布时间】:2022-01-19 07:26:54
【问题描述】:

我在一列中有多个逗号分隔的值,大小最多为 20000 个字符,我想将该列拆分为一列,但它基于字符值 2000(就像一个新列将需要 2000 个字符和如果长度大于 2000,那么它将像这样在第二列中。

当它的逗号分隔值进入第一个新列时,它应该是有意义的,就像它应该基于 , 并且最多只能有 2000 个字符。

我只从行级值到列级,但它应该是 2000 个字符并且基于逗号

您能帮我解决这个问题吗?

【问题讨论】:

  • 一开始就不要以这种方式存储此类数据。这打破了最基本的设计规则,意味着您不能使用任何索引来加快查询速度。别介意查询变得非常困难。您不妨将数据存储到文件中并将其拆分到内存中。无论如何,所有受支持的 SQL Server 版本都有STRING_SPLIT
  • 如果没有数据示例以及您想要做什么,无论如何都无法提供帮助,除非指向STRING_SPLIT。并重复在列中存储逗号分隔或固定宽度的文件是一个非常糟糕的主意。如果要查询数据,将数据插入数据库之前对其进行清理和拆分。 bcpBULK INSERT 可以处理 CSV 和固定宽度文件
  • 可以查找2000年后第一个,的索引,就是2000 + CHARINDEX(',',RIGHT(YOUR_COLUMN,LEN(YOUR_COLUMN) - 2000))
  • @Panagiotis Kanavos 我附上了预期的输出,但它的拆分仅基于逗号。我想根据 , 和 customDimesion1 中的 2000 个字符来拆分它,然后像这样
  • @siddhesh 另一方面,看起来这既不是 CSV 也不是固定表,而是根本没有表。该图片中的每个值似乎既是 ID 也是描述。显然,您只需要每个字段中的 ID,并且应该在查找表中调用描述。 Dimension 这个名字暗示了这些东西是星型模式中的不同维度。这比仅仅分割每 2K 个字符和每一个逗号和__ 复杂得多。

标签: sql sql-server string split string-concatenation


【解决方案1】:

siddesh,虽然这个问题缺乏我想指出一些事情并帮助你的一切(因为你是一个没有经验的 SO 用户):

首先我设置了一个最小的可重现示例。下次就交给你了。
我将从一个已声明的表开始,其中插入了一些行。
我们在 SO 上可以将其复制粘贴到我们的环境中,这样很容易回答。

DECLARE @tbl TABLE(ID INT IDENTITY, YourCSVString VARCHAR(MAX));
INSERT INTO @tbl VALUES('1 this is long text, 2 some second fragment, 3 third fragment, 4 adfjksdahfljsadhfjhadlfhasdjks alsdjfsadhf k, 5 halksjfh asdkf ')
                      ,('1 this is other long text, 2 some second fragment to show that this works with tabular data, 3 again a third fragment, 4 adfjksdahfljsadhfjhadlfhasdjks alsdjfsadhf k, 5 halksjfh asdkf ');

--这是你实际需要的:

SELECT fkID = t.ID
      ,B.fragmentPosition
      ,B.fragmentContent 
      ,C.framgentLength
FROM @tbl t
CROSS APPLY OPENJSON(CONCAT(N'["',REPLACE(t.YourCSVString,N',','","'),'"]')) A
CROSS APPLY(VALUES(A.[key],TRIM(A.[value]))) B(fragmentPosition,fragmentContent)
CROSS APPLY(VALUES(LEN(B.fragmentContent))) C(framgentLength);

结果应该存储在物理表中,其中fkID 指向原始行的ID,fragmentPosition 存储顺序。 fkIDfragmentPosition 应该是组合的唯一键。

如果你真的想做,你在问题中的建议(不推荐!)你可以尝试一下:

DECLARE @maxPerColumn INT=75;  --You can set the portion's max size, in your case 2000.

WITH cte AS
(
    SELECT fkID = t.ID
          ,B.fragmentPosition
          ,B.fragmentContent 
          ,C.framgentLength
    FROM @tbl t
    CROSS APPLY OPENJSON(CONCAT(N'["',REPLACE(t.YourCSVString,N',','","'),'"]')) A
    CROSS APPLY(VALUES(A.[key],TRIM(A.[value]))) B(fragmentPosition,fragmentContent)
    CROSS APPLY(VALUES(LEN(B.fragmentContent))) C(framgentLength)
)
,recCTE AS
(
    SELECT * 
          ,countPerColumn = 1
          ,columnCounter = 1
          ,sumLength = LEN(fragmentContent)
          ,growingString = CAST(fragmentContent AS NVARCHAR(MAX)) 
    FROM cte WHERE fragmentPosition=0

    UNION ALL
    SELECT r.fkID
          ,cte.fragmentPosition
          ,cte.fragmentContent
          ,cte.framgentLength
          ,CASE WHEN A.newSumLength>@maxPerColumn THEN 1 ELSE r.countPerColumn + 1 END
          ,r.columnCounter + CASE WHEN A.newSumLength>@maxPerColumn THEN 1 ELSE 0 END
          ,CASE WHEN A.newSumLength>@maxPerColumn THEN LEN(cte.fragmentContent) ELSE newSumLength END
          ,CASE WHEN A.newSumLength>@maxPerColumn THEN cte.fragmentContent ELSE CONCAT(r.growingString,N', ',cte.fragmentContent) END
    FROM cte
    INNER JOIN recCTE r ON r.fkID=cte.fkID AND r.fragmentPosition+1=cte.fragmentPosition 
    CROSS APPLY(VALUES(r.sumLength+LEN(cte.fragmentContent))) A(newSumLength)
)
SELECT TOP 1 WITH TIES 
       fkID
      ,growingString
      ,LEN(growingString)
FROM recCTE 
ORDER BY ROW_NUMBER() OVER(PARTITION BY fkID,columnCounter ORDER BY countPerColumn DESC );

结果

fkID    pos Content
1       2   1 this is long text, 2 some second fragment, 3 third fragment
1       4   4 adfjksdahfljsadhfjhadlfhasdjks alsdjfsadhf k, 5 halksjfh asdkf
2       0   1 this is other long text
2       1   2 some second fragment to show that this works with tabular data
2       3   3 again a third fragment, 4 adfjksdahfljsadhfjhadlfhasdjks alsdjfsadhf k
2       4   5 halksjfh asdkf

简而言之:

  • 第一个 cte 进行拆分(如上)
  • 递归 cte 将遍历字符串并发挥作用。
  • 最后的SELECT 使用了一个带有TOP 1 WITH TIESORDER BY ROW_NUMBER() OVER(...) 的hack。这将只返回最高的中间结果。

提示:不要这样做...

更新

只是为了好玩:

你可以用这个替换最后的SELECT

,getPortions AS
(
    SELECT TOP 1 WITH TIES 
           fkID
          ,fragmentPosition
          ,growingString
          ,LEN(growingString) portionLength
    FROM recCTE 
    ORDER BY ROW_NUMBER() OVER(PARTITION BY fkID,columnCounter ORDER BY countPerColumn DESC )
)
SELECT p.*
FROM
(
    SELECT fkID
          ,CONCAT(N'col',ROW_NUMBER() OVER(PARTITION BY fkID ORDER BY fragmentPosition)) AS ColumnName
          ,growingString
    FROM getPortions
) t
PIVOT(MAX(growingString) FOR ColumnName IN(col1,col2,col3,col4,col5)) p;

这将完全返回您所要求的内容。

但是 - 如前所述 - 这违反了最佳实践的所有规则...

【讨论】:

    猜你喜欢
    • 2016-05-02
    • 2015-01-15
    • 2021-08-27
    • 1970-01-01
    • 2019-11-05
    • 1970-01-01
    • 1970-01-01
    • 2020-11-08
    • 2021-07-24
    相关资源
    最近更新 更多