17. Korzystanie z SGBD i PostgreSQL
SGBD PostgreSQL jest dostępny bezpłatnie. Stanowi on alternatywę dla wersji „community” MySQL.
Wykorzystujemy go tutaj, aby pokazać, że migracja skryptów Python / MySQL do skryptów Python / PostgreSQL jest dość prosta.
W przypadku SGBD i MySQL architektura naszych skryptów wyglądała następująco:
W przypadku SGBD i PostgreSQL będzie ona wyglądać następująco:

17.1. Instalacja SGBD PostgreSQL
Dystrybucje SGBD i PostgreSQL są dostępne w URL i [https://www.postgresql.org/download/] (maj 2019 r.). Poniżej przedstawiamy instalację wersji dla systemu Windows 64-bitowego:


- w [1-4] należy pobrać instalator z SGBD;
Uruchamiamy pobrany instalator:

- w [6] należy wskazać folder instalacyjny;

- w [8] opcja [Stack Builder] nie jest potrzebna do tego, co chcemy tutaj zrobić;
- w [10] pozostaw domyślną wartość;

- w polu [12-13] wprowadzono hasło [root]. Będzie to hasło administratora SGBD, który nazywa się [postgres]. PostgreSQL nazywa go również superużytkownikiem;
- w polu [15] należy pozostawić wartość domyślną: jest to port nasłuchowy serwera SGBD;

- w polu [17] pozostaw wartość domyślną;
- w [19] – podsumowanie konfiguracji instalacji;


W systemie Windows SGBD PostgreSQL jest zainstalowany jako usługa systemu Windows uruchamiana automatycznie. W większości przypadków nie jest to pożądane. Zmienimy tę konfigurację. Wpisz [services] w pasku wyszukiwania systemu Windows [24-26]:

- W [29] widać, że usługa SGBD PostgreSQL działa w trybie automatycznym. Można to zmienić, otwierając właściwości usługi [30]:

- w przypadku usługi [31-32] należy ustawić uruchamianie w trybie ręcznym;
- w przypadku usługi [33] należy zatrzymać usługę;
Gdy zechcesz ręcznie uruchomić usługę SGBD, wróć do aplikacji [services], kliknij prawym przyciskiem myszy usługę [postgresql] (34) i uruchom ją (35).
17.2. Zarządzanie usługą PostgreSQL za pomocą narzędzia [pgAdmin]
Uruchom usługę systemu Windows o nazwie SGBD PostgreSQL (poprzedni akapit). Następnie, w taki sam sposób, w jaki uruchomiłeś narzędzie [services], uruchom narzędzie [pgadmin], które umożliwia zarządzanie SGBD, PostgreSQL i [1-3]:

Możliwe, że w pewnym momencie pojawi się prośba o podanie hasła superużytkownika. Nazywa się ono [postgres]. Hasło to zostało przez Państwa ustawione podczas instalacji programu SGBD. W niniejszym dokumencie podczas instalacji nadaliśmy superużytkownikowi hasło [root].
- w [4], [pgAdmin] jest aplikacją internetową;
- w [5] znajduje się lista serwerów PostgreSQL wykrytych przez [pgAdmin], w tym przypadku 1;
- w [6] – serwer PostgreSQL, który uruchomiliśmy;
- w [7] – bazy danych serwera SGBD, w tym przypadku 1;
- w [8] baza [postgresql] jest zarządzana przez superużytkownika [postgres];
Najpierw utwórzmy użytkownika o nazwie [admpersonnes] z hasłem [nobody]:


- w [17] wpisano [nobody];

- z [21] na kod SQL, który narzędzie [pgAdmin] wygeneruje w kierunku SGBD PostgreSQL. Jest to sposób na naukę języka SQL, będącego własnością PostgreSQL;
- w [22], po zatwierdzeniu przez kreatora [Save], utworzono użytkownika [admpersonnes];
Teraz tworzymy bazę danych [dbpersonnes]:

Klikamy prawym przyciskiem myszy na [23], a następnie na [24-25], aby utworzyć nową bazę danych. W zakładce [26] określamy nazwę bazy danych [27] oraz jej właściciela [admpersonnes] [28].

- w [30] – kod SQL tworzenia bazy;
- na [31], po zatwierdzeniu przez kreatora [Save], baza [dbpersonnes] zostaje utworzona;
Będziemy korzystać z bazy danych [dbpersonnes] za pomocą skryptów w języku Python.
17.3. Instalacja łącznika Python dla bazy danych SGBD i PostgreSQL

Na powyższym schemacie przedstawiono łącznik łączący skrypty w języku Python z SGBD i PostgreSQL. Istnieje ich kilka. Instalujemy łącznik [psycopg2]. Odbywa się to w terminalu Pythona (nie ma znaczenia, w jakim katalogu jest otwarty ten terminal). Łącznik instaluje się za pomocą polecenia [pip install psycopg2]:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\troiscouches\v01\tests>pip install psycopg2
Collecting psycopg2
Downloading psycopg2-2.8.5-cp38-cp38-win_amd64.whl (1.1 MB)
|| 1.1 MB 3.2 MB/s
Installing collected packages: psycopg2
Successfully installed psycopg2-2.8.5
17.4. Przeniesienie skryptów MySQL do skryptów PostgreSQL

- folder [1] ze skryptami MySQL jest duplikowany (Ctrl-C / Ctrl-V), a następnie nazwy plików są zmieniane, ale ich zawartość pozostaje bez zmian;
17.4.1. moduł [pgres_module]
Moduł ten jest kopią modułu [mysql_module] (patrz akapit |skrypt [mysql-04]: wykonanie pliku poleceń SQL|). Zmienia się importy:
Zamiast:
# importy
from mysql.connector import DatabaseError, InterfaceError
from mysql.connector.connection import MySQLConnection
from mysql.connector.cursor import MySQLCursor
piszemy:
# importy
from psycopg2 import DatabaseError, InterfaceError
from psycopg2.extensions import connection, cursor
Sygnatura funkcji [afficher_infos] brzmiała:
def afficher_infos(curseur: MySQLCursor):
Zmienia się na:
def afficher_infos(curseur: cursor)
Sygnatura funkcji [execute_list_of_commands] brzmiała:
def execute_list_of_commands(connexion: MySQLConnection, sql_commands: list,
suivi: bool = False, arrêt: bool = True, with_transaction: bool = True)
Zmienia się na:
def execute_list_of_commands(connexion: connection, sql_commands: list,
suivi: bool = False, arrêt: bool = True, with_transaction: bool = True):
Poza tym nic się nie zmienia.
17.4.2. skrypt [pgres_01]
Skrypt [pgres_01] jest kopią skryptu [mysql_01] (patrz akapit |skrypt [mysql-01]: połączenie z bazą danych MySQL – 1|). Wprowadza się w nim następujące zmiany:
Zamiast:
# import modułu mysql.connector
from mysql.connector import connect, DatabaseError, InterfaceError
piszemy:
# import modułu psycopg2
from psycopg2 import connect, DatabaseError, InterfaceError
Pozostałe elementy pozostają bez zmian. Wyniki są takie same jak w przypadku MySQL.
17.4.3. skrypt [pgres_02]
Skrypt [pgres_02] jest kopią skryptu [mysql_02] (patrz akapit |skrypt [mysql-02]: połączenie z bazą danych MySQL – 2|). Wprowadza się w nim następujące zmiany:
Zamiast:
# import modułu mysql.connector
from mysql.connector import DatabaseError, InterfaceError, connect
piszemy:
# import modułu psycopg2
from psycopg2 import DatabaseError, InterfaceError, connect
Wyniki nie są takie same jak w przypadku skryptu [mysql_02]:
Skrypt [pgres_02] wygląda następująco:
# import modułu mysql.connector
from psycopg2 import DatabaseError, InterfaceError, connect
# ---------------------------------------------------------------------------------
def connexion(host: str, database: str, login: str, pwd: str):
# loguje się, a następnie wylogowuje (login, hasło) z bazy danych [database] na serwerze [host]
# rzuca wyjątek DatabaseError w przypadku wystąpienia problemu
connexion = None
try:
# połączenie
connexion = connect(host=host, user=login, password=pwd, database=database)
print(
f"Connexion réussie à la base database={database}, host={host} sous l'identité user={login}, passwd={pwd}")
finally:
# zamyka się połączenie, jeśli zostało otwarte
if connexion:
connexion.close()
print("Déconnexion réussie\n")
# ---------------------------------------------- main
# dane logowania
USER = "admpersonnes"
PASSWD = "nobody"
HOST = "localhost"
DATABASE = "dbpersonnes"
# logowanie istniejącego użytkownika
try:
connexion(host=HOST, login=USER, pwd=PASSWD, database=DATABASE)
except (InterfaceError, DatabaseError) as erreur:
# wyświetla się komunikat o błędzie
print(erreur)
# logowanie użytkownika, który nie istnieje
try:
connexion(host=HOST, login="xx", pwd="yy", database=DATABASE)
except (InterfaceError, DatabaseError) as erreur:
# wyświetlany jest komunikat o błędzie
print(erreur)
Chociaż w wierszach 36–41 powinien pojawić się komunikat o błędzie informujący, że połączenie ze skryptem SGBD nie powiodło się, nic nie jest wyświetlane. W rzeczywistości, gdy przyjrzymy się tej kwestii bliżej, okazuje się, że w [except] w wierszach 35–37 wszystko przebiega prawidłowo, ale zmienna [erreur] przyjmuje wartość [None]. Dotyczy to wersji 2.8.4 łącznika [psycopg2].
Problem ten można obejść, wpisując komunikat ogólny, ale mniej precyzyjny:
# logowanie użytkownika, który nie istnieje
try:
connexion(host=HOST, login="xx", pwd="yy", database=DATABASE)
except (InterfaceError, DatabaseError) as erreur:
# wyświetlany jest błąd
print(f"Erreur de connexion à la base [{DATABASE}] par l'utilisateur [xx/yy]")
Wyniki są wówczas następujące:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/databases/postgresql/pgres_02.py
Connexion réussie à la base database=dbpersonnes, host=localhost sous l'identité user=admpersonnes, passwd=nobody
Déconnexion réussie
Erreur de connexion à la base [dbpersonnes] par l'utilisateur [xx/yy]
Process finished with exit code 0
17.4.4. skrypt [pgres_03]
Skrypt [pgres_03] jest kopią skryptu [mysql_03] (patrz akapit |skrypt [mysql-03]: utworzenie tabeli MySQL|). Wprowadza się w nim następujące zmiany:
Zamiast:
from mysql.connector import DatabaseError, InterfaceError, connect
from mysql.connector.connection import MySQLConnection
piszemy:
from psycopg2 import DatabaseError, InterfaceError, connect
from psycopg2.extensions import connection
Ponadto sygnatura funkcji [execute_sql], która brzmiała:
def execute_sql(connexion: MySQLConnection, update: str):
zmienia się na:
def execute_sql(connexion: connection, update: str):
Pozostałe elementy pozostają bez zmian. Wynik jest następujący:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/databases/postgresql/pgres_03.py
create table personnes (id int PRIMARY KEY, prenom varchar(30) NOT NULL, nom varchar(30) NOT NULL, age integer NOT NULL, unique(nom,prenom)) : requête réussie
Process finished with exit code 0
Obecność tabeli [personnes] można sprawdzić za pomocą narzędzia administracyjnego [pgAdmin]:

17.4.5. skrypt [pgres_04]
Skrypt [pgres_04] jest kopią skryptu [mysql_04] (patrz akapit |Skrypt [mysql-04]: wykonanie pliku poleceń SQL|). Wykorzystuje on moduł [pgres_module]:
# pobierana jest konfiguracja aplikacji
import config_04
config = config_04.configure()
# ścieżka systemowa jest skonfigurowana – można przeprowadzić importy
import sys
from pgres_module import execute_file_of_commands
from psycopg2 import connect, DatabaseError, InterfaceError
Pozostałe elementy pozostają bez zmian.
Tworzymy konfigurację [pgres pgres-04 without_transaction] tak samo, jak to zrobiono w akapicie |skrypt [mysql-04]: wykonanie pliku poleceń SQL|. Podobnie tworzymy konfigurację [pgres pgres-04 with_transaction].
Wykonanie konfiguracji [pgres pgres-04 without_transaction] daje następujące wyniki:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/databases/postgresql/pgres_04.py false
--------------------------------------------------------------------
Exécution du fichier SQL C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\databases\postgresql/data/commandes.sql sans transaction
--------------------------------------------------------------------
[drop table if exists personnes] : Exécution réussie
nombre de lignes modifiées : -1
[create table personnes (id int primary key, prenom varchar(30) not null, nom varchar(30) not null, age integer not null, unique (nom,prenom))] : Exécution réussie
nombre de lignes modifiées : -1
[insert into personnes(id, prenom, nom, age) values(1, 'Paul','Langevin',48)] : Exécution réussie
nombre de lignes modifiées : 1
[insert into personnes(id, prenom, nom, age) values (2, 'Sylvie','Lefur',70)] : Exécution réussie
nombre de lignes modifiées : 1
[select prenom, nom, age from personnes] : Exécution réussie
prenom, nom, age,
*****************
('Paul', 'Langevin', 48)
('Sylvie', 'Lefur', 70)
*****************
xx : Erreur (ERREUR: erreur de syntaxe sur ou près de « xx »
LINE 1: xx
^
)
[insert into personnes(id, prenom, nom, age) values (3, 'Pierre','Nicazou',35)] : Exécution réussie
nombre de lignes modifiées : 1
[insert into personnes(id, prenom, nom, age) values (4, 'Geraldine','Colou',26)] : Exécution réussie
nombre de lignes modifiées : 1
[insert into personnes(id, prenom, nom, age) values (5, 'Paulette','Girond',56)] : Exécution réussie
nombre de lignes modifiées : 1
[select prenom, nom, age from personnes] : Exécution réussie
prenom, nom, age,
*****************
('Paul', 'Langevin', 48)
('Sylvie', 'Lefur', 70)
('Pierre', 'Nicazou', 35)
('Geraldine', 'Colou', 26)
('Paulette', 'Girond', 56)
*****************
[select nom,prenom from personnes order by nom asc, prenom desc] : Exécution réussie
nom, prenom,
************
('Colou', 'Geraldine')
('Girond', 'Paulette')
('Langevin', 'Paul')
('Lefur', 'Sylvie')
('Nicazou', 'Pierre')
************
[select nom,prenom,age from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc] : Exécution réussie
nom, prenom, age,
*****************
('Nicazou', 'Pierre', 35)
('Colou', 'Geraldine', 26)
*****************
[insert into personnes(id, prenom, nom, age) values(6, 'Josette','Bruneau',46)] : Exécution réussie
nombre de lignes modifiées : 1
[update personnes set age=47 where nom='Bruneau'] : Exécution réussie
nombre de lignes modifiées : 1
[select nom,prenom,age from personnes where nom='Bruneau'] : Exécution réussie
nom, prenom, age,
*****************
('Bruneau', 'Josette', 47)
*****************
[delete from personnes where nom='Bruneau'] : Exécution réussie
nombre de lignes modifiées : 1
[select nom,prenom,age from personnes where nom='Bruneau'] : Exécution réussie
nom, prenom, age,
*****************
*****************
--------------------------------------------------------------------
Exécution terminée
--------------------------------------------------------------------
Il y a eu 1 erreur(s)
xx : Erreur (ERREUR: erreur de syntaxe sur ou près de « xx »
LINE 1: xx
^
)
Process finished with exit code 0
- wiersz 5: konieczne było zmodyfikowanie polecenia usuwania tabeli [personnes]. W przeciwieństwie do łącznika MySQL, łącznik PostgreSQL zgłasza wyjątek, jeśli tabela przeznaczona do usunięcia nie istnieje. Polecenie [drop table] ma wariant [drop table if exists], który nie generuje wyjątku, jeśli tabela nie istnieje. Wykorzystaliśmy go w tym przypadku. Jest to przykład sytuacji, w której dwa polecenia SGBD nie zachowują się tak samo w analogicznych okolicznościach;
Tabela [personnes] w narzędziu [pgAdmin] wygląda następująco:

Wykonanie konfiguracji [pgres pgres_04 with_transaction] daje następujące wyniki:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/databases/postgresql/pgres_04.py true
--------------------------------------------------------------------
Exécution du fichier SQL C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\databases\postgresql/data/commandes.sql avec transaction
--------------------------------------------------------------------
[drop table if exists personnes] : Exécution réussie
nombre de lignes modifiées : -1
[create table personnes (id int primary key, prenom varchar(30) not null, nom varchar(30) not null, age integer not null, unique (nom,prenom))] : Exécution réussie
nombre de lignes modifiées : -1
[insert into personnes(id, prenom, nom, age) values(1, 'Paul','Langevin',48)] : Exécution réussie
nombre de lignes modifiées : 1
[insert into personnes(id, prenom, nom, age) values (2, 'Sylvie','Lefur',70)] : Exécution réussie
nombre de lignes modifiées : 1
[select prenom, nom, age from personnes] : Exécution réussie
prenom, nom, age,
*****************
('Paul', 'Langevin', 48)
('Sylvie', 'Lefur', 70)
*****************
xx : Erreur (ERREUR: erreur de syntaxe sur ou près de « xx »
LINE 1: xx
^
)
--------------------------------------------------------------------
Exécution terminée
--------------------------------------------------------------------
Il y a eu 1 erreur(s)
xx : Erreur (ERREUR: erreur de syntaxe sur ou près de « xx »
LINE 1: xx
^
)
Process finished with exit code 0
Tabela [personnes] w narzędziu [pgAdmin] wygląda następująco:

W tym przypadku wynik różni się od tego uzyskanego przy użyciu MySQL. Jeśli skrypty zostaną uruchomione w tych samych warunkach, tj. po uruchomieniu skryptu bez transakcji, otrzymujemy następujące wyniki:
- w przypadku MySQL tabela [personnes] jest pusta;
- w przypadku skryptu PostgreSQL tabela [personnes] nie jest pusta;
Różnica wynika z odmiennych sposobów, w jakie te dwa skrypty SGBD cofają transakcję:
- MySQL nie cofa zleceń [drop table] i [create table]. W rezultacie otrzymujemy pustą tabelę [personnes];
- PostgreSQL cofa zlecenia [drop table] i [create table]. Tabela powraca do stanu, w jakim znajdowała się przed wykonaniem skryptu z transakcją;
17.4.6. skrypt [pgres_05]
Skrypt [pgres_05] jest kopią skryptu [mysql_05] (zob. akapit |Skrypt [mysql-05]: korzystanie z zapytań parametrycznych|). Skrypt został zmodyfikowany w następujący sposób:
Zamiast:
# importy
from mysql.connector import connect, DatabaseError, InterfaceError
piszemy:
# importy
from psycopg2 import connect, DatabaseError, InterfaceError
Pozostałe elementy pozostają bez zmian.
Wyniki uzyskane w [pgAdmin] są następujące:

17.5. Wniosek
Przeniesienie skryptów MySQL do skryptów PostgreSQL przebiegło dość łatwo. Jest to wyjątek. Oba skrypty SGBD nie obsługują tych samych reguł nazewnictwa obiektów co skrypty SQL (bazy danych, tabele, kolumny, ograniczenia, typy danych…), mają też niekompatybilne rozszerzenia SQL… Aby zapewnić łatwą migrację, w obu przypadkach należy trzymać się standardu SQL, nie próbując korzystać z zastrzeżonych rozszerzeń SGBD. Odbywa się to jednak kosztem wydajności.