2011-09-20 55 views
2

问题如何将列对值的并集转换为线性表?

我们有重复的客户号码表:

A varchar(16) NOT NULL, 
B varchar(16) NOT NULL 

这些列开始是新旧(删除和保留),但移交给既不是首选的位置。这些专栏实际上只是“A”和“B” - 对于同一个客户而言,以任何顺序都是两个数字。

此外,该表可以具有任意数量的成对的同一个客户。您可能会看到像

a,b 
b,c 

意义a,b,c都是针对同一个客户。您可能还会看到像

a,b 
b,a 
c,a 

这意味着a,b,c都是同一个客户。

这是不是一个干净的非循环表示法,如“旧”和“新”值。客户的客户ID列表在此表中以一行或多行的块表示,其中唯一的连接是一行中的A或B列的值可能显示在其他行的A或B列中。我的任务是将它们全部绑定到每个客户的列表中。

我想这个烂摊子转换为类似

MasterKey int NOT NULL, 
CustNum varchar(16) NOT NULL UNIQUE, 
PRIMARY KEY(MasterKey, CustNum) 

的一个或多个号码,客户将分享此表中的MasterKey。如UNIQUE约束所述,给定的CustNum不能出现一次以上。

因此,举例来说,像这样的行从原来的

1a,1b 
1b,1c 
2a,2b 
2b,2c 
2d,2a 
... 

应该结束了在新表

1 1a 
1 1b 
1 1c 
2 2a 
2 2b 
2 2c 
2 2d 
... 

编辑这样行:以上数值只是为了格局明确。实际的客户号码值是任意varchar s。

我尝试的解决方案

这感觉就像递归工作,因此一个CTE。但源数据的潜在循环特性使我很难获得锚定案例。我试图将它预先清理成更多的非循环形式,但我似乎无法得到正确的结果。

我也固执地试图做这个基于集合的SQL操作,而不是诉诸于游标和循环。但也许这是不可能的。

我花了好几个小时思考这个,尝试不同的方法,但它不断滑落。任何关于正确方法的想法或建议,甚至是一些示例代码?

回答

0

我会做一些我以前从未做过,并张贴回答 我自己的问题。我需要非常感谢Beth和JBrooks 让我朝着正确的方向前进。我真的想要解决这个 在一个基于集合,声明的方式。也许这仍然可以使用 CTE和递归。但是,一旦我放弃了,并说它可以做到必要和迭代,那么做起来就容易多了。

无论如何,鉴于从我的问题这个目标表:

CREATE TABLE UniqueCustomers 
(
    uid  int NOT NULL, 
    gpid varchar(16) NOT NULL UNIQUE, -- Important: UNIQUE to disallow duplicates 
    PRIMARY KEY(uid, gpid) -- Important: Disallow duplicates 
) 

我想出了以下存储过程。它可以被调用,当 新的愚蠢报道,一个接一个。它也可以在传统表格 上调用,该表格以随机 的顺序将其作为成对存储。

