如何使用SQL(Oracle)获取不同的行集?

时间:2016-01-04 17:17:17

标签: sql oracle

我有一种情况,我需要在现有的应用程序中使用EAV数据,其中重构不是一个选项,所以我不能改变数据的结构。也就是说,我有一个返回数据的查询

| account | field | value | group |
|---------|-------|-------|-------|
| 1       | A     | 1     | 1     |
| 1       | B     | foo   | 1     |
| 1       | A     | 2     | 2     |
| 1       | B     | foo   | 2     |
| 1       | A     | 2     | 3     |
| 1       | B     | foo   | 3     |
| 2       | A     | 1     | 4     |
| 2       | A     | 2     | 5     |
| 2       | A     | 1     | 6     |

如何根据第2组和第4组分别拥有所有相同的帐户,字段和值这一事实,如何删除第3组和第6组?

我想过使用像

这样的东西
select account, field, value, rank() over (partition by account, field, value order by group)

但是该组的成员将具有不同的排名,具体取决于之前是否已经看到每个特定行。

换句话说,是否可以使用“group”列来获取行的不同“集合”,从而消除具有相同行数和相同值的其他行,其中行数不同团体可能会有所不同吗?

编辑:

我不确定最初的例子是非常好的。使用distinct / unique将不起作用,因为我对不同的行组感兴趣,而不是对不同的行感兴趣。作为一个更好的例子,考虑

| account | field | value | group |
|---------|-------|-------|-------|
| 1       | A     | 1     | 1     |
| 1       | B     | foo   | 1     |
| 1       | A     | 1     | 2     |
| 1       | B     | bar   | 2     |
| 1       | A     | 2     | 3     |
| 1       | B     | foo   | 3     |
| 2       | A     | 1     | 4     |
| 2       | A     | 2     | 5     |
| 2       | A     | 1     | 6     |
| 3       | A     | 1     | 7     |
| 3       | B     | foo   | 7     |
| 3       | C     | bar   | 7     |
| 3       | A     | 1     | 8     |
| 3       | B     | foo   | 8     |
| 3       | C     | baz   | 8     |
| 3       | A     | 1     | 9     |
| 3       | B     | foo   | 9     |
| 3       | C     | bar   | 9     |

在这种情况下,我只想删除第6组和第9组,因为它们分别与第4组和第7组相同。我仍然需要保留关于其他组的所有信息,包括它们被分组的事实。

5 个答案:

答案 0 :(得分:2)

基于评论的新答案:

WITH Prior AS
(
  -- First find matches to Prior groups
  SELECT A.account, A.field, A.value, A.group, MIN(B.group) as Prior_Group
  FROM TABLE A
  LEFT JOIN TABLE B ON A.account=B.account 
                   AND A.Field = B.field
                   AND A.value = B.value
                   AND A.group > B.group
  GROUP BY A.account, A.field, A.value, A.group
), Counts AS 
(
  -- Count group members and priors
  -- Using a trick that nulls for Prior_Group won't be counted
  SELECT account, field, value, group, 
         Count(*) AS Group_Count, Count(Prior_Group) as Prior_Count
  FROM Prior 
  GROUP BY account, field, value, group
)
SELECT account, field, value, group
FROM TABLE
WHERE (account, field, value, group) NOT IN 
  (SELECT account, field, value, group
   FROM Counts 
   WHERE Group_Count = Prior_Count)

您可以使用

SELECT DISTINCT account, field, value 
FROM (
   -- PRIOR QUERY
) x

SELECT account, field, value 
FROM (
   -- PRIOR QUERY
) x
GROUP BY account, field, value 

最后,如果你想在不同的集合中包含“最低群体”,你可以这样做

SELECT account, field, value, group
FROM (
  SELECT account, field, value, group
         row_number() OVER (PARTITION BY account, field, value ORDER BY group ASC) AS rn
  FROM (
     -- PRIOR QUERY
  ) x
) x2
WHERE rn = 1

附注,使用row_number()技巧,如果它们是不同分区的一部分,您可以无需担心地包含任何其他列。

答案 1 :(得分:0)

如果我正确理解了这个问题,那就是你要找的。

select distinct account, field, value from table

答案 2 :(得分:0)

也许我错过了一些东西,但这似乎是一个简单的分组和最小聚合。假设您总是希望在存在重复的帐户,字段和值时返回最小组#。

SELECT account, field, value, min(group) as group
FROM  table
GROUP BY account, field, value

答案 3 :(得分:0)

我已经使用@Hogan

的部分建议解决了这个问题
WITH hashes AS
(SELECT group,
        SUM(Ora_hash(Concat(Concat(account,field), value))) AS hash
 FROM table
 GROUP BY group)
SELECT account,
       field,
       value,
       group
FROM table
WHERE group IN (SELECT group
                FROM (SELECT group,
                             row_number() over (PARTITION BY hash ORDER BY NULL) AS rn
                      FROM hashes)
                WHERE  rn = 1);

这样做的缺点是,如果传递给ora_hash()的字段串联长度超过varchar2字段的最大字符长度,我认为会出现问题。 Ora_hash()对于LOB字段不是确定性的,因此转换为CLOB无济于事。在我的例子中,数据库中的帐户,字段和值列的长度限制将防止这种情况发生。在其他情况下,查看dbms_crypto.hash()函数可能很有用。

编辑:

对于这类问题,ora_hash()函数似乎具有不可接受的高冲突率。考虑到我已经依赖于连接不会通过varcar2字符限制这一事实,最好直接比较这些值。这只会在两个不同字段的连接产生相同结果的情况下失败,例如, ('a','bc')和('ab','c'),而不是任何值与ora_hash()冲突的可能性。

WITH group_vals AS
(SELECT group,
        listagg(account || field || value, ',') AS vals
 FROM table
 GROUP BY group)
SELECT account,
       field,
       value,
       group
FROM table
WHERE group IN (SELECT group
                FROM (SELECT group,
                             row_number() over (PARTITION BY vals ORDER BY NULL) AS rn
                      FROM group_vals)
                WHERE  rn = 1);

答案 4 :(得分:0)

类似的方法,但使用LISTAGG而不是SUM和ora_hash(如果您使用的是11g R2或更高版本):

WITH lists AS
  (SELECT account, field, "value", "group", LISTAGG(account||field||"value", '-')
    WITHIN GROUP (ORDER BY account, field, "value") OVER (PARTITION BY "group") AS list
    FROM test
  )
SELECT account, field, "value", "group"
FROM lists
WHERE "group" IN
  (SELECT "group"
    FROM
      (SELECT "group", row_number() over (partition BY list order by "group") AS rn
        FROM lists)
    WHERE rn = 1
  )
ORDER BY "group", account, field, "value";