it-swarm.com.de

So wählen Sie die ersten 3 Werte aus jeder Gruppe in einer Tabelle mit SQL aus, die Duplikate haben

Angenommen, wir haben eine Tabelle mit zwei Spalten, eine Spalte enthält die Namen einiger Personen und die andere Spalte enthält einige Werte, die sich auf jede Person beziehen. Eine Person kann mehr als einen Wert haben. Jeder Wert hat einen numerischen Typ. Die Frage ist, dass wir die drei obersten Werte für jede Person aus der Tabelle auswählen möchten. Wenn eine Person weniger als 3 Werte hat, wählen wir alle Werte für diese Person aus. 

Das Problem kann gelöst werden, wenn die in diesem Artikel bereitgestellte Abfrage keine Duplikate in der Tabelle enthält. Wählen Sie die drei obersten Werte aus jeder Gruppe in einer Tabelle mit SQL aus. Wenn es aber Duplikate gibt, was ist die Lösung? 

Wenn zum Beispiel John einen Namen hat, hat er 5 Werte, die sich auf ihn beziehen. Sie sind 20,7,7,7,4. Ich muss die Name/Wert-Paare wie folgt sortieren, wobei der Wert für jeden Namen absteigend ist:

-----------+-------+
| name     | value |
-----------+-------+
| John     |    20 |
| John     |     7 |
| John     |     7 |
-----------+-------+

Für John sollten nur drei Zeilen zurückgegeben werden, obwohl es für John drei Sieben gibt.

11
PixelsTech

In vielen modernen DBMS (z. B. Postgres, Oracle, SQL-Server, DB2 und vielen anderen) funktioniert das Folgende einwandfrei. Es verwendet CTEs und die Rankingfunktion ROW_NUMBER(), die Teil des neuesten SQL-Standards ist:

 WITH cte AS
  ( SELECT name, value,
           ROW_NUMBER() OVER (PARTITION BY name
                              ORDER BY value DESC
                             )
             AS rn
    FROM t
  )
SELECT name, value, rn
FROM cte
WHERE rn <= 3
ORDER BY name, rn ;

Ohne CTE nur ROW_NUMBER():

SELECT name, value, rn
FROM 
  ( SELECT name, value,
           ROW_NUMBER() OVER (PARTITION BY name
                              ORDER BY value DESC
                             )
             AS rn
    FROM t
  ) tmp 
WHERE rn <= 3
ORDER BY name, rn ; 

Getestet in: 


In MySQL und anderen DBMS, die keine Ranking-Funktionen haben, müssen entweder abgeleitete Tabellen, korrelierte Unterabfragen oder Self-Joins mit GROUP BY verwendet werden.

Es wird angenommen, dass der (tid) der Primärschlüssel der Tabelle ist:

SELECT t.tid, t.name, t.value,              -- self join and GROUP BY
       COUNT(*) AS rn
FROM t
  JOIN t AS t2
    ON  t2.name = t.name
    AND ( t2.value > t.value
        OR  t2.value = t.value
        AND t2.tid <= t.tid
        )
GROUP BY t.tid, t.name, t.value
HAVING COUNT(*) <= 3
ORDER BY name, rn ;


SELECT t.tid, t.name, t.value, rn
FROM
  ( SELECT t.tid, t.name, t.value,
           ( SELECT COUNT(*)                -- inline, correlated subquery
             FROM t AS t2
             WHERE t2.name = t.name
              AND ( t2.value > t.value
                 OR  t2.value = t.value
                 AND t2.tid <= t.tid
                  )
           ) AS rn
    FROM t
  ) AS t
WHERE rn <= 3
ORDER BY name, rn ;

Getestet in MySQL

26
ypercubeᵀᴹ

Ich wollte die Frage ablehnen. Mir wurde jedoch klar, dass es sich tatsächlich um eine datenbankübergreifende Lösung handelt.

Angenommen, Sie suchen nach einer datenbankunabhängigen Methode, dies ist der einzige Weg, an den ich denken kann, wenn Sie korrelierte Unterabfragen (oder Nicht-Äquijoins) verwenden. Hier ist ein Beispiel:

select distinct t.personid, val, rank
from (select t.*,
             (select COUNT(distinct val) from t t2 where t2.personid = t.personid and t2.val >= t.val
             ) as rank
      from t
     ) t
where rank in (1, 2, 3)

Jede Datenbank, die Sie erwähnen (und Hadoop ist keine Datenbank), bietet eine bessere Möglichkeit, dies zu tun. Leider ist keines von ihnen Standard-SQL.

Hier ein Beispiel dafür, dass es in SQL Server funktioniert:

with t as (
      select 1 as personid, 5 as val union all
      select 1 as personid, 6 as val union all
      select 1 as personid, 6 as val union all
      select 1 as personid, 7 as val union all
      select 1 as personid, 8 as val
     )
select distinct t.personid, val, rank
from (select t.*,
             (select COUNT(distinct val) from t t2 where t2.personid = t.personid and t2.val >= t.val
             ) as rank
      from t
     ) t
where rank in (1, 2, 3);
0
Gordon Linoff

Dies funktioniert für MS SQL. Sollte in einem anderen SQL-Dialekt verarbeitbar sein, der Zeilennummern in einer Gruppe durch eine oder eine übergeordnete Klausel (oder eine Äquivalente) zuweisen kann

if object_id('tempdb..#Data') is not null drop table #Data;
GO

create table #data (name varchar(25), value integer);
GO
set nocount on;
insert into #data values ('John', 20);
insert into #data values ('John', 7);
insert into #data values ('John', 7);
insert into #data values ('John', 7);
insert into #data values ('John', 5);
insert into #data values ('Jack', 5);
insert into #data values ('Jane', 30);
insert into #data values ('Jane', 21);
insert into #data values ('John', 5);
insert into #data values ('John', -1);
insert into #data values ('John', -1);
insert into #data values ('Jane', 18);
set nocount off;
GO

with D as (
SELECT
     name
    ,Value
    ,row_number() over (partition by name order by value desc) rn
From
    #Data
)
SELECT Name, Value
FROM D
WHERE RN <= 3
order by Name, Value Desc

Name    Value
Jack    5
Jane    30
Jane    21
Jane    18
John    20
John    7
John    7
0
WernerCD

Mit GROUP_CONCAT und FIND_IN_SET können Sie das tun.Check SQLFIDDLE .

SELECT *
FROM tbl t
WHERE FIND_IN_SET(t.value,(SELECT
                             SUBSTRING_INDEX(GROUP_CONCAT(t1.value ORDER BY VALUE DESC),',',3)
                           FROM tbl t1
                           WHERE t1.name = t.name
                           GROUP BY t1.name)) > 0
ORDER BY t.name,t.value desc
0
Deval Shah

Wenn Ihre Ergebnismenge nicht so umfangreich ist, können Sie eine gespeicherte Prozedur (oder einen anonymen PL/SQL-Block) für dieses Problem schreiben, das die Ergebnismenge durchläuft und die drei großen Werte anhand eines einfachen Vergleichsalgorithmus ermittelt.

0
user1433439

Versuche dies -

CREATE TABLE #list ([name] [varchar](100) NOT NULL, [value] [int] NOT NULL)
INSERT INTO #list VALUES ('John', 20), ('John', 7), ('John', 7), ('John', 7), ('John', 4);

WITH cte
AS (
SELECT NAME
    ,value
    ,ROW_NUMBER() OVER (
        PARTITION BY NAME ORDER BY (value) DESC
        ) RN
FROM #list
)
SELECT NAME
,value
FROM cte
WHERE RN < 4
ORDER BY value DESC
0
rplusm