如何在SQL Server中修改重复的字符串?

时间:2019-11-25 22:27:56

标签: sql sql-server database tsql truncate

我正在处理将表中的一列字符串值从15个字符截断为10个字符(这是我想为该列允许的新最大长度)。

表中的一对列上有一个唯一键,这个键就是其中之一。

由于被截断,有可能被违反。

例如:

| ID |        C1       | C2 |
| -- | --------------- | -- |
| 1  | 123456789012345 |  1 |
| 2  | 123456789012346 |  1 |
| 3  | 123456789012345 |  2 |
| 4  | 123456789012346 |  2 |

假设我在C1和C2上有一个唯一的密钥。 C1当前为varchar(15),但由于超出我控制范围的原因,将其更改为varchar(10)。

我必须将C1中的值截断为长度为10的字符串。但是,如果我不加思索地这样做,显然(在上面的示例中)我将违反唯一键约束。

所以,我知道如何使用类似的方法查找所有重复项:

select
    t1.ID,
    LEFT(t1.C1, 10) as C1,
    t1.C2
INTO
    #ColumnDuplicates
FROM
    t t1
    join t t2 on
        t1.ID <> t2.ID
        AND LEFT(t1.C1, 10) = LEFT(t2.C1, 10)
WHERE
    t1.C2 = t2.C2

SELECT * FROM #ColumnDuplicates

请参阅上表,此查询将使我了解

| ID |     C1     | C2 |
| -- | ---------- | -- |
| 1  | 1234567890 |  1 |
| 2  | 1234567890 |  1 |
| 3  | 1234567890 |  2 |
| 4  | 1234567890 |  2 |

现在,在这里我不确定如何执行下一步。我需要做的是通过某种方式做到这一点:

| ID |     C1     | C2 |
| -- | ---------- | -- |
| 1  | 123456_001 |  1 |
| 2  | 123456_002 |  1 |
| 3  | 123456_001 |  2 |
| 4  | 123456_002 |  2 |

有效地,我想为每个C2值查找所有重复的C1值,然后将最后4个字符更改为 _ [0-9] [0-9] [0-9] 模式,并逐渐将这些重复项从000(或001,我不太在乎哪个用作起点)到最大999进行编号。这将为我提供处理每个C2值大约999重复项的空间,根据对所使用数据的熟悉程度,我可以确定这不会成为问题。

然后我可以轻松地使用此临时表来更新我要修改的主表中的C1值。

此刻我对SQL的了解是非常基础的,所以我真的不知道如何实现。

2 个答案:

答案 0 :(得分:0)

如果幸运的话,您可以查看前六个字符中的重复项。我说幸运的是,因为这是假设您的重复数不会超过1000:

with toupdate as (
      select t.*,
             row_number() over (partition by left(c1, 6), c2 order by c2) as seqnum,
             count(*) over (partition by left(c1, 6), c2) as cnt
      from t
     )
update toupdate
    set c1 = (case when cnt > 1
                   then concat(left(c1, 6), '_', format(seqnum, '000'))
                   else left(c1, 10)
              end);

以上对于重复项有些悲观。在使用row_number()之前过滤掉已知的单例可能很有意义:

with toupdate as (
      select t.*,
             row_number() over (partition by left(c1, 6), c2,
                                             (case when cnt10 > 1 then 1 else 2 end)
                                order by c2
                               ) as seqnum,
             count(*) over (partition by left(c1, 6), c2,
                                         (case when cnt10 > 1 then 1 else 2 end)
                           ) as cnt6
      from (select t.*,
                   count(*) over (partition by left(c1, 10), c2) as cnt10
            from t
           ) t
     )
update toupdate
    set c1 = (case when cnt10 > 1
                   then concat(left(c1, 6), '_', format(seqnum, '000'))
                   else left(c1, 10)
              end);

答案 1 :(得分:0)

您可以使用可更新的CTE来实现:

CREATE TABLE dbo.YourTable (ID int NOT NULL,
                            C1 varchar(15) NOT NULL,
                            C2 int NOT NULL);
CREATE UNIQUE INDEX YourIndex ON dbo.YourTable (C1,C2);
GO

INSERT INTO dbo.YourTable (ID, C1, C2)
VALUES (1,'123456789012345',1),
       (2,'123456789012346',1),
       (3,'123456789012345',2),
       (4,'123456789012346',2);
GO

WITH CTE AS(
    SELECT C1,
           LEFT(YT.C1,6) + '_' + RIGHT(CONCAT('000',ROW_NUMBER() OVER (ORDER BY YT.C1, YT.C2 ASC)),3) AS NewC1
    FROM dbo.YourTable YT
    WHERE LEN(YT.C1) > 10) --Unsure if that WHERE is needed
UPDATE CTE
SET C1 = NewC1;
GO

DROP INDEX YourIndex ON dbo.YourTable; --Has to be dropped to alter

ALTER TABLE dbo.YourTable ALTER COLUMN C1 varchar(10) NOT NULL;
GO

CREATE UNIQUE INDEX YourIndex ON dbo.YourTable (C1,C2); --Recreate

GO

SELECT *
FROM dbo.YourTable;

GO

DROP TABLE dbo.YourTable;