CREATE PROCEDURE ReportDuplicateCustomerIDs 
(
    @id1 varchar(16), 
    @id2 varchar(16) 
) 
AS 
BEGIN 
    IF @id1 <> @id2 
    BEGIN 
     -- Retrieve the uid (if any) for each of the ids 
     DECLARE @uid1 int 
     SELECT @uid1 = NULL 
     SELECT @uid1 = uid FROM UniqueCustomers WHERE gpid = @id1 

     DECLARE @uid2 int 
     SELECT @uid2 = NULL 
     SELECT @uid2 = uid FROM UniqueCustomers WHERE gpid = @id2 

     -- If we've seen NEITHER of the id's yet 
     IF @uid1 IS NULL AND @uid2 IS NULL 
     BEGIN 
      -- Add both of them using a brand-new uid 
      DECLARE @uidNew int 
      SELECT @uidNew = Max(uid) + 1 FROM UniqueCustomers 
      IF @uidNew IS NULL 
       SET @uidNew = 0 
      INSERT INTO UniqueCustomers VALUES(@uidNew, @id1) 
      INSERT INTO UniqueCustomers VALUES(@uidNew, @id2) 
     END 
     ELSE 
     BEGIN 
      -- If we've seen BOTH id's already 
      IF @uid1 IS NOT NULL AND @uid2 IS NOT NULL 
      BEGIN 
       -- If this pair bridges two existing chains. 
       IF @uid1 <> @uid2 
       BEGIN 
        -- Update everything using uid2 to use uid1 instead. 
        -- Consolidates the two dupe chains into one. 
        UPDATE UniqueCustomers SET uid = @uid1 WHERE uid = @uid2 
       END 
       -- ELSE nothing to do 
      END 
      ELSE 
       -- If we've seen only id1, then insert id2 using 
       -- the same uid that id1 is already using 
       IF @uid1 IS NOT NULL 
        INSERT INTO UniqueCustomers VALUES(@uid1, @id2) 
       -- If we've seen only id2, then insert id1 using 
       -- the same uid that id2 is already using 
       ELSE -- @uid2 IS NOT NULL 
        INSERT INTO UniqueCustomers VALUES(@uid2, @id1) 
     END 
    END 
END 
GO 
+0

我放弃了对这两个答案的支持,但我接受了我自己的答案,因为它是最正确和最完整的答案。我觉得这很奇怪,但从常见问题解答看来,这应该是我应该做的。再次感谢Beth和JBrooks! –

0

看起来像工作的工会给我。下面的代码假设你不能在同一记录中有1a,2b。

创建表#TEMP(一个varchar(10)中,b为varchar(10))

insert into #temp 
values ('1a', '1b') 
,('1b', '1c') 
,('2a', '2b') 
,('2b', '2c') 
,('2d', '2a') 

select * from #temp 

select a, b, left (a, 1) as id into #temp2 from #temp 

select id, a from #temp2 
union 
select id, b from #temp2 
+0

感谢您的快速回答,但我在示例中使用了'1a','1b'等来使所需图案更清晰。如果实际的数据已经有这样的模式,这会更容易。 :)相反,实际值将是任意'varchar's,如'FOO12','BAR666','GURGLE721'。很遗憾,我们无法使用'Left()'从值中获取'MasterKey'值。 –

1

给定的输入数据:

a,b 
b,c 
d,e 
e,f 
g,d 

我添加两个新的表,一个与所述的PK值,和一个在与所述PKS的一对多的关系pk和重复的值,如下所示:

pk 
a 
b 
c 
d 
e 
f 
g 


pk dup 
a b 
b a 
b c 
c b 
d e 
e d 
e f 
f e 
g d 
d g 

行中的PK/DUP表是通过输入文件填充pks并重复插入(pk,dup)序列和(dup,pk)序列。

这可以让你的第一组键和副本之间的关系,但是你需要再通过这套迭代得到间接的关系,如“c是一个重复”

你可以得到这些关系通过自我加入pkdup1.dup = pkdup2.pk上的pk/dup表。这将行(a,b)与行(b,a)和(b,c)结合在一起,允许您识别关系(a,c)。它也将拾取(d,f)(f,d)(g,e)。你需要重复迭代回暖(G,F)

HTH

0

什么是找到关键的格局?如果比这个数开始将其拔出

select substring('FOO12',patindex('%[0-9]%','FOO12'),100) 

select substring('12FOO',1,patindex('%[A-Z]%','12FOO')-1) 

两种方法都返回12

如果字符串中只是第一个数字那么这将拉出来
1

我想你将不得不做一些循环。在这里,我一次只查看1行,以确保获得属于单个主密钥的所有链接值。

