Postgres Tipps und Tricks

Tipps und Tricks im Umgang mit SQL

Große Tabellen durchsuchen

exists statt concat

Where Bedingungen mit verketteten Strings (Concat) verlangsamen die Laufzeit beträchtlich. Beispiel:

select  distinct  sgd_join_id,pbe_serial,pbe_von,pbe_bis,merkmal_bis+1,'Folgetag  Endzeitpunkt  '
quelle  from
tmp_zeitraum1  where  merkmal_bis<pbe_bis
and  sgd_join_id::varchar(90)||pbe_serial::varchar(90)||merkmal_bis  not  in  (select
sgd_join_id::varchar(90)||pbe_serial::varchar(90)||merkmal_von  from  tmp_zeitraum1_copy);

Schneller geht es  mit "exists", hier ein Beispiel für Postgres:

select  distinct  sgd_join_id,pbe_serial,pbe_von,pbe_bis,merkmal_bis+1,'Folgetag  Endzeitpunkt  '
quelle  from  tmp_zeitraum1  where  merkmal_bis<pbe_bis
and  not  exists  (select  1  from    tmp_zeitraum1_copy  c  where  tmp_zeitraum1.sgd_join_id  =  c.sgd_join_id  and
tmp_zeitraum1.pbe_serial  =  c.pbe_serial  and  tmp_zeitraum1.merkmal_bis=  c.merkmal_von)

meine kann auch Informix, noch mal checken ggfs.

langsame Updates vermeiden (lateral)

Ab PostgreSQL 9.3

https://www.postgresql.org/docs/current/queries-table-expressions.html#QUERIES-LATERAL

Subselects

Subselects vermeiden

Eine Spalte

Wenn Sie eine Spalte mit einem Subselect ändern wollen, geht dies mit Variante 1

Aber Achtung: wenn ein solcher Update in Variante 1 beim Subselect einen Fehler "A subquery has not returned exactly one row" liefert, entfällt dieser bei Variante 2a - Postgres nimmt einfach den "erstbesten" Datensatz.

zwei Spalten

<#if  SQLdialect='Postgres'<#if  SQLdialect='Postgres'>
UPDATE  tmp_buch
SET  z_name  =  gxstage_zp.druck,
z_name2  =  gxstage_zp.lang_1
FROM  gxstage_zp
WHERE  z_nr  is  not  null  and  gxstage_zp.apnr  =  tmp_buch.z_nr;
<#else>

UPDATE  tmp_buch
SET  (z_name,  z_name2)  =
((SELECT  druck,  lang_1
FROM  gxstage_zp
WHERE  gxstage_zp.apnr=tmp_buch.z_nr))
WHERE  z_nr  is  not  null;

Aggregatfunktionen

Postgres With

Die WITH-Klausel erlaubt es, ohne temp.Tabellen Resultsets zwischenzuspeichern, vergl. postgres with.

Beschleunigung von Updates

statt

update  ${tabellenname}  set  ${cifx_key.name}=(select  min(apnr)  from  trans_cifx  Z
where  Z.key=${cifx_key.key}
and  Z.systeminfo_id=${systeminfo_id}
and    Z.sourcesystem_id=${tabellenname}.${cifx_key.name}
and  Z.sourcesystem=${Quellsystem_var}  --hisinone
);

schneller

