PREIS
INDEX
SQL · Magento · Preisberechnung · Rabattlogik · EAV
Preis- und Rabattlogik in Magento-Datenbanken nachvollziehen

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.

catalog_product_index_price Tier Price Catalog Rule salesrule_coupon

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.

10. FAQ: Magento Preislogik SQL

1 Welche Tabelle enthält den tatsächlich angezeigten Produktpreis?
catalog_product_index_price enthält den vorberechneten Endpreis, den Magento auf dem Frontend anzeigt. Diese Tabelle wird vom Preisindexer befüllt und muss aktuell sein.
2 Was ist der Unterschied zwischen Basispreis und final_price im Index?
Der Basispreis steht in catalog_product_entity_decimal. Der final_price im Index ist der bereits berechnete Preis nach Anwendung von Sonderpreis und Katalogregeln — der Wert, der tatsächlich angezeigt wird.
3 Warum zeigt ein Produkt trotz korrektem EAV-Preis den falschen Preis?
Der Preisindexer ist wahrscheinlich veraltet. catalog_product_index_price enthält noch den alten Wert. Ein Reindex löst das Problem: bin/magento indexer:reindex catalog_product_price.
4 Wie findet man heraus, ob eine Katalogregel auf ein Produkt angewendet wird?
In catalog_rule_product_price nach der product_id und dem aktuellen Datum suchen. Wenn dort ein Eintrag existiert, wurde die Regel für heute vorberechnet.
5 Wo sind Tier Prices in der Magento-Datenbank gespeichert?
In catalog_product_entity_tier_price — pro Produkt, Kundengruppe, Website und Mindestmenge. Der niedrigste Tier-Preis erscheint als tier_price in catalog_product_index_price.
6 Wie prüft man, ob ein Couponcode gültig ist?
In salesrule_coupon nach dem Code suchen und times_used mit usage_limit vergleichen. Zusätzlich prüfen, ob die zugehörige Regel in salesrule aktiv ist und ob from_date/to_date passen.
7 Welche Preisstufe hat Vorrang: Sonderpreis oder Katalogregel?
Magento nimmt den niedrigsten Preis aus Sonderpreis und Katalogregeln. Der Preisindexer berechnet final_price als Minimum aller anwendbaren Preisquellen.
8 Warum gibt es unterschiedliche final_price-Werte für verschiedene Kundengruppen?
catalog_product_index_price speichert je einen Eintrag pro Kombination aus Produkt, Website und Kundengruppe. Katalogregeln und Tier Prices können kundengruppen-spezifisch sein.
9 Sind Warenkorbregeln im Preisindex sichtbar?
Nein. Warenkorbregeln (salesrule) werden erst beim Checkout berechnet und erscheinen nicht in catalog_product_index_price. Sie sind nur in der Bestellung selbst sichtbar.
10 Wie findet man alle Bestellungen, bei denen ein bestimmter Coupon verwendet wurde?
In sales_order nach dem Feld coupon_code filtern. Zusätzlich zeigt discount_amount, wie viel Rabatt gewährt wurde.