递归自联接文件数据

时间:2018-04-26 14:29:04

标签: sql sql-server database sql-server-2016

我知道有很多关于递归自联接的问题,但是它们主要是在分层数据结构中,如下所示:

 ID   |   Value    | Parent id
 -----------------------------

但我想知道是否有办法在特定情况下执行此操作,我不一定拥有父ID。我最初加载文件时,我的数据将如下所示。

 ID   |  Line            | 
 -------------------------
 1    | 3,Formula,1,2,3,4,...
 2    | *,record,abc,efg,hij,...
 3    | ,,1,x,y,z,...
 4    | ,,2,q,r,s,...
 5    | 3,Formula,5,6,7,8,...
 6    | *,record,lmn,opq,rst,...
 7    | ,,1,t,u,v,...
 8    | ,,2,l,m,n,...

基本上,它是一个CSV文件,其中表中的每一行都是文件中的一行。第1行和第5行标识对象标题,第3,第4,第7和第8行标识属于该对象的行。对象标题行只能有40个属性,这就是为什么对象在CSV文件的多个部分中被分解的原因。

我想做的是拿表,将记录#列分开,然后多次连接它,这样就可以达到这样的目的:

 ID   |  Line            | 
 -------------------------
 1    | 3,Formula,1,2,3,4,5,6,7,8,...
 2    | *,record,abc,efg,hij,lmn,opq,rst
 3    | ,,1,x,y,z,t,u,v,...
 4    | ,,2,q,r,s,l,m,n,...

我知道它可能有可能,我只是不确定从哪里开始。我最初的想法是创建一个视图来分隔视图中的第一列和第二列,并使用视图作为在这两列上以重复方式连接的方式。但是,我有一些问题:

  1. 我不知道文件中会出现多少个部分 对象
  2. 该文件也可以包含其他对象,因此如果您有类似
  3. 之类的内容,那么加入前两列会有问题

     ID   |  Line            | 
     -------------------------
     1    | 3,Formula,1,2,3,4,...
     2    | *,record,abc,efg,hij,...
     3    | ,,1,x,y,z,...
     4    | ,,2,q,r,s,...
     5    | 3,Formula,5,6,7,8,...
     6    | *,record,lmn,opq,rst,...
     7    | ,,1,t,u,v,...
     8    | ,,2,l,m,n,...
     9    | ,4,Data,1,2,3,4,...
     10   | *,record,lmn,opq,rst,...
     11   | ,,1,t,u,v,...
    

    在上面的例子中,我的计划可以通过匹配记录值1来连接第9行中Data对象中的行和Formula对象的第一行。

    更新

    我知道这有点令人困惑。我尝试用C#做了这个,但我基本上写了一个递归的正确解析器来解析特定的文件格式,它只需要很长时间,因为我之后必须在数据库中获取它并且它对于实体框架来说太多了。转换一个文件只需要几个小时,因为这些文件过大。

    无论哪种方式,@ Nolan Shang的结果与我想要的最接近。唯一的区别是这(遗憾的是格式错误):

    +----+------------+------------------------------------------+-----------------------+
    | ID | header     | x                                        | value                                         
    |
    +----+------------+------------------------------------------+-----------------------+
    | 1  | 3,Formula, | ,1,2,3,4,5,6,7,8                         |3,Formula,1,2,3,4,5,6,7,8                     |
    | 2  | ,,         | ,1,x,y,z,t,u,v                           | ,1,x,y,z,t,u,v                    |
    | 3  | ,,         | ,2,q,r,s,l,m,n                           | ,2,q,r,s,l,m,n                    | 
    | 4  | *,record,  | ,abc,efg,hij,lmn,opq,rst             |*,record,abc,efg,hij,lmn,opq,rst           |
    | 5  | ,4,        | ,Data,1,2,3,4                            |,4,Data,1,2,3,4                               |
    | 6  | *,record,  | ,lmn,opq,rst                             | ,lmn,opq,rst                                  |
    | 7  | ,,         | ,1,t,u,v                                 | ,1,t,u,v                      |
    +----+------------+------------------------------------------+-----------------------------------------------+
    

2 个答案:

答案 0 :(得分:0)

