Wie wähle ich die erste Zeile für jede Gruppe in MySQL aus?

70

In C # wäre es so:

table
   .GroupBy(row => row.SomeColumn)
   .Select(group => group
       .OrderBy(row => row.AnotherColumn)
       .First()
   )

Linq-To-Sql übersetzt es in den folgenden T-SQL-Code:

SELECT [t3].[AnotherColumn], [t3].[SomeColumn]
FROM (
    SELECT [t0].[SomeColumn]
    FROM [Table] AS [t0]
    GROUP BY [t0].[SomeColumn]
    ) AS [t1]
OUTER APPLY (
    SELECT TOP (1) [t2].[AnotherColumn], [t2].[SomeColumn]
    FROM [Table] AS [t2]
    WHERE (([t1].[SomeColumn] IS NULL) AND ([t2].[SomeColumn] IS NULL))
      OR (([t1].[SomeColumn] IS NOT NULL) AND ([t2].[SomeColumn] IS NOT NULL)
        AND ([t1].[SomeColumn] = [t2].[SomeColumn]))
    ORDER BY [t2].[AnotherColumn]
    ) AS [t3]
ORDER BY [t3].[AnotherColumn]

Es ist jedoch nicht mit MySQL kompatibel.

Jader Dias
quelle
Können
@Iexu Ja, ich kann, und ich habe es mit MS SQL Server gemacht. Aber ich habe kein Linq-zu-MySQL, nur Linq-zu-SQL
Jader Dias

Antworten:

87

Ich habe meine Antwort nur auf den Titel Ihres Beitrags gestützt, da ich C # nicht kenne und die angegebene Abfrage nicht verstanden habe. Aber in MySQL empfehle ich Ihnen, Unterauswahlen zu versuchen. Holen Sie sich zuerst einen Satz Primärschlüssel mit interessanten Spalten und wählen Sie dann Daten aus diesen Zeilen aus:

SELECT somecolumn, anothercolumn 
  FROM sometable 
 WHERE id IN (
               SELECT min(id) 
                 FROM sometable 
                GROUP BY somecolumn
             );
lfagundes
quelle
Ich denke, es wird für mich funktionieren, aber diese Lösung erfordert, dass ich eine PK idfür meine Tabelle erstelle .
Jader Dias
Die C # / T-SQL-Lösung benötigt dies zwar nicht.
Jader Dias
7
Nun, es ist eine gute Praxis, immer einen Primärschlüssel zu haben. Wenn Sie theoretisch keinen Primärschlüssel haben, sollte der Satz der gesamten Zeile Ihr Primärschlüssel sein (obwohl MySQL eine Tabelle ohne Primärschlüssel mit wiederholten Zeilen akzeptieren würde). .
lfagundes
4
IN ist in der Regel sehr langsam, wenn Sie ein großes Recordset auswerten. EXISTS bietet häufig eine bessere Leistung, wenn Sie es verwenden können. In vielen Fällen (zum Beispiel in diesem Fall) können Sie einen INNER JOIN verwenden, der noch schneller ist. SELECT c1, c2 FROM t1 INNER JOIN (SELECT min (c2) c2 FROM t1) a1 ON t1.c2 = a1.c2
Praesagus
21

Wenn ich schreibe

SELECT AnotherColumn
FROM Table
GROUP BY SomeColumn
;

Es klappt. IIRC in anderen RDBMS ist eine solche Anweisung nicht möglich, da auf eine Spalte, die nicht zum Gruppierungsschlüssel gehört, ohne irgendeine Art von Aggregation verwiesen wird.

Diese "Eigenart" verhält sich sehr genau so, wie ich es will. Also habe ich es benutzt, um das gewünschte Ergebnis zu erzielen:

