MSSQL парсинг XML
В MSSQL нет опыт работы с XML, поэтому непонятно как сделать.
Имеется таблица с каталогом, к ней джойнится получаемый извне XML. XML содержит параметры товара, один из которых множественный - периоды действия акции. Формат XML в части периодов возможно поменять.
Как распарсить XML во временную табличку? для:
a. читабельного отображения в определенном формате параметров дат ([beginDt1; endDt1], [beginDt2; endDt2])
b. для вычисления на следующем шаге вхождения текущей даты в один из периодов на лету
Как при выборке из каталога, с джойном полученного на шаге 1 - определить действует ли акция для товара на текущий день?
Исходный код:
DECLARE @response XML = '<response>
<sku artId = "5023">
<p>
<n>Наименование товара</n>
<v>Кофе Tchibo сублимированный</v>
</p>
<p>
<n>Категория</n>
<v>Кофе</v>
</p>
<p>
<n>Вес</n>
<v>0.2</v>
</p>
<p>
<n>Периоды акций</n>
<v>
<val1><type>Total</type><beginDt>2024-06-20</beginDt><endDt>2024-07-22</endDt></val1>
<val2><type>Total</type><beginDt>2024-08-21</beginDt><endDt>2024-09-18</endDt></val2>
</v>
</p>
</sku>
</response>
'
SELECT @response
DROP TABLE IF EXISTS #tbl;
CREATE TABLE #tbl(
num INT NOT NULL PRIMARY KEY IDENTITY(1,1)
,artId INTEGER
,paramName NVARCHAR(255)
,val NVARCHAR(MAX)
,multipleVal NVARCHAR(MAX)
);
INSERT INTO #tbl(
paramName
,val
,artId
,multipleVal
)
SELECT
tTbl.paramName
,tTbl.val
,tTbl.artId
,tTbl.multipleVal
FROM(
SELECT
n.value('(n)[1]','NVARCHAR(255)') AS paramName
,n.value('../@artId','INT') AS artId
,CASE
WHEN n.value('(n)[1]','NVARCHAR(255)') = 'Периоды акций'
-- TODO :: вывести все периоды в строке в формате [beginDt1; endDt1], [beginDt2; endDt2]
THEN '[beginDt1; endDt1], [beginDt2; endDt2]'
ELSE n.value('(v)[1]','NVARCHAR(MAX)')
END AS val
,CASE
WHEN n.value('(n)[1]','NVARCHAR(255)') = 'Периоды акций'
THEN CAST(n.query('.') AS VARCHAR(MAX))
ELSE NULL
END AS multipleVal
FROM @response.nodes('/response/sku/p') col(n)
) AS tTbl
SELECT * FROM #tbl
DECLARE @todayDt DATE = GETDATE();
DROP TABLE IF EXISTS #tCatalogue;
CREATE TABLE #tCatalogue(
id INTEGER NOT NULL PRIMARY KEY IDENTITY(1,1)
,artId INTEGER NOT NULL
,name VARCHAR(MAX)
,category VARCHAR(MAX)
,groupName VARCHAR(MAX)
,weight DECIMAL(6,2)
,unit VARCHAR(MAX)
);
INSERT INTO #tCatalogue(artId,name,category,groupName,weight,unit)
VALUES
(5022, 'Хлеб белый нарезной', 'Хлеб', 'Мучные изделия','0.5','kg')
,(5023, 'Кофе Tchibo сублимированный', 'Кофе', 'Чай и кофе','0.2','kg')
SELECT
tCatg.artId
,tCatg.name
,tParam.val as period
-- найти попадает ли текущая дата (@todayDt) в один из периодов акций, если да - вывести 1, иначе - 0
FROM #tCatalogue AS tCatg
LEFT JOIN #tbl AS tParam
ON tCatg.artId = tParam.artId
AND tParam.paramName = 'Периоды акций'
artId | name | period | isInPromotion |
---|---|---|---|
5022 | Хлеб белый нарезной | NULL | NULL |
5023 | Кофе Tchibo сублимированный | [beginDt1; endDt1], [beginDt2; endDt2] | 1 |
Ответы (1 шт):
Попробуйте следующее решение на основе SQL Server XML и XQuery.
SQL
DECLARE @response XML =
N'<response>
<sku artId="5023">
<p>
<n>Наименование товара</n>
<v>Кофе Tchibo сублимированный</v>
</p>
<p>
<n>Категория</n>
<v>Кофе</v>
</p>
<p>
<n>Вес</n>
<v>0.2</v>
</p>
<p>
<n>Периоды акций</n>
<v>
<val1>
<type>Total</type>
<beginDt>2024-06-20</beginDt>
<endDt>2024-07-22</endDt>
</val1>
<val2>
<type>Total</type>
<beginDt>2024-08-21</beginDt>
<endDt>2024-09-18</endDt>
</val2>
</v>
</p>
</sku>
</response>';
DECLARE @todayDt DATE = GETDATE()
, @period NVARCHAR(30) = N'Периоды акций';
DECLARE @Catalogue TABLE (
id INTEGER NOT NULL PRIMARY KEY IDENTITY(1,1)
,artId INTEGER NOT NULL
,name NVARCHAR(MAX)
,category NVARCHAR(MAX)
,groupName NVARCHAR(MAX)
,weight DECIMAL(6,2)
,unit VARCHAR(MAX)
);
INSERT INTO @Catalogue (artId, name, category, groupName, weight, unit) VALUES
(5022, N'Хлеб белый нарезной', N'Хлеб', N'Мучные изделия','0.5','kg')
,(5023, N'Кофе Tchibo сублимированный', N'Кофе', N'Чай и кофе','0.2','kg');
SELECT * FROM @Catalogue;
SELECT artId
, name
, IIF(artId != @response.value('(/response/sku/@artId)[1]', 'INT')
, NULL
, @response.query('for $x in /response/sku/p[n/text()=sql:variable("@period")]/v/*
return concat("[", ($x/beginDt/text())[1], ";", ($x/endDt/text())[1], "]")
').value('.', 'VARCHAR(100)')) AS period
, @todayDt AS todayDT
-- найти попадает ли текущая дата (@todayDt) в один из периодов акций, если да - вывести 1, иначе - 0
, @response.query('
/response/sku[@artId eq sql:column("artId")]
and
some $r in /response/sku/p[n/text()=sql:variable("@period")]/v/*
satisfies (($r/beginDt/text())[1] ge sql:variable("@todayDt")
and sql:variable("@todayDt") le ($r/endDt/text())[1])
').value('.', 'BIT') AS isInPromotion
FROM @Catalogue;
Результат
artId | name | period | todayDT | isInPromotion |
---|---|---|---|---|
5022 | Хлеб белый нарезной | NULL | 2024-07-18 | 0 |
5023 | Кофе Tchibo сублимированный | [2024-06-20;2024-07-22] [2024-08-21;2024-09-18] | 2024-07-18 | 1 |