Magento Preislogik SQL: Wie Basispreis, Sonderpreis, Katalogregeln, Tier Prices und Warenkorbregeln ineinander greifen — und wie man per SQL nachvollzieht, warum ein Produkt einen bestimmten Preis zeigt.
Inhaltsverzeichnis
- 1. Warum Magento Preislogik so schwer zu durchschauen ist
- 2. Basispreis im EAV-Modell: catalog_product_entity_decimal
- 3. Der Preisindex: catalog_product_index_price
- 4. Katalogpreisregeln: catalog_rule und catalog_rule_product_price
- 5. Mengenrabatte: catalog_product_entity_tier_price
- 6. Warenkorb-Rabatte: salesrule und salesrule_coupon
- 7. SQL: Warum zeigt dieses Produkt diesen Preis?
- 8. Unterstützung
- 9. Zusammenfassung
- 10. FAQ
1. Warum Magento Preislogik so schwer zu durchschauen ist
Magento Preislogik SQL zu verstehen bedeutet, sich durch mehrere Tabellenschichten zu arbeiten, die alle auf denselben Endpreis einwirken. Ein Produkt hat einen Basispreis im EAV-Modell, einen vorberechneten Endpreis im Index, potenzielle Katalogregeln und Mengenrabatte — und erst am Checkout kommen Warenkorbregeln und Coupons dazu.
Das Problem in der Praxis: Wenn ein Kunde einen falschen Preis sieht oder ein Coupon nicht angewendet wird, gibt es keine einzige Tabelle, die den Fehler erklärt. Man muss den Preis durch alle Stufen verfolgen. Genau dafür sind die SQL-Blöcke in diesem Artikel gemacht.
Wichtig ist auch der Unterschied zwischen dem berechneten Preis zur Laufzeit und dem gespeicherten Preis im Index. Magento berechnet den Frontendpreis nicht bei jeder Anfrage neu — stattdessen hält der Preisindexer eine vorberechnete Tabelle aktuell. Wenn der Index veraltet ist, kann ein Produkt den falschen Preis zeigen, ohne dass sich an den Basistabellen etwas geändert hat.
2. Basispreis im EAV-Modell: catalog_product_entity_decimal
Magento speichert Produktpreise als EAV-Attribute. Das Preisattribut mit dem Code price liegt in catalog_product_entity_decimal. Neben dem Basispreis gibt es dort auch Sonderpreis (special_price) und Einkaufspreis (cost). Der attribute_id-Wert für diese Attribute ist installationsabhängig und muss über eav_attribute abgefragt werden.
-- Find attribute_ids for price-related attributes
SELECT attribute_id, attribute_code, backend_type
FROM eav_attribute
WHERE entity_type_id = (
SELECT entity_type_id FROM eav_entity_type WHERE entity_type_code = 'catalog_product'
)
AND attribute_code IN ('price', 'special_price', 'cost', 'special_from_date', 'special_to_date')
ORDER BY attribute_code;
-- Read base price and special price for a product (replace attribute_ids from above)
SELECT
e.sku,
price_attr.value AS base_price,
special_attr.value AS special_price,
from_attr.value AS special_from,
to_attr.value AS special_to
FROM catalog_product_entity e
LEFT JOIN catalog_product_entity_decimal price_attr
ON price_attr.entity_id = e.entity_id
AND price_attr.attribute_id = (SELECT attribute_id FROM eav_attribute WHERE attribute_code = 'price' AND entity_type_id = 4)
AND price_attr.store_id = 0
LEFT JOIN catalog_product_entity_decimal special_attr
ON special_attr.entity_id = e.entity_id
AND special_attr.attribute_id = (SELECT attribute_id FROM eav_attribute WHERE attribute_code = 'special_price' AND entity_type_id = 4)
AND special_attr.store_id = 0
LEFT JOIN catalog_product_entity_datetime from_attr
ON from_attr.entity_id = e.entity_id
AND from_attr.attribute_id = (SELECT attribute_id FROM eav_attribute WHERE attribute_code = 'special_from_date' AND entity_type_id = 4)
AND from_attr.store_id = 0
LEFT JOIN catalog_product_entity_datetime to_attr
ON to_attr.entity_id = e.entity_id
AND to_attr.attribute_id = (SELECT attribute_id FROM eav_attribute WHERE attribute_code = 'special_to_date' AND entity_type_id = 4)
AND to_attr.store_id = 0
WHERE e.sku = '24-MB01';
3. Der Preisindex: catalog_product_index_price
Der tatsächlich auf der Frontend-Seite angezeigte Preis stammt nicht direkt aus den EAV-Tabellen, sondern aus dem Preisindex catalog_product_index_price. Der Indexer berechnet dort für jede Kombination aus Produkt, Website und Kundengruppe den Minimal- und Maximalpreis, inklusive Tier Prices. Das ist der Preis, den Magento im Katalog anzeigt.
-- Read the precomputed price from the price index
SELECT
cpip.entity_id,
cpe.sku,
cpip.website_id,
cpip.customer_group_id,
cpip.price AS original_price,
cpip.final_price AS final_price,
cpip.min_price,
cpip.max_price,
cpip.tier_price
FROM catalog_product_index_price cpip
JOIN catalog_product_entity cpe ON cpe.entity_id = cpip.entity_id
WHERE cpe.sku = '24-MB01'
ORDER BY cpip.website_id, cpip.customer_group_id;
-- Compare: is the index outdated vs. the EAV base price?
SELECT
cpe.sku,
cpd.value AS eav_base_price,
cpip.final_price AS index_final_price,
ABS(cpd.value - cpip.price) AS price_diff
FROM catalog_product_entity cpe
JOIN catalog_product_entity_decimal cpd
ON cpd.entity_id = cpe.entity_id
AND cpd.attribute_id = (SELECT attribute_id FROM eav_attribute WHERE attribute_code = 'price' AND entity_type_id = 4)
AND cpd.store_id = 0
JOIN catalog_product_index_price cpip
ON cpip.entity_id = cpe.entity_id
AND cpip.website_id = 1
AND cpip.customer_group_id = 0
WHERE ABS(cpd.value - cpip.price) > 0.001
LIMIT 20;
4. Katalogpreisregeln: catalog_rule und catalog_rule_product_price
Katalogpreisregeln werden in catalog_rule definiert und auf Produktebene in catalog_rule_product_price vorberechnet. Der Preisindexer liest diese Tabelle bei der Indexierung aus und integriert den regelbasierten Preis in catalog_product_index_price. Das bedeutet: eine aktive Katalogregel erscheint im Index als reduzierter final_price.
-- List active catalog price rules
SELECT
rule_id,
name,
is_active,
from_date,
to_date,
simple_action,
discount_amount
FROM catalog_rule
WHERE is_active = 1
ORDER BY sort_order;
-- Check if a specific product has a catalog rule price applied
SELECT
crpp.product_id,
cpe.sku,
crpp.rule_date,
crpp.customer_group_id,
crpp.website_id,
crpp.rule_price,
cr.name AS rule_name
FROM catalog_rule_product_price crpp
JOIN catalog_product_entity cpe ON cpe.entity_id = crpp.product_id
JOIN catalog_rule cr ON cr.rule_id = crpp.rule_id
WHERE cpe.sku = '24-MB01'
AND crpp.rule_date = CURDATE();
5. Mengenrabatte: catalog_product_entity_tier_price
Tier Prices ermöglichen mengenbasierte Rabatte. Sie werden in catalog_product_entity_tier_price gespeichert und können pro Kundengruppe und Website definiert werden. Der Preisindexer liest Tier Prices aus und schreibt den niedrigsten Tier-Preis als tier_price in den Preisindex.
-- Read tier price configuration for a product
SELECT
tp.entity_id,
cpe.sku,
tp.all_groups,
tp.customer_group_id,
tp.qty,
tp.value AS tier_price,
tp.percentage_value,
tp.website_id
FROM catalog_product_entity_tier_price tp
JOIN catalog_product_entity cpe ON cpe.entity_id = tp.entity_id
WHERE cpe.sku = '24-MB01'
ORDER BY tp.qty;
-- Find products where the index tier_price differs from the final_price
-- (these products have quantity-based discounts available)
SELECT
cpe.sku,
cpip.final_price,
cpip.tier_price,
cpip.final_price - cpip.tier_price AS savings_at_min_qty
FROM catalog_product_index_price cpip
JOIN catalog_product_entity cpe ON cpe.entity_id = cpip.entity_id
WHERE cpip.tier_price IS NOT NULL
AND cpip.tier_price < cpip.final_price
AND cpip.website_id = 1
AND cpip.customer_group_id = 0
ORDER BY savings_at_min_qty DESC
LIMIT 20;
6. Warenkorb-Rabatte: salesrule und salesrule_coupon
Warenkorbregeln und Gutscheincodes werden erst beim Checkout angewendet — nicht im Preisindex. Sie sind in salesrule definiert, Coupons in salesrule_coupon. Welche Regel auf eine Bestellung angewendet wurde, steht in sales_order (Felder coupon_code, discount_amount) und auf Positionsebene in sales_order_item.
-- List active cart price rules with coupon information
SELECT
sr.rule_id,
sr.name,
sr.is_active,
sr.uses_per_customer,
sr.uses_per_coupon,
sr.coupon_type,
sr.simple_action,
sr.discount_amount,
COUNT(src.coupon_id) AS coupon_count
FROM salesrule sr
LEFT JOIN salesrule_coupon src ON src.rule_id = sr.rule_id
WHERE sr.is_active = 1
GROUP BY sr.rule_id
ORDER BY sr.sort_order;
-- Find all orders where a specific coupon was used
SELECT
so.entity_id,
so.increment_id,
so.coupon_code,
so.discount_amount,
so.grand_total,
so.created_at,
so.status
FROM sales_order so
WHERE so.coupon_code = 'SUMMER20'
ORDER BY so.created_at DESC
LIMIT 50;
-- Coupon usage statistics
SELECT
src.code AS coupon_code,
sr.name AS rule_name,
src.times_used,
src.usage_limit,
src.usage_limit - src.times_used AS remaining_uses,
SUM(so.discount_amount) AS total_discount_given
FROM salesrule_coupon src
JOIN salesrule sr ON sr.rule_id = src.rule_id
LEFT JOIN sales_order so ON so.coupon_code = src.code
WHERE sr.is_active = 1
GROUP BY src.coupon_id
ORDER BY src.times_used DESC
LIMIT 20;
7. SQL: Warum zeigt dieses Produkt diesen Preis?
Mit den bisher gezeigten Tabellen lässt sich die vollständige Preiskette für jedes Produkt nachvollziehen. Die Reihenfolge der Preisberechnung in Magento ist: Basispreis im EAV → Sonderpreis wenn aktiv → Katalogregeln → Tier Prices (der niedrigste dieser Werte wird final_price im Index) → Warenkorbregeln werden erst am Checkout obenauf angewendet.
-- Full price trace for a product: all price sources in one view
SELECT
cpe.sku,
cpe.entity_id,
-- EAV base prices
price_eav.value AS eav_base_price,
special_eav.value AS eav_special_price,
from_eav.value AS special_from_date,
to_eav.value AS special_to_date,
-- Price index (pre-calculated by indexer)
cpip.price AS index_original_price,
cpip.final_price AS index_final_price,
cpip.tier_price AS index_min_tier_price,
-- Is there an active catalog rule?
crpp.rule_price AS catalog_rule_price,
cr.name AS catalog_rule_name,
-- Effective display price
LEAST(
COALESCE(cpip.final_price, 9999999),
COALESCE(cpip.tier_price, 9999999)
) AS effective_display_price
FROM catalog_product_entity cpe
-- EAV: base price
LEFT JOIN catalog_product_entity_decimal price_eav
ON price_eav.entity_id = cpe.entity_id
AND price_eav.attribute_id = (SELECT attribute_id FROM eav_attribute WHERE attribute_code = 'price' AND entity_type_id = 4)
AND price_eav.store_id = 0
-- EAV: special price
LEFT JOIN catalog_product_entity_decimal special_eav
ON special_eav.entity_id = cpe.entity_id
AND special_eav.attribute_id = (SELECT attribute_id FROM eav_attribute WHERE attribute_code = 'special_price' AND entity_type_id = 4)
AND special_eav.store_id = 0
-- EAV: special_from_date
LEFT JOIN catalog_product_entity_datetime from_eav
ON from_eav.entity_id = cpe.entity_id
AND from_eav.attribute_id = (SELECT attribute_id FROM eav_attribute WHERE attribute_code = 'special_from_date' AND entity_type_id = 4)
AND from_eav.store_id = 0
-- EAV: special_to_date
LEFT JOIN catalog_product_entity_datetime to_eav
ON to_eav.entity_id = cpe.entity_id
AND to_eav.attribute_id = (SELECT attribute_id FROM eav_attribute WHERE attribute_code = 'special_to_date' AND entity_type_id = 4)
AND to_eav.store_id = 0
-- Price index
LEFT JOIN catalog_product_index_price cpip
ON cpip.entity_id = cpe.entity_id
AND cpip.website_id = 1
AND cpip.customer_group_id = 0
-- Active catalog rule price for today
LEFT JOIN catalog_rule_product_price crpp
ON crpp.product_id = cpe.entity_id
AND crpp.website_id = 1
AND crpp.customer_group_id = 0
AND crpp.rule_date = CURDATE()
LEFT JOIN catalog_rule cr ON cr.rule_id = crpp.rule_id
WHERE cpe.sku = '24-MB01';
Mironsoft
Magento Preislogik SQL transparent machen — vom Index bis zum Checkout
Wir helfen dabei, Preisfehler in Magento-Shops zu diagnostizieren, Preisindexer zu überwachen und Katalogregel- sowie Coupon-Probleme nachvollziehbar zu lösen.
Preisanalyse
Fehlpreise durch alle Stufen der Magento-Preiskette zurückverfolgen
Indexer-Monitoring
catalog_product_index_price auf Aktualität und Konsistenz prüfen
Coupon-Diagnose
salesrule und salesrule_coupon auf Nutzung und Fehler auswerten
9. Zusammenfassung
Magento Preislogik SQL — Das Wichtigste auf einen Blick
Preiskette
Basispreis (EAV) → Sonderpreis → Katalogregeln → Tier Prices → Preisindex → Warenkorbregeln am Checkout.
Schlüsseltabellen
catalog_product_entity_decimal, catalog_product_index_price, catalog_rule_product_price, catalog_product_entity_tier_price, salesrule_coupon.
Häufiger Fehler
Veralteter Preisindex zeigt alten Preis — Diagnose: EAV-Preis und Indexpreis vergleichen, dann Indexer prüfen.
Praxisregel
Preisfehler immer durch alle Stufen verfolgen — kein einzelner Tabellenwert erklärt den Endpreis vollständig.