SELECT * FROM 
(
 SELECT * FROM `table`
 ORDER BY AnotherColumn
) t1
GROUP BY SomeColumn
;
Jader Dias
quelle
In einem ähnlichen Fall funktioniert der Auswahlteil für mich, aber wenn ich versuche, das mit dieser Abfrage in MySQL erhaltene Ergebnis zu aktualisieren, funktioniert es nicht. Ich habe bisher viele Lösungen für "Update" ohne Erfolg ausprobiert. Würde mich über jede Hilfe / Anregung dort freuen.
Sucher
5
Diskussion darüber, warum die erste Anweisung funktioniert: stackoverflow.com/questions/1225144/…. Beim Starten von MySQL 5.7.5 ist dies standardmäßig deaktiviert. Dev.mysql.com/doc/refman/5.7/en/…
Juha Palomäki
Es wird keine solche Reihenfolge in MySQL in Betracht gezogen, und die Aufzeichnung, die in der Gruppe von aufgenommen wird, ist zufällig oder die erste
Shreyan Mehta
19

Hier ist eine andere Möglichkeit, die Sie versuchen könnten, ohne dieses ID-Feld zu benötigen.

select some_column, min(another_column)
  from i_have_a_table
 group by some_column

Trotzdem stimme ich lfagundes zu, dass Sie einen Primärschlüssel hinzufügen sollten.

Beachten Sie auch, dass Sie auf diese Weise nicht (leicht) zu den anderen Werten gelangen können, die dieselbe Zeile wie das resultierende Paar some_colum, another_column sind! Dafür brauchst du lfagundes apprach und eine PK!

Lexu
quelle
das macht überhaupt mehr Sinn!
Marcos Bergamo
Das ist die perfekte Lösung für mich.
MeLight
6
SELECT
    t1.*

FROM
    table_name AS t1

    LEFT JOIN table_name AS t2 ON (
        t2.group_by_column = t1.group_by_column
        -- group_by_column is the column you would use in the GROUP BY statement
        AND
        t2.order_by_column < t1.order_by_column
        -- order_by_column is column you would use in the ORDER BY statement
        -- usually is the autoincremented key column
    )

WHERE
    t2.group_by_column IS NULL;

Mit MySQL v8 + können Sie Fensterfunktionen verwenden

Ricardo Tribaldos
quelle
1
Dies ist die einzige Antwort, die ich in 5.7+ mit ONLY_FULL_GROUP_BYaktiviertem Gerät für meinen Anwendungsfall arbeiten konnte. Wir haben eine PK eingerichtet und aus irgendeinem Grund dachte MySQL 5.7 immer wieder, dass dies nicht funktional von der Spalte abhängt, die wir benötigen GROUP BY. Die anderen Antworten scheinen sehr spezifisch für das jeweilige Problem zu sein oder erfordern SQL-Variablen ... Dies ist eine direkte Abfrage, die für viele Zwecke allgemein genug ist. Das einzige, was ich ändern musste, war die Ungleichheit für die ORDER BYSpalten, aber das ist je nach Bedarf zu erwarten.
Schlacht innerhalb des
5

Sie sollten eine Aggregatfunktion verwenden, um den gewünschten Wert von AnotherColumn abzurufen. Wenn Sie also den niedrigsten Wert von AnotherColumn für jeden Wert von SomeColumn (entweder numerisch oder lexikografisch) möchten, können Sie Folgendes verwenden:

SELECT SomeColumn, MIN(AnotherColumn)
FROM YourTable
GROUP BY SomeColumn

Einige hoffentlich hilfreiche Links:

http://dev.mysql.com/doc/refman/5.1/en/group-by-functions.html

http://www.oreillynet.com/databases/blog/2007/05/debunking_group_by_myths.html

David M.
quelle
Wenn ich das mache, ist der SomeColumn-Wert nicht unbedingt der Wert in der Zeile, in der AnotherColumn = Min (AnotherColumn)
Jader Dias
@Jader Dias: Wie ich in meiner Antwort sagte, brauchst du deshalb eine PK!
Lexu
1
Min (AnotherColumn) im Gruppierungskontext ist die niedrigste AnotherColumn für die Zeilengruppe mit demselben Wert von SomeColumn, nicht für alle Werte von AnotherColumn für die gesamte Tabelle.
David M
3
Die zu verwendende Aggregatfunktion ist MINaber nicht FIRST, was MySQL fehlt.
Reinierpost
5

