SQLAlchemy 2.0 Dokumentation
Häufig gestellte Fragen
- Installation
- Verbindungen / Engines
- MetaData / Schema
- SQL-Ausdrücke
- ORM Konfiguration¶
- Wie ordne ich eine Tabelle, die keinen Primärschlüssel hat?
- Wie konfiguriere ich eine Spalte, die ein Python-Reservatwort oder ähnlich ist?
- Wie erhalte ich eine Liste aller Spalten, Beziehungen, zugeordneten Attribute usw. einer zugeordneten Klasse?
- Ich erhalte eine Warnung oder Fehlermeldung über „Implizites Kombinieren der Spalte X unter dem Attribut Y“
- Ich verwende Declarative und setze primaryjoin/secondaryjoin mit einem
and_()oderor_()und erhalte eine Fehlermeldung bezüglich Fremdschlüsseln. - Warum wird
ORDER BYfürLIMITempfohlen (insbesondere mitsubqueryload())? - Was sind
default,default_factoryundinsert_defaultund was sollte ich verwenden?
- Performance
- Sessions / Abfragen
- Probleme bei der Integration von Drittanbietern
Projektversionen
- Vorheriges: SQL-Ausdrücke
- Nächstes: Performance
- Nach oben: Startseite
- Auf dieser Seite
- ORM-Konfiguration
- Wie ordne ich eine Tabelle, die keinen Primärschlüssel hat?
- Wie konfiguriere ich eine Spalte, die ein Python-Reservatwort oder ähnlich ist?
- Wie erhalte ich eine Liste aller Spalten, Beziehungen, zugeordneten Attribute usw. einer zugeordneten Klasse?
- Ich erhalte eine Warnung oder Fehlermeldung über „Implizites Kombinieren der Spalte X unter dem Attribut Y“
- Ich verwende Declarative und setze primaryjoin/secondaryjoin mit einem
and_()oderor_()und erhalte eine Fehlermeldung bezüglich Fremdschlüsseln. - Warum wird
ORDER BYfürLIMITempfohlen (insbesondere mitsubqueryload())? - Was sind
default,default_factoryundinsert_defaultund was sollte ich verwenden?
ORM Konfiguration¶
Wie ordne ich eine Tabelle, die keinen Primärschlüssel hat?¶
Das SQLAlchemy ORM benötigt, um eine bestimmte Tabelle zuordnen zu können, mindestens eine Spalte, die als Primärschlüsselspalte gekennzeichnet ist; Mehrspaltige, d.h. zusammengesetzte Primärschlüssel sind natürlich ebenfalls möglich. Diese Spalten müssen dem Datenbank nicht tatsächlich als Primärschlüsselspalten bekannt sein, obwohl es ratsam ist. Es ist nur notwendig, dass die Spalten sich wie ein Primärschlüssel verhalten, z. B. als eindeutiger und nicht-null-fähiger Identifikator für eine Zeile.
Die meisten ORMs verlangen, dass Objekte einen Primärschlüssel definiert haben, da das Objekt im Speicher einer eindeutig identifizierbaren Zeile in der Datenbanktabelle entsprechen muss; zumindest ermöglicht dies, dass das Objekt für UPDATE- und DELETE-Anweisungen gezielt ausgewählt werden kann, die nur die Zeile dieses Objekts und keine andere beeinflussen. Die Bedeutung des Primärschlüssels geht jedoch weit darüber hinaus. In SQLAlchemy sind alle ORM-zugeordneten Objekte jederzeit innerhalb einer Session über ein Muster namens Identitäts-Map eindeutig mit ihrer spezifischen Datenbankzeile verknüpft. Dies ist zentral für das von SQLAlchemy verwendete Unit-of-Work-System und auch entscheidend für die gängigsten (und weniger gängigen) Muster der ORM-Nutzung.
Hinweis
Es ist wichtig zu beachten, dass wir hier nur vom SQLAlchemy ORM sprechen; eine Anwendung, die auf Core aufbaut und sich nur mit Table-Objekten, select()-Konstrukten und ähnlichem beschäftigt, **benötigt keinen** Primärschlüssel, der in irgendeiner Weise auf einer Tabelle vorhanden oder mit ihr verbunden ist (obwohl in SQL alle Tabellen wirklich eine Art Primärschlüssel haben sollten, damit Sie bestimmte Zeilen tatsächlich aktualisieren oder löschen können).
In fast allen Fällen hat eine Tabelle einen sogenannten Kandidatenschlüssel, d. h. eine Spalte oder eine Reihe von Spalten, die eine Zeile eindeutig identifizieren. Wenn eine Tabelle dies wirklich nicht hat und tatsächlich vollständig doppelte Zeilen aufweist, entspricht die Tabelle nicht der ersten normalen Form und kann nicht zugeordnet werden. Andernfalls können die Spalten, die den besten Kandidatenschlüssel bilden, direkt dem Mapper zugewiesen werden.
class SomeClass(Base):
__table__ = some_table_with_no_pk
__mapper_args__ = {
"primary_key": [some_table_with_no_pk.c.uid, some_table_with_no_pk.c.bar]
}Noch besser ist es, wenn Sie vollständig deklarierte Tabellenmetadaten verwenden und das Flag primary_key=True für diese Spalten setzen.
class SomeClass(Base):
__tablename__ = "some_table_with_no_pk"
uid = Column(Integer, primary_key=True)
bar = Column(String, primary_key=True)Alle Tabellen in einer relationalen Datenbank sollten Primärschlüssel haben. Selbst eine Many-to-Many-Assoziationstabelle – der Primärschlüssel wäre die Kombination der beiden Assoziationsspalten.
CREATE TABLE my_association (
user_id INTEGER REFERENCES user(id),
account_id INTEGER REFERENCES account(id),
PRIMARY KEY (user_id, account_id)
)Wie konfiguriere ich eine Spalte, die ein Python-Reservatwort oder ähnlich ist?¶
Spaltenbasierte Attribute können beliebige Namen in der Zuordnung erhalten. Siehe Deklarative zugeordnete Spalten explizit benennen.
Wie erhalte ich eine Liste aller Spalten, Beziehungen, zugeordneten Attribute usw. einer zugeordneten Klasse?¶
Diese Informationen sind alle vom Mapper-Objekt verfügbar.
Um auf den Mapper für eine bestimmte zugeordnete Klasse zuzugreifen, rufen Sie die Funktion inspect() darauf auf.
from sqlalchemy import inspect
mapper = inspect(MyClass)Von dort aus können alle Informationen über die Klasse über Eigenschaften wie
Mapper.attrs- ein Namensraum aller zugeordneten Attribute. Die Attribute selbst sind Instanzen vonMapperProperty, die zusätzliche Attribute enthalten, die bei Bedarf zum zugeordneten SQL-Ausdruck oder zur Spalte führen können.Mapper.column_attrs- der zugeordnete Attribut-Namensraum, beschränkt auf Spalten- und SQL-Ausdruck-Attribute. Sie möchten vielleichtMapper.columnsverwenden, um direkt auf dieColumn-Objekte zuzugreifen.Mapper.relationships- Namensraum allerRelationshipProperty-Attribute.Mapper.all_orm_descriptors- Namensraum aller zugeordneten Attribute sowie benutzerdefinierter Attribute, die mit Systemen wiehybrid_property,AssociationProxyund anderen definiert wurden.Mapper.columns- Ein Namensraum vonColumn-Objekten und anderen benannten SQL-Ausdrücken, die der Zuordnung zugeordnet sind.Mapper.mapped_table- DieTableoder ein anderer wählbarer Ausdruck, dem dieser Mapper zugeordnet ist.Mapper.local_table- DieTable, die für diesen Mapper "lokal" ist; dies unterscheidet sich vonMapper.mapped_tableim Falle eines Mappers, der über Vererbung einem zusammengesetzten wählbaren Ausdruck zugeordnet ist.
Ich erhalte eine Warnung oder Fehlermeldung bezüglich "Implizite Kombination von Spalte X unter Attribut Y"¶
Diese Bedingung bezieht sich auf den Fall, dass eine Zuordnung zwei Spalten enthält, die aufgrund ihres Namens unter demselben Attributnamen zugeordnet werden, es aber keinen Hinweis darauf gibt, dass dies beabsichtigt ist. Eine zugeordnete Klasse muss explizite Namen für jedes Attribut haben, das einen unabhängigen Wert speichern soll; wenn zwei Spalten denselben Namen haben und nicht disambiguiert werden, fallen sie unter dasselbe Attribut, und die Auswirkung ist, dass der Wert von einer Spalte in die andere **kopiert** wird, basierend darauf, welche Spalte zuerst dem Attribut zugewiesen wurde.
Dieses Verhalten ist oft wünschenswert und wird ohne Warnung zugelassen, wenn die beiden Spalten über eine Fremdschlüsselbeziehung innerhalb einer Vererbungszuordnung verbunden sind. Wenn die Warnung oder der Fehler auftritt, kann das Problem gelöst werden, indem entweder die Spalten anders benannten Attributen zugewiesen werden oder, wenn die Kombination gewünscht ist, column_property() verwendet wird, um dies explizit zu machen.
Angesichts des folgenden Beispiels
from sqlalchemy import Integer, Column, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
class B(A):
__tablename__ = "b"
id = Column(Integer, primary_key=True)
a_id = Column(Integer, ForeignKey("a.id"))Ab SQLAlchemy-Version 0.9.5 wird diese Bedingung erkannt und warnt, dass die id-Spalte von A und B unter dem gleichnamigen Attribut id kombiniert wird, was oben ein ernstes Problem darstellt, da es bedeutet, dass der Primärschlüssel eines B-Objekts immer das Spiegelbild seines A sein wird.
Eine Zuordnung, die dies löst, sieht wie folgt aus
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
class B(A):
__tablename__ = "b"
b_id = Column("id", Integer, primary_key=True)
a_id = Column(Integer, ForeignKey("a.id"))Angenommen, wir wollten, dass A.id und B.id Spiegelbilder voneinander sind, obwohl B.a_id dort ist, wo A.id verknüpft ist. Wir könnten sie über column_property() kombinieren.
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
class B(A):
__tablename__ = "b"
# probably not what you want, but this is a demonstration
id = column_property(Column(Integer, primary_key=True), A.id)
a_id = Column(Integer, ForeignKey("a.id"))Ich verwende Declarative und setze primaryjoin/secondaryjoin mit einem and_() oder or_() und erhalte eine Fehlermeldung bezüglich Fremdschlüsseln.¶
Tun Sie dies?
class MyClass(Base):
# ....
foo = relationship(
"Dest", primaryjoin=and_("MyClass.id==Dest.foo_id", "MyClass.foo==Dest.bar")
)Das ist ein and_() aus zwei String-Ausdrücken, für die SQLAlchemy keine Zuordnung anwenden kann. Declarative erlaubt es, dass relationship()-Argumente als Strings angegeben werden, die mit eval() in Ausdrucksobjekte umgewandelt werden. Dies geschieht jedoch nicht innerhalb eines and_()-Ausdrucks – es ist eine spezielle Operation, die Declarative nur auf die **Gesamtheit** dessen anwendet, was als String an primaryjoin oder andere Argumente übergeben wird.
class MyClass(Base):
# ....
foo = relationship(
"Dest", primaryjoin="and_(MyClass.id==Dest.foo_id, MyClass.foo==Dest.bar)"
)Oder, wenn die benötigten Objekte bereits verfügbar sind, überspringen Sie die Strings.
class MyClass(Base):
# ....
foo = relationship(
Dest, primaryjoin=and_(MyClass.id == Dest.foo_id, MyClass.foo == Dest.bar)
)Dasselbe gilt für alle anderen Argumente wie foreign_keys.
# wrong !
foo = relationship(Dest, foreign_keys=["Dest.foo_id", "Dest.bar_id"])
# correct !
foo = relationship(Dest, foreign_keys="[Dest.foo_id, Dest.bar_id]")
# also correct !
foo = relationship(Dest, foreign_keys=[Dest.foo_id, Dest.bar_id])
# if you're using columns from the class that you're inside of, just use the column objects !
class MyClass(Base):
foo_id = Column(...)
bar_id = Column(...)
# ...
foo = relationship(Dest, foreign_keys=[foo_id, bar_id])Warum wird ORDER BY für LIMIT empfohlen (insbesondere mit subqueryload())?¶
Wenn ORDER BY nicht für eine SELECT-Anweisung verwendet wird, die Zeilen zurückgibt, ist die relationale Datenbank frei, passende Zeilen in beliebiger Reihenfolge zurückzugeben. Während diese Reihenfolge sehr oft der natürlichen Reihenfolge der Zeilen innerhalb einer Tabelle entspricht, ist dies nicht für alle Datenbanken und alle Abfragen der Fall. Die Folge davon ist, dass jede Abfrage, die Zeilen mit LIMIT oder OFFSET begrenzt oder einfach die erste Zeile des Ergebnisses auswählt und den Rest verwirft, nicht deterministisch hinsichtlich der zurückgegebenen Ergebniszeile ist, vorausgesetzt, es gibt mehr als eine Zeile, die den Kriterien der Abfrage entspricht.
Obwohl wir dies bei einfachen Abfragen auf Datenbanken, die normalerweise Zeilen in ihrer natürlichen Reihenfolge zurückgeben, vielleicht nicht bemerken, wird es bei der Verwendung von subqueryload() zum Laden zugehöriger Sammlungen problematischer, und wir laden die Sammlungen möglicherweise nicht wie beabsichtigt.
SQLAlchemy implementiert subqueryload(), indem eine separate Abfrage ausgeführt wird, deren Ergebnisse mit den Ergebnissen der ersten Abfrage abgeglichen werden. Wir sehen zwei Abfragen, die so ausgegeben werden:
>>> session.scalars(select(User).options(subqueryload(User.addresses))).all()
-- the "main" query
SELECT users.id AS users_id
FROM users
-- the "load" query issued by subqueryload
SELECT addresses.id AS addresses_id,
addresses.user_id AS addresses_user_id,
anon_1.users_id AS anon_1_users_id
FROM (SELECT users.id AS users_id FROM users) AS anon_1
JOIN addresses ON anon_1.users_id = addresses.user_id
ORDER BY anon_1.users_id
Die zweite Abfrage bettet die erste Abfrage als Zeilenquelle ein. Wenn die innere Abfrage OFFSET und/oder LIMIT ohne Sortierung verwendet, können die beiden Abfragen nicht dieselben Ergebnisse liefern.
>>> user = session.scalars(
... select(User).options(subqueryload(User.addresses)).limit(1)
... ).first()
-- the "main" query
SELECT users.id AS users_id
FROM users
LIMIT 1
-- the "load" query issued by subqueryload
SELECT addresses.id AS addresses_id,
addresses.user_id AS addresses_user_id,
anon_1.users_id AS anon_1_users_id
FROM (SELECT users.id AS users_id FROM users LIMIT 1) AS anon_1
JOIN addresses ON anon_1.users_id = addresses.user_id
ORDER BY anon_1.users_id
Abhängig von den Datenbankdetails kann es vorkommen, dass wir für die beiden Abfragen ein Ergebnis wie das folgende erhalten:
-- query #1
+--------+
|users_id|
+--------+
| 1|
+--------+
-- query #2
+------------+-----------------+---------------+
|addresses_id|addresses_user_id|anon_1_users_id|
+------------+-----------------+---------------+
| 3| 2| 2|
+------------+-----------------+---------------+
| 4| 2| 2|
+------------+-----------------+---------------+Oben erhalten wir zwei addresses-Zeilen für user.id von 2 und keine für 1. Wir haben zwei Zeilen verschwendet und die Sammlung nicht geladen. Dies ist ein heimtückischer Fehler, denn ohne die SQL-Abfrage und die Ergebnisse zu überprüfen, zeigt das ORM kein Problem an. Wenn wir auf die addresses für den User zugreifen, den wir haben, wird eine Lazy-Load für die Sammlung ausgelöst, und wir werden nicht sehen, dass etwas schief gelaufen ist.
Die Lösung für dieses Problem besteht darin, immer eine deterministische Sortierreihenfolge anzugeben, damit die Hauptabfrage immer dieselbe Menge an Zeilen zurückgibt. Dies bedeutet im Allgemeinen, dass Sie auf einer eindeutigen Spalte der Tabelle Select.order_by() anwenden sollten. Der Primärschlüssel ist hierfür eine gute Wahl.
session.scalars(
select(User).options(subqueryload(User.addresses)).order_by(User.id).limit(1)
).first()Beachten Sie, dass die Eager-Loader-Strategie joinedload() nicht dasselbe Problem hat, da nur eine Abfrage ausgeführt wird, sodass die Ladeabfrage nicht von der Hauptabfrage abweichen kann. Ebenso hat die Eager-Loader-Strategie selectinload() dieses Problem ebenfalls nicht, da sie ihre Sammlungsleere direkt an die gerade geladenen Primärschlüsselwerte bindet.
Siehe auch
Was sind default, default_factory und insert_default und was sollte ich verwenden?¶
Hier gibt es einen kleinen Konflikt in der SQLAlchemy-API aufgrund der Einführung von PEP-681 Dataclass-Transforms, das bei seinen Namenskonventionen streng ist. PEP-681 kommt ins Spiel, wenn Sie MappedAsDataclass verwenden, wie in Deklarative Dataclass-Zuordnung gezeigt. Wenn Sie MappedAsDataclass nicht verwenden, kommt es nicht zur Anwendung.
Teil Eins – Klassisches SQLAlchemy ohne Dataclasses¶
Wenn Sie MappedAsDataclass **nicht** verwenden, was in SQLAlchemy seit vielen Jahren der Fall ist, unterstützt die Konstruktion mapped_column() (und Column) einen Parameter mapped_column.default. Dies gibt einen Standardwert auf Python-Seite an (im Gegensatz zu einem serverseitigen Standardwert, der Teil der Schemadefinition Ihrer Datenbank wäre), der beim Ausgeben einer INSERT-Anweisung angewendet wird. Dieser Standardwert kann **jeder** statische Python-Wert sein, wie eine Zeichenkette, **oder** eine aufrufbare Python-Funktion, **oder** ein SQLAlchemy SQL-Konstrukt. Eine vollständige Dokumentation für mapped_column.default finden Sie unter Vom Client aufgerufene SQL-Ausdrücke.
Wenn Sie mapped_column.default mit einer ORM-Zuordnung verwenden, die MappedAsDataclass **nicht** verwendet, erscheint dieser Standardwert/Aufruf **nicht sofort auf Ihrem Objekt, wenn Sie es erstellen**. Er wird erst angewendet, wenn SQLAlchemy eine INSERT-Anweisung für Ihr Objekt erstellt.
Ein sehr wichtiger Punkt ist, dass bei der Verwendung von mapped_column() (und Column) der klassische Parameter mapped_column.default unter einem neuen Namen verfügbar ist, nämlich mapped_column.insert_default. Wenn Sie eine mapped_column() erstellen und **keine** MappedAsDataclass verwenden, sind die Parameter mapped_column.default und mapped_column.insert_default **synonym**.
Teil Zwei – Verwendung der Dataclass-Unterstützung mit MappedAsDataclass¶
Wenn Sie MappedAsDataclass verwenden, d. h. die spezifische Form der Zuordnung, die unter Deklarative Dataclass-Zuordnung verwendet wird, ändert sich die Bedeutung des Schlüsselworts mapped_column.default. Wir erkennen an, dass es nicht ideal ist, dass dieser Name sein Verhalten ändert, aber es gab keine Alternative, da PEP-681 verlangt, dass mapped_column.default diese Bedeutung annimmt.
Wenn Dataclasses verwendet werden, muss der Parameter mapped_column.default so verwendet werden, wie er unter Python Dataclasses beschrieben wird – er bezieht sich auf einen konstanten Wert wie eine Zeichenkette oder eine Zahl und wird **sofort auf Ihr Objekt angewendet, wenn es erstellt wird**. Er wird derzeit auch auf den Parameter mapped_column.default von Column angewendet, wo er in einer INSERT-Anweisung automatisch verwendet wird, auch wenn er auf dem Objekt nicht vorhanden ist. Wenn Sie stattdessen einen aufrufbaren Wert für Ihre Dataclass verwenden möchten, der auf das Objekt angewendet wird, wenn es erstellt wird, würden Sie mapped_column.default_factory verwenden.
Um auf das reine INSERT-Verhalten von mapped_column.default zuzugreifen, das im obigen Teil eins beschrieben wird, würden Sie stattdessen den Parameter mapped_column.insert_default verwenden. mapped_column.insert_default stellt bei der Verwendung von Dataclasses weiterhin einen direkten Weg zum Core-Level "Standard"-Prozess dar, bei dem der Parameter ein statischer Wert oder ein Aufruf sein kann.
Konstrukt |
Funktioniert mit Dataclasses? |
Funktioniert ohne Dataclasses? |
Akzeptiert Skalarwerte? |
Akzeptiert Aufrufe? |
Füllt Objekt sofort auf? |
|---|---|---|---|---|---|
✔ |
✔ |
✔ |
Nur wenn keine Dataclasses vorhanden sind |
Nur wenn Dataclasses vorhanden sind |
|
✔ |
✔ |
✔ |
✔ |
✖ |
|
✔ |
✖ |
✖ |
✔ |
Nur wenn Dataclasses vorhanden sind |
Die Designs von flambé! dem Drachen und Der Alchemist wurden von Rotem Yaari erstellt und großzügig gespendet.
Erstellt mit Sphinx 7.2.6. Dokumentation zuletzt generiert: Di 11 Mär 2025 14:40:17 EDT