it-swarm.com.de

Wie kann ich eine Zeilennummer generieren, ohne eine Fensterfunktion zu verwenden?

Wie generieren Sie in PostgreSQL eine Zeilennummer:

  • OHNE Fensterfunktion (wie row_number () )
  • OHNE Temp-Tabelle
  • Nur mit einem einzigen SELECT

Hier sind einige Beispieldaten zum Spielen:

CREATE TEMP TABLE foo AS 
SELECT * FROM ( VALUES
  ('wgates', 'Gates', 'William' ),
  ('wgrant', 'Grant', 'Wallace' ),
  ('jjones', 'Jones', 'John' ),
  ('psmith', 'Smith', 'Paul' )
) AS t(name_id, last_name, first_name);

Die gewünschte Ausgabe wäre:

 row_number │ name_id │ last_name │ first_name 
────────────┼─────────┼───────────┼────────────
          1 │ wgates  │ Gates     │ William
          2 │ wgrant  │ Grant     │ Wallace
          3 │ jjones  │ Jones     │ John
          4 │ psmith  │ Smith     │ Paul

Einige dieser Methoden können schwierig werden. Bitte erläutern Sie Ihre Antworten. Ich kann mir auch zwei Kategorien von Antworten vorstellen, die funktionieren:

  • daten mit einem UNIQUE oder PRIMARY KEY (wir können hier immer noch name_id verwenden)
  • nichts UNIQUE überhaupt.

Alle Funktionen für die neueste Version von PostgreSQL sind in der Tabelle enthalten.

Letztendlich benötige ich einen eindeutigen Schlüssel für eine Tabelle ohne ID, damit ich ihn gegen eine gegenseitige Verknüpfung aktualisieren kann. Ich frage auch aus einfacher Neugier.

3
Evan Carroll

UNIQUE Spalte erforderlich

Ein Ansatz, den ich gefunden habe (in SIMULATING ROW NUMBER IN POSTGRESQL PRE 8.4 von Leo Hsu und Regina Obe), heißt "The all in one WTF" . Es wurde leicht angepasst, aber es ist erstaunlich.

SELECT row_number, name_id, last_name, first_name
FROM people
CROSS JOIN (
  SELECT array_agg(name_id ORDER BY last_name, first_name) AS id FROM people
) AS oldids
CROSS JOIN generate_series(1, (SELECT count(*) FROM people))
  AS gs(row_number)
WHERE id[row_number] = people.name_id;

Gehen wir also das Kaninchenloch hinunter:

  1. Erstellen Sie ein ARRAY[] Einer bestimmten Spalte und generieren Sie keine zusätzlichen Zeilen.

    SELECT *
    FROM people
    CROSS JOIN (
      SELECT array_agg(name_id ORDER BY last_name, first_name) AS id
      FROM people
    ) AS oldids;
    
     name_id │ last_name │ first_name │              id               
    ─────────┼───────────┼────────────┼───────────────────────────────
     jjones  │ Jones     │ John       │ {wgates,wgrant,jjones,psmith}
     psmith  │ Smith     │ Paul       │ {wgates,wgrant,jjones,psmith}
     wgates  │ Gates     │ William    │ {wgates,wgrant,jjones,psmith}
     wgrant  │ Grant     │ Wallace    │ {wgates,wgrant,jjones,psmith}
    
  2. Erstellen Sie ein kartesisches Produkt mit einer generate_series(1..last_row). Um die letzte Zeile zu erhalten, führen wir eine Unterauswahl mit count() durch.

     SELECT *
     FROM people
     CROSS JOIN (
       SELECT array_agg(name_id ORDER BY last_name, first_name) AS id FROM people
     ) AS oldids
     CROSS JOIN generate_series(1, (SELECT count(*) FROM people))
       AS gs(row_number);
    
      name_id │ last_name │ first_name │              id               │ row_number 
     ─────────┼───────────┼────────────┼───────────────────────────────┼────────────
      jjones  │ Jones     │ John       │ {wgates,wgrant,jjones,psmith} │          1
      psmith  │ Smith     │ Paul       │ {wgates,wgrant,jjones,psmith} │          1
      wgates  │ Gates     │ William    │ {wgates,wgrant,jjones,psmith} │          1
      wgrant  │ Grant     │ Wallace    │ {wgates,wgrant,jjones,psmith} │          1
      jjones  │ Jones     │ John       │ {wgates,wgrant,jjones,psmith} │          2
      psmith  │ Smith     │ Paul       │ {wgates,wgrant,jjones,psmith} │          2
      wgates  │ Gates     │ William    │ {wgates,wgrant,jjones,psmith} │          2
      wgrant  │ Grant     │ Wallace    │ {wgates,wgrant,jjones,psmith} │          2
      jjones  │ Jones     │ John       │ {wgates,wgrant,jjones,psmith} │          3
      psmith  │ Smith     │ Paul       │ {wgates,wgrant,jjones,psmith} │          3
      wgates  │ Gates     │ William    │ {wgates,wgrant,jjones,psmith} │          3
      wgrant  │ Grant     │ Wallace    │ {wgates,wgrant,jjones,psmith} │          3
      jjones  │ Jones     │ John       │ {wgates,wgrant,jjones,psmith} │          4
      psmith  │ Smith     │ Paul       │ {wgates,wgrant,jjones,psmith} │          4
      wgates  │ Gates     │ William    │ {wgates,wgrant,jjones,psmith} │          4
      wgrant  │ Grant     │ Wallace    │ {wgates,wgrant,jjones,psmith} │          4
    
  3. Jetzt kommt die Magie, wir verwenden die Zeilennummer als Index für das Array, das wir erstellt haben. Da das Array eine Funktion von: (a) der Spalte UNIQUE und (b) der Reihenfolge in der Menge ist, können wir das kartesische Produkt reduzieren und die Zeilennummer beibehalten. Wir fügen lediglich die Klausel WHERE id[row_number] = people.name_id; Hinzu.

