Welche Tabellen bei einer Bestellung beteiligt sind
Von der Quote bis zur Rückerstattung: Ein vollständiger SQL-Trace durch alle Datenbankebenen einer Magento-Bestellung – mit konkreten JOINs, Abfragen und dem nötigen Kontext, um Probleme direkt an der Datenbank zu diagnostizieren.
Warum die Magento Checkout Datenbank kennen?
Wer mit Magento arbeitet, stößt früher oder später auf Situationen, in denen ein Blick hinter die Kulissen unumgänglich ist: Eine Bestellung erscheint im Admin-Panel nicht, obwohl die Zahlung erfolgreich war. Ein Lagerbestand stimmt nicht mehr nach einer Stornierung. Eine Rechnung fehlt, obwohl der Kunde sie erhalten hat. In all diesen Fällen führt der schnellste Weg zur Lösung direkt in die Magento Checkout Datenbank – also zu den SQL-Tabellen, die den gesamten Bestellprozess abbilden.
Magento unterteilt den Checkout-Prozess in zwei große Phasen: die Quote-Phase (Warenkorb) und die Order-Phase (Bestellung, Rechnung, Versand, Rückerstattung). Jede Phase hat eigene Tabellen mit klaren Verantwortlichkeiten. Dieser Artikel erklärt alle beteiligten Tabellen, ihre Struktur und zeigt, wie man mit SQL einen vollständigen Trace einer einzelnen Bestellung durch alle Tabellen zieht.
- 1. Die Quote-Phase: quote, quote_item, quote_address
- 2. Gast-Warenkorb: quote_id_mask
- 3. Bestellerstellung: sales_order, sales_order_item, sales_order_address
- 4. Zahlung: sales_order_payment
- 5. Rechnung: sales_invoice, sales_invoice_item
- 6. Versand: sales_shipment, sales_shipment_track
- 7. Rückerstattung: sales_creditmemo
- 8. Lagerreservierung: inventory_reservation
- 9. Vollständiger SQL-Trace einer Bestellung
- 10. Zusammenfassung
- 11. FAQ
1. Die Quote-Phase: quote, quote_item, quote_address
Bevor eine Bestellung entsteht, lebt jeder Einkauf als Quote in der Datenbank. Die Tabelle quote ist der Warenkorb. Sie wird angelegt, sobald ein Produkt in den Warenkorb gelegt wird, und bleibt so lange aktiv, bis die Bestellung aufgegeben oder der Warenkorb bereinigt wird.
Die wichtigsten Spalten der quote-Tabelle im Überblick:
-- Warenkorb-Übersicht: aktive Quotes der letzten 24 Stunden
SELECT
entity_id,
store_id,
customer_id, -- NULL bei Gästen
customer_email,
is_active, -- 1 = aktiv, 0 = in Bestellung umgewandelt oder abgelaufen
items_count,
items_qty,
grand_total,
base_grand_total,
checkout_method, -- 'guest' oder 'customer'
created_at,
updated_at
FROM quote
WHERE is_active = 1
AND updated_at >= NOW() - INTERVAL 24 HOUR
ORDER BY updated_at DESC;
Jede Position im Warenkorb hat einen eigenen Datensatz in quote_item. Die Verknüpfung erfolgt über quote_id. Konfigurierbare Produkte erzeugen zwei Einträge: einen für das konfigurierbare Elternprodukt und einen für die gewählte Variante.
-- Alle Positionen eines bestimmten Warenkorbs
SELECT
qi.item_id,
qi.product_id,
qi.sku,
qi.name,
qi.qty,
qi.price,
qi.row_total,
qi.product_type, -- 'simple', 'configurable', 'bundle' etc.
qi.parent_item_id -- bei Variante: ID des Eltern-Items
FROM quote_item qi
WHERE qi.quote_id = 42
ORDER BY qi.item_id;
Adressen werden in quote_address gespeichert. Es gibt zwei Datensätze pro Quote: einen mit address_type = 'billing' und einen mit address_type = 'shipping'. Hier stehen Versandkosten, angewendete Steuerbeträge und die Versandmethode, die der Kunde im Checkout gewählt hat.
-- Liefer- und Rechnungsadresse eines Warenkorbs
SELECT
address_type,
firstname,
lastname,
street,
city,
postcode,
country_id,
shipping_method, -- z.B. 'flatrate_flatrate'
shipping_amount,
tax_amount,
subtotal
FROM quote_address
WHERE quote_id = 42
ORDER BY address_type;
2. Gast-Warenkorb: quote_id_mask
Für Gäste erzeugt Magento einen öffentlich sicheren Token, der im Browser gespeichert wird. Dieser Token verweist auf die interne quote_id, ohne sie direkt preiszugeben. Die Tabelle quote_id_mask ist die Brücke zwischen dem öffentlichen Token und dem internen Warenkorb.
-- Quote anhand des öffentlichen Guest-Cart-Tokens finden
SELECT
qm.entity_id,
qm.quote_id,
qm.masked_id, -- öffentlicher UUID-Token aus dem Browser/Cookie
q.customer_email,
q.grand_total,
q.is_active,
q.created_at
FROM quote_id_mask qm
JOIN quote q ON q.entity_id = qm.quote_id
WHERE qm.masked_id = 'abc123def456...'; -- aus API-Request oder Browser
Sobald ein Gast zur Kasse geht und eine Bestellung aufgibt, bleibt der quote_id_mask-Eintrag erhalten. Er ist nützlich für Debugging, wenn ein Gast-Checkout fehlschlägt und man herausfinden muss, welcher Quote involviert war.
3. Bestellerstellung: sales_order, sales_order_item, sales_order_address
Mit dem Abschluss des Checkouts wird aus dem Quote eine Bestellung. Die Tabelle sales_order ist das Herzstück des Bestellsystems. Die quote_id-Spalte stellt die Verbindung zum ursprünglichen Warenkorb her.
-- Bestellung anhand der Bestellnummer suchen
SELECT
entity_id,
increment_id, -- sichtbare Bestellnummer z.B. '100012345'
quote_id, -- Verweis auf den ursprünglichen Warenkorb
store_id,
customer_id, -- NULL bei Gastbestellung
customer_email,
state, -- 'new', 'processing', 'complete', 'closed', 'canceled'
status, -- konfigurierbar: 'pending', 'pending_payment', etc.
grand_total,
base_grand_total,
subtotal,
tax_amount,
shipping_amount,
discount_amount,
total_paid,
total_refunded,
total_qty_ordered,
weight,
created_at
FROM sales_order
WHERE increment_id = '100012345';
Jede Bestellposition liegt in sales_order_item. Im Unterschied zu quote_item bleiben diese Datensätze dauerhaft erhalten und bilden die Grundlage für Rechnungen, Sendungen und Rückerstattungen.
-- Positionen einer Bestellung mit Mengendetails
SELECT
item_id,
order_id,
sku,
name,
product_type,
qty_ordered,
qty_invoiced, -- wie viel wurde bereits in Rechnung gestellt
qty_shipped, -- wie viel wurde bereits versendet
qty_refunded, -- wie viel wurde zurückerstattet
qty_canceled, -- wie viel wurde storniert
price,
row_total,
tax_amount,
discount_amount
FROM sales_order_item
WHERE order_id = 12345 -- entity_id aus sales_order
AND parent_item_id IS NULL; -- nur Eltern-Produkte, keine Varianten
Adressen werden analog zu quote_address in sales_order_address gespeichert – ebenfalls mit address_type 'billing' und 'shipping'. Diese Adressen sind eine Snapshot-Kopie der Adresse zum Zeitpunkt der Bestellung und ändern sich nicht, selbst wenn der Kunde seine Adresse später aktualisiert.
4. Zahlung: sales_order_payment
Jede Bestellung hat genau einen Datensatz in sales_order_payment. Diese Tabelle speichert alle zahlungsrelevanten Informationen: die verwendete Zahlungsmethode, Transaktions-IDs des Payment-Providers und den aktuellen Zahlungsstatus.
-- Zahlungsinformationen zu einer Bestellung
SELECT
sop.entity_id,
sop.parent_id, -- = sales_order.entity_id
sop.method, -- z.B. 'paypal_express', 'checkmo', 'braintree'
sop.last_trans_id, -- letzte Transaktions-ID des Payment-Providers
sop.amount_ordered,
sop.amount_authorized,
sop.amount_paid,
sop.amount_refunded,
sop.amount_canceled,
sop.cc_type, -- Kartentyp bei Kreditkarte: 'VI', 'MC' etc.
sop.cc_last_4, -- letzte 4 Ziffern (maskiert)
sop.cc_exp_month,
sop.cc_exp_year,
sop.additional_information -- serialisiertes Array mit Provider-Daten
FROM sales_order_payment sop
WHERE sop.parent_id = 12345;
Das Feld additional_information enthält ein JSON/serialisiertes Array mit providerspezifischen Daten, z.B. PayPal-Transaktionsdetails oder Braintree-Tokens. Um es lesbar zu machen:
-- additional_information als JSON-Felder lesen (MySQL 5.7.8+ / MariaDB 10.2+)
SELECT
parent_id,
method,
JSON_UNQUOTE(JSON_EXTRACT(additional_information, '$.transaction_id')) AS transaction_id,
JSON_UNQUOTE(JSON_EXTRACT(additional_information, '$.paypal_payer_email')) AS payer_email
FROM sales_order_payment
WHERE method = 'paypal_express'
AND parent_id = 12345;
5. Rechnung: sales_invoice, sales_invoice_item
Eine Rechnung ist nicht automatisch vorhanden. Sie muss explizit erstellt werden – entweder manuell im Admin oder automatisch durch eine Zahlungsmethode (z.B. bei sofortiger Zahlung). Eine Bestellung kann mehrere Rechnungen haben, z.B. bei Teillieferungen.
-- Alle Rechnungen zu einer Bestellung
SELECT
si.entity_id,
si.increment_id, -- sichtbare Rechnungsnummer z.B. '100012345'
si.order_id,
si.state, -- 1 = pending, 2 = paid, 3 = canceled
si.grand_total,
si.base_grand_total,
si.subtotal,
si.tax_amount,
si.shipping_amount,
si.total_qty,
si.created_at,
so.increment_id AS order_number
FROM sales_invoice si
JOIN sales_order so ON so.entity_id = si.order_id
WHERE si.order_id = 12345;
-- Rechnungspositionen
SELECT
sii.entity_id,
sii.parent_id, -- = sales_invoice.entity_id
sii.order_item_id, -- = sales_order_item.item_id
sii.sku,
sii.name,
sii.qty,
sii.price,
sii.row_total,
sii.tax_amount
FROM sales_invoice_item sii
WHERE sii.parent_id = 456; -- entity_id der Rechnung
6. Versand: sales_shipment, sales_shipment_track
Sendungen werden in sales_shipment gespeichert. Auch hier können mehrere Sendungen pro Bestellung existieren. Trackingnummern landen in sales_shipment_track.
-- Versandübersicht mit Trackingnummern
SELECT
ss.entity_id,
ss.increment_id,
ss.order_id,
ss.total_qty,
ss.created_at,
sst.carrier_code,
sst.title AS carrier_name,
sst.track_number,
so.increment_id AS order_number
FROM sales_shipment ss
JOIN sales_order so ON so.entity_id = ss.order_id
LEFT JOIN sales_shipment_track sst ON sst.parent_id = ss.entity_id
WHERE ss.order_id = 12345;
Über sales_shipment_item (analog zu invoice_item) werden die versendeten Positionen mit Mengen gespeichert – wichtig für Teillieferungen, bei denen nicht alle Artikel einer Bestellung gleichzeitig versendet werden.
7. Rückerstattung: sales_creditmemo
Rückerstattungen werden als Credit Memo abgebildet. Die Tabelle sales_creditmemo verknüpft sich mit der Bestellung und optional mit einer Rechnung.
-- Rückerstattungen zu einer Bestellung
SELECT
sc.entity_id,
sc.increment_id,
sc.order_id,
sc.invoice_id, -- NULL wenn keine spezifische Rechnung
sc.state, -- 1 = pending, 2 = refunded, 3 = canceled
sc.grand_total,
sc.base_grand_total,
sc.subtotal,
sc.tax_amount,
sc.shipping_amount,
sc.adjustment_positive,
sc.adjustment_negative,
sc.created_at
FROM sales_creditmemo sc
WHERE sc.order_id = 12345;
8. Lagerreservierung: inventory_reservation
Seit Magento 2.3 (Multi-Source Inventory) werden Lagerbestände nicht mehr direkt subtrahiert, sondern über ein Reservierungssystem verwaltet. Die Tabelle inventory_reservation enthält Buchungen: negative Werte für Reservierungen (Bestellungen), positive für Auflösungen (Stornierungen, Sendungen).
-- Alle Reservierungen für eine bestimmte Bestellung
SELECT
ir.reservation_id,
ir.stock_id,
ir.sku,
ir.quantity, -- negativ = reserviert, positiv = freigegeben
ir.metadata -- JSON mit 'object_type' und 'object_id' (order_id)
FROM inventory_reservation ir
WHERE ir.metadata LIKE '%"object_type":"order"%'
AND ir.metadata LIKE '%"object_id":"12345"%';
-- Aktuellen Netto-Reservierungsbestand pro SKU berechnen
SELECT
sku,
SUM(quantity) AS net_reservation -- sollte 0 sein wenn alles ausgeglichen
FROM inventory_reservation
WHERE stock_id = 1
GROUP BY sku
HAVING ABS(SUM(quantity)) > 0
ORDER BY net_reservation ASC;
9. Vollständiger SQL-Trace einer Bestellung
Hier ist die zentrale Abfrage, mit der man alle wesentlichen Informationen zu einer Bestellung mit einer einzigen SQL-Query zusammenstellen kann. Sie eignet sich ideal für Support-Analysen und Debugging.
-- Vollständiger Trace zu Bestellung #100012345
-- Schritt 1: Basisdaten der Bestellung
SELECT
so.entity_id AS order_id,
so.increment_id AS order_number,
so.state,
so.status,
so.customer_email,
so.grand_total,
so.total_paid,
so.total_refunded,
so.created_at,
-- Quote-Verknüpfung
so.quote_id,
-- Zahlung
sop.method AS payment_method,
sop.last_trans_id,
sop.amount_paid,
-- Rechnungsanzahl
(SELECT COUNT(*) FROM sales_invoice si WHERE si.order_id = so.entity_id)
AS invoice_count,
-- Sendungsanzahl
(SELECT COUNT(*) FROM sales_shipment ss WHERE ss.order_id = so.entity_id)
AS shipment_count,
-- Rückerstattungsanzahl
(SELECT COUNT(*) FROM sales_creditmemo sc WHERE sc.order_id = so.entity_id)
AS creditmemo_count
FROM sales_order so
JOIN sales_order_payment sop ON sop.parent_id = so.entity_id
WHERE so.increment_id = '100012345';
-- Schritt 2: Alle Positionen mit Mengen und Bearbeitungsstand
SELECT
soi.sku,
soi.name,
soi.qty_ordered,
soi.qty_invoiced,
soi.qty_shipped,
soi.qty_refunded,
soi.qty_canceled,
(soi.qty_ordered - soi.qty_invoiced) AS qty_pending_invoice,
(soi.qty_invoiced - soi.qty_shipped) AS qty_pending_shipment,
soi.row_total
FROM sales_order_item soi
JOIN sales_order so ON so.entity_id = soi.order_id
WHERE so.increment_id = '100012345'
AND soi.parent_item_id IS NULL;
-- Schritt 3: Alle Rechnungen, Sendungen, Rückerstattungen
SELECT 'invoice' AS type, increment_id, grand_total, state, created_at
FROM sales_invoice WHERE order_id = (SELECT entity_id FROM sales_order WHERE increment_id = '100012345')
UNION ALL
SELECT 'shipment' AS type, increment_id, total_qty, 0, created_at
FROM sales_shipment WHERE order_id = (SELECT entity_id FROM sales_order WHERE increment_id = '100012345')
UNION ALL
SELECT 'creditmemo' AS type, increment_id, grand_total, state, created_at
FROM sales_creditmemo WHERE order_id = (SELECT entity_id FROM sales_order WHERE increment_id = '100012345')
ORDER BY created_at;
Mironsoft
Magento-Entwicklung & Datenbankanalyse
Magento-Datenbankprobleme professionell lösen?
Wir analysieren Ihre Magento-Datenbank, finden Inkonsistenzen in Bestelldaten, optimieren langsame Abfragen und begleiten Sie bei komplexen Datenmigrations- und Debugging-Projekten.
Datenbank-Audit
Analyse von Bestellinkonsistenzen, doppelten Datensätzen und fehlenden Verknüpfungen
SQL-Debugging
Direktanalyse von Checkout-Fehlern, Zahlungsabbrüchen und fehlenden Rechnungen
Datenmigration
Sichere Migration von Bestelldaten zwischen Magento-Instanzen oder Shop-Systemen
10. Zusammenfassung
Die Magento Checkout Datenbank besteht aus zwei klar getrennten Schichten: der Quote-Schicht (Warenkorb) und der Order-Schicht (Bestellung). Das Verständnis dieser Tabellen ist die Grundlage für jedes ernsthafte Debugging und jede Datenanalyse in Magento.
Magento Checkout Datenbank – Alle Tabellen auf einen Blick
Quote-Phase (Warenkorb)
quote (Warenkorb-Header), quote_item (Positionen), quote_address (Adressen), quote_id_mask (Gast-Token)
Order-Phase (Bestellung)
sales_order, sales_order_item, sales_order_address, sales_order_payment
Folgevorgänge
sales_invoice + sales_invoice_item, sales_shipment + sales_shipment_track, sales_creditmemo + sales_creditmemo_item
Lager
inventory_reservation: negative Einträge beim Kauf, positive Einträge bei Stornierung/Versand – Nettobetrag = aktueller Reservierungsstand
11. FAQ: Magento Checkout Datenbank
1Welche Tabelle speichert den Warenkorb in Magento?
quote gespeichert. Positionen liegen in quote_item, Adressen in quote_address. Gast-Warenkörbe haben zusätzlich einen Eintrag in quote_id_mask mit dem öffentlichen Cart-Token.2Was passiert in der Magento Datenbank wenn eine Bestellung aufgegeben wird?
sales_order, sales_order_item, sales_order_address und sales_order_payment erzeugt. Der Quote-Datensatz bleibt erhalten, wird aber auf is_active=0 gesetzt. Gleichzeitig schreibt der Reservierungs-Mechanismus einen negativen Eintrag in inventory_reservation.3Wie finde ich alle Datenbankeinträge zu einer bestimmten Bestellung?
increment_id aus sales_order ermitteln Sie die entity_id. Mit dieser verknüpfen Sie über JOINs alle Tabellen: sales_order_item, sales_order_address, sales_order_payment, sales_invoice, sales_shipment und sales_creditmemo.4Was ist der Unterschied zwischen quote_id und quote_id_mask?
quote_id ist die interne numerische ID in der quote-Tabelle. Die masked_id in quote_id_mask ist ein UUID-Format-Token, der im Frontend für Gäste als sicherer Cart-Token verwendet wird, ohne die interne ID zu exponieren.5Kann eine Bestellung in Magento mehrere Rechnungen haben?
sales_invoice_item. Die Verknüpfung erfolgt über die Spalte order_id in sales_invoice.6Wie funktioniert die Lagerreservierung in Magento bei einer Bestellung?
quantity-Wert in inventory_reservation (z.B. -2.0000 für 2 bestellte Artikel). Diese Reservierung wird aufgelöst, sobald eine Sendung oder Stornierung erstellt wird, indem ein positiver Gegeneintrag geschrieben wird.7Was speichert sales_order_payment?
sales_order_payment speichert Zahlungsmethode (method), Transaktions-IDs (last_trans_id), Betrags-Felder (amount_paid, amount_authorized) sowie providerspezifische Daten in additional_information.8Wie ist sales_creditmemo mit einer Bestellung verknüpft?
sales_creditmemo enthält eine Spalte order_id, die auf sales_order.entity_id zeigt. Über invoice_id ist das Creditmemo zusätzlich mit einer bestimmten Rechnung verknüpft. Erstattete Positionen stehen in sales_creditmemo_item.9Was ist der Zweck der Tabelle sales_shipment_track?
sales_shipment_track speichert Sendungsverfolgungsinformationen: Trackingnummer (track_number), Carrier (carrier_code), Paketdienstname (title) und die Verknüpfung zur Sendung über parent_id (= sales_shipment.entity_id).10Wann wird ein quote in Magento gelöscht?
sales_clean_quotes bereinigt. Standardmäßig gelten Gast-Quotes nach 1 Stunde und Kunden-Quotes nach 14 Tagen Inaktivität als abgelaufen und können dann bereinigt werden.