Aus der MySQL 5.7-Dokumentation

MySQL 5.7.5 und höher implementiert die Erkennung der funktionalen Abhängigkeit. Wenn der SQL-Modus ONLY_FULL_GROUP_BY aktiviert ist (dies ist standardmäßig der Fall), lehnt MySQL Abfragen ab, für die die Auswahlliste, die HAVING-Bedingung oder die ORDER BY-Liste auf nicht aggregierte Spalten verweisen, die weder in der GROUP BY-Klausel benannt sind noch funktional von ihnen abhängig sind .

Dies bedeutet, dass die Lösung von @Jader Dias nicht überall funktionieren würde.

Hier ist eine Lösung, die funktionieren würde, wenn sie ONLY_FULL_GROUP_BYaktiviert ist:

SET @row := NULL;
SELECT
    SomeColumn,
    AnotherColumn
FROM (
    SELECT
        CASE @id <=> SomeColumn AND @row IS NOT NULL 
            WHEN TRUE THEN @row := @row+1 
            ELSE @row := 0 
        END AS rownum,
        @id := SomeColumn AS SomeColumn,
        AnotherColumn
    FROM
        SomeTable
    ORDER BY
        SomeColumn, -AnotherColumn DESC
) _values
WHERE rownum = 0
ORDER BY SomeColumn;
Nicolai
quelle
Verifiziert, dass dies eine funktionierende Lösung ist. Dies ist derzeit die einzige funktionierende Lösung, die ich für MySQL 5.7.5 mit der Standardeinstellung für ONLY_FULL_GROUP_BY gesehen habe.
Josh
5

Ich habe die folgende Lösung unter den Antworten nicht gesehen, also dachte ich, ich würde sie dort veröffentlichen.

Das Problem besteht darin, Zeilen auszuwählen, die die ersten Zeilen sind, wenn sie AnotherColumnin allen nach gruppierten Gruppen sortiert sind SomeColumn.

Die folgende Lösung erledigt dies in MySQL. idmuss eine eindeutige Spalte sein, die keine enthaltenden Werte enthalten darf -(die ich als Trennzeichen verwende).

select t1.*
from mytable t1
inner join (
  select SUBSTRING_INDEX(
    GROUP_CONCAT(t3.id ORDER BY t3.AnotherColumn DESC SEPARATOR '-'),
    '-', 
    1
  ) as id
  from mytable t3
  group by t3.SomeColumn
) t2 on t2.id = t1.id


-- Where 
SUBSTRING_INDEX(GROUP_CONCAT(id order by AnotherColumn desc separator '-'), '-', 1)
-- can be seen as:
FIRST(id order by AnotherColumn desc)

-- For completeness sake:
SUBSTRING_INDEX(GROUP_CONCAT(id order by AnotherColumn desc separator '-'), '-', -1)
-- would then be seen as:
LAST(id order by AnotherColumn desc)

Es gibt eine Funktionsanforderung für FIRST()und LAST()im MySQL-Bug-Tracker, die jedoch vor vielen Jahren geschlossen wurde.

Lars Nyström
quelle
3

Beste Leistung und einfach zu bedienen:

SELECT id, code,
SUBSTRING_INDEX( GROUP_CONCAT(price ORDER BY id DESC), ',', 1) first_found_price
FROM stocks
GROUP BY code
ORDER BY id DESC
Martin Zvarík
quelle
2

Ich schlage vor, diesen offiziellen Weg von MySql zu verwenden:

SELECT article, dealer, price
FROM   shop s1
WHERE  price=(SELECT MAX(s2.price)
              FROM shop s2
              WHERE s1.article = s2.article
              GROUP BY s2.article)
ORDER BY article;

Auf diese Weise können wir den höchsten Preis für jeden Artikel erzielen

Onkel Bob
quelle
1

Wie wäre es damit:

SELECT SUBSTRING_INDEX(
      MIN(CONCAT(OrderColumn, '|', IFNULL(TargetColumn, ''))
    ), '|', -1) as TargetColumn
