One of the questions I have received, from time to time, is that how to store a lot of prices per SKU in Optimizely (B2C) Commerce Cloud. While this is usually a perfect candidate for Optimizely B2B Commerce, there are many customers invested in B2C and want to make the best out of it. Is it possible?
It’s important to understand the pricing system of Optimizely Commerce (which is, written in detail in my book – shameless plug). But in short:
- There are two price systems,
- One is handling prices in batch – i.e. prices per SKU (
IPriceService), and one is handling prices per individual price (
- Both are cached in latest version (cache for
IPriceDetailServicewas added in late 13.x version)
With that in mind, it would be very problematic if you use
IPriceService for such high number of prices per SKU, because each time you save a price, you save a lot of prices at once (same as loading prices). This is how the default
IPriceService implementation saves prices of a SKU
create procedure dbo.ecf_Pricing_SetCatalogEntryPrices @CatalogKeys udttCatalogKey readonly, @PriceValues udttCatalogEntryPrice readonly as begin begin try declare @initialTranCount int = @@TRANCOUNT if @initialTranCount = 0 begin transaction delete pv from @CatalogKeys ck join dbo.PriceGroup pg on ck.CatalogEntryCode = pg.CatalogEntryCode join dbo.PriceValue pv on pg.PriceGroupId = pv.PriceGroupId merge into dbo.PriceGroup tgt using (select distinct CatalogEntryCode, MarketId, CurrencyCode, PriceTypeId, PriceCode from @PriceValues) src on ( tgt.CatalogEntryCode = src.CatalogEntryCode and tgt.MarketId = src.MarketId and tgt.CurrencyCode = src.CurrencyCode and tgt.PriceTypeId = src.PriceTypeId and tgt.PriceCode = src.PriceCode) when matched then update set Modified = GETUTCDATE() when not matched then insert (Created, Modified, CatalogEntryCode, MarketId, CurrencyCode, PriceTypeId, PriceCode) values (GETUTCDATE(), GETUTCDATE(), src.CatalogEntryCode, src.MarketId, src.CurrencyCode, src.PriceTypeId, src.PriceCode); insert into dbo.PriceValue (PriceGroupId, ValidFrom, ValidUntil, MinQuantity, MaxQuantity, UnitPrice) select pg.PriceGroupId, src.ValidFrom, src.ValidUntil, src.MinQuantity, src.MaxQuantity, src.UnitPrice from @PriceValues src left outer join PriceGroup pg on src.CatalogEntryCode = pg.CatalogEntryCode and src.MarketId = pg.MarketId and src.CurrencyCode = pg.CurrencyCode and src.PriceTypeId = pg.PriceTypeId and src.PriceCode = pg.PriceCode delete tgt from dbo.PriceGroup tgt join @CatalogKeys ck on tgt.CatalogEntryCode = ck.CatalogEntryCode left join dbo.PriceValue pv on pv.PriceGroupId = tgt.PriceGroupId where pv.PriceGroupId is null if @initialTranCount = 0 commit transaction end try begin catch declare @msg nvarchar(4000), @severity int, @state int select @msg = ERROR_MESSAGE(), @severity = ERROR_SEVERITY(), @state = ERROR_STATE() if @initialTranCount = 0 rollback transaction raiserror(@msg, @severity, @state) end catch end
If you have experience with SQL (which you probably should), you will see that it’s a deletion of rows in
PriceValue that have
CatalogEntryCode same as , then a merge, then a deletion of left over rows. To make matters worse,
IPriceService system stores data on 3 tables:
PriceType. Imagine doing that with a few dozen of thousands rows.
Even if you change just one price, all prices of that specific SKU will be touched. It’d be fine if you have like ten prices, but if you have ten thousands prices, it’ll be a huge waste.
Not just that. To save one price, you would still need to load all prices of that specific SKU. That’s two layers of waste: the read operations at database layer, and then on application, a lot of price objects will need to be constructed, and then you need to recreate a datatable to send all the data back to the database to do the expensive operation above.
And wait, because the prices saved to
IPriceService needs to be synchronized to
IPriceDetailService (however, you can disable this). Prices that were changed (which is, all of them) need to be replicated to another table.
So in short,
IPriceService was not designed to handle many prices per SKU. If you have less than a few hundred prices per SKU (on average), it’s fine. But if you have more than 1000 prices per SKU, it’s time to look at other options.