while (1=1) 
begin 

    -- get the next key that is not inserted yet as MasterKey or key 
    select top 1 @masterKey = a 
    from myTable 
    where not exists (select 1 
     from #temp 
     where #temp.MasterKey = myTable.a 
     or #temp.Key = myTable.a) 

    if(@masterKey is null) -- out of a's so now work the b's 
     select top 1 @masterKey = b 
     from myTable 
     where not exists (select 1 
      from #temp 
      where #temp.MasterKey = myTable.b 
      or #temp.Key = myTable.b) 

    if(@masterKey is null) -- totally done. 
     break 

    insert into #temp 
    (masterKey, key) 
    values(@masterKey, @masterKey) 


    while (1=1) -- now insert anything new with this masterKey in a 
    begin 
     insert into #temp 
     select top 1 @masterKey, myTable.b 
     from myTable 
     where myTable.a = @masterKey 
     not exists (select 1 
     from #temp 
     where #temp.MasterKey = myTable.b 
     or #temp.Key = myTable.b)) 

     if @@rowcount < 1 
      break 
    end 


    while (1=1) -- now insert anything with this masterKey in b 
    begin 
     insert into #temp 
     select top 1 @masterKey, myTable.a 
     from myTable 
     where myTable.b = @masterKey 
     not exists (select 1 
     from #temp 
     where #temp.MasterKey = myTable.a 
     or #temp.Key = myTable.a)) 

     if @@rowcount < 1 
      break 

    end 

end 

你真得2个插入段包装成另一个循环,以确保其获得下masterKey之前用完,但你的想法。

+0

这看起来很有希望,但我没有按照最后两条插入语句的操作。在这两种情况下,'where myTable.x = @ masterKey'后面跟着'not exists ...',两者之间没有粘连。我想知道你是否打算把'AND'放在那里,但这似乎不正确?还有一个额外的结束准字。对不起,如果我很密集。 –

+0

不,你是对的 - 两者之间应该有一个AND。闭幕式是额外的,但我没有给你100%的语法,更多的是解决这个问题的方法。 – JBrooks

+0

好吧,我明白了。 (我绝对不希望你为我编写所有的代码:)我确实想确保我对你的想法感兴趣。)谢谢。 –

0

根据评论中的一些样本数据,我认为这应该有所斩断?

CREATE TABLE #sample 
(A NVARCHAR(50) 
,B NVARCHAR(50)) 

INSERT INTO #sample VALUES('FOO12','12DEF') 
INSERT INTO #sample VALUES('12GHJ','12ABC') 
INSERT INTO #sample VALUES('GURGLE721','GURGLZ721') 
INSERT INTO #sample VALUES('word21','book721') 
INSERT INTO #sample VALUES('orange21','apple21') 

;WITH CTE as 
(
SELECT A 
,PATINDEX('%[A-Za-z]%',A) as text_start 
,PATINDEX('%[0-9]%',A) as num_start 
FROM #sample 
UNION ALL 
SELECT B 
,PATINDEX('%[A-Za-z]%',B) as text_start 
,PATINDEX('%[0-9]%',B) as num_start 
FROM #sample 
) 
,cte2 AS 
(
SELECT 
* 
,CASE WHEN text_start > num_start --Letters after numbers 
    THEN SUBSTRING(A,text_start - num_start + 1,99999) 
    WHEN text_start = 1 --Letters at start of string 
    THEN SUBSTRING(A,1,num_start - 1) 
    END AS letters 
,CASE WHEN num_start > text_start --Numbers after letters 
    THEN SUBSTRING(A,num_start - text_start + 1,99999) 
    WHEN num_start = 1 --Numbers at start of string 
    THEN SUBSTRING(A,1,text_start- 1) 
    END AS numbers 
FROM cte 
) 
SELECT DISTINCT 
DENSE_RANK() OVER (ORDER BY numbers ASC) as group_num 
,numbers + letters as cust_details 
FROM cte2 
ORDER BY numbers + letters asc 
相关问题