with  tmp_${cifx_key.key}  (${cifx_key.name},  apnr)  as  (
select  S.${cifx_key.name},  min(apnr)  as  apnr
from  trans_cifx  Z  inner  join  ${tabellenname}  S
on  Z.sourcesystem_id=S.${cifx_key.name}
where  Z.key=${cifx_key.key}
and  Z.systeminfo_id=${systeminfo_id}
and  Z.sourcesystem=${Quellsystem_var}
group  by  S.${cifx_key.name}
update  ${tabellenname}  set  ${cifx_key.name}=(select  apnr  from  tmp_${cifx_key.key}
where  ${tabellenname}.${cifx_key.name}  =  tmp_${cifx_key.key}.${cifx_key.name})
where  ${tabellenname}.${cifx_key.name}  in  (select  ${cifx_key.name}  from  tmp_${cifx_key.key})

evtl noch schneller:

with...
update  ${tabellenname}  set  ${cifx_key.name}=tmp_${cifx_key.key}.apnr
from  tmp_${cifx_key.key}  where  ${tabellenname}.${cifx_key.name}  =  tmp_${cifx_key.key}.${cifx_key.name}
and  ${tabellenname}.${cifx_key.name}  in  (select  ${cifx_key.name}  from  tmp_${cifx_key.key})

Beispiel:

create  index  ix_tmp_erg1  on  tmp_erg  (buchungsab_fb);
with  tmp_fb  (buchungsab_fb,  ktobez)  as  (
select  F.buchungsab_fb,  max(ktobez)  as  ktobz
from  fin_buchab_fb  F  inner  join  tmp_erg  T
on  T.buchungsab_fb=F.buchungsab_fb
where  F.jahr=<>

group  by  1)
update  tmp_erg  T  set  buchungsab_fb_bez=tmp_fb.ktobez
from  tmp_fb  where  T.buchungsab_fb  =  tmp_fb.buchungsab_fb;

Rekursives Durchsuchen einer key-parent-Hierarchie

Postgres erlaubt auch rekursive Suche, hier ein Beispiel für das Organigramm:

with  recursive  org_rec  (ebene,name,key_apnr,parent  ,gueltig_seit,gueltig_bis,sort1)
as  (select  1  as  ebene,O.name,  O.key_apnr,O.parent  ,O.gueltig_seit,O.gueltig_bis,trim(O.key_apnr)  as  sort1
from  organigramm  O
where  O.parent  is  null
and  today()  between  O.gueltig_seit  and  O.gueltig_bis
union  all  (select  R.ebene  +1  as  ebene,O2.name,  O2.key_apnr,O2.parent  ,O2.gueltig_seit,O2.gueltig_bis,  R.sort1  ||  '_'  ||  trim(O2.key_apnr)
from  organigramm  O2,org_rec  R
where  R.key_apnr=O2.parent
and  today()  between  R.gueltig_seit  and  R.gueltig_bis
and  today()  between  O2.gueltig_seit  and  O2.gueltig_bis
)
)
--search  depth  first  by  name  set  sort1
select  ebene,trim(name)  as  name,trim(key_apnr)  as  key,trim(parent)  as  parent,trim(sort1)  as  sort1  from    org_rec
order  by  sort1;

ergibt z.B.

ebene  |                      name                      |      key      |  parent  |              sort1
------+--------------------------+---------+--------+-------------------
1  |  Universität                            |  000          |                |  000
2  |  Lehre  Hochschule                  |  _9000      |  000        |  000__9000
3  |  01-  WISO-Fakultät                |  01            |  _9000    |  000__9000_01
3  |  02-  Rechtswiss.  F.              |  02            |  _9000    |  000__9000_02
3  |  03-  Medizinische  F.            |  03            |  _9000    |  000__9000_03
3  |  04-  Philosophische  F.        |  04            |  _9000    |  000__9000_04
3  |  05-  Math.-Nat.  F.                |  05            |  _9000    |  000__9000_05
3  |  06-  Humanwiss.  F.                |  06            |  _9000    |  000__9000_06

Dieses Resultset könnte man direkt in Sichten als Quelle nutzen.

Kreuztabellen

Postgres unterstützt seit 9.5 auch Kreuztabellen. Zuerst muss man das contrib-Paket installieren:

apt-get  install  postgresql-contrib

und dann in der DB die extension aktivieren:

CREATE  EXTENSION  tablefunc;

Beispiel:

CREATE  TABLE  ct(id  SERIAL,  rowid  TEXT,  attribute  TEXT,  value  TEXT);
INSERT  INTO  ct(rowid,  attribute,  value)  VALUES('test1','att1','val1');
INSERT  INTO  ct(rowid,  attribute,  value)  VALUES('test1','att2','val2');
INSERT  INTO  ct(rowid,  attribute,  value)  VALUES('test1','att3','val3');
INSERT  INTO  ct(rowid,  attribute,  value)  VALUES('test1','att4','val4');
INSERT  INTO  ct(rowid,  attribute,  value)  VALUES('test2','att1','val5');
INSERT  INTO  ct(rowid,  attribute,  value)  VALUES('test2','att2','val6');
INSERT  INTO  ct(rowid,  attribute,  value)  VALUES('test2','att3','val7');
INSERT  INTO  ct(rowid,  attribute,  value)  VALUES('test2','att4','val8');

SELECT  *
FROM  crosstab(
'select  rowid,  attribute,  value
from  ct
where  attribute  =  att2  or  attribute  =  att3
order  by  1,2')
AS  mycrosstab(row_name  text,  category_1  text,  category_2  text,  category_3  text);


row_name  |  category_1  |  category_2  |  category_3
----------+------------+------------+------------
test1        |  val2              |  val3              |
test2        |  val6              |  val7              |

Beispiel für SuperX:

SELECT  *  from    crosstab(
'select  sem_rueck_beur_ein::varchar(255)  as  sem_rueck_beur_ein,geschlecht::varchar(255)  as  geschlecht,
sum(summe)::integer  as  summe  FROM    kenn_stg_aggr  group  by  1,2  order  by  1,2'  )
AS  ct(sem_rueck_beur_ein  varchar(255),  geschlecht_m  integer,geschlecht_w  integer);

ergibt:

sem_rueck_beur_ein  |  geschlecht_m  |  geschlecht_w
--------------------+--------------+--------------
20081                            |                  2116  |                    779
20082                            |                  2564  |                    893
20091                            |                  2311  |                    814
20092                            |                  2796  |                    974
(4  rows)

Hier ist ein Link auf das Thema in der Postgres Doku vom 9.5:

https://www.postgresql.org/docs/9.5/tablefunc.html

Gruppierung

Zeilen zu Spalten

Mit der array_agg Funktion kann man mehrere Zeilen zu einer zusammenfassen. Das folgende Beispiel gruppiert nach Matrikelnr., Abschluss und Studiengang-Nr, so daß alle Fächer nach Fachnummer sortiert in einem Array stehen:

select  S.matrikel_nr,S.studiengang_nr,D.abschluss_str,  array_agg(D.stg_str)  as  faecher
into  temp  tmp_1
from
sos_stg_aggr  S,  dim_studiengang  D
where  S.tid_stg=D.tid
and  S.sem_rueck_beur_ein=20142
and  S.stichtag=1
group  by  1,2,3  ,fach_nr
order  by  1,2  ,3,fach_nr;

Man kann dieses Array dann wieder ausgeben

select  matrikel_nr,studiengang_nr,abschluss_str,  faecher[1],faecher[2]
from  tmp_1;
select  matrikel_nr,studiengang_nr,abschluss_str,  array_to_string(faecher,  ',','leer')
from  tmp_1;

Das letzte Argument ist optional und ersetzt NULLs durch einen String.

Fortgeschrittene arithmetische Funktionen

Mit SQL kann man leicht summieren, mit der Funktion "SUM()". Wenn Sie stattdessen multiplizieren wollen, gibt es hier

https://blog.jooq.org/how-to-write-a-multiplication-aggregate-function-in-sql/

eine gute Anleitung.

Postgres Copy

Postgres Copy mit Headern in beliebiger Reihenfolge

Man kann mit Postgres-Copy auch CSV Einspielen mit Headern bei der die Reihenfolge der Spalten in der CSV-Datei nicht unbedingt mit der Reihenfolge in der Datenbank übereinstimmen muss:

COPY    targetTable  (col2,col3,col1)    FROM  STDIN  (FORMAT  csv,    DELIMITER  '^',NULL    ,ENCODING  'UTF8',  header  true)

Die Angabe von Header true führt bei COPY nur dazu, dass die 1. Zeile des CSV ignoriert wird, man muss in Klammern die Spaltennamen angeben.

Aktuell wird dies unterstützt durch Klasse de.superx.bin.GxstageCSVImport in Branch gxstage1.4_openjdk2 -

ist in der Datei db/module/gxstage/rohdaten/webservice_jars/superx5.0.jar enthalten . (Noch nicht in Master und standard SuperX5.0)

Die Klasse liest header aus 1. Zeiele und baut entsprechend COPY Befehl mit (colB,colA,colC) auf.

Die

Umgehen von Interpretation von Anführungszeichen

Bei CSV-Dateien mit Anführungszeichen " wird dies standardmäßig von Postgres-Copy als Quote interpretiert, wenn nicht geschlossen, kann es zu Problemen kommen. Mit folgendem Trick kann " als Quote ignoriert werden:

Man gibt als QUOTE ein backspace Zeichen an, dass wohl nicht in CSV vorkommt QUOTE '\b'

//default quote ist ", kann aber vorkommen, daher quote Zeichen auf nicht erwartetes Backspace Oktal 10 setzen

"COPY  "  +  targetTable  +  cols+  "    FROM  STDIN  (FORMAT  csv,  QUOTE  '\b',  DELIMITER  '"+delim+"',NULL  ,ENCODING  '"+  encoding+"'"+(header?",  HEADER  true":"https://superxhosting.de/wiki/index.php/null")+")";

Entladen einer Tabelle mit Schema

Das folgende Kommando entlädt eine Tabelle inkl. SQL-Schema:

pg_dump  $DBNAME  -t  costage_bw_hzb_arten  >  costage_bw_hzb_arten.sql

weitere Postgres-Tricks

letzter Tag des Monats ermitteln

SELECT  (date_trunc('month',  '2017-01-05'::date)  +  interval  '1  month'  -  interval  '1  day')::date

AS end_of_month;

Quellcode einer Funktion einsehen

In psql können Sie die Funktionen abrufen, mit

\df

erhalten Sie eine Liste mit den Namen, und mit

select  proname,prosrc  from  pg_proc  where  proname=  _Name_der_Funktion_;

können Sie den Quellcode abrufen.

Werkzeuge

PGAdmin

Der Web-PGAdmin bietet die Möglichkeit, Datenbanken ohne direkten DB-Zugriff zu verwalten. Die Kommunikation läuft über eine webbasierte Middleware (Tomcat).

Es wird zunächst ein User für die jew. Datenbank angelegt bspw. schmidt

CREATE  USER  schmidt  WITH      NOSUPERUSER  PASSWORD  'password';
GRANT  USAGE  ON  SCHEMA  public  TO  schmidt  ;
GRANT  SELECT  ON  ALL  TABLES  IN  SCHEMA  public  TO  schmidt  ;
ALTER  DEFAULT  PRIVILEGES  IN  SCHEMA  public  GRANT  SELECT  ON  TABLES  TO  schmidt  ;

Danach unter -Servername-/pgadmin4/ als AdminUser anmelden.

Es wird ein neuer User schmidt angelegt. Dafür wird zunächst das User Management geöffnet.

pgadmin  einrichten
 

Im User Management werden die Benutzerdaten angelegt, bestehend aus einer E-Mail-Adresse und einem Passwort. Die E-Mail-Adresse darf fiktiv sein.

pgadmin  einrichten
 

Anschließend mit dem neu angelegten User anmelden. Nach erfolreicher Anmeldung wird ein Server registriert.

pgadmin  einrichten
 

Der Server bekommt einen frei wählbaren Namen.

pgadmin  einrichten
 

Im Reiter Connection wird die Serveradresse angegeben. Zudem werden die Benutzerdaten für den Datenbankzugriff angegeben, welche am Anfang diese Abschnittes erstellt wurden.

pgadmin  einrichten
 

Es werden verschiedene Datenbanken aufgelistet, der User hat aber nur Zugriff auf die ihm zugehörige Datenbank.

pgadmin  einrichten
 

Für andere Datenbanken wird der Zugriff verweigert.

pgadmin  einrichten