Happy Party oder ein paar Zeilen Erinnerungen an die Bekanntschaft mit der Partitionierung in PostgreSQL10

Vorwort oder wie die Idee der Partitionierung entstand

Der Beginn der Geschichte hier: Erinnerst du dich, wie alles begann? Alles war zum ersten Mal und neu. Nachdem fast alle Ressourcen zur Optimierung der Abfrage zu diesem Zeitpunkt erschöpft waren, stellte sich die Frage – was nun? So entstand die Idee der Partitionierung.

Happy Party oder ein paar Zeilen Erinnerungen an die Bekanntschaft mit der Partitionierung in PostgreSQL10

Lyrische Abschweifung:
Genau 'zu diesem Zeitpunkt', denn wie sich herausstellte, gab es ungenutzte Optimierungsreserven. Danke asmm und Habr !

Wie können wir den Auftraggeber also noch glücklicher machen und gleichzeitig unsere Fähigkeiten verbessern?

Wenn wir alles so weit wie möglich vereinfachen, dann gibt es nur zwei grundlegende Wege, die Performance der Datenbank signifikant zu verbessern:
1) Extensiver Weg – Ressourcen aufstocken, Konfiguration ändern;
2) Intensiver Weg – Abfragen optimieren.

Da, ich wiederhole, zu diesem Zeitpunkt nicht klar war, was man noch an der Abfrage ändern könnte, um sie zu beschleunigen, wurde der Weg gewählt – Änderungen im Tabellen-Design.

Also – die zentrale Frage bleibt – was und wie wollen wir ändern?

Anfangsbedingungen

Erstens gibt es so ein ERD (symbolisch vereinfacht dargestellt):
Happy Party oder ein paar Zeilen Erinnerungen an die Bekanntschaft mit der Partitionierung in PostgreSQL10
Hauptmerkmale:

  1. die Beziehung 'viele zu viele'
  2. die Tabelle hat bereits einen potenziellen Schlüssel für die Partitionierung

Ursprüngliche Abfrage:

SELECT
            p."PARAMETER_ID" as  parameter_id,
            pc."PC_NAME" AS pc_name,
            pc."CUSTOMER_PARTNUMBER" AS customer_partnumber,
            w."LASERMARK" AS lasermark,
            w."LOTID" AS lotid,
            w."REPORTED_VALUE" AS reported_value,
            w."LOWER_SPEC_LIMIT" AS lower_spec_limit,
            w."UPPER_SPEC_LIMIT" AS upper_spec_limit,
            p."TYPE_CALCUL" AS type_calcul,
            s."SHIPMENT_NAME" AS shipment_name,
            s."SHIPMENT_DATE" AS shipment_date,
            extract(year from s."SHIPMENT_DATE") AS year,
            extract(month from s."SHIPMENT_DATE") as month,
            s."REPORT_NAME" AS report_name,
            p."SPARAM_NAME" AS SPARAM_name,
            p."CUSTOMERPARAM_NAME" AS customerparam_name
        FROM data w INNER JOIN shipment s ON s."SHIPMENT_ID" = w."SHIPMENT_ID"
             INNER JOIN parameters p ON p."PARAMETER_ID" = w."PARAMETER_ID"
             INNER JOIN shipment_pc sp ON s."SHIPMENT_ID" = sp."SHIPMENT_ID"
             INNER JOIN pc pc ON pc."PC_ID" = sp."PC_ID"
             INNER JOIN ( SELECT w2."LASERMARK" , MAX(s2."SHIPMENT_DATE") AS "SHIPMENT_DATE"
                          FROM shipment s2 INNER JOIN data w2 ON s2."SHIPMENT_ID" = w2."SHIPMENT_ID" 
                          GROUP BY w2."LASERMARK"
                         ) md ON md."SHIPMENT_DATE" = s."SHIPMENT_DATE" AND md."LASERMARK" = w."LASERMARK"
        WHERE 
             s."SHIPMENT_DATE" >= '2018-07-01' AND s."SHIPMENT_DATE" <= '2018-09-30';

Ergebnisse der Ausführung in der Testdatenbank:
Kosten : 502 997.55
Ausführungszeit: 505 Sekunden.

Was sehen wir? Eine gewöhnliche Abfrage über einen zeitlichen Ausschnitt.
Wir treffen eine einfache logische Annahme: Wenn es eine Stichprobe eines zeitlichen Ausschnitts gibt, hilft uns das? Richtig – die Partitionierung.

Was zu partitionieren?