我同意将其导出为脚本语言并在那里执行会更好。这将是TSQL的大量工作。

您已经暗示您还没有其他可能出现的情况,所以我显然无法提供全面的解决方案。我猜这不是你需要在重复的基础上快速完成的事情。更多的是一次性转型,因此性能不是问题。

一种方法是对可能的识别子字符串的硬编码表执行LEFT JOIN,如:

3,Formula,
*,record,
,,1,
,,2,
,4,Data,

看起来它几乎必须是人为选择和硬编码的,因为我找不到可用于仅选择这些子字符串的可靠模式。

然后从这个人工创建的表(或派生表,或CTE)中选择并使用LIKE LEFT JOIN到您的实际表中,以获取使用这些值作为其起始子字符串的所有行,删除起始字符以获取字符串的其余部分,并使用STUFF..FOR XML trick构建所需的Line

如何获取ID列取决于您想要的内容,例如在第二个示例中,我不知道您想要,4,Data,...行的ID。你想要5,因为它是结果中的下一个数字,还是你想要9因为那个子串的第一个出现的ID?相应的代码。如果你想要5,那就是ROW_NUMBER()。如果您想要9,则可以将ID列添加到您在此方法开始时创建的仿真表中。

顺便说一句,对于你需要做的事情,我们并没有任何递归,所以如果你仍然在思考这些方面,那么现在就是停下来的好时机。这更像是一个" Group Concatenation"问题

答案 1 :(得分:0)

这是一个示例,但您需要一些不同的示例。 这是因为我使用第二个逗号作为组头的值,所以,1和,, 2将被视为同一组,如果你可以使用父ID来表示一个组会更好

    DECLARE  @testdata TABLE(ID int,Line varchar(8000))
    INSERT INTO @testdata
        SELECT 1,'3,Formula,1,2,3,4,...' UNION ALL 
        SELECT 2,'*,record,abc,efg,hij,...'  UNION ALL 
        SELECT 3,',,1,x,y,z,...'  UNION ALL 
        SELECT 4,',,2,q,r,s,...'  UNION ALL 
        SELECT 5,'3,Formula,5,6,7,8,...'  UNION ALL 
        SELECT 6,'*,record,lmn,opq,rst,...'  UNION ALL 
        SELECT 7,',,1,t,u,v,...'  UNION ALL 
        SELECT 8,',,2,l,m,n,...'  UNION ALL 
        SELECT 9,',4,Data,1,2,3,4,...'  UNION ALL 
        SELECT 10,'*,record,lmn,opq,rst,...'  UNION ALL 
        SELECT 11,',,1,t,u,v,...'
    ;WITH t AS(
    SELECT *,REPLACE(SUBSTRING(t.Line,LEN(c.header)+1,LEN(t.Line)),',...','')  AS data
    FROM @testdata AS t
    CROSS APPLY(VALUES(LEFT(t.Line,CHARINDEX(',',t.Line, CHARINDEX(',',t.Line)+1 )))) c(header)
    )
    SELECT MIN(ID) AS ID,t.header,c.x,t.header+STUFF(c.x,1,1,'') AS value
    FROM t
    OUTER APPLY(SELECT ','+tb.data FROM t AS tb WHERE tb.header=t.header FOR XML PATH('') ) c(x)
    GROUP BY t.header,c.x
+----+------------+------------------------------------------+-----------------------------------------------+
| ID | header     | x                                        | value                                         |
+----+------------+------------------------------------------+-----------------------------------------------+
| 1  | 3,Formula, | ,1,2,3,4,5,6,7,8                         | 3,Formula,1,2,3,4,5,6,7,8                    |
| 3  | ,,         | ,1,x,y,z,2,q,r,s,1,t,u,v,2,l,m,n,1,t,u,v | ,,1,x,y,z,2,q,r,s,1,t,u,v,2,l,m,n,1,t,u,v    |
| 2  | *,record,  | ,abc,efg,hij,lmn,opq,rst,lmn,opq,rst     | *,record,abc,efg,hij,lmn,opq,rst,lmn,opq,rst |
| 9  | ,4,        | ,Data,1,2,3,4                            | ,4,Data,1,2,3,4                              |
+----+------------+------------------------------------------+-----------------------------------------------+