Like-for-like PY store analysis: fixing the status mismatch

Like-for-like PY store analysis is where technically correct turns into functionally broken. Fourteen years of building data pipelines taught me that one the hard way. If your dashboard marks a store as ‘Comparable’ this year while its previous year (PY) metrics are calculated against a ‘Non-Comparable’ state, nobody is going to admire your clean schema. The client will just say the report is wrong.

The case that put me here was a retail store that closed temporarily for a refresh. The data was accurate. It was also unusable, because the statuses did not line up across the two periods. Anyone who has hit this knows standard joins fall apart once a store carries several events in the same history: an opening, a closing, a refresh.

Where the naive join breaks

Most people reach for Power Query or a plain SQL join and treat the store key as a unique identifier. That holds until a store collects more than one lifecycle event. Our Rome store had a temporary closure in 2023 and a permanent one in 2024, which is two rows behind one key, and a standard join turns that into a Cartesian product that makes the like-for-like PY comparison worthless.

I have written before about robust like-for-like Power BI models, but the PY side of it needs something more procedural. The fix is a separate L4L key for the previous year, one that forces the calculation to read the store’s current status context.

The procedural SQL fix

When set-based logic falls over because of row duplication, I go procedural: a SQL cursor that walks the store dates and updates a mapping table. Every store-month combination comes out with the status key it needs, without a stack of nested joins to maintain.

-- The Senior Dev's Workaround: Procedural Status Mapping
DECLARE @StoreKey INT, @OpenDate DATE, @CloseDate DATE, @L4LKey INT;

DECLARE sd CURSOR FOR
    SELECT StoreKey, OpenDate, CloseDate, L4LKey
    FROM #tmp_Store_Dates
    ORDER BY CloseDate;

OPEN sd;
FETCH NEXT FROM sd INTO @StoreKey, @OpenDate, @CloseDate, @L4LKey;

WHILE @@FETCH_STATUS = 0
BEGIN
    -- Update based on the current L4L state for the PY calculation
    UPDATE [#tmp_Stores_Months]
    SET [L4LKey_PY] = CASE @L4LKey
                        WHEN 2 THEN IIF(@OpenDate >= [FirstDayOfMonth], @L4LKey, NULL)
                        WHEN 3 THEN IIF(@CloseDate <= [LastDayOfMonth], @L4LKey, NULL)
                        ELSE 1
                      END
    WHERE [L4LKey_PY] IS NULL AND [StoreKey] = @StoreKey;

    FETCH NEXT FROM sd INTO @StoreKey, @OpenDate, @CloseDate, @L4LKey;
END
CLOSE sd;
DEALLOCATE sd;

Wiring it into DAX time intelligence

Once L4LKey_PY exists in the backend, the Power BI side gets much simpler. The tangle of filter logic goes away and the USERELATIONSHIP function carries it, so a measure can switch between this year’s status and last year’s.

Retail Sales (PY) = 
CALCULATE(
    [Retail Sales],
    'Time Intelligence'[Time Measures] = "PY",
    USERELATIONSHIP('Bridge_L4L'[L4LKey_PY], 'DIM_L4L'[L4LKey])
)

Now when someone filters for “Comparable” stores, both sides of the comparison agree on what that means. “Refresh” stores stop leaking into “Permanent” results, which was the confusion that started all of this. The model ends up matching the way the business talks about its own stores.

My guide on escaping the SQL jungle covers the transformation side of this. For the L4L patterns themselves, SQLBI has good documentation on model-independent functions.

If like-for-like PY store analysis is eating your dev hours, I can take it on. I have been wrestling with WordPress since the 4.x days.

How I test this

A DAX measure that returns a number is not the same as a DAX measure that is right. I cross-check against the ugly cases first, mid-month closures above all. Procedural SQL looks dirty next to a tidy set-based query, and it is the version that keeps the report standing when the source data misbehaves. Put the effort into the architecture rather than into being clever in Power Query.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.