Auf den ersten Blick scheint die Wahl offensichtlich – die deklarative Partitionierung der Tabelle „shipment“ nach dem Schlüssel „SHIPMENT_DATE“ (wenn wir zu weit vorausgreifen – am Ende kam es in der Produktion etwas anders.).

Wie partitionieren?

Diese Frage ist ebenfalls nicht allzu schwierig. Glücklicherweise gibt es in PostgreSQL 10 jetzt einen benutzerfreundlichen Partitionierungsmechanismus.
Also:

  1. Wir speichern das Dump der ursprünglichen Tabelle – pg_dump source_table
  2. Wir löschen die ursprüngliche Tabelle – drop table source_table
  3. Wir erstellen die übergeordnete Tabelle mit Partitionierung nach Bereich – create table source_table
  4. Wir erstellen Partitionen – create table source_table, create index
  5. Wir importieren das Dump, das in Schritt 1 erstellt wurde – pg_restore

Skripte zur Partitionierung

Zur Vereinfachung wurden die Schritte 2, 3 und 4 in einem Skript zusammengefasst.

Also:
Wir speichern das Dump der ursprünglichen Tabelle

pg_dump postgres --file=/dump/shipment.dmp --format=c --table=shipment --verbose > /dump/shipment.log 2>&1

Wir löschen die ursprüngliche Tabelle + wir erstellen die übergeordnete Tabelle mit Partitionierung nach Bereich + wir erstellen Partitionen

--create_partition_shipment.sql
do language plpgsql $$
declare 
rec_shipment_date RECORD;
partition_name varchar;
index_name varchar;
current_year varchar;
current_month varchar;
begin_year varchar;
begin_month varchar;
next_year varchar;
next_month varchar;
first_flag boolean;
i integer;
begin
  RAISE NOTICE 'TEMPORÄRE TABELLE FÜR SHIPMENT_DATE ERSTELLEN';
  CREATE TEMP TABLE tmp_shipment_date as select distinct "SHIPMENT_DATE" from shipment order by "SHIPMENT_DATE";

  RAISE NOTICE 'TABELLE shipment LÖSCHEN';
  drop table shipment cascade;
  
  CREATE TABLE public.shipment
  (
    "SHIPMENT_ID" integer NOT NULL DEFAULT nextval('shipment_shipment_id_seq'::regclass),
    "SHIPMENT_NAME" character varying(30) COLLATE pg_catalog."default",
    "SHIPMENT_DATE" timestamp without time zone,
    "REPORT_NAME" character varying(40) COLLATE pg_catalog."default"
  )
  PARTITION BY RANGE ("SHIPMENT_DATE")
  WITH (
      OIDS = FALSE
  )
  TABLESPACE pg_default;

  RAISE NOTICE 'PARTITIONEN FÜR TABELLE shipment ERSTELLEN';

  current_year:='0';
  current_month:='0';

  begin_year := '0';
  begin_month := '0';
  next_year := '0';
  next_month := '0';

  FOR rec_shipment_date IN SELECT * FROM tmp_shipment_date LOOP
      
      RAISE NOTICE 'SHIPMENT_DATE=%',rec_shipment_date."SHIPMENT_DATE";
      
      current_year := date_part('year', rec_shipment_date."SHIPMENT_DATE");
      current_month := date_part('month', rec_shipment_date."SHIPMENT_DATE");

      IF to_number(current_month,'99') = to_date(begin_year||'.'||begin_month, 'YYYY.MM') AND 
         to_date(current_year||'.'||current_month, 'YYYY.MM') < to_date(next_year||'.'||next_month, 'YYYY.MM') AND 
         NOT first_flag
      THEN
         CONTINUE;
      ELSE
       --NEUE Grenzen nur für das zweite und folgende Mal
       begin_year := current_year;
       begin_month := current_month;
   
        IF current_month = '12' THEN
          next_year := date_part('year', rec_shipment_date."SHIPMENT_DATE" + interval '1 year');
        ELSE
          next_year := current_year;
        END IF;
     
       next_month := date_part('month', rec_shipment_date."SHIPMENT_DATE" + interval '1 month');

      END IF;

      partition_name := 'shipment_shipment_date_'||begin_year||'-'||begin_month||'-01-'|| next_year||'-'||next_month||'-01';
 
     EXECUTE format('CREATE TABLE ' || quote_ident(partition_name) || ' PARTITION OF shipment FOR VALUES FROM (%L) TO (%L)', current_year||'-'||current_month||'-01', next_year||'-'||next_month||'-01');

      index_name := partition_name||'_shipment_id_idx';
      RAISE NOTICE 'INDEX NAME =%', index_name;
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("SHIPMENT_ID") TABLESPACE pg_default');

      --Erstes Flag zurücksetzen
      first_flag := false;
   
  END LOOP;

