!pip install edurel
from edurel.core.er_schema_man import ERSchemaMan
from edurel.core.rel_schema_man import RelSchemaMan
from edurel.core.duckdb_man import DuckDbMan
from edurel.utils.md import display_sql, display_md, display_yaml
from edurel.utils.sql import transpile_postgres_sql, validate_postgres_sql
from edurel.llm.conversation_rel import DataGenConversation, SQLGenConversation
from edurel.llm.conversation_er import ERRequirementsConversation, ERDesignConversation
er_yaml = """
entities:
- entityname: Lehrperson
key: LPID
attributes:
- attributename: Name
type: VARCHAR(100)
- attributename: EMail
type: VARCHAR(100)
nullable: True
"""
erman = ERSchemaMan.fromStr(er_yaml)
erman.display_mermaid_diagram(direction="LR", height="200px")
relman = RelSchemaMan.fromAST(erman.get_rel())
relman.display_mermaid_diagram(direction="LR", height="200px")
display_sql(relman.get_sql())
CREATE TABLE Lehrperson (
LPID INTEGER NOT NULL,
Name VARCHAR(100) NOT NULL,
EMail VARCHAR(100),
PRIMARY KEY (LPID)
);
er_yaml = """
entities:
- entityname: Lehrperson
key: LPID
attributes:
- attributename: Name
type: VARCHAR(100)
- attributename: EMail
type: VARCHAR(100)
nullable: True
- entityname: Gebaeude
key: GID
attributes:
- attributename: Bez
type: VARCHAR(50)
relationships:
- relationshipname: hat_buero_in
entities:
- targetentity: Lehrperson
cardinality: OPTIONAL_MANY
- targetentity: Gebaeude
cardinality: OPTIONAL_ONE
"""
erman = ERSchemaMan.fromStr(er_yaml)
erman.display_mermaid_diagram(direction="LR", height="200px")
relman = RelSchemaMan.fromAST(erman.get_rel())
relman.display_mermaid_diagram(direction="LR", height="300px")
display_sql(relman.get_sql())
CREATE TABLE Lehrperson (
LPID INTEGER NOT NULL,
Name VARCHAR(100) NOT NULL,
EMail VARCHAR(100),
GID INTEGER,
PRIMARY KEY (LPID)
);
CREATE TABLE Gebaeude (
GID INTEGER NOT NULL,
Bez VARCHAR(50) NOT NULL,
PRIMARY KEY (GID)
);
ALTER TABLE Lehrperson
ADD CONSTRAINT fk_hat_buero_in FOREIGN KEY (GID) REFERENCES Gebaeude (GID);
er_yaml = """
entities:
- entityname: Modul
key: MID
attributes:
- attributename: Bez
type: VARCHAR(50)
valuelists:
- valuelistname: Modulart
values:
- Pflicht
- Wahlpflicht
- Wahl
many_to_one_from_entities:
- sourceentity: Modul
"""
erman = ERSchemaMan.fromStr(er_yaml)
erman.display_mermaid_diagram(direction="TB", height="200px")
relman = RelSchemaMan.fromAST(erman.get_rel())
relman.display_mermaid_diagram(direction="LR", height="250px")
display_sql(relman.get_sql())
CREATE TABLE Modul (
MID INTEGER NOT NULL,
Bez VARCHAR(50) NOT NULL,
ModulartID INTEGER NOT NULL,
PRIMARY KEY (MID)
);
CREATE TABLE Modulart (
ID INTEGER NOT NULL,
Description VARCHAR(100) NOT NULL,
IsValid INTEGER NOT NULL,
SortOrder INTEGER NOT NULL,
PRIMARY KEY (ID)
);
ALTER TABLE Modul
ADD CONSTRAINT fk_Modul_Modulart FOREIGN KEY (ModulartID) REFERENCES Modulart (ID);
INSERT INTO Modulart (ID, Description, IsValid, SortOrder) VALUES (1, 'Pflicht', 1, 1);
INSERT INTO Modulart (ID, Description, IsValid, SortOrder) VALUES (2, 'Wahlpflicht', 1, 2);
INSERT INTO Modulart (ID, Description, IsValid, SortOrder) VALUES (3, 'Wahl', 1, 3);
er_yaml = """
entities:
- entityname: Kunde
key: KID
attributes:
- attributename: Name
type: VARCHAR(100)
- entityname: Kundenkategorie
key: KKID
attributes:
- attributename: Bez
type: VARCHAR(30)
- attributename: BeginGueltig
type: DATE
- attributename: EndeGueltig
type: DATE
- entityname: Lieferant
key: LID
attributes:
- attributename: Name
type: VARCHAR(100)
- entityname: Artikel
key: AID
attributes:
- attributename: Bez
type: VARCHAR(200)
associative_entities:
- associationname: Bestellung
identification:
localkey: BID
keytype: INTEGER
associations:
- targetentity: Kunde
cardinality: OPTIONAL_ONE
attributes:
- attributename: BestellDatum
type: DATE
- associationname: Bestellposition
identification:
localkey: LfdNr
keytype: INTEGER
global:
- targetentity: Bestellung
associations:
- targetentity: Artikel
cardinality: OPTIONAL_ONE
attributes:
- attributename: Menge
type: INTEGER
- associationname: Verkaufkondition
identification:
global:
- targetentity: Kundenkategorie
role: fuer_KK
- targetentity: Artikel
role: VK_Artikel
attributes:
- attributename: Preis
type: DECIMAL(9,2)
- associationname: Einkaufskondition
identification:
global:
- targetentity: Lieferant
role: fuer_Lieferant
- targetentity: Artikel
role: EK_Artikel
attributes:
- attributename: DauerInTagen
type: INTEGER
- attributename: Preis
type: DECIMAL(9,2)
relationships:
- relationshipname: hat_Kategorie
entities:
- targetentity: Kunde
cardinality: MANY
- targetentity: Kundenkategorie
cardinality: OPTIONAL_ONE
"""
erman = ERSchemaMan.fromStr(er_yaml)
erman.display_mermaid_diagram(direction="RL", height="600px")
relman = RelSchemaMan.fromAST(erman.get_rel())
relman.display_mermaid_diagram(direction="RL", height="700px")
display_sql(relman.get_sql())
CCREATE TABLE Kunde (
KID INTEGER NOT NULL,
Name VARCHAR(100) NOT NULL,
KKID INTEGER NOT NULL,
PRIMARY KEY (KID)
);
CREATE TABLE Kundenkategorie (
KKID INTEGER NOT NULL,
Bez VARCHAR(30) NOT NULL,
BeginGueltig DATE NOT NULL,
EndeGueltig DATE NOT NULL,
PRIMARY KEY (KKID)
);
CREATE TABLE Lieferant (
LID INTEGER NOT NULL,
Name VARCHAR(100) NOT NULL,
PRIMARY KEY (LID)
);
CREATE TABLE Artikel (
AID INTEGER NOT NULL,
Bez VARCHAR(200) NOT NULL,
PRIMARY KEY (AID)
);
CREATE TABLE Bestellung (
BID INTEGER NOT NULL,
BestellDatum DATE NOT NULL,
KID INTEGER NOT NULL,
PRIMARY KEY (BID)
);
CREATE TABLE Bestellposition (
LfdNr INTEGER NOT NULL,
BID INTEGER NOT NULL,
Menge INTEGER NOT NULL,
AID INTEGER NOT NULL,
PRIMARY KEY (LfdNr, BID)
);
CREATE TABLE Verkaufkondition (
fuer_KKID INTEGER NOT NULL,
VK_ArtikelID INTEGER NOT NULL,
Preis DECIMAL(9,2) NOT NULL,
PRIMARY KEY (fuer_KKID, VK_ArtikelID)
);
CREATE TABLE Einkaufskondition (
fuer_LieferantID INTEGER NOT NULL,
EK_ArtikelID INTEGER NOT NULL,
DauerInTagen INTEGER NOT NULL,
Preis DECIMAL(9,2) NOT NULL,
PRIMARY KEY (fuer_LieferantID, EK_ArtikelID)
);
ALTER TABLE Kunde
ADD CONSTRAINT fk_hat_Kategorie FOREIGN KEY (KKID) REFERENCES Kundenkategorie (KKID);
ALTER TABLE Bestellung
ADD CONSTRAINT fk_Bestellung_Kunde_assoc FOREIGN KEY (KID) REFERENCES Kunde (KID);
ALTER TABLE Bestellposition
ADD CONSTRAINT fk_Bestellposition_Bestellung FOREIGN KEY (BID) REFERENCES Bestellung (BID);
ALTER TABLE Bestellposition
ADD CONSTRAINT fk_Bestellposition_Artikel_assoc FOREIGN KEY (AID) REFERENCES Artikel (AID);
ALTER TABLE Verkaufkondition
ADD CONSTRAINT fk_Verkaufkondition_fuer_KK FOREIGN KEY (fuer_KKID) REFERENCES Kundenkategorie (KKID);
ALTER TABLE Verkaufkondition
ADD CONSTRAINT fk_Verkaufkondition_VK_Artikel FOREIGN KEY (VK_ArtikelID) REFERENCES Artikel (AID);
ALTER TABLE Einkaufskondition
ADD CONSTRAINT fk_Einkaufskondition_fuer_Lieferant FOREIGN KEY (fuer_LieferantID) REFERENCES Lieferant (LID);
ALTER TABLE Einkaufskondition
ADD CONSTRAINT fk_Einkaufskondition_EK_Artikel FOREIGN KEY (EK_ArtikelID) REFERENCES Artikel (AID);
er_yaml = """
entities:
- entityname: Person
key: PID
attributes:
- attributename: Name
type: VARCHAR(100)
- entityname: Lehrperson
attributes:
- attributename: Steuernummer
type: VARCHAR(20)
- entityname: StudentIn
attributes:
- attributename: MatrNr
type: VARCHAR(10)
inheritances:
- superentity: Person
subentities:
- Lehrperson
- StudentIn
"""
erman = ERSchemaMan.fromStr(er_yaml)
erman.display_mermaid_diagram(direction="TB", height="400px")
relman = RelSchemaMan.fromAST(erman.get_rel())
relman.display_mermaid_diagram(direction="BT", height="500px")
display_sql(relman.get_sql())
CREATE TABLE Person (
PID INTEGER NOT NULL,
Name VARCHAR(100) NOT NULL,
PRIMARY KEY (PID)
);
CREATE TABLE Lehrperson (
PID INTEGER NOT NULL,
Steuernummer VARCHAR(20) NOT NULL,
PRIMARY KEY (PID)
);
CREATE TABLE StudentIn (
PID INTEGER NOT NULL,
MatrNr VARCHAR(10) NOT NULL,
PRIMARY KEY (PID)
);
ALTER TABLE Lehrperson
ADD CONSTRAINT fk_Lehrperson_Person FOREIGN KEY (PID) REFERENCES Person (PID);
ALTER TABLE StudentIn
ADD CONSTRAINT fk_StudentIn_Person FOREIGN KEY (PID) REFERENCES Person (PID);
er_yaml = """
entities:
- entityname: Lehrperson
key: LPID
attributes:
- attributename: Name
type: VARCHAR(100)
- attributename: EMail
type: VARCHAR(100)
nullable: True
- entityname: Gebaeude
key: GID
attributes:
- attributename: Bez
type: VARCHAR(50)
relationships:
- relationshipname: hat_buero_in
entities:
- targetentity: Lehrperson
cardinality: OPTIONAL_MANY
- targetentity: Gebaeude
cardinality: OPTIONAL_ONE
"""
erman = ERSchemaMan.fromStr(er_yaml)
# erman.display_mermaid_diagram(direction="LR", height="200px")
relman = RelSchemaMan.fromAST(erman.get_rel())
# relman.display_mermaid_diagram(direction="LR", height="300px")
sql = relman.get_sql()
osql = transpile_postgres_sql(sql, target_dialect="oracle")
display_sql(osql)
CREATE TABLE Lehrperson (LPID NUMBER NOT NULL, Name VARCHAR2(100) NOT NULL, EMail VARCHAR2(100), GID NUMBER, PRIMARY KEY (LPID));
CREATE TABLE Gebaeude (GID NUMBER NOT NULL, Bez VARCHAR2(50) NOT NULL, PRIMARY KEY (GID));
ALTER TABLE Lehrperson ADD CONSTRAINT fk_hat_buero_in FOREIGN KEY (GID) REFERENCES Gebaeude (GID)
DuckDB unterstützt kein ALTER TABLE für Fremdschlüssel.
Daher werden Fremdschlüssel in der CREATE TABLE-Anweisung definiert.
Das erfolgt durch durch den Parameter fk_external=False bei der SQL-Code-Erzeugung
er_yaml = """
entities:
- entityname: Lehrperson
key: LPID
attributes:
- attributename: Name
type: VARCHAR(100)
- attributename: EMail
type: VARCHAR(100)
nullable: True
- entityname: Gebaeude
key: GID
attributes:
- attributename: Bez
type: VARCHAR(50)
relationships:
- relationshipname: hat_buero_in
entities:
- targetentity: Lehrperson
cardinality: OPTIONAL_MANY
- targetentity: Gebaeude
cardinality: OPTIONAL_ONE
"""
erman = ERSchemaMan.fromStr(er_yaml)
# erman.display_mermaid_diagram(direction="LR", height="200px")
relman = RelSchemaMan.fromAST(erman.get_rel())
# relman.display_mermaid_diagram(direction="LR", height="300px")
display_sql(relman.get_sql(fk_external=False))
CREATE TABLE Gebaeude (
GID INTEGER NOT NULL,
Bez VARCHAR(50) NOT NULL,
PRIMARY KEY (GID)
);
CREATE TABLE Lehrperson (
LPID INTEGER NOT NULL,
Name VARCHAR(100) NOT NULL,
EMail VARCHAR(100),
GID INTEGER,
PRIMARY KEY (LPID),
CONSTRAINT fk_hat_buero_in FOREIGN KEY (GID) REFERENCES Gebaeude (GID)
);
dbman = DuckDbMan.fromMem()
sql = """
CREATE TABLE Gebaeude (
GID INTEGER NOT NULL,
Bez VARCHAR(50) NOT NULL,
PRIMARY KEY (GID)
);
CREATE TABLE Lehrperson (
LPID INTEGER NOT NULL,
Name VARCHAR(100) NOT NULL,
EMail VARCHAR(100),
GID INTEGER,
PRIMARY KEY (LPID),
CONSTRAINT fk_hat_buero_in FOREIGN KEY (GID) REFERENCES Gebaeude (GID)
);
"""
dbman.execute(sql)
sql = """
insert into Gebaeude values (1, 'C');
insert into Lehrperson values (1, 'Claßen', 'ingo.classen@htw-berlin.de', 1);
insert into Lehrperson values (2, 'Kempa', 'martin.kempa@htw-berlin.de', 1);
"""
dbman.execute(sql)
sql = """
select g.Bez as Gebaeude, lp.Name as Lehrperson, lp.EMail
from Gebaeude g
join Lehrperson lp on g.GID = lp.GID
"""
dbman.print(sql)
┌──────────┬────────────┬────────────────────────────┐
│ Gebaeude │ Lehrperson │ EMail │
│ varchar │ varchar │ varchar │
├──────────┼────────────┼────────────────────────────┤
│ C │ Claßen │ ingo.classen@htw-berlin.de │
│ C │ Kempa │ martin.kempa@htw-berlin.de │
└──────────┴────────────┴────────────────────────────┘
Modulart wird ausgeschlossener_yaml = """
entities:
- entityname: Modul
key: MID
attributes:
- attributename: Bez
type: VARCHAR(50)
valuelists:
- valuelistname: Modulart
values:
- Pflicht
- Wahlpflicht
- Wahl
many_to_one_from_entities:
- sourceentity: Modul
"""
erman = ERSchemaMan.fromStr(er_yaml)
relman = RelSchemaMan.fromAST(erman.get_rel())
datagen_conv = DataGenConversation()
datagen_conv.set_database_schema(relman.get_yaml())
datagen_conv.insert_datagen_message(
no_of_records_per_table=5,
exclude_tables=["Modulart"]
)
print(datagen_conv.gen_prompt())
Der Prompt für die KI:
You are a helpful assistant for generating synthetic data for SQL tables.
You will be given a YAML description of the tables and their relationships.
Your task is to generate SQL insert statements to create synthetic data
for the tables, while respecting the relationships and constraints described in the YAML.
The following tables are given:
```yaml
tables:
- tablename: Modul
columns:
- columnname: MID
type: INTEGER
- columnname: Bez
type: VARCHAR(50)
- columnname: ModulartID
type: INTEGER
primary_key:
- MID
foreign_keys:
-
fkname: fk_Modul_Modulart
sourcecolumns:
- ModulartID
targettable: Modulart
targetcolumns:
- ID
- tablename: Modulart
columns:
- columnname: ID
type: INTEGER
- columnname: Description
type: VARCHAR(100)
- columnname: IsValid
type: INTEGER
- columnname: SortOrder
type: INTEGER
primary_key:
- ID
datalists:
- tablename: Modulart
values:
- Pflicht
- Wahlpflicht
- Wahl
```
Future request will be based on this schema.
Create at least 5 insert statements for each table.
Don't repeat already given data.
Don'create insert statements for the following tables: 'Modulart'.
Kopieren des Prompts in die KI (hier ChatGPT).
Screenshot:
dbman = DuckDbMan.fromMem()
dbman.execute(relman.get_sql(fk_external=False))
dbman.print("select * from Modulart")
┌───────┬─────────────┬─────────┬───────────┐
│ ID │ Description │ IsValid │ SortOrder │
│ int32 │ varchar │ int32 │ int32 │
├───────┼─────────────┼─────────┼───────────┤
│ 1 │ Pflicht │ 1 │ 1 │
│ 2 │ Wahlpflicht │ 1 │ 2 │
│ 3 │ Wahl │ 1 │ 3 │
└───────┴─────────────┴─────────┴───────────┘
sql = """
-- Insert statements for table: Modul
-- Assumption:
-- Modulart IDs already exist in table Modulart
-- 1 = Pflicht
-- 2 = Wahlpflicht
-- 3 = Wahl
INSERT INTO Modul (MID, Bez, ModulartID)
VALUES (101, 'Datenbanksysteme', 1);
INSERT INTO Modul (MID, Bez, ModulartID)
VALUES (102, 'Software Engineering', 1);
INSERT INTO Modul (MID, Bez, ModulartID)
VALUES (103, 'Künstliche Intelligenz', 2);
INSERT INTO Modul (MID, Bez, ModulartID)
VALUES (104, 'Webentwicklung', 2);
INSERT INTO Modul (MID, Bez, ModulartID)
VALUES (105, 'IT-Sicherheit', 3);
INSERT INTO Modul (MID, Bez, ModulartID)
VALUES (106, 'Cloud Computing', 3);
"""
dbman.execute(sql)
sql = """
select m.bez as Modul, ma.description as Modulart
from Modul m
join Modulart ma on m.ModulartID = ma.ID
"""
dbman.print(sql)
┌────────────────────────┬─────────────┐
│ Modul │ Modulart │
│ varchar │ varchar │
├────────────────────────┼─────────────┤
│ Datenbanksysteme │ Pflicht │
│ Software Engineering │ Pflicht │
│ Künstliche Intelligenz │ Wahlpflicht │
│ Webentwicklung │ Wahlpflicht │
│ IT-Sicherheit │ Wahl │
│ Cloud Computing │ Wahl │
└────────────────────────┴─────────────┘
er_conv = ERDesignConversation()
design_msg = """
Entwirf ein einfaches ER-Diagramm für ein Krankenhaussystem.
Es soll Informationen über Patienten, Ärzte und Behandlungen speichern.
Jede Behandlung hat ein Datum und eine Beschreibung.
"""
er_conv.insert_design_message(design_msg)
print(er_conv.gen_prompt())
Der Prompt wird hier nicht gezeigt, da er sehr lang ist.
er_yaml = """
entities:
- entityname: Patient
key: PatientID
attributes:
- attributename: Vorname
type: TEXT
- attributename: Nachname
type: TEXT
- attributename: Geburtsdatum
type: DATE
nullable: True
- entityname: Arzt
key: ArztID
attributes:
- attributename: Vorname
type: TEXT
- attributename: Nachname
type: TEXT
- attributename: Fachgebiet
type: TEXT
nullable: True
associative_entities:
- associationname: Behandlung
identification:
localkey: BehandlungID
associations:
- targetentity: Patient
role: Patient
cardinality: ONE
- targetentity: Arzt
role: Arzt
cardinality: ONE
attributes:
- attributename: Behandlungsdatum
type: DATE
- attributename: Beschreibung
type: TEXT
"""
erman = ERSchemaMan.fromStr(er_yaml)
erman.display_mermaid_diagram(direction="TB", height="450")
er_conv = ERRequirementsConversation()
req_msg = """
Beschreibe die Anforderungen für ein Krankhaussystem.
"""
er_conv.insert_requirements_message(req_msg)
print(er_conv.gen_prompt())
Der Prompt wird hier nicht gezeigt, da er sehr lang ist.
req_md = """
Hier den Markdown-Output der KI einfügen
"""
display_md(req_md)
Der Output wird hier nicht abgebildet, da er sehr lang ist.
Das Markdown-Dokument dann in die ERDesignConversation einfügen.
relman.get_sql(fk_external=False)er_yaml = """
entities:
- entityname: Modul
key: MID
attributes:
- attributename: Bez
type: VARCHAR(50)
valuelists:
- valuelistname: Modulart
values:
- Pflicht
- Wahlpflicht
- Wahl
many_to_one_from_entities:
- sourceentity: Modul
"""
erman = ERSchemaMan.fromStr(er_yaml)
relman = RelSchemaMan.fromAST(erman.get_rel())
dbman = DuckDbMan.fromMem()
dbman.execute(relman.get_sql(fk_external=False))
dbman.print("select * from Modulart")
┌───────┬─────────────┬─────────┬───────────┐
│ ID │ Description │ IsValid │ SortOrder │
│ int32 │ varchar │ int32 │ int32 │
├───────┼─────────────┼─────────┼───────────┤
│ 1 │ Pflicht │ 1 │ 1 │
│ 2 │ Wahlpflicht │ 1 │ 2 │
│ 3 │ Wahl │ 1 │ 3 │
└───────┴─────────────┴─────────┴───────────┘
Modulsql = """
INSERT INTO Modul (MID, Bez, ModulartID)
VALUES (101, 'Datenbanksysteme', 1);
INSERT INTO Modul (MID, Bez, ModulartID)
VALUES (102, 'Software Engineering', 1);
INSERT INTO Modul (MID, Bez, ModulartID)
VALUES (103, 'Künstliche Intelligenz', 2);
INSERT INTO Modul (MID, Bez, ModulartID)
VALUES (104, 'Webentwicklung', 2);
INSERT INTO Modul (MID, Bez, ModulartID)
VALUES (105, 'IT-Sicherheit', 3);
INSERT INTO Modul (MID, Bez, ModulartID)
VALUES (106, 'Cloud Computing', 3);
"""
dbman.execute(sql)
dbman.print("select * from Modul")
┌───────┬────────────────────────┬────────────┐
│ MID │ Bez │ ModulartID │
│ int32 │ varchar │ int32 │
├───────┼────────────────────────┼────────────┤
│ 101 │ Datenbanksysteme │ 1 │
│ 102 │ Software Engineering │ 1 │
│ 103 │ Künstliche Intelligenz │ 2 │
│ 104 │ Webentwicklung │ 2 │
│ 105 │ IT-Sicherheit │ 3 │
│ 106 │ Cloud Computing │ 3 │
└───────┴────────────────────────┴────────────┘
sql_conv = SQLGenConversation()
sql_conv.set_database_schema(relman.get_yaml())
sql_conv.insert_question_message("Anzahl Module pro Modulart")
print(sql_conv.gen_prompt())
You are an expert SQL query generator.
Convert natural language questions into valid SQL queries.
The following database schema is given:
tables:
- tablename: Modul
columns:
- columnname: MID
type: INTEGER
- columnname: Bez
type: VARCHAR(50)
- columnname: ModulartID
type: INTEGER
primary_key:
- MID
foreign_keys:
-
fkname: fk_Modul_Modulart
sourcecolumns:
- ModulartID
targettable: Modulart
targetcolumns:
- ID
- tablename: Modulart
columns:
- columnname: ID
type: INTEGER
- columnname: Description
type: VARCHAR(100)
- columnname: IsValid
type: INTEGER
- columnname: SortOrder
type: INTEGER
primary_key:
- ID
datalists:
- tablename: Modulart
values:
- Pflicht
- Wahlpflicht
- Wahl
Future request will be based on this schema.
The user's question is:
Anzahl Module pro Modulart
Turn this question into a valid postgres SQL query based on the given database schema.
Return the SQL query only, without any explanation or additional text.
sql = """
SELECT
ma.Description AS modulart,
COUNT(m.MID) AS anzahl_module
FROM Modul m
JOIN Modulart ma ON m.ModulartID = ma.ID
GROUP BY ma.Description
ORDER BY anzahl_module DESC;
"""
dbman.print(sql)
┌─────────────┬───────────────┐
│ modulart │ anzahl_module │
│ varchar │ int64 │
├─────────────┼───────────────┤
│ Wahl │ 2 │
│ Wahlpflicht │ 2 │
│ Pflicht │ 2 │
└─────────────┴───────────────┘