So either this was more complicated than I expected, or I simply made it more complicated than it needs to be. I'm not entirely certain if it works for more than 2 attribute combinations, nor if it works on more than just 1 test product at a time. I'm sure there is a more elegant way to get the cartesian product without hammering it out in 2 different CTEs, but this was simply the first way that worked.
It should get you closer though, and I would test test test, I can't emphasize enough how much I would test this before touching production data. I didn't even look at the execution plan because there are just some things I don't need to know. The @MAXoptionCount is intended to help prevent blowing up your database in case you happen to have a product with an unruly number of attributes.
Also, there is some question on where available stock comes from. I know where it comes from in my source data, but I just winged it for this case. The final SELECT is mostly redundant so you can pull in your GTIN from wherever it resides.
DECLARE @MINoptionCount int,
@MAXoptionCount int
SET @MINoptionCount = 1;
SET @MAXoptionCount = 3;
--get products with attributes and how many potential combinations
WITH multiOptions AS (
SELECT ProductAttributeMappingId,
COUNT(DISTINCT(ProductAttributeMappingId)) AS [optionCount]
FROM ProductAttributeValue
WHERE [Name] IS NOT NULL
GROUP BY ProductAttributeMappingId
ORDER BY COUNT(DISTINCT(ProductAttributeMappingId)) DESC
--required for CTE
OFFSET 0 ROWS
),
--get associated mappings
optionArray1 AS
(
SELECT val.Id AS "valId",
map.Id AS "mapId",
map.ProductId,
val.AssociatedProductId,
val.PriceAdjustment AS "Price",
val.Quantity
FROM ProductAttributeValue val
INNER JOIN Product_ProductAttribute_Mapping map
ON val.ProductAttributeMappingId = map.Id
INNER JOIN multiOptions sub
ON val.ProductAttributeMappingId = sub.ProductAttributeMappingId
WHERE val.[Name] IS NOT NULL
AND sub.optionCount BETWEEN @MINoptionCount AND @MAXoptionCount
--testing
AND map.ProductId= 40987
),
optionArray2 AS
(
SELECT val.Id AS "valId",
map.Id AS "mapId",
map.ProductId,
val.PriceAdjustment AS "Price",
val.Quantity
FROM ProductAttributeValue val
INNER JOIN Product_ProductAttribute_Mapping map
ON val.ProductAttributeMappingId = map.Id
INNER JOIN multiOptions sub
ON val.ProductAttributeMappingId = sub.ProductAttributeMappingId
WHERE val.[Name] IS NOT NULL
AND sub.optionCount BETWEEN @MINoptionCount AND @MAXoptionCount
--testing
AND map.ProductId= 40987
),
cartesianProduct AS (
SELECT
CAST(opt1.valId AS VARCHAR(12)) + ',' + CAST(opt2.valId AS VARCHAR(12)) AS [XMLstring],
opt1.mapId,
opt1.ProductId,
opt1.Price,
--get minimum quantity from available options or from somewhere else?
opt1.Quantity
FROM optionArray1 opt1
CROSS JOIN optionArray2 opt2
WHERE opt1.ProductId = opt2.ProductId
AND opt1.mapId < opt2.mapId
),
--parse CSV from cartesian product to XML
buildXML AS (
SELECT
--Id
--ProductId
ProductId,
--AttributesXml
CONVERT(NVARCHAR(Max),
(SELECT mapId AS "@ID",
(SELECT Value
FROM STRING_SPLIT([XMLstring], ',') AS [Value]
FOR XML PATH ('ProductAttributeValue'), TYPE
)
FOR XML PATH('ProductAttribute'), TYPE, ROOT('Attributes')
)) AS [AttributesXml],
--StockQuantity
Quantity,
--OverriddenPrice
Price
FROM cartesianProduct
)
INSERT INTO ProductAttributeCombination (
--Id,
ProductId,
AttributesXml,
StockQuantity,
AllowOutOfStockOrders,
Sku,
ManufacturerPartNumber,
Gtin,
OverriddenPrice,
NotifyAdminForQuantityBelow,
PictureId
)
SELECT
--Id
--ProductId
ProductId,
--AttributesXml
[AttributesXml],
--StockQuantity
Quantity,
--AllowOutOfStockOrders
1,
--Sku
null,
--ManufacturerPartNumber
null,
--Gtin
'[YOUR_DATA_HERE]',
--OverriddenPrice
Price,
--NotifyAdminForQuantityBelow
0,
--PictureId
0
FROM buildXML;