end
$$;

Importiere Dump

pg_restore -d postgres --data-only --format=c --table=shipment --verbose shipment.dmp > /tmp/data_dump/shipment_restore.log 2>&1

Überprüfen Sie die Ergebnisse der Partitionierung

Was haben wir als Ergebnis? Der vollständige Text des Implementierungsplans ist lang und langweilig, daher kann man sich ziemlich auf die abschließenden Zahlen beschränken.

War

Kosten: 502 997.55
Ausführungszeit: 505 Sekunden.

Wurde

Kosten: 77 872.36
Ausführungszeit: 79 Sekunden.

Ein durchaus gutes Ergebnis. Wir haben sowohl die Kosten als auch die Ausführungszeit reduziert. Dadurch zeigt die Verwendung von Partitionierung die erwartete Wirkung und insgesamt - ohne Überraschungen.

Den Auftraggeber erfreuen

Die Testergebnisse wurden dem Auftraggeber zur Prüfung vorgelegt. Und nach der Durchsicht wurde ihm ein etwas unerwartetes Urteil gefällt: „Ausgezeichnet, partitionieren Sie die Tabelle „data“.”

Ja, aber wir haben doch eine ganz andere Tabelle „shipment“ untersucht, die Tabelle „data“ hat kein Feld „SHIPMENT_DATE“.

Kein Problem, fügen Sie es hinzu, ändern Sie es. Hauptsache, der Auftraggeber ist mit dem Ergebnis zufrieden, die Details der Umsetzung sind nicht so wichtig.

Partitionieren wir die Haupttabelle „data“

Es haben sich eigentlich keine besonderen Schwierigkeiten ergeben. Obwohl sich der Partitionierungsalgorithmus natürlich ein wenig geändert hat.

Fügen wir die Spalte „SHIPMENT_DATA“ zur Tabelle „data“ hinzu

psql -h host -U database -d user
=> ALTER TABLE data ADD COLUMN "SHIPMENT_DATE" timestamp without time zone ;

Füllen wir die Werte der Spalte „SHIPMENT_DATA“ in der Tabelle „data“ mit den Werten der gleichnamigen Spalte aus der Tabelle „shipment“

-----------------------------
--update_data.sql
--Aktualisierung für die geänderte Tabelle "data" mit Werten von "shipment_data" aus der Tabelle "shipment"
--Version 1.0
do language plpgsql $$
declare 
rec_shipment_data RECORD ;
shipment_date timestamp without time zone ; 
row_count integer ;
total_rows integer ;
begin

  select count(*) into total_rows from shipment ; 
  RAISE NOTICE 'Insgesamt %', total_rows;
  row_count:= 0 ;

  FOR rec_shipment_data IN SELECT * FROM shipment LOOP

   update data set "SHIPMENT_DATE" = rec_shipment_data."SHIPMENT_DATE" where "SHIPMENT_ID" = rec_shipment_data."SHIPMENT_ID";
   
   row_count:=  row_count +1 ;
   RAISE NOTICE 'Zeilenanzahl = % , von %', row_count, total_rows;
  END LOOP;

end
$$;

Speichern wir das Dump der Tabelle „data“

pg_dump postgres --file=\/dump\/data.dmp --format=c --table=data --verbose > \/dump\/data.log 2>&1<\/source

Stellen wir die partitionierte Tabelle „data“ neu her

--create_partition_data.sql
--Erstellen von Partitionen für die Tabelle "Waferdaten" nach dem Bereich "Versanddatum" mit einer Dauer von einem Monat
--Version 1.0
do language plpgsql $$
declare 
rec_shipment_date RECORD ;
partition_name varchar;
index_name varchar;
current_year varchar ;
current_month varchar ;
begin_year varchar ;
begin_month varchar ;
next_year varchar ;
next_month varchar ;
first_flag boolean ;
i integer ;

