Wie erhalte ich den ersten und letzten Datensatz aus einer SQL-Abfrage?

74

Ich habe eine Tabelle in PostgreSQL und führe eine Abfrage mit mehreren Bedingungen aus, die mehrere Zeilen zurückgibt, die nach einer der Spalten sortiert sind. Im Allgemeinen ist es:

SELECT <some columns> 
FROM mytable
<maybe some joins here>
WHERE <various conditions>
ORDER BY date DESC

Jetzt bin ich nur daran interessiert, die erste und die letzte Zeile aus dieser Abfrage zu erhalten. Ich konnte sie außerhalb der Datenbank, innerhalb meiner Anwendung (und das ist, was ich tatsächlich mache) bekommen, fragte mich aber, ob ich für eine bessere Leistung nicht nur die 2 Datensätze aus der Datenbank bekommen sollte, an denen ich tatsächlich interessiert bin.

Und wenn ja, wie ändere ich meine Abfrage?

Kender
quelle
2
Verwenden Sie die Aggregatfunktionen MIN & MAX: postgresql.org/docs/8.2/static/tutorial-agg.html
OMG Ponies
2
@rexem: min & max funktioniert nicht für mehrere Spalten - und sie funktionieren nur für einzelne Spalten, wenn Sie nach dieser Spalte bestellen.
Vielleicht möchten Sie auch einen Blick darauf werfen SELECT DISTINCT ON (...) ... ORDER BY .... Siehe PostgreSQL-Dokumentation .
RhinoDevel

Antworten:

113

[Vorsichtsmaßnahme: Könnte nicht der effizienteste Weg sein, dies zu tun]:

(SELECT <some columns>
FROM mytable
<maybe some joins here>
WHERE <various conditions>
ORDER BY date DESC
LIMIT 1)

UNION ALL

(SELECT <some columns>
FROM mytable
<maybe some joins here>
WHERE <various conditions>
ORDER BY date ASC    
LIMIT 1)
Mitch Wheat
quelle
10
Ich denke, das Schlüsselwort 'Top' ist nur für SQL Server, MySQL / Postgre verwendet 'Limit'
Robo
2
Die Verwendung von UNION ALL beschleunigt dies geringfügig, da die Überprüfung auf Duplikate entfernt wird. Es wird sich natürlich in der Funktionsweise unterscheiden, wenn die erste und die letzte Zeile gleich sind - UNION gibt nur eine Zeile zurück, UNION ALL gibt dieselbe Zeile zweimal zurück.
Magnus Hagander
@Magnus Hagander: Ich bin mir nicht sicher, ob es schneller sein wird, wenn es höchstens 2 Reihen gibt. Zugegeben, ich würde normalerweise zwischen UNION und UNION ALL unterscheiden.
Mitch Wheat
Wenn ich die Abfrage so ausführe, wie sie hier ist, erhalte ich einen Syntaxfehler in der Nähe von UNION, möglicherweise weil es nur ein Limit und eine Reihenfolge nach geben muss. Ich habe es gelöst und die Abfragen in Klammern gesetzt, wie(SELECT ... LIMIT 1) UNION ALL (SELECT ... LIMIT 1)
Fermin Silva
Kann jemand erklären, warum dies möglicherweise nicht effizient ist?
Joseph K.
34

Vielleicht möchten Sie dies versuchen, möglicherweise schneller als zwei Abfragen:

select <some columns>
from (
    SELECT <some columns>,
           row_number() over (order by date desc) as rn,
           count(*) over () as total_count
    FROM mytable
    <maybe some joins here>
    WHERE <various conditions>
) t
where rn = 1
   or rn = total_count
ORDER BY date DESC
ein Pferd ohne Name
quelle
24

Erste Aufzeichnung:

SELECT <some columns> FROM mytable
<maybe some joins here>
WHERE <various conditions>
ORDER BY date ASC
LIMIT 1

Letzte Aufzeichnung:

