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,
IPriceService
andIPriceDetailService
- One is handling prices in batch – i.e. prices per SKU (
IPriceService
), and one is handling prices per individual price (IPriceDetailService
) - Both are cached in latest version (cache for
IPriceDetailService
was 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: PriceValue
, PriceGroup
and 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.