begin

  RAISE NOTICE 'TEMPORÄRE TABELLE FÜR VERSANDDATUM ERSTELLEN';
  CREATE TEMP TABLE tmp_shipment_date as select distinct "SHIPMENT_DATE" from shipment order by "SHIPMENT_DATE" ;


  RAISE NOTICE 'TABELLE Daten löschen';
  drop table data cascade ;


  RAISE NOTICE 'PARTITIONIERTE TABELLE Daten ERSTELLEN';
  
  CREATE TABLE public.data
  (
    "RUN_ID" integer,
    "LASERMARK" character varying(20) COLLATE pg_catalog."default" NOT NULL,
    "LOTID" character varying(80) COLLATE pg_catalog."default",
    "SHIPMENT_ID" integer NOT NULL,
    "PARAMETER_ID" integer NOT NULL,
    "INTERNAL_VALUE" character varying(75) COLLATE pg_catalog."default",
    "REPORTED_VALUE" character varying(75) COLLATE pg_catalog."default",
    "LOWER_SPEC_LIMIT" numeric,
    "UPPER_SPEC_LIMIT" numeric , 
    "SHIPMENT_DATE" timestamp without time zone
  )
  PARTITION BY RANGE ("SHIPMENT_DATE")
  WITH (
    OIDS = FALSE
  )
  TABLESPACE pg_default ;


  RAISE NOTICE 'PARTITIONEN FÜR DIE TABELLE Daten ERSTELLEN';

  current_year:='0';
  current_month:='0';

  begin_year := '0' ;
  begin_month := '0'  ;
  next_year := '0' ;
  next_month := '0'  ;
  i := 1;

  FOR rec_shipment_date IN SELECT * FROM tmp_shipment_date LOOP
      
      RAISE NOTICE 'VERSANDDATUM=%',rec_shipment_date."SHIPMENT_DATE";
      
      current_year := date_part('year' ,rec_shipment_date."SHIPMENT_DATE");
      current_month := date_part('month' ,rec_shipment_date."SHIPMENT_DATE") ; 

      --Init Grenzen
      IF   begin_year = '0' THEN
       RAISE NOTICE '***Grenzen initialisieren';
       first_flag := true ; --Flag für das erste Mal
       begin_year := current_year ;
       begin_month := current_month ;   
   
        IF current_month = '12' THEN
          next_year := date_part('year' ,rec_shipment_date."SHIPMENT_DATE" + interval '1 year') ;
        ELSE
          next_year := current_year ;
        END IF;
     
       next_month := date_part('month' ,rec_shipment_date."SHIPMENT_DATE" + interval '1 month') ;

      END IF;

--      RAISE NOTICE 'current_year=% , current_month=% ',current_year,current_month;
--      RAISE NOTICE 'begin_year=% , begin_month=% ',begin_year,begin_month;
--      RAISE NOTICE 'next_year=% , next_month=% ',next_year,next_month;

      -- Überprüfen Sie das aktuelle Datum in den Grenzen NICHT beim ersten Mal

      RAISE NOTICE 'Aktuelle Daten = %',to_char( to_date( current_year||'.'||current_month, 'YYYY.MM'), 'YYYY.MM');
      RAISE NOTICE 'Beginn Daten = %',to_char( to_date( begin_year||'.'||begin_month, 'YYYY.MM'), 'YYYY.MM');
      RAISE NOTICE 'Nächste Daten = %',to_char( to_date( next_year||'.'||next_month, 'YYYY.MM'), 'YYYY.MM');

      IF to_date( current_year||'.'||current_month, 'YYYY.MM') >= to_date( begin_year||'.'||begin_month, 'YYYY.MM') AND 
         to_date( current_year||'.'||current_month, 'YYYY.MM') < to_date( next_year||'.'||next_month, 'YYYY.MM') AND 
         NOT first_flag 
      THEN
         RAISE NOTICE '***FORTSETZEN';
         CONTINUE ; 
      ELSE
       --NEUE Grenzen nur für das zweite und folgende Mal 
       RAISE NOTICE '***NEUE GRENZEN';
       begin_year := current_year ;
       begin_month := current_month ;   
   
        IF current_month = '12' THEN
          next_year := date_part('year' ,rec_shipment_date."SHIPMENT_DATE" + interval '1 year') ;
        ELSE
          next_year := current_year ;
        END IF;
     
       next_month := date_part('month' ,rec_shipment_date."SHIPMENT_DATE" + interval '1 month') ;


      END IF;      

      IF to_number(current_month,'99') < 10 THEN
        current_month := '0'||current_month ; 
      END IF ;

      IF to_number(begin_month,'99') < 10 THEN
        begin_month := '0'||begin_month ; 
      END IF ;

      IF to_number(next_month,'99') < 10 THEN
        next_month := '0'||next_month ; 
      END IF ;

      RAISE NOTICE 'current_year=% , current_month=% ',current_year,current_month;
      RAISE NOTICE 'begin_year=% , begin_month=% ',begin_year,begin_month;
      RAISE NOTICE 'next_year=% , next_month=% ',next_year,next_month;

      partition_name := 'data_'||begin_year||begin_month||'01_'||next_year||next_month||'01'  ;

      RAISE NOTICE 'PARTITION NUMMER % , TABELLENNAME =%',i , partition_name;
      
      EXECUTE format('CREATE TABLE ' || quote_ident(partition_name) || ' PARTITION OF data FOR VALUES FROM ( %L ) TO ( %L )  ' , begin_year||'-'||begin_month||'-01' , next_year||'-'||next_month||'-01'  ) ; 

      index_name := partition_name||'_shipment_id_parameter_id_idx';
      RAISE NOTICE 'INDEXNAME =%',index_name;
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("SHIPMENT_ID", "PARAMETER_ID") TABLESPACE pg_default ' ) ; 

      index_name := partition_name||'_lasermark_idx';
      RAISE NOTICE 'INDEXNAME =%',index_name;
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("LASERMARK" COLLATE pg_catalog."default") TABLESPACE pg_default ' ) ; 

      index_name := partition_name||'_shipment_id_idx';
      RAISE NOTICE 'INDEXNAME =%',index_name;
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("SHIPMENT_ID") TABLESPACE pg_default ' ) ; 

      index_name := partition_name||'_parameter_id_idx';
      RAISE NOTICE 'INDEXNAME =%',index_name;
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("PARAMETER_ID") TABLESPACE pg_default ' ) ; 

      index_name := partition_name||'_shipment_date_idx';
      RAISE NOTICE 'INDEXNAME =%',index_name;
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("SHIPMENT_DATE") TABLESPACE pg_default ' ) ; 

      --Erstes Mal-Flag löschen
      first_flag := false ;

  END LOOP;