SELECT <some columns> FROM mytable
<maybe some joins here>
WHERE <various conditions>
ORDER BY date DESC
LIMIT 1
Roboter
quelle
1
Die im anderen Kommentar erwähnte UNION ALL-Methode ist definitiv schneller als das Ausgeben von zwei Abfragen.
Magnus Hagander
17

letzte Aufzeichnung:

SELECT * FROM `aboutus` order by id desc limit 1

erste Aufzeichnung:

SELECT * FROM `aboutus` order by id asc limit 1
neha
quelle
2
Das ist ungültiges SQL für PostgreSQL (es verwendet standardmäßige doppelte Anführungszeichen "zum Zitieren von Objektnamen - und sie werden hier sowieso überhaupt nicht benötigt)
a_horse_with_no_name
@souleiman Jede Abfrage ist so schnell wie möglich. Der Abfrageplaner verwendet den entsprechenden Index und gibt so schnell wie möglich O (log (N)) zurück. Dies in zwei separaten Abfragen ist jedoch langsamer und / oder weniger effizient als eine Abfrage, wenn Sie immer sowohl die erste als auch die erste Abfrage wünschen letzte Aufzeichnung wie im OP angegeben. Verwenden Sie einfach UNION ALL (schneller) zwischen den beiden Abfragen (oder UNION, wenn Sie keine Duplikate möchten).
DrFriedParts
8

Bei allen bisher verfügbaren Vorgehensweisen muss der Scan zweimal durchgeführt werden, einer für die erste und einer für die letzte Zeile.

Mit der Fensterfunktion "ROW_NUMBER () OVER (...)" plus "WITH Queries" können Sie nur einmal scannen und beide Elemente abrufen.

Fensterfunktion: https://www.postgresql.org/docs/9.6/static/functions-window.html

MIT Abfragen: https://www.postgresql.org/docs/9.6/static/queries-with.html

Beispiel:

WITH scan_plan AS (
SELECT
    <some columns>,
    ROW_NUMBER() OVER (ORDER BY date DESC) AS first_row, /*It's logical required to be the same as major query*/
    ROW_NUMBER() OVER (ORDER BY date ASC) AS last_row /*It's rigth, needs to be the inverse*/
FROM mytable
<maybe some joins here>
WHERE <various conditions>
ORDER BY date DESC)

SELECT
    <some columns>
FROM scan_plan
WHERE scan_plan.first_row = 1 OR scan_plan.last_row = 1;

Auf diese Weise führen Sie Beziehungen, Filterungen und Datenmanipulationen nur einmal durch.

Versuchen Sie es mit EXPLAIN ANALYZE auf beiden Wegen.

Natan Medeiros
quelle
Lino Bossio
Das count(*) over () as total_countObige ist etwas leistungsfähiger, da nur eines verwendet wird WindowAggund der Datensatz ebenfalls nur einmal sortiert wird.
Ruloweb
5
SELECT <rows> FROM TABLE_NAME WHERE ROWID=(SELECT MIN(ROWID) FROM TABLE_NAME) 
UNION
SELECT <rows> FROM TABLE_NAME WHERE ROWID=(SELECT MAX(ROWID) FROM TABLE_NAME)

oder

SELECT * FROM TABLE_NAME WHERE ROWID=(SELECT MIN(ROWID) FROM TABLE_NAME) 
                            OR ROWID=(SELECT MAX(ROWID) FROM TABLE_NAME)
sundeepkumar dv
quelle
9
PostgreSQL hat keine rowid, es heißt ctiddort (und weder die Rowid von Oracle noch die Ctid von PostgreSQL garantieren eine Bestellung)
a_horse_with_no_name
5
Warum nicht einfacher machen: SELECT * FROM TABLE_NAME WHERE rowid=(SELECT MIN(rowid) FROM TABLE_NAME) OR rowid=(SELECT MAX(rowid) FROM TABLE_NAME)
Matt Kneiser
1
select *
from {Table_Name}
where {x_column_name}=(
    select d.{x_column_name} 
    from (
        select rownum as rno,{x_column_name}
        from {Table_Name})d
        where d.rno=(
            select count(*)
            from {Table_Name}));
Ampolu Balaji
quelle
1
-- Create a function that always returns the first non-NULL item
CREATE OR REPLACE FUNCTION public.first_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE SQL IMMUTABLE STRICT AS $$
        SELECT $1;
$$;


-- And then wrap an aggregate around it
CREATE AGGREGATE public.FIRST (
        sfunc    = public.first_agg,
        basetype = anyelement,
        stype    = anyelement
);

-- Create a function that always returns the last non-NULL item
CREATE OR REPLACE FUNCTION public.last_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE SQL IMMUTABLE STRICT AS $$
        SELECT $2;
$$;

-- And then wrap an aggregate around it
CREATE AGGREGATE public.LAST (
        sfunc    = public.last_agg,
        basetype = anyelement,
        stype    = anyelement
);

Habe es von hier bekommen: https://wiki.postgresql.org/wiki/First/last_(aggregate)

Sergey P. aka azurblau
quelle
1

In einigen Fällen - wenn nicht so viele Spalten - nützlich sind die WINDOW-Funktionen FIRST_VALUE () und LAST_VALUE ().

 SELECT
    FIRST_VALUE(timestamp) over (ORDER BY timestamp ASC) as created_dt,
    LAST_VALUE(timestamp) over (ORDER BY timestamp ASC) as last_update_dt,
    LAST_VALUE(action) over (ORDER BY timestamp ASC) as last_action
FROM events

Diese Abfrage sortiert Daten nur einmal.

Es kann verwendet werden, um die ersten und letzten Zeilen anhand einer ID abzurufen

SELECT DISTINCT
    order_id,
    FIRST_VALUE(timestamp) over (PARTITION BY order_id ORDER BY timestamp ASC) as created_dt,
    LAST_VALUE(timestamp) over (PARTITION BY order_id ORDER BY timestamp ASC) as last_update_dt,
    LAST_VALUE(action) over (PARTITION BY order_id ORDER BY timestamp ASC) as last_action

FROM events as x
Mikhail Sotnikov
quelle
0
SELECT 
    MIN(Column), MAX(Column), UserId 
FROM 
    Table_Name
WHERE 
    (Conditions)
GROUP BY 
    UserId DESC

oder

SELECT        
    MAX(Column) 
FROM            
    TableName
WHERE        
    (Filter)

UNION ALL

SELECT        
    MIN(Column)
FROM            
    TableName AS Tablename1
WHERE        
    (Filter)
ORDER BY 
    Column
Ashish Bhatt
quelle
0

So erhalten Sie den ersten und letzten Datensatz der Datenbank in c #.

SELECT TOP 1 * 
  FROM ViewAttendenceReport 
 WHERE EmployeeId = 4 
   AND AttendenceDate >='1/18/2020 00:00:00' 
   AND AttendenceDate <='1/18/2020 23:59:59'
 ORDER BY Intime ASC
 UNION
SELECT TOP 1 * 
  FROM ViewAttendenceReport 
 WHERE EmployeeId = 4 
   AND AttendenceDate >='1/18/2020 00:00:00' 
   AND AttendenceDate <='1/18/2020 23:59:59' 
 ORDER BY OutTime DESC; 
TaTa
quelle
0

Ich denke, dieser Code wird gleich und ist leichter zu lesen.

SELECT <some columns> 
FROM mytable
<maybe some joins here>
WHERE date >= (SELECT date from mytable)
OR date <= (SELECT date from mytable);
Kike Lebowski
quelle
2
Während dieser Code die Frage möglicherweise beantwortet, verbessert die Bereitstellung eines zusätzlichen Kontexts darüber, warum und / oder wie dieser Code die Frage beantwortet, ihren langfristigen Wert.
Igor F.
-1

Warum nicht verwenden order by asc limit 1und umgekehrt order by desc limit 1?

Fstarocka Burns
quelle