FROM table
GROUP BY GroupColumn
Yura Fedoriv
quelle
1

Eine weitere Möglichkeit (ohne Primärschlüssel) wäre die Verwendung der JSON-Funktionen:

select somecolumn, json_unquote( json_extract(json_arrayagg(othercolumn), "$[0]") )
  from sometable group by somecolumn

oder vor 5.7.22

select somecolumn, 
  json_unquote( 
    json_extract( 
      concat('["', group_concat(othercolumn separator '","') ,'"]') 
    ,"$[0]" ) 
  ) 
  from sometable group by somecolumn

Die Bestellung (oder Filterung) kann vor der Gruppierung erfolgen:

select somecolumn, json_unquote( json_extract(json_arrayagg(othercolumn), "$[0]") ) 
  from (select * from sometable order by othercolumn) as t group by somecolumn

... oder nach der Gruppierung (natürlich):

select somecolumn, json_unquote( json_extract(json_arrayagg(othercolumn), "$[0]") ) as other 
  from sometable group by somecolumn order by other

Zugegeben, es ist ziemlich kompliziert und die Leistung ist wahrscheinlich nicht großartig (hat es nicht mit großen Datenmengen getestet, funktioniert gut mit meinen begrenzten Datenmengen).

Iikka
quelle
0

Noch ein anderer Weg, es zu tun

Wählen Sie max aus der Gruppe aus, die in Ansichten funktioniert

SELECT * FROM action a 
WHERE NOT EXISTS (
   SELECT 1 FROM action a2 
   WHERE a2.user_id = a.user_id 
   AND a2.action_date > a.action_date 
   AND a2.action_type = a.action_type
)
AND a.action_type = "CF"
Timo Huovinen
quelle
0

Wählen Sie die erste Zeile für jede Gruppe (wie in einer Spalte geordnet) in MySQL aus.

Wir haben:

eine Tabelle: mytable
eine Spalte, nach der wir bestellen: the_column_to_order_by
eine Spalte, nach der wir gruppieren möchten: the_group_by_column

Hier ist meine Lösung. Mit der inneren Abfrage erhalten Sie einen eindeutigen Satz von Zeilen, die als Doppelschlüssel ausgewählt sind. Die äußere Abfrage verbindet dieselbe Tabelle, indem beide Schlüssel (mit UND) verknüpft werden.

SELECT * FROM 
    ( 
        SELECT the_group_by_column, MAX(the_column_to_order_by) the_column_to_order_by 
        FROM mytable 
        GROUP BY the_group_by_column 
        ORDER BY MAX(the_column_to_order_by) DESC 
    ) as mytable1 
JOIN mytable mytable2 ON mytable2.the_group_by_column = 
mytablealiamytable2.the_group_by_column 
  AND mytable2.the_column_to_order_by = mytable1.the_column_to_order_by;

Zu Ihrer Information: Ich habe überhaupt nicht an Effizienz gedacht und kann auf die eine oder andere Weise nicht mit ihr sprechen.

Julian Orinyol
quelle
-3

Warum nicht das Schlüsselwort MySQL LIMIT verwenden?

SELECT [t2].[AnotherColumn], [t2].[SomeColumn]
FROM [Table] AS [t2]
WHERE (([t1].[SomeColumn] IS NULL) AND ([t2].[SomeColumn] IS NULL))
  OR (([t1].[SomeColumn] IS NOT NULL) AND ([t2].[SomeColumn] IS NOT NULL)
    AND ([t1].[SomeColumn] = [t2].[SomeColumn]))
ORDER BY [t2].[AnotherColumn]
LIMIT 1
GintsGints
quelle
1
Dies gibt die erste Zeile der gesamten Abfrage zurück , nicht die erste Zeile jeder Gruppe . Es soll ein Weg sein , diese für jede Gruppe zu tun, da , wie üblich ist diese Frage, aber die SQL - Gruppen waren zu sehr damit beschäftigt streiten über die Bedeutung von NULL mit praktischen Problemen wie diese zu stören.
Maury Markowitz