end
$$;

Wir laden das Dump-File hoch, das in Schritt 3 erstellt wurde.

pg_restore -h host -u user -d datenbank --data-only --format=c --table=data --verbose data.dmp > data_restore.log 2>&1

Wir erstellen einen separaten Abschnitt für alte Daten

---------------------------------------------------
--create_partition_for_old_dates.sql
--Erstelle Partitionen, um alte Daten zu behalten 
--Version 1.0
do language plpgsql $$
declare 
rec_shipment_date RECORD ;
partition_name varchar;
index_name varchar;

begin

      SELECT min("SHIPMENT_DATE") AS min_date INTO rec_shipment_date from data ;

      RAISE NOTICE 'Das alte Datum ist %',rec_shipment_date.min_date ;

      partition_name := 'data_old_dates'  ;

      RAISE NOTICE 'PARTITIONSNAME IST %',partition_name;

      EXECUTE format('CREATE TABLE ' || quote_ident(partition_name) || ' PARTITION OF data FOR VALUES FROM ( %L ) TO ( %L )  ' , '1900-01-01' , 
              to_char( rec_shipment_date.min_date,'YYYY')||'-'||to_char(rec_shipment_date.min_date,'MM')||'-01'  ) ; 

      index_name := partition_name||'_shipment_id_parameter_id_idx';
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("SHIPMENT_ID", "PARAMETER_ID") TABLESPACE pg_default ' ) ; 

      index_name := partition_name||'_lasermark_idx';
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("LASERMARK" COLLATE pg_catalog."default") TABLESPACE pg_default ' ) ; 

      index_name := partition_name||'_shipment_id_idx';
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("SHIPMENT_ID") TABLESPACE pg_default ' ) ; 

      index_name := partition_name||'_parameter_id_idx';
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("PARAMETER_ID") TABLESPACE pg_default ' ) ; 

      index_name := partition_name||'_shipment_date_idx';
      EXECUTE format('CREATE INDEX ' || quote_ident(index_name) || ' ON '|| quote_ident(partition_name) ||' USING btree ("SHIPMENT_DATE") TABLESPACE pg_default ' ) ; 

end
$$;

Zusammenfassende Ergebnisse:

War
Kosten: 502 997.55
Ausführungszeit: 505 Sekunden.

Wurde
Kosten: 68 533.70
Ausführungszeit: 69 Sekunden

Befriedigend, durchaus befriedigend. Und wenn man bedenkt, dass es gelungen ist, den Mechanismus der Partitionierung in PostgreSQL 10 mehr oder weniger zu erlernen — ein hervorragendes Ergebnis.

