如何过滤重复项?

时间:2014-12-18 02:53:55

标签: sql sql-server select duplicates

我有两个设计糟糕的表Form和FormDetails,我正在努力清理。

Form表包含有关我的应用程序中表单状态的信息:

+-----------------------------------------+
| form_id | status_id   | form_created_by |
+-----------------------------------------+
| 1       | 1           | abc             |
+-----------------------------------------+
| 2       | 3           | def             |
+-----------------------------------------+
Form表中的

form_id是主键

FormDetails表包含有关表单的其他信息:

+-----------------------------------------+
| form_id | status_id   | process_id      |
+-----------------------------------------+
| 1       | 1           | 1               |
+-----------------------------------------+
| 2       | 2           | 1               |
+-----------------------------------------+
| 2       | 3           | 1               |
+-----------------------------------------+
| 2       | 3           | 1               |
+-----------------------------------------+
Form表中的

form_id是 NOT 主键或外键。没有限制。此表设计不佳,随着时间的推移,重复数据已添加到此表中。

我想通过将唯一数据复制到新的FormDetails表中并使form_id成为Form表的外键来清理此表。

为了达到这个目的,我尝试了以下查询:

select * 
from FormDetails fd
right join Form f on f.form_id = fd.form_id and f.status_id = fd.status_id

不幸的是,由于form_id = 2有两行且status_id = 3,我仍然会获得具有重复form_id的行。

+-----------------------------------------+
| form_id | status_id   | process_id      |
+-----------------------------------------+
| 1       | 1           | 1               |
+-----------------------------------------+
| 2       | 3           | 1               |
+-----------------------------------------+
| 2       | 3           | 1               |
+-----------------------------------------+

我想写的是一个查询:选择FormDetails中与当前Form相对应的所有行。如果有重复,只需选择其中一个。

知道怎么写这样的查询吗?

我希望看到的是:

+-----------------------------------------+
| form_id | status_id   | process_id      |
+-----------------------------------------+
| 1       | 1           | 1               |
+-----------------------------------------+
| 2       | 3           | 1               |
+-----------------------------------------+

6 个答案:

答案 0 :(得分:0)

您可以使用ANSI SQL函数row_number()枚举FormDetails中每个form_id的行。您不关心特定订单是什么,因此以下内容应该符合您的要求:

select form_id, status_id, process_id
from (select fd.*, row_number() over (partition by form_id order by form_id) as seqnum
      from formdetails fd
     ) fd
where exists (select 1 from forms f where fd.form_id = f.form_id) and
      seqnum = 1;

答案 1 :(得分:0)

使用SELECT DISTINCT声明

SELECT DISTINCT * 
FROM FormDetails fd
RIGHT JOIN Form f ON f.form_id = fd.form_id AND f.status_id = fd.status_id

答案 2 :(得分:0)

Distinct rows表格中找到Form,然后将Right Outer joinFormdetails表格一起使用

SELECT f.form_id,
       f.status_id,
       f.process_id,
FROM   FormDetails fd
       RIGHT JOIN (SELECT DISTINCT form_id,
                                   status_id,
                                   process_id,
                   FROM   Form) f
               ON f.form_id = fd.form_id
                  AND f.status_id = fd.status_id 

答案 3 :(得分:0)

我会采取哪些措施来解决您的问题

1-创建一个新字段,作为formDetails表中的唯一键。

Alter Table formDetails Add formDetail_id int Not Null Identity(1,1) Constraint PKFormDetail Primary Key

2-然后我会创建一个临时字段,用于记录现有数据的评估。我的意思是创建的临时字段(我将其命名为Eval)可以包含来自3个字段的计算值(form_id,status_id和process_id)。

Alter Table formDetails Add Eval int

Update formDetails Set Eval = form_id * 100000 + status_id * 1000 + process_id 

无论您采取何种解决方案,都必须注意结果不会包含"碰撞" (取决于表中包含的数据。我的意思是在这种情况下,process_id必须低于1000且status_id * 1000必须低于100000)。

3-删除不需要的行,如下所示:

  Delete x
  FROM ( select *, rn=row_number() over (partition by Eval order by formDetail_id) 
         from [dbo].[formDetails]) x
  Where rn > 1;

(您可以在执行删除操作之前测试它执行select而不是删除以确保结果。)

4-从表中删除临时字段。

Alter Table formDetails Drop Column Eval

5-您现在可以直接在两个表的form_id字段之间设置外键。

Alter Table formDetails Add Constraint FKFormId Foreign Key (form_id) references forms(form_id)

答案 4 :(得分:0)

CREATE TABLE #Form
(
formId INT,
StatusID INT,
FormCreate VARCHAR(30)
)

INSERT INTO #Form VALUES(1,1,'abc')
INSERT INTO #Form VALUES(2,3,'def')

CREATE TABLE #Form1
(
formId INT,
StatusID INT,
ProcessID INT
)

INSERT INTO #Form1 VALUES(1,1,1)
INSERT INTO #Form1 VALUES(2,2,1)
INSERT INTO #Form1 VALUES(2,3,1)
INSERT INTO #Form1 VALUES(2,3,1)

SELECT DISTINCT * FROM #Form fd
LEFT JOIN #Form1 f ON f.formId = fd.formId AND f.StatusID = fd.StatusID

答案 5 :(得分:0)

在一些朋友的帮助下,我设法找到了这个问题的答案。我的问题最接近的答案是@Gordon Linoff,但它仍然不是我想要的。

为了确定Form表中有多少行在FormDetails表中没有任何相应的记录,我们编写了以下查询:

SELECT *
FROM Form
LEFT OUTER JOIN FormDetails ON Form.form_id = FormDetails.form_id 
           AND Form.status_id = FormDetails.status_id
WHERE
      FormDetails.form_id IS NULL

当我在我的桌子上运行它时,它返回了38行。这个数字帮助我们确定我们写的查询是否返回了正确的行数。 @Gordon Linoff的回答是不同的26行所以我认为它返回的行多于预期,但它比所有其他答案更接近并朝着正确的方向迈出了一步。

然后我们把这个查询放在一起使用CROSS APPLY(我之前从未见过的一些sql)。

查询如下所示:

SELECT ad.*
FROM Form f
CROSS APPLY (SELECT TOP 1 * FROM FormDetails fd WHERE 
             f.form_id = fd.form_id AND f.status_id = fd.status_id) AS ad

当我将Form表中的行数与此查询返回的行数进行比较时,得到的差值为38.这似乎表明这是解决我问题的一个很好的解决方案。

我不会想到使用CROSS APPLY来解决这个问题......但我想我会在这里记录解决方案,以防将来帮助某人。