仅选择具有重复的记录(列A ||列B)但不同(列C)值

时间:2013-09-11 21:32:16

标签: mysql sql oracle

我为这个令人困惑的标题道歉,我无法弄清楚这个问题的正确措辞。相反,我只会给你背景信息和目标:

这是在一个表中,一个人可能有也可能没有多行数据,这些行可能包含activity_id的相同值,或者可能不包含。{1}}。每行都有一个自动递增的ID。人们没有附加名称的唯一标识符,因此我们只能使用first_name / last_name来标识某个人。

我需要能够找到此表中包含多行的人,但只能找到包含多个不同activity_id的多行的人。

以下是我们正在查看的数据示例:

unique_id | first_name    |   last_name    |    activity_id
---------------------------------------------------------------
 1        | ted           | stevens        | 544
 2        | ted           | stevens        | 544
 3        | ted           | stevens        | 545
 4        | ted           | stevens        | 546
 5        | rachel        | jameson        | 633
 6        | jennifer      | tyler          | 644
 7        | jennifer      | tyler          | 655
 8        | jennifer      | tyler          | 655
 9        | jack          | fillion        | 544
 10       | mallory       | taylor         | 633
 11       | mallory       | taylor         | 633

从这个小样本中,这里是我想要返回的记录:

unique_id | first_name    |   last_name    |    activity_id
---------------------------------------------------------------
 dontcare | ted           | stevens        | 544
 dontcare | jennifer      | tyler          | 655

请注意,返回unique_id的哪个值是不可靠的,只要它是属于该人的unique_id之一,并且只要为该人返回一条记录即可。

任何人都可以弄明白如何编写这样的查询吗? 我不关心您使用的SQL版本,如果它有所不同,我可以将其转换为Oracle。

4 个答案:

答案 0 :(得分:9)

我愿意:

SELECT first_name, last_name, COUNT(DISTINCT activity_id)
FROM <table_name>
GROUP BY first_name, last_name
HAVING COUNT(DISTINCT activity_id) > 0;

答案 1 :(得分:1)

我将与你一起构建逻辑。首先,让我们找到所有拥有多个条目的人:

名称+活动ID的唯一列表:

select first_name, last_name,activity_id, count(1)
from yourtable
group by first_name, last_name,activity_id

现在我们将其转换为子查询,并查找活动数超过1的用户

Select first_name, last_name
from 
    (select first_name, last_name,activity_id, count(1)
    from yourtable
    group by first_name, last_name,activity_id) a
group by  first_name, last_name
having count(1) > 1

应该这样工作...我没有返回activity_id,将max(activity_id)添加到select语句将获得最高的一个。

答案 2 :(得分:0)

请注意,返回unique_id的哪个值是irrelvant,只要它是属于该人的unique_id之一,并且只要为该人返回一条记录。

这些查询应该可以解决问题。不需要使用不同的关键字或子查询来获取BumbleShrimp需要的结果(如果BumbleShrimp需要正确的unique_id,则需要子查询来匹配正确的值)

以下是我能想到的最简单的查询应该可行,但在大型表格上可能会很慢。

SELECT 
   first_name
 , last_name 
 , activity_id
FROM 
 person
GROUP BY 
   first_name
 , last_name
 , activity_id
HAVING COUNT(*) >= 2

可能很慢,因为解释显示“使用索引;使用临时;使用filesort”。 使用临时表可以触发基于磁盘的临时表,因此我们使用内部自联接来消除使用临时表的需要。

SELECT 
   person1.first_name
 , person1.last_name
 , person1.activity_id
FROM 
 person person1
INNER JOIN
 person person2

ON
 person1.unique_id < person2.unique_id
AND 
 person1.first_name = person2.first_name
AND 
 person1.last_name = person2.last_name
AND 
 person1.activity_id = person2.activity_id

ORDER BY 
    activity_id asc

请参阅演示http://sqlfiddle.com/#!2/fe3ba/29

请注意,如果有三个或更多重复项,则内部联接将失败 见demo http://sqlfiddle.com/#!2/1ff33/15

新查询

SELECT 
   first_name
 , last_name 
 , activity_id
FROM 
 person
GROUP BY 
   activity_id
 , last_name
 , first_name
HAVING COUNT(activity_id) >= 2
ORDER BY 
 activity_id asc

请参阅demo http://sqlfiddle.com/#!2/1e418/3修复三个或更多重复问题/订单activity_id,并且可以在大型表上使用,因为不需要关闭临时表可以减慢执行速度

答案 3 :(得分:0)

要获得名称,最简单的是:

SELECT 
    first_name
  , last_name 
FROM 
    person
GROUP BY 
    first_name
  , last_name
HAVING 
    COUNT(DISTINCT activity_id) >= 2 ;

要为每个名称获取一行,您可以使用窗口函数(在Oracle中正常工作):

WITH cte AS
  ( SELECT 
        unique_id, first_name, last_name, activity_id
      , COUNT(DISTINCT activity_id) OVER (PARTITION BY last_name, first_name)
          AS cnt 
      , MIN(unique_id) OVER (PARTITION BY last_name, first_name)
          AS min_id 
    FROM 
        person
  )
SELECT
    unique_id, first_name, last_name, activity_id
FROM 
    cte
WHERE
    cnt >= 2
  AND
    min_id = unique_id ;

您可以使用MIN(unique_id) OVER ...(或MIN(activity_id) OVER ...)而不是MAX(),而不是min_id = activity_id。或ROW_NUMBER()功能。由于您无论如何都需要COUNT(DISTINCT activity_id),请允许我添加此版本。

使用(last_name, first_name, activity_id, unique_id)上的索引,它应该非常有效:

WITH cte AS
  ( SELECT 
        unique_id, first_name, last_name, activity_id
      , COUNT(DISTINCT activity_id) OVER (PARTITION BY last_name, first_name)
          AS cnt 
      , ROW_NUMBER() OVER (PARTITION BY last_name, first_name 
                           ORDER BY activity_id, unique_id)
          AS rown 
    FROM 
        person
  )
SELECT
    unique_id, first_name, last_name, activity_id
FROM 
    cte
WHERE
    cnt >= 2
  AND
    rown = 1 ;

SQL-Fiddle

进行测试