Lyrische Abschweifung

Könnte es noch besser gemacht werden — JA, DAS KANN ES!Dafür muss ein MATERIALIZED VIEW verwendet werden.
CREATE MATERIALIZED VIEW LASERMARK_VIEW

CREATE MATERIALIZED VIEW LASERMARK_VIEW 
AS
SELECT w."LASERMARK" , MAX(s."SHIPMENT_DATE") AS "SHIPMENT_DATE"
FROM shipment s INNER JOIN data w ON s."SHIPMENT_ID" = w."SHIPMENT_ID" 
GROUP BY w."LASERMARK" ;

CREATE INDEX lasermark_vw_shipment_date_ind on lasermark_view USING btree ("SHIPMENT_DATE") TABLESPACE pg_default;
analyze lasermark_view ;

Ein weiteres Mal schreiben wir die Abfrage um:
Abfrage unter Verwendung des materialized view

SELECT
            p."PARAMETER_ID" als parameter_id,
            pc."PC_NAME" AS pc_name,
            pc."CUSTOMER_PARTNUMBER" AS customer_partnumber,
            w."LASERMARK" AS lasermark,
            w."LOTID" AS lotid,
            w."REPORTED_VALUE" AS reported_value,
            w."LOWER_SPEC_LIMIT" AS lower_spec_limit,
            w."UPPER_SPEC_LIMIT" AS upper_spec_limit,
            p."TYPE_CALCUL" AS type_calcul,
            s."SHIPMENT_NAME" AS shipment_name,
            s."SHIPMENT_DATE" AS shipment_date,
            extract(year from s."SHIPMENT_DATE") AS year,
            extract(month from s."SHIPMENT_DATE") as month,
            s."REPORT_NAME" AS report_name,
            p."STC_NAME" AS STC_name,
            p."CUSTOMERPARAM_NAME" AS customerparam_name
        VON data w INNER JOIN shipment s ON s."SHIPMENT_ID" = w."SHIPMENT_ID"
             INNER JOIN parameters p ON p."PARAMETER_ID" = w."PARAMETER_ID"
             INNER JOIN shipment_pc sp ON s."SHIPMENT_ID" = sp."SHIPMENT_ID"
             INNER JOIN pc pc ON pc."PC_ID" = sp."PC_ID"
             INNER JOIN LASERMARK_VIEW md ON md."SHIPMENT_DATE" = s."SHIPMENT_DATE" UND md."LASERMARK" = w."LASERMARK"
        WO
              s."SHIPMENT_DATE" >= '2018-07-01' UND s."SHIPMENT_DATE" <= '2018-09-30';

Und wir erhalten ein weiteres Ergebnis:
War
Kosten: 502 997.55
Ausführungszeit: 505 Sekunden

Wurde
Kosten: 42 481.16
Ausführungszeit: 43 Sekunden.

Obwohl ein so vielversprechendes Ergebnis täuschend sein kann, müssen die Präsentationen aktualisiert werden. Das endgültige Datenabrufzeit wird also nicht viel helfen. Aber als Experiment ist das durchaus interessant.

Tatsächlich, wie sich herausstellte, nochmals vielen Dank asmm und Habr !- Die Anfrage kann noch verbessert werden.

Nachwort

Also, der Kunde ist zufrieden. Und wissen soll die Situation nutzen.

Neue Aufgabe: Was können wir uns einfallen lassen, um zu vertiefen und zu erweitern?

Und da fällt mir ein – Leute, wir haben keine Überwachung unserer PostgreSQL-Datenbanken.

Ehrlich gesagt, es gibt eine gewisse Überwachung in Form von Cloud Watch auf AWS. Aber welchen Nutzen hat diese Überwachung für den DBA? Praktisch keinen.

Wenn sich die Chance bietet, etwas Nützliches und Interessantes auch für sich selbst zu tun, darf man diese Chance nicht ungenutzt lassen …
DENN

Happy Party oder ein paar Zeilen Erinnerungen an die Bekanntschaft mit der Partitionierung in PostgreSQL10

So sind wir zum Interessantesten gekommen:

3. Dezember 2018.
Entscheidung zur Einleitung von Arbeiten zur Untersuchung der bestehenden Möglichkeiten zur Überwachung der Abfrageleistung von PostgreSQL.

Aber das ist eine ganz andere Geschichte.

Fortsetzung folgt …

Quelle: habr.com

60GB SSD 8Gb DDR4