更新并更正具有复合主键的表

时间:2017-05-16 08:20:12

标签: sql sql-server-2014-express

我在数据库中有一个表,用于记录特定前提下的设备项目。它有一个复合主键,它由前提的id的id(以int形式)组成,然后是为每个单独的设备添加的单独数字(在这种情况下为十进制)。 / p>

虽然这个设置在几年后为复合键的第二部分生成一组连续数字时效果很好,因为在计划中添加或删除了一些设备,它开始看起来像下面的例子。

Composite key after a few years

我的问题非常双重。首先是有一个查询可以写入复合键的第二部分,它将一组连续的数字恢复到它?例如,简单地用39替换38.3将立即与现有记录产生冲突,数字为39.我假设接近这个的方法可能是通过某种计数然后在表中向后工作,尽管我怀疑即使这样做也可能导致冲突,导致更新失败。

其次,也许更重要的是,这是许多年前设计的。我确信有更好的方法可以构建这样一个表格,所以我欢迎一些最好的练习'这种情况的想法。

1 个答案:

答案 0 :(得分:1)

Assuming the following table structure.

CREATE TABLE foo (id_1 int NOT NULL, id_2 decimal NOT NULL, data text NOT NULL);

ROW_NUMBER

You can generate an ordered id_2 int by using ROW_NUMBER.

SELECT id_1,  
       id_2,  
       ROW_NUMBER() OVER (PARTITION BY id_1 ORDER BY id_2),  
       data  
FROM foo;  

The ROW_NUMBER() function will begin numbering each set of rows having the same id_1 (PARTITION BY) in ascending order of id_2 (ORDER BY). You wrote that (id_1, id_2) is a key, so it's unique. Then you don't need a tie breaker. If you would need a tie breaker, because (id_1, id_2) were not unique, then you could just add another column to your ORDER BY in the OVER clause.

Example result (without data):

id_1 | id_2 | ROW_NUMBER  
------------------------  
   1 |  1.1 |          1  
   1 |  1.2 |          2  
   1 |  2.1 |          3  
   1 |  5.5 |          4  
   2 |    1 |          1  
   2 |  3.3 |          2  

So here both (id_1, id_2) and (id_1, ROW_NUMBER) are unique keys identifying the rows.

Multiply decimal fractions away

Assume that decimal numbers only have 1 decimal behind the point. Then you can just multiply them all by 10 and cast them to int. This way, you will retain a close relationship between this data and how it was before, which may be good for historic reasons.

id_1 | id_2 | id_2 * 10 :: int  
------------------------  
   1 |  1.1 |         11  
   1 |  1.2 |         12  
   1 |  2.1 |         21  
   1 |  5.5 |         55  
   2 |    1 |         10  
   2 |  3.3 |         33  

You can keep both the multiplied column as part of a new key and the old id_2 decimal column as unrelated data only column.

** Splitting the id_2 into two columns **

This is a variation of the ROW_NUMBER approach by rounding id_2 down (floor) and using the ROW_NUMBER as third column for unique key (id_1, floor(id_2), ROW_NUMBER).

SELECT id_1,  
       id_2,  -- keep, if needed
       floor(id_2) AS "id_2_floor",
       ROW_NUMBER() OVER (PARTITION BY id_1 ORDER BY id_2) AS "id_3",  
       data  
FROM foo;  

id_1 | id_2 | id_2_floor | id_3 (ROW_NUMBER)  
--------------------------------------------  
   1 |  1.1 |          1 | 1  
   1 |  1.2 |          1 | 2
   1 |  2.1 |          2 | 1
   1 |  5.5 |          5 | 1
   2 |    1 |          1 | 1
   2 |  3.3 |          3 | 1

This will mix up the fractional parts of your decimal numbers though.

Splitting decimal into two ints at the decimal point

This approach splits a decimal number (1.2) into two ints (1, 2). You can then make (id_1, floor( id_2 ), id_2 * 10 % 10) unique key.

SELECT id_1,  
       id_2,  -- keep if needed  
       floor( id_2 ) AS "id_2_new",  
       int( id_2 * 10 % 10 ) AS "id_3",  -- and cast to int
       data  
FROM foo;  

id_1 | id_2 | id_2_new | id_3  
-----------------------------  
   1 |  1.1 |        1 | 1  
   1 |  1.2 |        1 | 2  
   1 |  2.1 |        2 | 1  
   1 |  5.5 |        5 | 5  
   2 |    1 |        1 | 0  
   2 |  3.3 |        3 | 3  

This approach will allow you to reconstruct the original id_2 column values at any time using

SELECT id_1,  
       double( id_2 + id_3 / 10 ) AS "id_2_old"  
FROM   foo;