1
Evan Carroll

Wenn Sie sich Sorgen um die Leistung machen, verwenden Sie row_number ohne order by um ein Sortieren zu vermeiden.

row_number() over ()

Identifizieren Sie eine gute technische PK für die Entfernung von Duplikaten

Das ist eine ganz andere Frage als eine Problemumgehung für row_number() zu finden.

In Postgres können Sie dafür ctid verwenden. Keine Notwendigkeit für Fensterfunktionen oder langsame und nicht skalierbare Problemumgehungen.


Um die direkte Frage zu beantworten:

Dies kann ohne Fensterfunktionen durchgeführt werden, aber dies wird schrecklich langsam sein:

select name_id, last_name, first_name, 
       (select count(*)
        from the_table t2
        where t2.name_id <= t1.name_id) as row_number
from the_table t1
order by name_id;

Das Obige ist identisch mit:

select name_id, last_name, first_name, 
       row_number() over (order by name_id) as row_number
from the_table
order by name_id;

Aber die Lösung mit einer Fensterfunktion wird ist ein viel schneller. Wenn Sie keine Bestellung benötigen, verwenden Sie

select name_id, last_name, first_name, 
       row_number() over () as row_number
from the_table
order by name_id;

Auf diese Weise erhalten Sie keine "stabile" Zeilennummer, aber sie wird eindeutig sein.

Eine andere mögliche Alternative besteht darin, eine Sequenz zu erstellen und dann nextval() in der select-Anweisung zu verwenden.

Keine UNIQUE Spalte erforderlich, aber eine eindeutige Reihenfolge muss möglich sein

Diese Methode erfordert keine einzelne eindeutige Spalte , erfordert jedoch eine eindeutige Reihenfolge. Dies ist effektiv die Fensterversion von rank() und nicht row_number(); rank() ist jedoch row_number(), wenn Sie eine eindeutige Reihenfolge erhalten können. Ich habe diesen Ansatz auf SIMULIEREN DER REIHENNUMMER IN POSTGRESQL PRE 8.4 von Leo Hsu und Regina Obe basiert. Er wird als "plattformübergreifende Methode" aufgeführt.

SELECT (
  SELECT COUNT(*) FROM people
  WHERE 
  (COALESCE(people.last_name,'') || COALESCE(people.first_name,'')) <= 
  (COALESCE(oldtable.last_name,'') || COALESCE(oldtable.first_name,''))
) AS row_number,
  oldtable.*
FROM people AS oldtable
ORDER BY oldtable.last_name, oldtable.first_name;

In den Kommentaren dieser Seite finden wir eine leicht vereinfachte Version, die pg-spezifisch gemacht wurde

SELECT (
  SELECT COUNT(*) FROM people
  WHERE 
  ROW(people.last_name, people.first_name) <= 
  ROW(oldtable.last_name,first_name)
) AS row_number,
  oldtable.*
FROM people AS oldtable
ORDER BY oldtable.last_name, oldtable.first_name;

Es gibt wirklich keinen eleganten Weg, dies zu brechen ... Wir haben eine

SELECT oldtable.*
FROM people AS oldtable
ORDER BY oldtable.last_name, oldtable.first_name;

Jetzt führen wir eine korrelierte Unterabfrage in SELECT durch, die vergleicht, wie viele Zeilen <= Die aktuelle Zeile sind.

  • Im ersten Beispiel führen wir den Vergleich mit der datenbankunabhängigen Methode zum Verketten von Spalten durch, um etwas zu erstellen, das wahrscheinlich eindeutig machen sollte.
  • Im zweiten Beispiel führen wir den Vergleich mit dem Konstruktor PostgreSQL-spezifischer ROW()-Konstruktor durch

Es ist keine einfache Abfrage, sie aufzuschlüsseln, aber wir können eine einfachere Tabelle erstellen.

CREATE TABLE foobar AS
SELECT x FROM generate_series(1,10)
  AS t(x) ORDER BY random();

SELECT
  x,
  (SELECT count(*) FROM foobar AS f2 WHERE f2.x <= f1.x)
FROM foobar AS f1
ORDER BY x;

In diesem Beispiel noch einmal

  1. nehmen Sie ein ungeordnetes Set, das eine eindeutige Bestellung ermöglicht
  2. bestellen Sie das Set
  3. vergleichen Sie es mit sich selbst, um zu sehen, wie viele Zeilen <= die aktuelle Zeile sind.

Hier wäre die Ausgabe des obigen vereinfachten Beispiels. Wir vereinfachen nur, indem wir eine einzelne Spalte mit einer eindeutigen Reihenfolge versehen (anstatt eine zusammengesetzte Reihenfolge und den Schutz vor Nullen zu umgehen). Abgesehen davon können wir ROW(people.*) <= ROW(oldtable.*) vergleichen, wenn Zeilen durch eine Reihenfolge der Spalten von links nach rechts eindeutig sind

 x  | count 
----+-------
  1 |     1
  2 |     2
  3 |     3
  4 |     4
  5 |     5
  6 |     6
  7 |     7
  8 |     8
  9 |     9
 10 |    10
1
Evan Carroll