
Guten Tag.
Heute möchte ich über ORM SQLAlchemy sprechen. Lassen Sie uns darüber sprechen, was es mit seinen Fähigkeiten und seiner Flexibilität auf sich hat, und auch Fälle betrachten, die nicht immer klar beschrieben werden.
Dieses ORM hat eine überdurchschnittliche Eintrittsschwelle, daher werde ich versuchen, alles in einer einfachen Sprache und mit Beispielen zu erklären. Dieser Artikel ist nützlich für diejenigen, die bereits mit sqlalchemy arbeiten und ihre Fähigkeiten verbessern oder sich einfach nur mit dieser Bibliothek vertraut machen möchten.
Die verwendete Programmiersprache ist Python 3.6.
DB - PostgreSQL.
Github Link
Was ist ORM?
ORM (Object-Relational Mapping) ist eine Technologie, mit der Sie Modelle zuordnen können, deren Typen nicht kompatibel sind. Zum Beispiel: eine Datenbanktabelle und ein Programmiersprachenobjekt.
Mit anderen Worten, Sie können auf Klassenobjekte zugreifen, um Daten in Datenbanktabellen zu verwalten. Sie können auch Klassenobjekte erstellen, ändern, löschen, filtern und vor allem erben, die Datenbanktabellen zugeordnet sind, wodurch der Inhalt der Codebasis erheblich reduziert wird.
Um SQLAlchemy verwenden zu können, müssen Sie verstehen, wie es funktioniert.
Entwickler, die Django-ORM verwenden, müssen ihre Denkweise ein wenig überarbeiten, um ORM-Abfragen zu erstellen. Meiner Meinung nach ist SQLAlchemy ein funktionierendes Monster, dessen Fähigkeiten Sie nutzen können und sollten, aber Sie müssen verstehen, dass ORMs nicht immer perfekt sind. Daher werden wir die Momente diskutieren, in denen der Einsatz dieser Technologie ratsam ist.
SQLAlchemy hat das Konzept deklarativer und nicht deklarativer Modelldefinitionen.
Nicht deklarative Definitionen implizieren die Verwendung von mapper (), das die Zuordnung jeder Datenbankspalte und Modellklasse beschreibt.
Dieser Artikel verwendet eine deklarative Definition von Modellen.
Mehr hier
DB-Struktur
Um eine vollständige Datenkonsistenz zu gewährleisten, erstellen wir die folgenden Tabellen.
Das Grundmodell wird verwendet, um die Grundspalten in der Datenbank zu bestimmen.
class BaseModel(Base): __abstract__ = True id = Column(Integer, nullable=False, unique=True, primary_key=True, autoincrement=True) created_at = Column(TIMESTAMP, nullable=False) updated_at = Column(TIMESTAMP, nullable=False) def __repr__(self): return "<{0.__class__.__name__}(id={0.id!r})>".format(self)
Mitarbeiter - Eine Tabelle, die den Mitarbeiter beschreibt, der im Büro arbeitet
class Employee(BaseModel): __tablename__ = 'employees' first_name = Column(VARCHAR(255), nullable=False) last_name = Column(VARCHAR(255), nullable=False) phone = Column(VARCHAR(255), unique=True, nullable=True) description = Column(VARCHAR(255), nullable=True)
EmployeeWithSkills ist keine Tabelle. Die von Employee geerbte Klasse. Eine großartige Gelegenheit, die Logik zu trennen und die Klasse so zu verwenden, als wäre es eine separate Tabelle.
class EmployeeWithSkills(Employee): skills = relation(Skill, secondary=EmployeesSkills.__tablename__, lazy='joined')
Abteilung - die Abteilung, in der dieser Mitarbeiter arbeitet. Eine Person kann aus mehreren Abteilungen bestehen.
class Department(BaseModel): __tablename__ = 'departments' name = Column(VARCHAR(255), nullable=False) description = Column(VARCHAR(255), nullable=False)
Die Korrespondenztabelle des Mitarbeiters und die Einheiten, in denen er Mitglied ist.
class EmployeeDepartments(BaseModel): __tablename__ = 'employee_departments' employee_id = Column(Integer, ForeignKey('employees.id', ondelete='CASCADE'), nullable=False, index=True) department_id = Column(Integer, ForeignKey('departments.id', ondelete='CASCADE'), nullable=False, index=True)
Korrespondenztabelle der Mitarbeiter und ihrer Fähigkeiten.
class EmployeesSkills(BaseModel): __tablename__ = 'employees_skills' employee_id = Column(ForeignKey('employee.id', ondelete='CASCADE'), nullable=False, index=True) skill_id = Column(ForeignKey('skills.id', ondelete='CASCADE'), nullable=False, index=True)
Wir erstellen Migrationen mit dem Alembic-Paket, mit dem Sie sie automatisch generieren können. In dieser Lektion ist die automatische Generierung von Migrationen durchaus akzeptabel.
Die letzte Migration enthält Testdaten, die die Datenbank füllen.
Informationen zum Konfigurieren von Alembic finden Sie hier
Wir führen einen geschätzten Alembic-Upgrade-Kopf durch, um die Migration durchzuführen.
Anfragen und Beziehungen
Lassen Sie uns die erste Anfrage stellen und Informationen über den Mitarbeiter anhand seiner ID erhalten.
Die Anfrage sieht folgendermaßen aus:
Lektion 1:
employee = session.query(Employee).filter(Employee.id == eid).one() output: ID: 2, Tony Stark
.one()
am Ende bedeutet, dass wir nur einen Eintrag erhalten .one()
. Wenn mehrere Einträge vorhanden sind, wird eine entsprechende Ausnahme ausgelöst.
Wenn wir alle verfügbaren Abteilungen erhalten möchten, können wir die folgende Abfrage mit .all()
Lektion 2:
emmployee = session.query(Department).all() output: ID: 2, name: Guards ID: 4, name: Legions
Arbeiten Sie mit Aggregationsfunktionen.
Mit der integrierten Funktion können wir die Anzahl der verfügbaren Abteilungen ermitteln.
.count()
oder benutze func.count()
. Mit der zweiten Methode können Sie auf alle SQL-Funktionen zugreifen, indem Sie entweder select
oder Zwischenergebnisse berechnen.
Lektion 3:
def get_departments_count(session: DBSession) -> int: count = session.query(Department).count() return count def get_departments_func_count(session: DBSession) -> int: count = session.query(func.count(Department.id)).scalar() return count
Viele Entwickler verwenden die Funktion count()
, um in einer Anfrage nach Daten zu suchen. Dies ist keine bewährte Methode, da zusätzliche Datenbankressourcen verwendet und die Ausführungszeit für Abfragen verlängert werden. Eine gute Lösung wäre die Verwendung der Funktion exists()
, die einen Skalarwert zurückgibt:
Lektion 3:
def check_department_exists(session: DBSession, department_name: str) -> bool: is_exists = session.query(exists().where(Department.name == department_name)).scalar() return is_exists
Im weiteren Verlauf erschweren wir die Aufgabe und machen uns mit der Entitätsbeziehung oder -beziehung vertraut. Tatsache ist, dass in SQLAlchemy
zusätzlich zur Verwendung von Foreign_key
Auf Datenbankebene werden auch Beziehungen zwischen Objekten verwendet.
Somit können wir die Datenbankzeile abhängig vom Fremdschlüssel im Objekt erhalten.
Diese Objekte sind eine Projektion auf die Datenbanktabellen, die miteinander verbunden sind.
Relations
in SQLAlchemy
über eine flexible Konfiguration, mit der Sie Daten aus der Datenbank auf unterschiedliche Weise zu unterschiedlichen Zeiten mithilfe des genannten Arguments lazy
.
Die Hauptgrade der "Faulheit":
select
ist die Standardeinstellung. ORM stellt nur beim Zugriff auf Daten eine Anfrage. Es wird in einer separaten Anfrage durchgeführt.dynamic
- Ermöglicht das Abrufen eines Anforderungsobjekts, das nach Bedarf geändert werden kann. Es empfängt Daten aus der Datenbank erst, nachdem alle () oder eine () oder eine andere verfügbare Methode aufgerufen wurden.joined
- wird der Hauptanforderung mit LEFT JOIN hinzugefügt. Es wird sofort durchgeführt.subquery
- ähnlich wie select, jedoch als Unterabfrage ausgeführt.
Der Standardwert ist select
.
Das Filtern in Abfragen kann statisch und dynamisch sein. Durch die dynamische Filterung kann die Anforderung mit Filtern gefüllt werden, die je nach Funktionsfortschritt variieren können.
Lektion 4:
def dynamic_filter(session: DBSession, filter: DFilter = None): query = session.query(Employee) if filter is not None: query = query.filter(*filter.conds) employees = query.all() return employees
Die DFilter-Filterklasse definiert Filter basierend auf einer beliebigen Eingabe. Wenn die Filterklasse definiert ist, gelten aber weiter in den Anforderungsbedingungen.
Die Funktion .filter () akzeptiert die binären Bedingungen von SQLAlchemy, sodass sie mit * dargestellt werden kann
Die Verwendung dynamischer Filter ist nur durch Vorstellungskraft begrenzt. Das Ergebnis der Abfrage zeigt, welche Helden derzeit inaktiv sind.
output: Inactive_heros: Name: Tony Stark Name: Scott Lang Name: Peter Parker
Ich schlage vor, mit der Viele-zu-Viele-Beziehung zu arbeiten.
Wir haben eine Employee-Tabelle, in der eine Beziehung zur EmployeesSkills-Korrespondenztabelle besteht. Es enthält Foreign_key in der Mitarbeitertabelle und Foreign_key
zum Skill-Tisch.
Lektion 5:
def get_employee_with_skills(session: DBSession, eid: int): employee = session.query(EmployeeWithSkills).filter(EmployeeWithSkills.id == eid).one() return employee output: Employee Tony Stark has skills: Skill: Fly, Desc: I belive I can Fly. I belive I can touch the sky Skill: Light Shield, Desc: Light protect. Perfect for everything
Wenn Sie die EmployeeWithSkills-Klasse in der obigen Abfrage verwenden, wird sie als Datenbanktabelle bezeichnet, aber in Wirklichkeit existiert eine solche Tabelle nicht. Diese Klasse unterscheidet sich von Mitarbeitern mit einer Beziehung, die eine Viele-zu-Viele-Beziehung hat. So können wir die Logik der Klassen unterscheiden und sie mit anderen Beziehungen füllen. Als Ergebnis der Anfrage werden wir die Fähigkeiten eines der Mitarbeiter sehen.
Da sich der Mitarbeiter in mehreren Abteilungen befinden kann, erstellen Sie eine Beziehung, mit der Sie diese Informationen abrufen können.
Erstellen Sie eine von Employee geerbte EmployeeWithDepartments-Klasse und fügen Sie Folgendes hinzu:
class EmployeeWithDepartments(Employee): departments = relation( Department, # primaryjoin=EmployeeDepartments.employee_id == Employee.id, secondary=EmployeeDepartments.__tablename__, # secondaryjoin=EmployeeDepartments.department_id == Department.id, )
Die erstellte Klasse ist keine neue Datenbanktabelle. Dies ist immer noch dieselbe Employee-Tabelle, die nur mithilfe der relation
erweitert wurde. Auf diese Weise können Sie in Abfragen auf die Tabelle Employee
oder EmployeeWithDepartments
zugreifen. Der Unterschied besteht nur in der Abwesenheit / Gegenwart einer relation
.
Das erste Argument gibt an, zu welcher Tabelle die relation
.
primaryjoin
ist die Bedingung, unter der die zweite Tabelle verbunden wird, bevor sie an das Objekt angehängt wird.
secondary
ist der Name der Tabelle, die fremde Schlüssel für den Abgleich enthält. Wird bei vielen zu vielen verwendet.
secondaryjoin
- Bedingungen für die Zuordnung der Zwischentabelle zur letzten.
primaryjoin
und secondaryjoin
dienen dazu, Entsprechungen in komplexen Situationen explizit anzuzeigen.
Manchmal treten Situationen auf, in denen Filter erstellt werden müssen, deren Felder in Relationen deklariert sind, und Relationen wiederum Relationen der ursprünglichen Klasse sind.
EmployeeWithCadreMovements -> relation(CadreMovement) -> field
Wenn die Relation eine Liste von Werten anzeigt, muss .any () verwendet werden. Wenn nur ein Wert angegeben wird, muss .has () verwendet werden
Zum besseren Verständnis wird dieses Konstrukt in der SQL-Sprache in das Konstrukt exist () interpretiert.
Wir nennen die Funktion get mit dem Parameter Parameter reason zum Beispiel simple
.
Lektion6
def has_in_relations(session: DBSession, reason: str): employees = session.query(EmployeeWithCadreMovements).filter(EmployeeWithCadreMovements.cadre_movements.any(CadreMovement.reason == reason)).all() return employees output: [Steve Rogers, Tony Stark]
lession7
Berücksichtigen Sie die Möglichkeit, mithilfe der Aggregationsfunktion eine Beziehung zu erhalten. Zum Beispiel erhalten wir die letzte Personalbewegung eines bestimmten Benutzers.
Primaryjoin ist eine Bedingung für das Verknüpfen von Tabellen (wenn faul = 'verbunden' verwendet wird). Denken Sie daran, dass select standardmäßig verwendet wird.
In diesem Fall wird beim Zugriff auf das Klassenattribut eine separate Anforderung generiert. Für diese Anfrage können wir die Filterbedingungen angeben.
Wie Sie wissen, können Sie Aggregationsfunktionen in einer WHERE-Bedingung nicht in einer „reinen“ Form verwenden. Daher können wir diese Funktion durch Angabe einer Beziehung implementieren
mit folgenden Parametern:
last_cadre_movement = relation( CadreMovement, primaryjoin=and_( CadreMovement.employee == Employee.id, uselist=False, CadreMovement.id == select([func.max(CadreMovement.id)]).where(CadreMovement.employee == Employee.id) ) )
Bei der Ausführung wird die Anforderung wie folgt kompiliert:
SELECT cadre_movements.id AS cadre_movements_id, cadre_movements.created_at AS cadre_movements_created_at, cadre_movements.updated_at AS cadre_movements_updated_at, cadre_movements.employee AS cadre_movements_employee, cadre_movements.old_department AS cadre_movements_old_department, cadre_movements.new_department AS cadre_movements_new_department, cadre_movements.reason AS cadre_movements_reason FROM cadre_movements WHERE cadre_movements.employee = %(param_1)s AND cadre_movements.id = ( SELECT max(cadre_movements.id) AS max_1 FROM cadre_movements WHERE cadre_movements.employee = %(param_1)s )
Github Link
Zusammenfassung
SQLAlchemy ist ein leistungsstarkes Tool zum Erstellen von Abfragen, das die Entwicklungszeit verkürzt, indem es die Vererbung unterstützt.
Sie sollten jedoch eine feine Linie zwischen der Verwendung von ORM und dem Schreiben komplexer Abfragen einhalten. In einigen Fällen kann ORM den Entwickler verwirren oder den Code umständlich und unlesbar machen.
Viel Glück