3549 lines
131 KiB
Transact-SQL
3549 lines
131 KiB
Transact-SQL
USE [mag_pbi]
|
||
GO
|
||
|
||
/****** Object: StoredProcedure [analytics].[create_forecast_loop] Script Date: 2026-02-22 12:07:01 ******/
|
||
SET ANSI_NULLS ON
|
||
GO
|
||
SET QUOTED_IDENTIFIER ON
|
||
GO
|
||
CREATE procedure [analytics].[create_forecast_loop] as begin
|
||
DECLARE @from_month DATE = DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1);
|
||
DECLARE @to_month_excl DATE = '2027-01-01';
|
||
DECLARE @scenario_id INT = 4;
|
||
DECLARE @path NVARCHAR(255);
|
||
|
||
DECLARE cur CURSOR LOCAL FAST_FORWARD FOR
|
||
SELECT [path]
|
||
FROM [pbi].[groups] g
|
||
WHERE [lvl] = 2
|
||
AND g.[path] NOT LIKE '*%' -- (опционально) исключить служебные группы
|
||
|
||
/*AND (
|
||
g.[path] LIKE N'Р%' OR
|
||
g.[path] LIKE N'С%' OR
|
||
g.[path] LIKE N'Т%' OR
|
||
g.[path] LIKE N'У%' OR
|
||
g.[path] LIKE N'Ф%' OR
|
||
g.[path] LIKE N'Х%' OR
|
||
g.[path] LIKE N'Ц%' OR
|
||
g.[path] LIKE N'Ч%' OR
|
||
g.[path] LIKE N'Ш%' OR
|
||
g.[path] LIKE N'Щ%' OR
|
||
g.[path] LIKE N'Э%' OR
|
||
g.[path] LIKE N'Ю%' OR
|
||
g.[path] LIKE N'Я%'
|
||
)*/
|
||
ORDER BY [path];
|
||
|
||
OPEN cur;
|
||
FETCH NEXT FROM cur INTO @path;
|
||
|
||
WHILE @@FETCH_STATUS = 0
|
||
BEGIN
|
||
PRINT CONCAT('Rebuild forecast for: ', @path);
|
||
EXEC [analytics].[sp_build_forecast_s4_by_group]
|
||
@path = @path,
|
||
@from_month = @from_month,
|
||
@to_month_excl = @to_month_excl,
|
||
@scenario_id = @scenario_id;
|
||
|
||
FETCH NEXT FROM cur INTO @path;
|
||
END
|
||
|
||
CLOSE cur;
|
||
DEALLOCATE cur;
|
||
END
|
||
GO
|
||
|
||
/****** Object: StoredProcedure [analytics].[create_seasonality_groups] Script Date: 2026-02-22 12:07:01 ******/
|
||
SET ANSI_NULLS ON
|
||
GO
|
||
SET QUOTED_IDENTIFIER ON
|
||
GO
|
||
CREATE PROCEDURE analytics.[create_seasonality_groups] as
|
||
BEGIN
|
||
/* 1) Таблица результата */
|
||
IF NOT EXISTS (
|
||
SELECT 1
|
||
FROM sys.tables t
|
||
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
||
WHERE s.name = 'analytics' AND t.name = 'seasonality_groups'
|
||
)
|
||
BEGIN
|
||
CREATE TABLE [analytics].[seasonality_groups](
|
||
[group_1c_id] BINARY(16) NOT NULL,
|
||
[month] TINYINT NOT NULL, -- 1..12
|
||
[seasonal_koef] DECIMAL(18,6) NOT NULL,
|
||
CONSTRAINT [PK_seasonality_groups] PRIMARY KEY ([group_1c_id], [month])
|
||
);
|
||
CREATE INDEX [IX_seasonality_groups_month]
|
||
ON [analytics].[seasonality_groups]([month]);
|
||
END
|
||
|
||
TRUNCATE TABLE [analytics].[seasonality_groups];
|
||
/* 2) Пересчёт коэффициентов сезонности для g и g1 */
|
||
;WITH
|
||
-- Продажи по товарам за последние 24 месяца (кол-во)
|
||
[base_sales] AS (
|
||
SELECT
|
||
[n].[1c_id] AS [sku_1c_id],
|
||
[gcur].[g],
|
||
[gcur].[g1],
|
||
[v].[Период] AS [dt],
|
||
CAST([v].[Количество] AS DECIMAL(18,6)) AS [qty]
|
||
FROM [mag_pbi].[pbiProd].[СводныйСебестоимость Для PBI] AS [v]
|
||
JOIN [pbi].[nomenclature] AS [n]
|
||
ON [n].[1c_id] = [v].[1c_id]
|
||
LEFT JOIN [pbi].[groups] AS [gcur]
|
||
ON [gcur].[1c_id] = [n].[1c_group]
|
||
WHERE [v].[Статья] = N'Реализация'
|
||
AND [v].[Период] >= DATEADD(YEAR, -2, CAST(GETDATE() AS date))
|
||
),
|
||
-- Агрегация по месяцам и уровню g (lvl=0)
|
||
[lvl0_monthly] AS (
|
||
SELECT
|
||
[g0].[1c_id] AS [group_1c_id],
|
||
DATEFROMPARTS(YEAR([b].[dt]), MONTH([b].[dt]), 1) AS [month_start],
|
||
SUM([b].[qty]) AS [qty_m]
|
||
FROM [base_sales] AS [b]
|
||
JOIN [pbi].[groups] AS [g0]
|
||
ON [g0].[lvl] = 1
|
||
AND [g0].[g] = [b].[g]
|
||
GROUP BY [g0].[1c_id], DATEFROMPARTS(YEAR([b].[dt]), MONTH([b].[dt]), 1)
|
||
),
|
||
-- Агрегация по месяцам и уровню g1 (lvl=1)
|
||
[lvl1_monthly] AS (
|
||
SELECT
|
||
[g1].[1c_id] AS [group_1c_id],
|
||
DATEFROMPARTS(YEAR([b].[dt]), MONTH([b].[dt]), 1) AS [month_start],
|
||
SUM([b].[qty]) AS [qty_m]
|
||
FROM [base_sales] AS [b]
|
||
JOIN [pbi].[groups] AS [g1]
|
||
ON [g1].[lvl] = 2
|
||
AND [g1].[g1] = [b].[g1]
|
||
GROUP BY [g1].[1c_id], DATEFROMPARTS(YEAR([b].[dt]), MONTH([b].[dt]), 1)
|
||
),
|
||
-- Объединяем уровни
|
||
[monthly_union] AS (
|
||
SELECT * FROM [lvl0_monthly]
|
||
UNION ALL
|
||
SELECT * FROM [lvl1_monthly]
|
||
),
|
||
-- Средние по «месяцу года»
|
||
[per_moy] AS (
|
||
SELECT
|
||
[u].[group_1c_id],
|
||
MONTH([u].[month_start]) AS [month],
|
||
AVG([u].[qty_m]) AS [avg_qty_moy]
|
||
FROM [monthly_union] AS [u]
|
||
GROUP BY [u].[group_1c_id], MONTH([u].[month_start])
|
||
),
|
||
-- Общая средняя по группе
|
||
[overall] AS (
|
||
SELECT
|
||
[u].[group_1c_id],
|
||
AVG([u].[qty_m]) AS [overall_avg_monthly]
|
||
FROM [monthly_union] AS [u]
|
||
GROUP BY [u].[group_1c_id]
|
||
),
|
||
-- Черновой коэффициент
|
||
[raw_koef] AS (
|
||
SELECT
|
||
[p].[group_1c_id],
|
||
[p].[month],
|
||
CASE
|
||
WHEN [o].[overall_avg_monthly] = 0 THEN 0
|
||
ELSE [p].[avg_qty_moy] / [o].[overall_avg_monthly]
|
||
END AS [k_raw]
|
||
FROM [per_moy] AS [p]
|
||
JOIN [overall] AS [o]
|
||
ON [o].[group_1c_id] = [p].[group_1c_id]
|
||
),
|
||
[norm] AS (
|
||
SELECT
|
||
[r].[group_1c_id],
|
||
[r].[month],
|
||
[r].[k_raw] / NULLIF(AVG([r].[k_raw]) OVER (PARTITION BY [r].[group_1c_id]), 0) AS [seasonal_koef]
|
||
FROM [raw_koef] AS [r]
|
||
)
|
||
INSERT INTO [analytics].[seasonality_groups] ([group_1c_id], [month], [seasonal_koef])
|
||
SELECT [group_1c_id], [month], CAST([seasonal_koef] AS DECIMAL(18,6))
|
||
FROM [norm];
|
||
|
||
END
|
||
GO
|
||
|
||
/****** Object: StoredProcedure [analytics].[sp_build_deficit_proposal] Script Date: 2026-02-22 12:07:01 ******/
|
||
SET ANSI_NULLS ON
|
||
GO
|
||
SET QUOTED_IDENTIFIER ON
|
||
GO
|
||
/* =======================================================================
|
||
[analytics].[sp_build_deficit_proposal]
|
||
— Дефицит и рекомендации к заказу с учётом:
|
||
• стартовых остатков (склад + МП),
|
||
• прихода из заказов (4 статуса, включая 'Согласован'),
|
||
• прогноза по сценарию,
|
||
• НЕТ бэк-ордеров: спрос не «копится» ниже нуля,
|
||
остаток считается итеративно и не уходит в минус,
|
||
• размер заказа = прогноз окна [T .. T + @cover_months).
|
||
======================================================================= */
|
||
CREATE PROCEDURE [analytics].[sp_build_deficit_proposal]
|
||
@scenario_id INT = 4, -- ID сценария прогноза
|
||
@group_path NVARCHAR(255) = N'', -- path группы ('' = весь каталог)
|
||
@lead_time_m INT = 4, -- срок поставки (мес.)
|
||
@cover_months INT = 6, -- покрытие (мес.)
|
||
@from_month DATE = NULL, -- старт (1-е число месяца)
|
||
@to_month_excl DATE = '2028-01-01', -- полуинтервал [from, to)
|
||
@debug BIT = 0
|
||
AS
|
||
BEGIN
|
||
SET NOCOUNT ON;
|
||
|
||
/* 0) Чистим прошлые данные по сценарию */
|
||
DELETE FROM [analytics].[deficit_proposal] WHERE scenario_id = @scenario_id;
|
||
|
||
/* Нормализация параметров */
|
||
SET @group_path = LTRIM(RTRIM(REPLACE(@group_path, '''', N'')));
|
||
IF @from_month IS NULL
|
||
SET @from_month = DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1);
|
||
|
||
/* 1) Календарь */
|
||
IF OBJECT_ID('tempdb..#cal') IS NOT NULL DROP TABLE #cal;
|
||
CREATE TABLE #cal (month_start DATE NOT NULL PRIMARY KEY);
|
||
;WITH cal AS (
|
||
SELECT @from_month AS month_start
|
||
UNION ALL
|
||
SELECT DATEADD(MONTH, 1, month_start)
|
||
FROM cal
|
||
WHERE month_start < DATEADD(MONTH, -1, @to_month_excl)
|
||
)
|
||
INSERT INTO #cal(month_start)
|
||
SELECT month_start FROM cal
|
||
OPTION (MAXRECURSION 32767);
|
||
|
||
/* 2) SKU по group_path */
|
||
IF OBJECT_ID('tempdb..#skus') IS NOT NULL DROP TABLE #skus;
|
||
CREATE TABLE #skus (
|
||
sku_1c_id BINARY(16) NOT NULL PRIMARY KEY,
|
||
code NVARCHAR(36) NULL,
|
||
minAvail DECIMAL(18,6) NULL
|
||
);
|
||
INSERT INTO #skus(sku_1c_id, code, minAvail)
|
||
SELECT n.[1c_id], n.[code], n.[minAvailableQty]
|
||
FROM [pbi].[nomenclature] n
|
||
JOIN [pbi].[groups] g ON g.[1c_id] = n.[1c_group]
|
||
WHERE g.[path] LIKE @group_path + N'%';
|
||
|
||
IF NOT EXISTS (SELECT 1 FROM #skus)
|
||
BEGIN
|
||
RAISERROR(N'По path=%s не найдено SKU.', 16, 1, @group_path);
|
||
RETURN;
|
||
END
|
||
|
||
/* 3) Прогноз по сценарию */
|
||
IF OBJECT_ID('tempdb..#fcast') IS NOT NULL DROP TABLE #fcast;
|
||
CREATE TABLE #fcast (
|
||
sku_1c_id BINARY(16) NOT NULL,
|
||
[month] DATE NOT NULL,
|
||
qty DECIMAL(18,3) NOT NULL,
|
||
PRIMARY KEY (sku_1c_id, [month])
|
||
);
|
||
INSERT INTO #fcast(sku_1c_id, [month], qty)
|
||
SELECT f.[1c_id], f.[month], f.[value]
|
||
FROM [analytics].[forecast] f
|
||
JOIN #skus s ON s.sku_1c_id = f.[1c_id]
|
||
WHERE f.[scenario_id] = @scenario_id
|
||
AND f.[month] >= @from_month
|
||
AND f.[month] < @to_month_excl;
|
||
|
||
/* 4) Приходы из заказов (по месяцам) */
|
||
IF OBJECT_ID('tempdb..#inb_status') IS NOT NULL DROP TABLE #inb_status;
|
||
CREATE TABLE #inb_status (
|
||
sku_1c_id BINARY(16) NOT NULL,
|
||
[month] DATE NOT NULL,
|
||
[status] NVARCHAR(100) NOT NULL,
|
||
units DECIMAL(18,3) NOT NULL,
|
||
PRIMARY KEY (sku_1c_id, [month], [status])
|
||
);
|
||
INSERT INTO #inb_status(sku_1c_id, [month], [status], units)
|
||
SELECT
|
||
s.sku_1c_id,
|
||
DATEFROMPARTS(TRY_CONVERT(INT, LEFT(o.[month], 4)),
|
||
TRY_CONVERT(INT, SUBSTRING(o.[month], 6, 2)), 1),
|
||
o.[status],
|
||
SUM(COALESCE(o.[units], 0.0))
|
||
FROM [analytics].[get_orders_by_group] o
|
||
JOIN #skus s ON s.code = o.[code]
|
||
WHERE o.[status] IN (N'В пути', N'В производстве', N'Выгружен на складе'/*, N'Согласован'*/)
|
||
AND TRY_CONVERT(INT, LEFT(o.[month], 4)) IS NOT NULL
|
||
AND TRY_CONVERT(INT, SUBSTRING(o.[month], 6, 2)) BETWEEN 1 AND 12
|
||
GROUP BY s.sku_1c_id,
|
||
DATEFROMPARTS(TRY_CONVERT(INT, LEFT(o.[month], 4)),
|
||
TRY_CONVERT(INT, SUBSTRING(o.[month], 6, 2)), 1),
|
||
o.[status];
|
||
|
||
IF OBJECT_ID('tempdb..#inb') IS NOT NULL DROP TABLE #inb;
|
||
CREATE TABLE #inb (
|
||
sku_1c_id BINARY(16) NOT NULL,
|
||
[month] DATE NOT NULL,
|
||
units DECIMAL(18,3) NOT NULL,
|
||
PRIMARY KEY (sku_1c_id, [month])
|
||
);
|
||
INSERT INTO #inb(sku_1c_id, [month], units)
|
||
SELECT sku_1c_id, [month], SUM(units)
|
||
FROM #inb_status
|
||
WHERE [month] >= @from_month AND [month] < @to_month_excl
|
||
GROUP BY sku_1c_id, [month];
|
||
|
||
/* 5) Стартовые остатки: склад + МП */
|
||
IF OBJECT_ID('tempdb..#stock_base') IS NOT NULL DROP TABLE #stock_base;
|
||
CREATE TABLE #stock_base (sku_1c_id BINARY(16) NOT NULL PRIMARY KEY, qty DECIMAL(18,3) NOT NULL);
|
||
INSERT INTO #stock_base(sku_1c_id, qty)
|
||
SELECT s.sku_1c_id, COALESCE(b.qty, 0.0)
|
||
FROM #skus s
|
||
LEFT JOIN (
|
||
SELECT code, SUM(COALESCE(quantity_base,0.0)) AS qty
|
||
FROM [analytics].[get_quantity_by_group]
|
||
GROUP BY code
|
||
) b ON b.code = s.code;
|
||
|
||
IF OBJECT_ID('tempdb..#stock_mp') IS NOT NULL DROP TABLE #stock_mp;
|
||
CREATE TABLE #stock_mp (sku_1c_id BINARY(16) NOT NULL PRIMARY KEY, qty DECIMAL(18,3) NOT NULL);
|
||
INSERT INTO #stock_mp(sku_1c_id, qty)
|
||
SELECT s.sku_1c_id, COALESCE(m.qty, 0.0)
|
||
FROM #skus s
|
||
LEFT JOIN (
|
||
SELECT code, SUM(COALESCE(quantity_base,0.0)) AS qty
|
||
FROM [analytics].[get_mp_quantity_by_group]
|
||
GROUP BY code
|
||
) m ON m.code = s.code;
|
||
|
||
IF OBJECT_ID('tempdb..#stock0') IS NOT NULL DROP TABLE #stock0;
|
||
CREATE TABLE #stock0 (sku_1c_id BINARY(16) NOT NULL PRIMARY KEY, qty DECIMAL(18,3) NOT NULL);
|
||
INSERT INTO #stock0(sku_1c_id, qty)
|
||
SELECT s.sku_1c_id, COALESCE(b.qty,0) + COALESCE(mp.qty,0)
|
||
FROM #skus s
|
||
LEFT JOIN #stock_base b ON b.sku_1c_id = s.sku_1c_id
|
||
LEFT JOIN #stock_mp mp ON mp.sku_1c_id = s.sku_1c_id;
|
||
|
||
/* 6) Лента (сырой спрос/приход) */
|
||
IF OBJECT_ID('tempdb..#tl') IS NOT NULL DROP TABLE #tl;
|
||
CREATE TABLE #tl (
|
||
sku_1c_id BINARY(16) NOT NULL,
|
||
[month] DATE NOT NULL,
|
||
demand_m DECIMAL(18,3) NOT NULL,
|
||
inb_m DECIMAL(18,3) NOT NULL,
|
||
cum_d DECIMAL(38,3) NULL, -- кумулятив обслуженного спроса
|
||
cum_i DECIMAL(38,3) NULL, -- кумулятив прихода
|
||
net_stock DECIMAL(38,3) NULL, -- итоговый остаток месяца (>=0)
|
||
served_m DECIMAL(18,3) NULL, -- обслуженный спрос в месяце
|
||
lost_m DECIMAL(18,3) NULL, -- потерянный спрос в месяце
|
||
PRIMARY KEY (sku_1c_id, [month])
|
||
);
|
||
INSERT INTO #tl(sku_1c_id, [month], demand_m, inb_m)
|
||
SELECT s.sku_1c_id, c.month_start,
|
||
COALESCE(f.qty, 0.0),
|
||
COALESCE(i.units, 0.0)
|
||
FROM #skus s
|
||
CROSS JOIN #cal c
|
||
LEFT JOIN #fcast f ON f.sku_1c_id = s.sku_1c_id AND f.[month] = c.month_start
|
||
LEFT JOIN #inb i ON i.sku_1c_id = s.sku_1c_id AND i.[month] = c.month_start;
|
||
|
||
/* 6.1) Пронумеруем месяцы (для рекурсивного расчёта без бэк-ордеров) */
|
||
IF OBJECT_ID('tempdb..#seq') IS NOT NULL DROP TABLE #seq;
|
||
CREATE TABLE #seq (
|
||
sku_1c_id BINARY(16) NOT NULL,
|
||
[month] DATE NOT NULL,
|
||
demand_m DECIMAL(18,3) NOT NULL,
|
||
inb_m DECIMAL(18,3) NOT NULL,
|
||
rn INT NOT NULL,
|
||
PRIMARY KEY (sku_1c_id, rn)
|
||
);
|
||
INSERT INTO #seq(sku_1c_id, [month], demand_m, inb_m, rn)
|
||
SELECT t.sku_1c_id, t.[month], t.demand_m, t.inb_m,
|
||
ROW_NUMBER() OVER (PARTITION BY t.sku_1c_id ORDER BY t.[month])
|
||
FROM #tl t;
|
||
|
||
/* 6.2) Рекурсивный расчёт: served/lost/net_stock (без накопления спроса ниже нуля) */
|
||
IF OBJECT_ID('tempdb..#flow') IS NOT NULL DROP TABLE #flow;
|
||
CREATE TABLE #flow(
|
||
sku_1c_id BINARY(16) NOT NULL,
|
||
rn INT NOT NULL,
|
||
[month] DATE NOT NULL,
|
||
demand_m DECIMAL(18,3) NOT NULL,
|
||
inb_m DECIMAL(18,3) NOT NULL,
|
||
served_m DECIMAL(18,3) NOT NULL,
|
||
lost_m DECIMAL(18,3) NOT NULL,
|
||
net_stock DECIMAL(38,3) NOT NULL,
|
||
PRIMARY KEY (sku_1c_id, rn)
|
||
);
|
||
|
||
;WITH r AS (
|
||
/* первый месяц */
|
||
SELECT
|
||
s.sku_1c_id, s.rn, s.[month], s.demand_m, s.inb_m,
|
||
CAST(
|
||
CASE WHEN st.qty + s.inb_m >= s.demand_m
|
||
THEN s.demand_m
|
||
ELSE st.qty + s.inb_m
|
||
END
|
||
AS DECIMAL(18,3)) AS served_m,
|
||
CAST(
|
||
CASE WHEN st.qty + s.inb_m >= s.demand_m
|
||
THEN 0.0
|
||
ELSE s.demand_m - (st.qty + s.inb_m)
|
||
END
|
||
AS DECIMAL(18,3)) AS lost_m,
|
||
CAST(
|
||
CASE WHEN st.qty + s.inb_m - s.demand_m < 0
|
||
THEN 0
|
||
ELSE st.qty + s.inb_m - s.demand_m
|
||
END
|
||
AS DECIMAL(38,3)) AS net_stock
|
||
FROM #seq s
|
||
JOIN #stock0 st ON st.sku_1c_id = s.sku_1c_id
|
||
WHERE s.rn = 1
|
||
|
||
UNION ALL
|
||
|
||
/* последующие месяцы */
|
||
SELECT
|
||
s.sku_1c_id, s.rn, s.[month], s.demand_m, s.inb_m,
|
||
CAST(
|
||
CASE WHEN r.net_stock + s.inb_m >= s.demand_m
|
||
THEN s.demand_m
|
||
ELSE r.net_stock + s.inb_m
|
||
END
|
||
AS DECIMAL(18,3)) AS served_m,
|
||
CAST(
|
||
CASE WHEN r.net_stock + s.inb_m >= s.demand_m
|
||
THEN 0.0
|
||
ELSE s.demand_m - (r.net_stock + s.inb_m)
|
||
END
|
||
AS DECIMAL(18,3)) AS lost_m,
|
||
CAST(
|
||
CASE WHEN r.net_stock + s.inb_m - s.demand_m < 0
|
||
THEN 0
|
||
ELSE r.net_stock + s.inb_m - s.demand_m
|
||
END
|
||
AS DECIMAL(38,3)) AS net_stock
|
||
FROM r
|
||
JOIN #seq s
|
||
ON s.sku_1c_id = r.sku_1c_id
|
||
AND s.rn = r.rn + 1
|
||
)
|
||
INSERT INTO #flow
|
||
SELECT * FROM r
|
||
OPTION (MAXRECURSION 32767);
|
||
|
||
/* 6.3) Обновим #tl: кумулятивы по обслуженному спросу/приходу и итоговый остаток */
|
||
;WITH cd AS (
|
||
SELECT f.sku_1c_id, s.[month],
|
||
SUM(f.served_m) OVER (PARTITION BY f.sku_1c_id ORDER BY f.rn ROWS UNBOUNDED PRECEDING) AS cum_d_served,
|
||
SUM(s.inb_m) OVER (PARTITION BY s.sku_1c_id ORDER BY s.rn ROWS UNBOUNDED PRECEDING) AS cum_i_raw,
|
||
f.net_stock, f.served_m, f.lost_m
|
||
FROM #flow f
|
||
JOIN #seq s ON s.sku_1c_id = f.sku_1c_id AND s.rn = f.rn
|
||
)
|
||
UPDATE t
|
||
SET t.cum_d = cd.cum_d_served,
|
||
t.cum_i = cd.cum_i_raw,
|
||
t.net_stock = cd.net_stock,
|
||
t.served_m = cd.served_m,
|
||
t.lost_m = cd.lost_m
|
||
FROM #tl t
|
||
JOIN cd ON cd.sku_1c_id = t.sku_1c_id AND cd.[month] = t.[month];
|
||
|
||
/* 7) Первый месяц дефицита T (учитываем lead_time) */
|
||
DECLARE @start_T DATE =
|
||
DATEFROMPARTS(YEAR(DATEADD(MONTH, @lead_time_m, @from_month)),
|
||
MONTH(DATEADD(MONTH, @lead_time_m, @from_month)), 1);
|
||
|
||
IF OBJECT_ID('tempdb..#first_def') IS NOT NULL DROP TABLE #first_def;
|
||
CREATE TABLE #first_def (sku_1c_id BINARY(16) NOT NULL PRIMARY KEY, T DATE NULL);
|
||
|
||
INSERT INTO #first_def(sku_1c_id, T)
|
||
SELECT d.sku_1c_id, MIN(d.[month]) AS T
|
||
FROM (
|
||
SELECT t.sku_1c_id, t.[month]
|
||
FROM #tl t
|
||
JOIN #skus s ON s.sku_1c_id = t.sku_1c_id
|
||
WHERE t.net_stock < COALESCE(s.minAvail, 0.0)
|
||
AND t.[month] >= @start_T
|
||
) d
|
||
GROUP BY d.sku_1c_id;
|
||
|
||
/* 8) Якоря: T, T+C, T+2C ... */
|
||
IF OBJECT_ID('tempdb..#anchors') IS NOT NULL DROP TABLE #anchors;
|
||
CREATE TABLE #anchors (
|
||
sku_1c_id BINARY(16) NOT NULL,
|
||
T DATE NOT NULL,
|
||
PRIMARY KEY (sku_1c_id, T)
|
||
);
|
||
;WITH recur AS (
|
||
SELECT fd.sku_1c_id, fd.T
|
||
FROM #first_def fd
|
||
WHERE fd.T IS NOT NULL
|
||
|
||
UNION ALL
|
||
SELECT r.sku_1c_id, DATEADD(MONTH, @cover_months, r.T)
|
||
FROM recur r
|
||
WHERE DATEADD(MONTH, @cover_months, r.T) < @to_month_excl
|
||
)
|
||
INSERT INTO #anchors(sku_1c_id, T)
|
||
SELECT sku_1c_id, T
|
||
FROM recur
|
||
OPTION (MAXRECURSION 32767);
|
||
|
||
/* 9) Подготовка строк к вставке (размер заказа = прогноз окна) */
|
||
IF OBJECT_ID('tempdb..#rows_to_insert') IS NOT NULL DROP TABLE #rows_to_insert;
|
||
CREATE TABLE #rows_to_insert (
|
||
[scenario_id] INT,
|
||
[group_name] NVARCHAR(255),
|
||
[1c_id] BINARY(16),
|
||
[code] NVARCHAR(36),
|
||
[place_month] DATE,
|
||
[arrival_month] DATE,
|
||
[demand_window_C] DECIMAL(18,3),
|
||
[projected_stock_at_T] DECIMAL(18,3),
|
||
[order_qty] DECIMAL(18,3)
|
||
);
|
||
|
||
;WITH base AS (
|
||
SELECT
|
||
s.sku_1c_id,
|
||
s.code,
|
||
a.T,
|
||
st.qty AS stock0,
|
||
|
||
/* прогноз окна [T .. T+C) */
|
||
(SELECT SUM(demand_m)
|
||
FROM #tl z
|
||
WHERE z.sku_1c_id = s.sku_1c_id
|
||
AND z.[month] >= a.T
|
||
AND z.[month] < DATEADD(MONTH, @cover_months, a.T)
|
||
) AS demand_window,
|
||
|
||
/* кумулятивы до T-1 (обслуженный спрос и приход) */
|
||
(SELECT TOP (1) z.cum_i
|
||
FROM #tl z
|
||
WHERE z.sku_1c_id = s.sku_1c_id
|
||
AND z.[month] < a.T
|
||
ORDER BY z.[month] DESC
|
||
) AS cum_i_before,
|
||
|
||
(SELECT TOP (1) z.cum_d
|
||
FROM #tl z
|
||
WHERE z.sku_1c_id = s.sku_1c_id
|
||
AND z.[month] < a.T
|
||
ORDER BY z.[month] DESC
|
||
) AS cum_d_before,
|
||
|
||
/* приход именно в T */
|
||
(SELECT SUM(inb_m)
|
||
FROM #tl z
|
||
WHERE z.sku_1c_id = s.sku_1c_id
|
||
AND z.[month] = a.T
|
||
) AS inb_t
|
||
FROM #anchors a
|
||
JOIN #skus s ON s.sku_1c_id = a.sku_1c_id
|
||
JOIN #stock0 st ON st.sku_1c_id = s.sku_1c_id
|
||
)
|
||
INSERT INTO #rows_to_insert
|
||
SELECT
|
||
@scenario_id,
|
||
@group_path,
|
||
b.sku_1c_id,
|
||
b.code,
|
||
DATEADD(MONTH, -@lead_time_m, b.T) AS place_month,
|
||
b.T AS arrival_month,
|
||
CAST(ISNULL(b.demand_window,0) AS DECIMAL(18,3)) AS demand_window_C,
|
||
|
||
/* старт на T (после прихода T), снизу 0 */
|
||
CAST(
|
||
CASE
|
||
WHEN ( ISNULL(b.stock0,0)
|
||
+ ISNULL(b.cum_i_before,0)
|
||
+ ISNULL(b.inb_t,0)
|
||
- ISNULL(b.cum_d_before,0) ) < 0
|
||
THEN 0
|
||
ELSE ( ISNULL(b.stock0,0)
|
||
+ ISNULL(b.cum_i_before,0)
|
||
+ ISNULL(b.inb_t,0)
|
||
- ISNULL(b.cum_d_before,0) )
|
||
END
|
||
AS DECIMAL(18,3)) AS projected_stock_at_T,
|
||
|
||
/* размер заказа = прогноз окна */
|
||
CAST(ISNULL(b.demand_window,0) AS DECIMAL(18,3)) AS order_qty
|
||
FROM base AS b;
|
||
|
||
/* 10) Вставка рекомендаций (незначащие — отбрасываем) */
|
||
INSERT INTO [analytics].[deficit_proposal]
|
||
([scenario_id], [group_name], [1c_id], [code],
|
||
[place_month], [arrival_month],
|
||
[demand_window_C], [projected_stock_at_T], [order_qty],
|
||
[updated_at])
|
||
SELECT
|
||
scenario_id, group_name, [1c_id], [code],
|
||
[place_month], [arrival_month],
|
||
[demand_window_C], [projected_stock_at_T], [order_qty],
|
||
GETDATE()
|
||
FROM #rows_to_insert
|
||
WHERE [order_qty] > 0;
|
||
|
||
/* 11) Отладка по желанию */
|
||
IF @debug = 1
|
||
BEGIN
|
||
SELECT TOP (200)
|
||
r.[code], r.[arrival_month], r.[place_month],
|
||
r.[projected_stock_at_T], r.[demand_window_C], r.[order_qty]
|
||
FROM #rows_to_insert r
|
||
ORDER BY r.[arrival_month], r.[code];
|
||
END
|
||
END
|
||
GO
|
||
|
||
/****** Object: StoredProcedure [analytics].[sp_build_forecast_s4_by_group] Script Date: 2026-02-22 12:07:01 ******/
|
||
SET ANSI_NULLS ON
|
||
GO
|
||
SET QUOTED_IDENTIFIER ON
|
||
GO
|
||
CREATE PROCEDURE [analytics].[sp_build_forecast_s4_by_group]
|
||
@path NVARCHAR(255), -- путь группы (например, N'Игрушки')
|
||
@from_month DATE = NULL, -- по умолчанию: 1-е число текущего месяца
|
||
@to_month_excl DATE = '2028-01-01', -- полуинтервал [from, to)
|
||
@scenario_id INT = 4,
|
||
@allow_fallback_all BIT = 0 -- 1 = если группа не найдена/пустая → считать весь каталог с дефолтной сезонностью
|
||
AS
|
||
BEGIN
|
||
SET NOCOUNT ON;
|
||
|
||
----------------------------------------------------------------
|
||
-- Нормализация пути: убираем лишние кавычки и пробелы
|
||
----------------------------------------------------------------
|
||
SET @path = LTRIM(RTRIM(REPLACE(@path, '''', N'')));
|
||
|
||
IF @from_month IS NULL
|
||
SET @from_month = DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1);
|
||
|
||
----------------------------------------------------------------
|
||
-- Идентификаторы групп для сезонности
|
||
----------------------------------------------------------------
|
||
DECLARE @season_group_id BINARY(16) = NULL;
|
||
DECLARE @DEFAULT_GROUP_ID BINARY(16) = 0x00000000000000000000000000000000;
|
||
|
||
-- Пытаемся найти группу ровно по path
|
||
SELECT @season_group_id = g.[1c_id]
|
||
FROM [pbi].[groups] g
|
||
WHERE g.[path] = @path;
|
||
|
||
-- Если нет точного совпадения, ищем верхний узел, начинающийся с @path
|
||
IF @season_group_id IS NULL
|
||
BEGIN
|
||
SELECT TOP (1) @season_group_id = g.[1c_id]
|
||
FROM [pbi].[groups] g
|
||
WHERE g.[path] LIKE @path + N'%'
|
||
ORDER BY g.[lvl] ASC;
|
||
END
|
||
|
||
-- Если группу так и не нашли: по умолчанию НЕ уходим на весь каталог (защита от опечаток)
|
||
IF @season_group_id IS NULL AND @allow_fallback_all = 0
|
||
BEGIN
|
||
RAISERROR (N'Группа с path = %s не найдена. Проверьте параметр @path или запустите с @allow_fallback_all=1 для расчёта по всему каталогу.', 16, 1, @path);
|
||
RETURN;
|
||
END
|
||
|
||
-- Если всё-таки нужно фолбэкнуть на дефолтную сезонность
|
||
IF @season_group_id IS NULL AND @allow_fallback_all = 1
|
||
SET @season_group_id = @DEFAULT_GROUP_ID;
|
||
|
||
----------------------------------------------------------------
|
||
-- TEMP: календарь месяцев [from, to)
|
||
----------------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#cal') IS NOT NULL DROP TABLE #cal;
|
||
CREATE TABLE #cal (month_start DATE NOT NULL PRIMARY KEY);
|
||
|
||
;WITH cal AS (
|
||
SELECT @from_month AS month_start
|
||
UNION ALL
|
||
SELECT DATEADD(MONTH, 1, month_start)
|
||
FROM cal
|
||
WHERE month_start < DATEADD(MONTH, -1, @to_month_excl)
|
||
)
|
||
INSERT INTO #cal(month_start)
|
||
SELECT month_start FROM cal
|
||
OPTION (MAXRECURSION 32767);
|
||
|
||
----------------------------------------------------------------
|
||
-- TEMP: SKU под деревом @path; если пусто и разрешён фолбэк → все SKU
|
||
----------------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#skus') IS NOT NULL DROP TABLE #skus;
|
||
CREATE TABLE #skus (
|
||
sku_1c_id BINARY(16) NOT NULL PRIMARY KEY,
|
||
code NVARCHAR(36) NULL
|
||
);
|
||
|
||
INSERT INTO #skus(sku_1c_id, code)
|
||
SELECT n.[1c_id], n.[code]
|
||
FROM [pbi].[nomenclature] n
|
||
JOIN [pbi].[groups] g ON g.[1c_id] = n.[1c_group]
|
||
WHERE g.[path] LIKE @path + N'%'
|
||
OPTION (RECOMPILE);
|
||
|
||
IF NOT EXISTS (SELECT 1 FROM #skus)
|
||
BEGIN
|
||
IF @allow_fallback_all = 1
|
||
BEGIN
|
||
INSERT INTO #skus(sku_1c_id, code)
|
||
SELECT n.[1c_id], n.[code]
|
||
FROM [pbi].[nomenclature] n;
|
||
SET @season_group_id = @DEFAULT_GROUP_ID;
|
||
END
|
||
ELSE
|
||
BEGIN
|
||
RAISERROR (N'По path = %s не найдено ни одного SKU. Расчёт не выполнялся.', 16, 1, @path);
|
||
RETURN;
|
||
END
|
||
END
|
||
|
||
----------------------------------------------------------------
|
||
-- TEMP: ставки продаж (шт/день)
|
||
----------------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#rate') IS NOT NULL DROP TABLE #rate;
|
||
CREATE TABLE #rate (
|
||
sku_1c_id BINARY(16) NOT NULL PRIMARY KEY,
|
||
rate_per_day DECIMAL(38,6) NOT NULL
|
||
);
|
||
|
||
INSERT INTO #rate(sku_1c_id, rate_per_day)
|
||
SELECT a.[1c_id], ISNULL(a.[Продажи шт / день],0)
|
||
FROM [analytics].[аналитика за 365 дн.] a
|
||
JOIN #skus s ON s.sku_1c_id = a.[1c_id];
|
||
|
||
----------------------------------------------------------------
|
||
-- TEMP: сезонность ТОЛЬКО по @season_group_id; если нет строк — используем дефолт
|
||
----------------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#season') IS NOT NULL DROP TABLE #season;
|
||
CREATE TABLE #season (
|
||
[month] TINYINT NOT NULL PRIMARY KEY, -- 1..12
|
||
seasonal_koef DECIMAL(18,6) NOT NULL
|
||
);
|
||
|
||
INSERT INTO #season([month], seasonal_koef)
|
||
/*SELECT sg.[month], sg.[seasonal_koef]
|
||
FROM [analytics].[seasonality_groups] sg
|
||
WHERE sg.[group_1c_id] = @season_group_id;*/
|
||
|
||
SELECT
|
||
[month]
|
||
, AVG([koef])
|
||
FROM [mag_pbi].[analytics].[seasonality_groups_summ_1]
|
||
GROUP BY [month];
|
||
|
||
/*IF @@ROWCOUNT = 0
|
||
BEGIN
|
||
INSERT INTO #season([month], seasonal_koef)
|
||
SELECT sg.[month], sg.[seasonal_koef]
|
||
FROM [analytics].[seasonality_groups] sg
|
||
WHERE sg.[group_1c_id] = @DEFAULT_GROUP_ID;
|
||
-- если и дефолта нет, #season останется пустой → в формуле будет 1.0
|
||
END
|
||
*/
|
||
----------------------------------------------------------------
|
||
-- Удаляем старые строки сценария (батчами, чтобы не держать тяжёлые блокировки)
|
||
----------------------------------------------------------------
|
||
WHILE 1 = 1
|
||
BEGIN
|
||
DELETE TOP (50000) f
|
||
FROM [analytics].[forecast] f
|
||
JOIN #skus s ON s.sku_1c_id = f.[1c_id]
|
||
WHERE f.[scenario_id] = @scenario_id
|
||
AND f.[month] >= @from_month
|
||
AND f.[month] < @to_month_excl;
|
||
|
||
IF @@ROWCOUNT = 0 BREAK;
|
||
END
|
||
|
||
----------------------------------------------------------------
|
||
-- Вставка прогноза: rate_per_day × дни_в_месяце × seasonal_koef(@path/@default)
|
||
----------------------------------------------------------------
|
||
INSERT INTO [analytics].[forecast]
|
||
([scenario_id], [1c_id], [code], [month], [value], [updated_at], [updated_by])
|
||
SELECT
|
||
@scenario_id,
|
||
s.sku_1c_id,
|
||
s.code,
|
||
c.month_start,
|
||
CAST(ISNULL(r.rate_per_day, 0)
|
||
* DAY(EOMONTH(c.month_start))
|
||
* ISNULL(se.seasonal_koef, 1.0) AS DECIMAL(18,3)) AS value,
|
||
GETDATE(),
|
||
SUSER_SNAME()
|
||
FROM #skus s
|
||
CROSS JOIN #cal c
|
||
LEFT JOIN #rate r ON r.sku_1c_id = s.sku_1c_id
|
||
LEFT JOIN #season se ON se.[month] = MONTH(c.month_start)
|
||
OPTION (RECOMPILE);
|
||
END
|
||
GO
|
||
|
||
/****** Object: StoredProcedure [analytics].[sp_create_analytics_365] Script Date: 2026-02-22 12:07:01 ******/
|
||
SET ANSI_NULLS ON
|
||
GO
|
||
SET QUOTED_IDENTIFIER ON
|
||
GO
|
||
CREATE PROCEDURE [analytics].[sp_create_analytics_365]
|
||
AS
|
||
BEGIN
|
||
/*
|
||
... твой закомментированный блок без изменений ...
|
||
*/
|
||
|
||
-------------------------------------------------------------
|
||
-- 1) Базовая таблица аналитики за 365 дней
|
||
-------------------------------------------------------------
|
||
DROP TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
|
||
|
||
SELECT
|
||
s.[1c_id],
|
||
s.Code,
|
||
SUM(s.Количество) as [Продано шт]
|
||
INTO [mag_pbi].[analytics].[аналитика за 365 дн.]
|
||
FROM [pbiProd].[СводныйСебестоимость Для PBI] s
|
||
WHERE s.Статья = N'Реализация'
|
||
AND s.[Период] >= DATEADD(day, -365, GETDATE())
|
||
GROUP BY [1c_id], s.Code;
|
||
|
||
|
||
-------------------------------------------------------------
|
||
-- 2) Продажи шт / день
|
||
-------------------------------------------------------------
|
||
IF COL_LENGTH('analytics.аналитика за 365 дн.', 'Продажи шт / день') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [analytics].[аналитика за 365 дн.]
|
||
ADD [Продажи шт / день] numeric(38,6) NULL;
|
||
/*ALTER TABLE [analytics].[аналитика за 365 дн.]
|
||
ADD [Продажи шт / день опт] numeric(38,6) NULL;*/
|
||
END;
|
||
|
||
;WITH bounds AS (
|
||
SELECT
|
||
CAST(DATEADD(day, -365, CAST(GETDATE() AS date)) AS date) AS start_date,
|
||
CAST(GETDATE() AS date) AS end_date
|
||
),
|
||
days365 AS (
|
||
SELECT
|
||
t.[_IDRREF] AS [1c_id],
|
||
SUM(t.[ostatok]) AS days_on_sale_365
|
||
--SUM(t.[ostatok10]) AS days_on_sale_365_opt
|
||
FROM [pbi].[w_ostatok_da_net] t
|
||
CROSS JOIN bounds b
|
||
WHERE t.[dt] >= b.start_date
|
||
AND t.[dt] < b.end_date -- исключаем текущий день
|
||
GROUP BY t.[_IDRREF]
|
||
)
|
||
UPDATE a
|
||
SET
|
||
|
||
[Продажи шт / день] =
|
||
CASE
|
||
WHEN d.days_on_sale_365 > 0
|
||
THEN CAST(a.[Продано шт] AS numeric(38,6)) / CAST(d.days_on_sale_365 AS numeric(38,6))
|
||
ELSE NULL
|
||
END
|
||
|
||
/*[Продажи шт / день опт] =
|
||
CASE
|
||
WHEN d.days_on_sale_365_opt > 0
|
||
THEN CAST(a.[Продано шт] AS numeric(38,6)) / CAST(d.days_on_sale_365_opt AS numeric(38,6))
|
||
ELSE NULL
|
||
END*/
|
||
|
||
FROM [analytics].[аналитика за 365 дн.] a
|
||
LEFT JOIN days365 d
|
||
ON d.[1c_id] = a.[1c_id];
|
||
|
||
|
||
|
||
-------------------------------------------------------------
|
||
-- 3) Остаток дней продаж
|
||
-------------------------------------------------------------
|
||
IF COL_LENGTH('analytics.аналитика за 365 дн.', 'Остаток дней продаж') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [analytics].[аналитика за 365 дн.]
|
||
ADD [Остаток дней продаж] numeric(38,6) NULL;
|
||
/*ALTER TABLE [analytics].[аналитика за 365 дн.]
|
||
ADD [Остаток дней продаж опт] numeric(38,6) NULL;*/
|
||
END;
|
||
|
||
UPDATE a
|
||
SET
|
||
[Остаток дней продаж] =
|
||
CASE
|
||
WHEN a.[Продажи шт / день] > 0
|
||
THEN sb.[Остаток склад + МП, шт] / a.[Продажи шт / день]
|
||
ELSE NULL
|
||
END
|
||
|
||
/*[Остаток дней продаж опт] =
|
||
CASE
|
||
WHEN a.[Продажи шт / день опт] > 0
|
||
THEN sb.[Остаток склад + МП, шт] / a.[Продажи шт / день опт]
|
||
ELSE NULL
|
||
END*/
|
||
FROM [analytics].[аналитика за 365 дн.] a
|
||
LEFT JOIN (
|
||
SELECT
|
||
n.[1c_id],
|
||
s.[Остаток склад + МП, шт]
|
||
FROM [mag_pbi].[analytics].[stock_balance] s
|
||
INNER JOIN [mag_pbi].[pbi].[nomenclature] n
|
||
ON n.artic_id = s.artic_id
|
||
) sb
|
||
ON sb.[1c_id] = a.[1c_id];
|
||
|
||
|
||
|
||
|
||
-------------------------------------------------------------
|
||
-- 4) Сумма продаж, учетка, ТН (год)
|
||
-------------------------------------------------------------
|
||
IF COL_LENGTH('analytics.аналитика за 365 дн.', 'Продажи / год, руб.') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
|
||
ADD [Продажи / год, руб.] decimal(38,2) NULL;
|
||
END;
|
||
|
||
IF COL_LENGTH('analytics.аналитика за 365 дн.', 'учетная сумма / год, руб.') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
|
||
ADD [учетная сумма / год, руб.] decimal(38,2) NULL;
|
||
END;
|
||
|
||
IF COL_LENGTH('analytics.аналитика за 365 дн.', 'ТН / год, руб.') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
|
||
ADD [ТН / год, руб.] decimal(38,2) NULL;
|
||
END;
|
||
|
||
IF COL_LENGTH('analytics.аналитика за 365 дн.', 'Стоимость МП год, руб.') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
|
||
ADD [Стоимость МП год, руб.] decimal(38,2) NULL;
|
||
END;
|
||
|
||
IF COL_LENGTH('analytics.аналитика за 365 дн.', '%ТН год, руб.') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
|
||
ADD [%ТН год, руб.] decimal(38,2) NULL;
|
||
END;
|
||
|
||
DECLARE @end date = CAST(GETDATE() AS date); -- сегодня (не включаем)
|
||
DECLARE @start date = DATEADD(day, -365, @end); -- 365 дней назад
|
||
|
||
;WITH Y AS (
|
||
SELECT
|
||
v.[1c_id],
|
||
SUM(v.[Сумма продаж]) AS sales_year,
|
||
SUM(v.[Учетная сумма]) AS acct_year,
|
||
SUM(v.[Стоимость обработки]) AS cost_mp,
|
||
SUM(v.[Торговая надбавка]) AS tn_year
|
||
FROM [analytics].[Продажи_Учёт_Маржа_по_дням] v
|
||
WHERE v.[d] >= @start AND v.[d] < @end
|
||
GROUP BY v.[1c_id]
|
||
)
|
||
UPDATE a
|
||
SET
|
||
a.[Продажи / год, руб.] = COALESCE(Y.sales_year, 0),
|
||
a.[учетная сумма / год, руб.] = COALESCE(Y.acct_year, 0),
|
||
a.[ТН / год, руб.] = COALESCE(Y.tn_year, 0),
|
||
a.[Стоимость МП год, руб.] = COALESCE(Y.cost_mp, 0),
|
||
a.[%ТН год, руб.] = CASE
|
||
WHEN NULLIF(COALESCE(Y.acct_year, 0), 0) IS NULL THEN NULL
|
||
ELSE
|
||
CAST(COALESCE(Y.tn_year, 0) AS decimal(19,6))
|
||
/ CAST(NULLIF(Y.acct_year, 0) AS decimal(19,6))
|
||
END
|
||
FROM [mag_pbi].[analytics].[аналитика за 365 дн.] AS a
|
||
LEFT JOIN Y
|
||
ON Y.[1c_id] = a.[1c_id];
|
||
|
||
|
||
-------------------------------------------------------------
|
||
-- 5) Дней в продаже / год и / квартал
|
||
-------------------------------------------------------------
|
||
SET @end = CAST(GETDATE() AS date);
|
||
|
||
DECLARE @startY date = DATEADD(day, -365, @end);
|
||
DECLARE @startQ date = DATEADD(month, -3, @end);
|
||
|
||
IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'Дней в продаже / год') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
|
||
ADD [Дней в продаже / год] bigint NULL;
|
||
END;
|
||
|
||
IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'Дней в продаже / квартал') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
|
||
ADD [Дней в продаже / квартал] bigint NULL;
|
||
END;
|
||
|
||
;WITH daysY AS (
|
||
SELECT [_IDRREF] AS [1c_id],
|
||
COUNT_BIG(*) AS days_in_sale_year
|
||
FROM [pbi].[w_ostatok_da_net]
|
||
WHERE [dt] >= @startY AND [dt] < @end
|
||
AND [ostatok] >= 1
|
||
GROUP BY [_IDRREF]
|
||
),
|
||
daysQ AS (
|
||
SELECT [_IDRREF] AS [1c_id],
|
||
COUNT_BIG(*) AS days_in_sale_quarter
|
||
FROM [pbi].[w_ostatok_da_net]
|
||
WHERE [dt] >= @startQ AND [dt] < @end
|
||
AND [ostatok] >= 1
|
||
GROUP BY [_IDRREF]
|
||
),
|
||
days AS (
|
||
SELECT COALESCE(y.[1c_id], q.[1c_id]) AS [1c_id],
|
||
y.days_in_sale_year,
|
||
q.days_in_sale_quarter
|
||
FROM daysY y
|
||
FULL OUTER JOIN daysQ q ON q.[1c_id] = y.[1c_id]
|
||
)
|
||
UPDATE a
|
||
SET
|
||
a.[Дней в продаже / год] = d.days_in_sale_year,
|
||
a.[Дней в продаже / квартал] = d.days_in_sale_quarter
|
||
FROM [mag_pbi].[analytics].[аналитика за 365 дн.] a
|
||
LEFT JOIN days d
|
||
ON d.[1c_id] = a.[1c_id];
|
||
|
||
-------------------------------------------------------------
|
||
-- 6) Сумма продаж, учетка, ТН (квартал)
|
||
-------------------------------------------------------------
|
||
IF COL_LENGTH('analytics.аналитика за 365 дн.', 'Продажи / квартал, руб.') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [analytics].[аналитика за 365 дн.]
|
||
ADD [Продажи / квартал, руб.] decimal(38,2) NULL;
|
||
END;
|
||
|
||
IF COL_LENGTH('analytics.аналитика за 365 дн.', 'учетная сумма / квартал, руб.') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [analytics].[аналитика за 365 дн.]
|
||
ADD [учетная сумма / квартал, руб.] decimal(38,2) NULL;
|
||
END;
|
||
|
||
IF COL_LENGTH('analytics.аналитика за 365 дн.', 'ТН / квартал, руб.') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [analytics].[аналитика за 365 дн.]
|
||
ADD [ТН / квартал, руб.] decimal(38,2) NULL;
|
||
END;
|
||
|
||
SET @start = DATEADD(month, -3, @end);
|
||
|
||
;WITH Q AS (
|
||
SELECT
|
||
v.[1c_id],
|
||
SUM(v.[Сумма продаж]) AS sales_q,
|
||
SUM(v.[Учетная сумма]) AS acct_q,
|
||
SUM(v.[Торговая надбавка]) AS tn_q
|
||
FROM [analytics].[Продажи_Учёт_Маржа_по_дням] v
|
||
WHERE v.[d] >= @start AND v.[d] < @end
|
||
GROUP BY v.[1c_id]
|
||
)
|
||
UPDATE a
|
||
SET
|
||
a.[Продажи / квартал, руб.] = Q.sales_q,
|
||
a.[учетная сумма / квартал, руб.] = Q.acct_q,
|
||
a.[ТН / квартал, руб.] = Q.tn_q
|
||
FROM [analytics].[аналитика за 365 дн.] AS a
|
||
LEFT JOIN Q
|
||
ON Q.[1c_id] = a.[1c_id];
|
||
|
||
|
||
-------------------------------------------------------------
|
||
-- 7) ТН / месяц (по текущей скорости)
|
||
-------------------------------------------------------------
|
||
IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'ТН / месяц, руб.') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
|
||
ADD [ТН / месяц, руб.] decimal(38,6) NULL;
|
||
END;
|
||
|
||
UPDATE a
|
||
SET a.[ТН / месяц, руб.] =
|
||
ISNULL(a.[ТН / год, руб.], 0)
|
||
/ NULLIF(a.[Продано шт], 0)
|
||
* ISNULL(a.[Продажи шт / день], 0)
|
||
* 30
|
||
FROM [mag_pbi].[analytics].[аналитика за 365 дн.] a;
|
||
|
||
|
||
-------------------------------------------------------------------------------------
|
||
-- 8) ДОПОЛНИТЕЛЬНО: Оплаченный остаток и рентабельности (ROIC и по остатку в руб.)
|
||
-- (год назад, квартал назад и на будущий год) по 12-месячному окну
|
||
-------------------------------------------------------------------------------------
|
||
|
||
-- новые колонки
|
||
IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'Оплаченный остаток') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
|
||
ADD [Оплаченный остаток] decimal(38,2) NULL;
|
||
END;
|
||
|
||
IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'Рентабельность / год') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
|
||
ADD [Рентабельность / год] decimal(38,6) NULL;
|
||
END;
|
||
|
||
IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'Рентабельность / квартал') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
|
||
ADD [Рентабельность / квартал] decimal(38,6) NULL;
|
||
END;
|
||
|
||
IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'Рентабельность / будущий год') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
|
||
ADD [Рентабельность / будущий год] decimal(38,6) NULL;
|
||
END;
|
||
|
||
-- новые поля рентабельности по остатку в рублях (сразу в %)
|
||
IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'Рентабельность по остатку / год') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
|
||
ADD [Рентабельность по остатку / год] decimal(38,6) NULL;
|
||
END;
|
||
|
||
IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'Рентабельность по остатку / квартал') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
|
||
ADD [Рентабельность по остатку / квартал] decimal(38,6) NULL;
|
||
END;
|
||
|
||
IF COL_LENGTH('mag_pbi.analytics.аналитика за 365 дн.', 'Рентабельность по остатку / будущий год') IS NULL
|
||
BEGIN
|
||
ALTER TABLE [mag_pbi].[analytics].[аналитика за 365 дн.]
|
||
ADD [Рентабельность по остатку / будущий год] decimal(38,6) NULL;
|
||
END;
|
||
|
||
---------------------------------------------------------
|
||
-- 8.1 Окно 12 полных месяцев
|
||
---------------------------------------------------------
|
||
DECLARE @mp_today date = CAST(GETDATE() AS date);
|
||
DECLARE @mp_lastFullMonth date = EOMONTH(DATEADD(month, -1, @mp_today));
|
||
DECLARE @mp_firstMonthStart date = DATEADD(
|
||
month, -11,
|
||
DATEFROMPARTS(YEAR(@mp_lastFullMonth), MONTH(@mp_lastFullMonth), 1)
|
||
);
|
||
DECLARE @mp_curMonthStart date = @mp_firstMonthStart;
|
||
|
||
IF OBJECT_ID('tempdb..#mp_months') IS NOT NULL DROP TABLE #mp_months;
|
||
CREATE TABLE #mp_months (
|
||
MonthStart date NOT NULL PRIMARY KEY
|
||
);
|
||
|
||
WHILE @mp_curMonthStart <= DATEFROMPARTS(YEAR(@mp_lastFullMonth), MONTH(@mp_lastFullMonth), 1)
|
||
BEGIN
|
||
INSERT INTO #mp_months (MonthStart) VALUES (@mp_curMonthStart);
|
||
SET @mp_curMonthStart = DATEADD(month, 1, @mp_curMonthStart);
|
||
END;
|
||
|
||
---------------------------------------------------------
|
||
-- 8.2 Набор SKU из аналитики 365
|
||
---------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#mp_s') IS NOT NULL DROP TABLE #mp_s;
|
||
CREATE TABLE #mp_s(
|
||
[1c_id] binary(16) NOT NULL,
|
||
code nchar(11) NOT NULL,
|
||
CONSTRAINT PK_mp_s PRIMARY KEY ([1c_id], code)
|
||
);
|
||
|
||
INSERT INTO #mp_s([1c_id], code)
|
||
SELECT DISTINCT a.[1c_id], a.[Code]
|
||
FROM [mag_pbi].[analytics].[аналитика за 365 дн.] a;
|
||
|
||
---------------------------------------------------------
|
||
-- 8.3 Внешние остатки
|
||
---------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#mp_ext_stock') IS NOT NULL DROP TABLE #mp_ext_stock;
|
||
CREATE TABLE #mp_ext_stock(
|
||
Dt date NOT NULL,
|
||
code nchar(11) NOT NULL,
|
||
qty numeric(18,3) NOT NULL,
|
||
CONSTRAINT PK_mp_ext_stock PRIMARY KEY (Dt, code)
|
||
);
|
||
|
||
INSERT INTO #mp_ext_stock (Dt, code, qty)
|
||
SELECT
|
||
CAST(o.[Дата обновления] AS date) AS Dt,
|
||
o.code,
|
||
SUM(o.[Количество]) AS qty
|
||
FROM analytics.[Внешние остатки] o
|
||
JOIN #mp_s s
|
||
ON s.code = o.code
|
||
GROUP BY CAST(o.[Дата обновления] AS date), o.code;
|
||
|
||
---------------------------------------------------------
|
||
-- 8.4 Приходы (Закупка / Приход)
|
||
---------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#mp_incoming') IS NOT NULL DROP TABLE #mp_incoming;
|
||
CREATE TABLE #mp_incoming(
|
||
MonthStart date NOT NULL,
|
||
[1c_id] binary(16) NOT NULL,
|
||
qty numeric(18,3) NOT NULL,
|
||
CONSTRAINT PK_mp_incoming PRIMARY KEY (MonthStart, [1c_id])
|
||
);
|
||
|
||
INSERT INTO #mp_incoming (MonthStart, [1c_id], qty)
|
||
SELECT
|
||
DATEFROMPARTS(YEAR(s.[Период]), MONTH(s.[Период]), 1) AS MonthStart,
|
||
s.[1c_id],
|
||
SUM(s.[Количество]) AS qty
|
||
FROM pbiProd.[СводныйСебестоимость Для PBI] s
|
||
JOIN #mp_s sf
|
||
ON sf.[1c_id] = s.[1c_id]
|
||
WHERE s.[Статья] = N'Закупка'
|
||
AND s.[Вид операции] = N'Приход'
|
||
AND s.[Период] >= @mp_firstMonthStart
|
||
GROUP BY
|
||
DATEFROMPARTS(YEAR(s.[Период]), MONTH(s.[Период]), 1),
|
||
s.[1c_id];
|
||
|
||
---------------------------------------------------------
|
||
-- 8.5 Обязательства по месяцам
|
||
---------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#mp_obligations') IS NOT NULL DROP TABLE #mp_obligations;
|
||
CREATE TABLE #mp_obligations(
|
||
MonthStart date NOT NULL,
|
||
[1c_id] binary(16) NOT NULL,
|
||
obligation numeric(18,2) NOT NULL,
|
||
CONSTRAINT PK_mp_obligations PRIMARY KEY (MonthStart, [1c_id])
|
||
);
|
||
|
||
-- 8.5 Обязательства: считаем по manufacturer_payment_stage (поддержка 2, 3 и более этапов)
|
||
-- Этап «оплачен», если конец месяца M >= дата прихода + days. Сумма этапа = full_sum * percent/100.
|
||
-- Два шага через CTE, чтобы избежать Msg 8124 (correlated subquery с несколькими внешними ссылками).
|
||
;WITH base_oblig AS (
|
||
SELECT
|
||
m.MonthStart,
|
||
inc.MonthStart AS inc_month,
|
||
inc.[1c_id],
|
||
inc.qty * n2.[Цена учетная, руб] AS full_sum,
|
||
EOMONTH(inc.MonthStart) AS inc_eom,
|
||
EOMONTH(m.MonthStart) AS eval_eom,
|
||
man.id AS manufacturer_id
|
||
FROM #mp_months m
|
||
JOIN #mp_incoming inc ON 1 = 1
|
||
JOIN pbi.nomenclature n2 ON n2.[1c_id] = inc.[1c_id]
|
||
LEFT JOIN analytics.manufacturers man ON man.[manufacturer] = n2.Производитель
|
||
),
|
||
oblig_with_paid AS (
|
||
SELECT
|
||
b.MonthStart,
|
||
b.eval_eom,
|
||
b.inc_eom,
|
||
b.[1c_id],
|
||
b.full_sum,
|
||
ISNULL(SUM(
|
||
CASE WHEN b.eval_eom >= DATEADD(day, st.[days], b.inc_eom)
|
||
THEN b.full_sum * st.[percent] / 100.0 ELSE 0 END
|
||
), 0) AS paid_to_month
|
||
FROM base_oblig b
|
||
LEFT JOIN analytics.manufacturer_payment_stage st ON st.manufacturer_id = b.manufacturer_id
|
||
GROUP BY b.MonthStart, b.inc_month, b.[1c_id], b.full_sum, b.eval_eom, b.inc_eom, b.manufacturer_id
|
||
),
|
||
ObligRows AS (
|
||
SELECT
|
||
MonthStart,
|
||
[1c_id],
|
||
CASE
|
||
WHEN eval_eom < inc_eom THEN 0
|
||
WHEN full_sum - paid_to_month > 0 THEN full_sum - paid_to_month
|
||
ELSE 0
|
||
END AS ObligationPerIncoming
|
||
FROM oblig_with_paid
|
||
)
|
||
INSERT INTO #mp_obligations (MonthStart, [1c_id], obligation)
|
||
SELECT
|
||
MonthStart,
|
||
[1c_id],
|
||
SUM(ObligationPerIncoming) AS obligation
|
||
FROM ObligRows
|
||
GROUP BY
|
||
MonthStart,
|
||
[1c_id];
|
||
|
||
---------------------------------------------------------
|
||
-- 8.6 ТН по месяцам
|
||
---------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#mp_tn_monthly') IS NOT NULL DROP TABLE #mp_tn_monthly;
|
||
|
||
CREATE TABLE #mp_tn_monthly(
|
||
MonthStart date NOT NULL,
|
||
[1c_id] binary(16) NOT NULL,
|
||
tn_amount numeric(18,2) NULL,
|
||
CONSTRAINT PK_mp_tn_monthly PRIMARY KEY (MonthStart, [1c_id])
|
||
);
|
||
|
||
INSERT INTO #mp_tn_monthly (MonthStart, [1c_id], tn_amount)
|
||
SELECT
|
||
DATEFROMPARTS(YEAR(p.[d]), MONTH(p.[d]), 1) AS MonthStart,
|
||
p.[1c_id],
|
||
SUM(ISNULL(p.[Торговая надбавка],0)) AS tn_amount
|
||
FROM [mag_pbi].[analytics].[Продажи_Учёт_Маржа_по_дням] p
|
||
JOIN #mp_s s
|
||
ON s.[1c_id] = p.[1c_id]
|
||
WHERE p.[d] >= @mp_firstMonthStart
|
||
AND p.[d] <= @mp_lastFullMonth
|
||
GROUP BY
|
||
DATEFROMPARTS(YEAR(p.[d]), MONTH(p.[d]), 1),
|
||
p.[1c_id];
|
||
|
||
---------------------------------------------------------
|
||
-- 8.7 Помесячный план по всем SKU (остаток, оплаченный остаток, ТН, рубли-дни, остаток учетка руб)
|
||
---------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#mp_plan') IS NOT NULL DROP TABLE #mp_plan;
|
||
|
||
SELECT
|
||
s.[1c_id],
|
||
s.code,
|
||
m.MonthStart,
|
||
[Остаток шт] =
|
||
ISNULL(intStock.quantity, 0) + ISNULL(ext.qty, 0),
|
||
[Остаток учетка руб] =
|
||
(ISNULL(intStock.quantity, 0) + ISNULL(ext.qty, 0))
|
||
* ISNULL(n.[Цена учетная, руб], 0),
|
||
[Оплаченный остаток] =
|
||
CAST(
|
||
CASE
|
||
WHEN paid_raw < 0 THEN 0
|
||
ELSE paid_raw
|
||
END
|
||
AS decimal(18,2)
|
||
),
|
||
[ТН] = ISNULL(tn.tn_amount, 0),
|
||
[рубли-дни] =
|
||
CAST(
|
||
CASE
|
||
WHEN paid_raw < 0 THEN 0
|
||
ELSE paid_raw
|
||
END * DAY(EOMONTH(m.MonthStart))
|
||
AS decimal(18,2)
|
||
)
|
||
INTO #mp_plan
|
||
FROM #mp_months m
|
||
CROSS JOIN #mp_s s
|
||
OUTER APPLY (
|
||
SELECT TOP (1) w1.quantity
|
||
FROM [mag_pbi].[pbi].[w_ostatok_da_net] w1
|
||
WHERE w1._IDRREF = s.[1c_id]
|
||
AND w1.dt <= EOMONTH(m.MonthStart)
|
||
ORDER BY w1.dt DESC
|
||
) AS intStock
|
||
LEFT JOIN #mp_ext_stock ext
|
||
ON ext.code = s.code
|
||
AND ext.Dt = EOMONTH(m.MonthStart)
|
||
LEFT JOIN #mp_obligations ob
|
||
ON ob.[1c_id] = s.[1c_id]
|
||
AND ob.MonthStart = m.MonthStart
|
||
LEFT JOIN [mag_pbi].[pbi].[nomenclature] n
|
||
ON n.[1c_id] = s.[1c_id]
|
||
LEFT JOIN #mp_tn_monthly tn
|
||
ON tn.[1c_id] = s.[1c_id]
|
||
AND tn.MonthStart = m.MonthStart
|
||
CROSS APPLY (
|
||
SELECT
|
||
(ISNULL(intStock.quantity, 0) + ISNULL(ext.qty, 0))
|
||
* ISNULL(n.[Цена учетная, руб], 0)
|
||
- ISNULL(ob.obligation, 0) AS paid_raw
|
||
) p;
|
||
|
||
---------------------------------------------------------
|
||
-- 8.8 Агрегаты по SKU: ROIC (год/квартал/будущий) + рентабельность по остатку (год/квартал/будущий)
|
||
---------------------------------------------------------
|
||
|
||
-- старт квартала (последние 3 месяца окна)
|
||
DECLARE @mp_q_start date = DATEADD(
|
||
month, -2,
|
||
DATEFROMPARTS(YEAR(@mp_lastFullMonth), MONTH(@mp_lastFullMonth), 1)
|
||
);
|
||
|
||
;WITH AggYear AS (
|
||
SELECT
|
||
p.[1c_id],
|
||
tn_year = SUM(p.[ТН]),
|
||
rd_year = SUM(p.[рубли-дни]),
|
||
avg_cap_year = AVG(NULLIF(p.[Остаток учетка руб], 0.0))
|
||
FROM #mp_plan p
|
||
GROUP BY p.[1c_id]
|
||
),
|
||
AggQuarter AS (
|
||
SELECT
|
||
p.[1c_id],
|
||
tn_q = SUM(p.[ТН]),
|
||
rd_q = SUM(p.[рубли-дни]),
|
||
avg_cap_q = AVG(NULLIF(p.[Остаток учетка руб], 0.0))
|
||
FROM #mp_plan p
|
||
WHERE p.MonthStart >= @mp_q_start
|
||
GROUP BY p.[1c_id]
|
||
),
|
||
RoicPast AS (
|
||
SELECT
|
||
ay.[1c_id],
|
||
RoicValue =
|
||
CASE
|
||
WHEN ay.rd_year > 0
|
||
THEN ay.tn_year * 365.0 / ay.rd_year * 100.0
|
||
ELSE NULL
|
||
END,
|
||
ay.tn_year,
|
||
ay.avg_cap_year
|
||
FROM AggYear ay
|
||
),
|
||
RoicQuarterPast AS (
|
||
SELECT
|
||
aq.[1c_id],
|
||
RoicQ =
|
||
CASE
|
||
WHEN aq.rd_q > 0
|
||
THEN aq.tn_q * 365.0 / aq.rd_q * 100.0
|
||
ELSE NULL
|
||
END,
|
||
aq.tn_q,
|
||
aq.avg_cap_q
|
||
FROM AggQuarter aq
|
||
),
|
||
LastRow AS (
|
||
SELECT
|
||
p.[1c_id],
|
||
p.[Оплаченный остаток],
|
||
p.[Остаток шт],
|
||
p.[Остаток учетка руб],
|
||
ROW_NUMBER() OVER (
|
||
PARTITION BY p.[1c_id]
|
||
ORDER BY p.MonthStart DESC
|
||
) AS rn
|
||
FROM #mp_plan p
|
||
),
|
||
FutureBase AS (
|
||
SELECT
|
||
a.[1c_id],
|
||
a.[Продажи шт / день] AS sales_per_day,
|
||
a.[Продано шт] AS sold_year,
|
||
a.[ТН / год, руб.] AS tn_year_hist,
|
||
lr.[Оплаченный остаток] AS paid_stock_now,
|
||
lr.[Остаток шт] AS q_stock,
|
||
lr.[Остаток учетка руб] AS stock_rub_now
|
||
FROM [mag_pbi].[analytics].[аналитика за 365 дн.] a
|
||
LEFT JOIN LastRow lr
|
||
ON lr.[1c_id] = a.[1c_id]
|
||
AND lr.rn = 1
|
||
),
|
||
FutureInputs AS (
|
||
SELECT
|
||
f.[1c_id],
|
||
f.sales_per_day,
|
||
f.sold_year,
|
||
tn_year = COALESCE(f.tn_year_hist, rp.tn_year),
|
||
f.paid_stock_now,
|
||
f.q_stock,
|
||
f.stock_rub_now,
|
||
tn_per_unit =
|
||
CASE
|
||
WHEN f.sold_year > 0 AND COALESCE(f.tn_year_hist, rp.tn_year) IS NOT NULL
|
||
THEN COALESCE(f.tn_year_hist, rp.tn_year) / f.sold_year
|
||
ELSE NULL
|
||
END,
|
||
max_sell_1y =
|
||
CASE
|
||
WHEN f.sales_per_day IS NULL THEN NULL
|
||
ELSE f.sales_per_day * 365.0
|
||
END
|
||
FROM FutureBase f
|
||
LEFT JOIN RoicPast rp
|
||
ON rp.[1c_id] = f.[1c_id]
|
||
),
|
||
FutureCalc AS (
|
||
SELECT
|
||
fi.[1c_id],
|
||
RoicFuture =
|
||
CASE
|
||
WHEN fi.paid_stock_now IS NULL OR fi.paid_stock_now <= 0
|
||
OR fi.sales_per_day IS NULL OR fi.sales_per_day <= 0
|
||
OR fi.tn_per_unit IS NULL
|
||
OR fi.q_stock IS NULL OR fi.q_stock <= 0
|
||
OR fi.max_sell_1y IS NULL OR fi.max_sell_1y <= 0
|
||
THEN NULL
|
||
ELSE
|
||
(
|
||
(
|
||
fi.tn_per_unit *
|
||
(CASE
|
||
WHEN fi.q_stock <= fi.max_sell_1y THEN fi.q_stock
|
||
ELSE fi.max_sell_1y
|
||
END)
|
||
) * 365.0
|
||
/
|
||
(
|
||
CASE
|
||
WHEN fi.q_stock / fi.sales_per_day >= 365.0
|
||
THEN fi.paid_stock_now * 365.0
|
||
ELSE fi.paid_stock_now * (fi.q_stock / fi.sales_per_day) / 2.0
|
||
END
|
||
) * 100.0
|
||
)
|
||
END,
|
||
tn_future =
|
||
CASE
|
||
WHEN fi.tn_per_unit IS NULL OR fi.max_sell_1y IS NULL OR fi.q_stock IS NULL
|
||
THEN NULL
|
||
ELSE
|
||
fi.tn_per_unit *
|
||
(CASE
|
||
WHEN fi.q_stock <= fi.max_sell_1y THEN fi.q_stock
|
||
ELSE fi.max_sell_1y
|
||
END)
|
||
END,
|
||
q_future =
|
||
CASE
|
||
WHEN fi.max_sell_1y IS NULL OR fi.q_stock IS NULL THEN NULL
|
||
WHEN fi.q_stock <= fi.max_sell_1y THEN fi.q_stock
|
||
ELSE fi.max_sell_1y
|
||
END
|
||
FROM FutureInputs fi
|
||
),
|
||
CapRentFuture AS (
|
||
SELECT
|
||
fi.[1c_id],
|
||
RentCapFuturePct =
|
||
CASE
|
||
WHEN fc.tn_future IS NULL
|
||
OR fi.stock_rub_now IS NULL OR fi.stock_rub_now <= 0
|
||
OR fi.q_stock IS NULL OR fi.q_stock <= 0
|
||
OR fc.q_future IS NULL OR fc.q_future <= 0
|
||
THEN NULL
|
||
ELSE
|
||
CASE
|
||
WHEN (fi.stock_rub_now * (1.0 - (fc.q_future / fi.q_stock) / 2.0)) <= 0
|
||
THEN NULL
|
||
ELSE
|
||
fc.tn_future
|
||
/ (fi.stock_rub_now * (1.0 - (fc.q_future / fi.q_stock) / 2.0))
|
||
* 100.0
|
||
END
|
||
END
|
||
FROM FutureInputs fi
|
||
LEFT JOIN FutureCalc fc
|
||
ON fc.[1c_id] = fi.[1c_id]
|
||
)
|
||
UPDATE a
|
||
SET
|
||
a.[Рентабельность / год] = rp.RoicValue,
|
||
a.[Рентабельность / квартал] = rq.RoicQ,
|
||
a.[Оплаченный остаток] = lr.[Оплаченный остаток],
|
||
a.[Рентабельность / будущий год] = fc.RoicFuture,
|
||
a.[Рентабельность по остатку / год] =
|
||
CASE
|
||
WHEN rp.avg_cap_year IS NOT NULL AND rp.avg_cap_year > 0
|
||
AND rp.tn_year IS NOT NULL
|
||
THEN (rp.tn_year / rp.avg_cap_year) * 100.0
|
||
ELSE NULL
|
||
END,
|
||
a.[Рентабельность по остатку / квартал] =
|
||
CASE
|
||
WHEN rq.avg_cap_q IS NOT NULL AND rq.avg_cap_q > 0
|
||
AND rq.tn_q IS NOT NULL
|
||
THEN (rq.tn_q / rq.avg_cap_q) * 100.0
|
||
ELSE NULL
|
||
END,
|
||
a.[Рентабельность по остатку / будущий год] = cf.RentCapFuturePct
|
||
FROM [mag_pbi].[analytics].[аналитика за 365 дн.] a
|
||
LEFT JOIN RoicPast rp ON rp.[1c_id] = a.[1c_id]
|
||
LEFT JOIN RoicQuarterPast rq ON rq.[1c_id] = a.[1c_id]
|
||
LEFT JOIN LastRow lr ON lr.[1c_id] = a.[1c_id] AND lr.rn = 1
|
||
LEFT JOIN FutureCalc fc ON fc.[1c_id] = a.[1c_id]
|
||
LEFT JOIN CapRentFuture cf ON cf.[1c_id] = a.[1c_id];
|
||
|
||
|
||
-------------------------------------------------------------------------------------
|
||
-- 9) Индекс (как был)
|
||
-------------------------------------------------------------------------------------
|
||
CREATE NONCLUSTERED INDEX [analyticsаналитика за 365 дн.]
|
||
ON [analytics].[аналитика за 365 дн.] ([1c_id])
|
||
INCLUDE (
|
||
[Продажи шт / день],
|
||
[Остаток дней продаж],
|
||
[ТН / год, руб.],
|
||
[ТН / квартал, руб.],
|
||
[Рентабельность / год],
|
||
[Рентабельность / квартал],
|
||
[Дней в продаже / год],
|
||
[Дней в продаже / квартал]
|
||
);
|
||
END
|
||
GO
|
||
|
||
/****** Object: StoredProcedure [analytics].[sp_fill_deficit_money_request] Script Date: 2026-02-22 12:07:01 ******/
|
||
SET ANSI_NULLS ON
|
||
GO
|
||
SET QUOTED_IDENTIFIER ON
|
||
GO
|
||
CREATE PROCEDURE [analytics].[sp_fill_deficit_money_request]
|
||
@scenario_id INT = NULL -- NULL = MAX(scenario_id) из deficit_proposal
|
||
AS
|
||
SET NOCOUNT ON;
|
||
|
||
DECLARE @scenario INT;
|
||
|
||
IF @scenario_id IS NOT NULL AND @scenario_id > 0
|
||
SET @scenario = @scenario_id;
|
||
ELSE
|
||
SET @scenario = (SELECT MAX(scenario_id) FROM [analytics].[deficit_proposal]);
|
||
|
||
IF @scenario IS NULL
|
||
BEGIN
|
||
RAISERROR(N'Нет данных в deficit_proposal. Выполните sp_build_deficit_proposal.', 16, 1);
|
||
RETURN;
|
||
END
|
||
|
||
-- Очищаем таблицу для данного сценария
|
||
DELETE FROM [analytics].[deficit_money_request] WHERE scenario_id = @scenario;
|
||
|
||
-- Заполняем: дефицит -> номенклатура (производитель, цена) -> manufacturers -> контрагент
|
||
-- Номенклатура: pbi.v_nomenclature_full содержит все коды, Производитель, учётную цену
|
||
INSERT INTO [analytics].[deficit_money_request] (
|
||
scenario_id,
|
||
manufacturer_id,
|
||
contractor_1c_id,
|
||
manufacturer_name,
|
||
contractor_name,
|
||
arrival_month,
|
||
amount_rub,
|
||
amount_usd,
|
||
currency,
|
||
order_qty_total,
|
||
sku_count,
|
||
updated_at
|
||
)
|
||
SELECT
|
||
@scenario AS scenario_id,
|
||
m.id AS manufacturer_id,
|
||
mcm.contractor_1c_id,
|
||
m.manufacturer AS manufacturer_name,
|
||
c.contractor_name,
|
||
CAST(FORMAT(dp.arrival_month, 'yyyy-MM-01') AS date) AS arrival_month,
|
||
SUM(dp.order_qty * ISNULL(n.[Цена учетная, руб], 0)) AS amount_rub,
|
||
SUM(dp.order_qty * ISNULL(n.[Цена учетная, usd], 0)) AS amount_usd,
|
||
CASE
|
||
WHEN SUM(dp.order_qty * ISNULL(n.[Цена учетная, usd], 0)) >
|
||
SUM(dp.order_qty * ISNULL(n.[Цена учетная, руб], 0)) * 0.01
|
||
THEN N'USD' ELSE N'руб.'
|
||
END AS currency,
|
||
SUM(dp.order_qty) AS order_qty_total,
|
||
COUNT(DISTINCT dp.code) AS sku_count,
|
||
SYSDATETIME() AS updated_at
|
||
FROM [analytics].[deficit_proposal] dp
|
||
-- Номенклатура: все коды, производитель и учётная цена (без фильтра по группе)
|
||
INNER JOIN [mag_pbi].[pbi].[v_nomenclature_full] n
|
||
ON n.[code] = dp.code
|
||
-- Производитель: сопоставление по имени (LTRIM/RTRIM как в contractorProducer)
|
||
INNER JOIN [analytics].[manufacturers] m
|
||
ON LTRIM(RTRIM(ISNULL(n.[Производитель], N''))) = LTRIM(RTRIM(m.manufacturer))
|
||
AND LTRIM(RTRIM(ISNULL(n.[Производитель], N''))) <> N''
|
||
LEFT JOIN [analytics].[manufacturer_counterparty_map] mcm
|
||
ON mcm.manufacturer_id = m.id
|
||
LEFT JOIN [analytics].[v_contractors] c
|
||
ON c.contractor_1c_id = mcm.contractor_1c_id
|
||
WHERE dp.scenario_id = @scenario
|
||
AND dp.order_qty > 0
|
||
AND (n.[Цена учетная, руб] IS NOT NULL OR n.[Цена учетная, usd] IS NOT NULL)
|
||
GROUP BY
|
||
m.id,
|
||
mcm.contractor_1c_id,
|
||
m.manufacturer,
|
||
c.contractor_name,
|
||
CAST(FORMAT(dp.arrival_month, 'yyyy-MM-01') AS date);
|
||
|
||
-- Логируем результат
|
||
DECLARE @rows INT = @@ROWCOUNT;
|
||
PRINT CONCAT(N'analytics.sp_fill_deficit_money_request: внесено ', @rows, N' записей для scenario_id=', @scenario);
|
||
GO
|
||
|
||
/****** Object: StoredProcedure [analytics].[sp_load_koef_groups] Script Date: 2026-02-22 12:07:01 ******/
|
||
SET ANSI_NULLS ON
|
||
GO
|
||
SET QUOTED_IDENTIFIER ON
|
||
GO
|
||
CREATE PROCEDURE [analytics].[sp_load_koef_groups] as BEGIN
|
||
|
||
Print('Запускайте код процедуры руками')
|
||
RETURN 0
|
||
|
||
DROP TABLE #koef
|
||
|
||
CREATE TABLE #koef (
|
||
[group_1c_id] nvarchar(256) NOT NULL,
|
||
[seasonal_koef] [decimal](18, 6) NOT NULL,
|
||
[month] int NOT NULL
|
||
);
|
||
|
||
BULK INSERT #koef
|
||
FROM '\\192.168.35.3\admin3\Обмен\powerbi\k1.csv'
|
||
WITH (
|
||
FIRSTROW = 2, -- пропустить заголовок
|
||
FIELDTERMINATOR = ';',
|
||
ROWTERMINATOR = '\n',
|
||
CODEPAGE = '1251' -- если кириллица
|
||
);
|
||
|
||
|
||
-- Проверить наличие данных во вр таблице
|
||
select top 100 * from #koef
|
||
|
||
|
||
--DELETE FROM [analytics].[seasonality_groups]
|
||
INSERT INTO [analytics].[seasonality_groups]
|
||
SELECT
|
||
g.[1c_id],
|
||
k.month,
|
||
k.seasonal_koef
|
||
FROM #koef k
|
||
INNER JOIN pbi.groups g
|
||
ON g.[1c_id]=k.group_1c_id
|
||
|
||
|
||
|
||
------------------------------
|
||
SET NOCOUNT ON;
|
||
|
||
------------------------------------------------------------
|
||
-- 1. Таблица месяцев 1..12
|
||
------------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#Months') IS NOT NULL DROP TABLE #Months;
|
||
|
||
CREATE TABLE #Months (
|
||
[month] tinyint NOT NULL PRIMARY KEY
|
||
);
|
||
|
||
INSERT INTO #Months ([month])
|
||
VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12);
|
||
|
||
|
||
------------------------------------------------------------
|
||
-- 2. База продаж 2024–2025 по g–g1 × месяц
|
||
------------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#SalesByGroupMonth') IS NOT NULL DROP TABLE #SalesByGroupMonth;
|
||
|
||
SELECT
|
||
g.[g],
|
||
g.[g1],
|
||
[month] = MONTH(s.[Период]),
|
||
qty = /* SUM(s.Сумма) */ SUM(s.[Количество])
|
||
INTO #SalesByGroupMonth
|
||
FROM [mag_pbi].[pbiProd].[СводныйСебестоимость Для PBI] AS s
|
||
INNER JOIN [pbi].[nomenclature] n
|
||
ON n.[1c_id] = s.[1c_id]
|
||
INNER JOIN [pbi].[groups] g
|
||
ON g.[1c_id] = n.[1c_group]
|
||
WHERE
|
||
s.[Статья] = N'Реализация'
|
||
AND s.[Период] >= '2023-12-01'
|
||
AND s.[Период] < '2025-12-01'
|
||
AND g.[g] NOT LIKE N'*%'
|
||
GROUP BY
|
||
g.[g],
|
||
g.[g1],
|
||
MONTH(s.[Период]);
|
||
|
||
|
||
------------------------------------------------------------
|
||
-- 3. Список всех g–g1, которые продавались
|
||
------------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#GroupList') IS NOT NULL DROP TABLE #GroupList;
|
||
|
||
SELECT DISTINCT
|
||
sbgm.[g],
|
||
sbgm.[g1]
|
||
INTO #GroupList
|
||
FROM #SalesByGroupMonth sbgm;
|
||
|
||
|
||
------------------------------------------------------------
|
||
-- 4. g–g1 × все 12 месяцев (qty = 0, если продаж не было)
|
||
------------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#AllGroupMonths') IS NOT NULL DROP TABLE #AllGroupMonths;
|
||
|
||
SELECT
|
||
gl.[g],
|
||
gl.[g1],
|
||
m.[month],
|
||
qty = ISNULL(sbgm.qty, 0)
|
||
INTO #AllGroupMonths
|
||
FROM #GroupList gl
|
||
CROSS JOIN #Months m
|
||
LEFT JOIN #SalesByGroupMonth sbgm
|
||
ON sbgm.[g] = gl.[g]
|
||
AND sbgm.[g1] = gl.[g1]
|
||
AND sbgm.[month] = m.[month];
|
||
|
||
|
||
------------------------------------------------------------
|
||
-- 5. Общий объём продаж по g–g1 за все 12 месяцев
|
||
------------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#GroupTotals') IS NOT NULL DROP TABLE #GroupTotals;
|
||
|
||
SELECT
|
||
agm.[g],
|
||
agm.[g1],
|
||
total_qty = SUM(agm.qty)
|
||
INTO #GroupTotals
|
||
FROM #AllGroupMonths agm
|
||
GROUP BY
|
||
agm.[g],
|
||
agm.[g1];
|
||
|
||
|
||
------------------------------------------------------------
|
||
-- 6. Финальный результат: g, g1, koef, month
|
||
-- Сумма koef по 12 месяцам для каждого g–g1 = 1
|
||
------------------------------------------------------------
|
||
SELECT
|
||
agm.[g],
|
||
agm.[g1],
|
||
agm.[month],
|
||
koef = CASE
|
||
WHEN gt.total_qty = 0 THEN 0
|
||
ELSE CAST(agm.qty AS decimal(18,6)) / NULLIF(gt.total_qty, 0)
|
||
END
|
||
--INTO analytics .seasonality_groups_summ_1
|
||
FROM #AllGroupMonths agm
|
||
INNER JOIN #GroupTotals gt
|
||
ON gt.[g] = agm.[g]
|
||
AND gt.[g1] = agm.[g1]
|
||
ORDER BY
|
||
agm.[g],
|
||
agm.[g1],
|
||
agm.[month];
|
||
|
||
END
|
||
GO
|
||
|
||
/****** Object: StoredProcedure [analytics].[sp_rebuild_stock_plan_by_arrival] Script Date: 2026-02-22 12:07:01 ******/
|
||
SET ANSI_NULLS ON
|
||
GO
|
||
SET QUOTED_IDENTIFIER ON
|
||
GO
|
||
CREATE PROCEDURE [analytics].[sp_rebuild_stock_plan_by_arrival]
|
||
@scenario_id INT,
|
||
@from_month DATE, -- например: DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1)
|
||
@to_month DATE -- последний месяц горизонта + 1 день (интервал [from, to) по месяцам)
|
||
AS
|
||
BEGIN
|
||
SET NOCOUNT ON;
|
||
|
||
-- Безопасная рамка
|
||
IF @from_month IS NULL SET @from_month = DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1);
|
||
IF @to_month IS NULL SET @to_month = DATEADD(MONTH, 12, @from_month); -- 12 мес по умолчанию
|
||
|
||
-- Чистим целевой диапазон
|
||
DELETE FROM [analytics].[stock_plan_by_arrival]
|
||
WHERE [scenario_id] = @scenario_id
|
||
AND [arrival_month] >= @from_month
|
||
AND [arrival_month] < @to_month;
|
||
|
||
;WITH months AS ( -- календарь месяцев [from, to)
|
||
SELECT CAST(@from_month AS DATE) AS m
|
||
UNION ALL
|
||
SELECT DATEADD(MONTH, 1, m) FROM months WHERE DATEADD(MONTH, 1, m) < @to_month
|
||
),
|
||
-- 1) Стартовый остаток на начало горизонта (берём последний известный quantity)
|
||
opening AS (
|
||
SELECT w._IDRREF AS [1c_id],
|
||
n.code AS [code],
|
||
CAST(@from_month AS DATE) AS [arrival_month],
|
||
CAST(
|
||
MAX(CASE WHEN w.dt = x.max_dt THEN w.quantity END)
|
||
AS DECIMAL(18,3)) AS opening_qty
|
||
FROM [pbi].[w_ostatok_da_net] w
|
||
JOIN (
|
||
SELECT _IDRREF, MAX(dt) AS max_dt
|
||
FROM [pbi].[w_ostatok_da_net]
|
||
WHERE dt < @from_month
|
||
GROUP BY _IDRREF
|
||
) x ON x._IDRREF = w._IDRREF AND x.max_dt = w.dt
|
||
JOIN [pbi].[nomenclature] n ON n.[1c_id] = w._IDRREF
|
||
GROUP BY w._IDRREF, n.code
|
||
),
|
||
-- 2) Подтвержденные приходы (ожидаемые заказы) по дате прихода → к первому дню месяца
|
||
inbound_confirmed AS (
|
||
SELECT
|
||
o.[1c_id],
|
||
n.code,
|
||
o.month AS arrival_month,
|
||
CAST(SUM(o.units) AS DECIMAL(18,3)) AS inbound_confirmed
|
||
FROM [mag_pbi].[analytics].[get_orders_by_group] o
|
||
JOIN [pbi].[nomenclature] n ON n.[1c_id] = o.[1c_id]
|
||
WHERE o.[status] IN (N'В пути', N'В производстве', N'Выгружен на складе', N'Согласован')
|
||
AND o.month >= @from_month
|
||
AND o.month < @to_month
|
||
GROUP BY o.[1c_id], n.code,
|
||
o.month
|
||
),
|
||
-- 3) Будущие заказы (дефицит → рекомендованные поставки) по месяцу прихода
|
||
inbound_deficit AS (
|
||
SELECT
|
||
d.[1c_id],
|
||
d.[code],
|
||
DATEFROMPARTS(YEAR(d.[arrival_month]), MONTH(d.[arrival_month]), 1) AS arrival_month,
|
||
CAST(SUM(d.[order_qty]) AS DECIMAL(18,3)) AS inbound_deficit
|
||
FROM [analytics].[deficit_proposal] d
|
||
WHERE d.[scenario_id] = @scenario_id
|
||
AND d.[arrival_month] >= @from_month
|
||
AND d.[arrival_month] < @to_month
|
||
GROUP BY d.[1c_id], d.[code],
|
||
DATEFROMPARTS(YEAR(d.[arrival_month]), MONTH(d.[arrival_month]), 1)
|
||
),
|
||
-- 4) Прогноз спроса по месяцам
|
||
forecast AS (
|
||
SELECT
|
||
f.[1c_id],
|
||
f.[code],
|
||
DATEFROMPARTS(YEAR(f.[month]), MONTH(f.[month]), 1) AS arrival_month,
|
||
CAST(SUM(f.[value]) AS DECIMAL(18,3)) AS forecast_demand
|
||
FROM [analytics].[forecast] f
|
||
WHERE f.[scenario_id] = @scenario_id
|
||
AND f.[month] >= @from_month
|
||
AND f.[month] < @to_month
|
||
GROUP BY f.[1c_id], f.[code],
|
||
DATEFROMPARTS(YEAR(f.[month]), MONTH(f.[month]), 1)
|
||
),
|
||
-- 5) Объединим каркас всех SKU × месяцы горизонта
|
||
sku_calendar AS (
|
||
SELECT DISTINCT n.[1c_id], n.[code], m.m AS arrival_month
|
||
FROM [pbi].[nomenclature] n
|
||
CROSS JOIN months m
|
||
),
|
||
base_union AS (
|
||
SELECT c.[1c_id], c.[code], c.[arrival_month],
|
||
COALESCE(op.opening_qty, 0) AS opening_qty,
|
||
COALESCE(ic.inbound_confirmed, 0) AS inbound_confirmed,
|
||
COALESCE(idf.inbound_deficit, 0) AS inbound_deficit,
|
||
COALESCE(fc.forecast_demand, 0) AS forecast_demand
|
||
FROM sku_calendar c
|
||
LEFT JOIN opening op ON op.[1c_id]=c.[1c_id] AND op.[code]=c.[code] AND op.[arrival_month]=@from_month
|
||
LEFT JOIN inbound_confirmed ic ON ic.[1c_id]=c.[1c_id] AND ic.[code]=c.[code] AND ic.[arrival_month]=c.[arrival_month]
|
||
LEFT JOIN inbound_deficit idf ON idf.[1c_id]=c.[1c_id] AND idf.[code]=c.[code] AND idf.[arrival_month]=c.[arrival_month]
|
||
LEFT JOIN forecast fc ON fc.[1c_id]=c.[1c_id] AND fc.[code]=c.[code] AND fc.[arrival_month]=c.[arrival_month]
|
||
WHERE c.[arrival_month] >= @from_month
|
||
AND c.[arrival_month] < @to_month
|
||
),
|
||
-- 6) Расчёт rolling opening/closing по месяцам
|
||
projected AS (
|
||
SELECT
|
||
@scenario_id AS scenario_id,
|
||
b.[1c_id],
|
||
b.[code],
|
||
b.[arrival_month],
|
||
-- opening: для первого месяца берем opening_qty из "opening", далее — прошлый closing
|
||
CAST(
|
||
CASE
|
||
WHEN b.[arrival_month] = @from_month
|
||
THEN b.[opening_qty]
|
||
ELSE 0
|
||
END
|
||
AS DECIMAL(18,3)) AS opening_qty,
|
||
b.[inbound_confirmed],
|
||
b.[inbound_deficit],
|
||
b.[forecast_demand]
|
||
FROM base_union b
|
||
)
|
||
-- Вставка построчно с расчетом closing через окно
|
||
INSERT INTO [analytics].[stock_plan_by_arrival]
|
||
([scenario_id],[arrival_month],[1c_id],[code],
|
||
[opening_qty],[inbound_confirmed],[inbound_deficit],[forecast_demand],[closing_qty],[updated_at])
|
||
SELECT
|
||
p.scenario_id,
|
||
p.arrival_month,
|
||
p.[1c_id],
|
||
p.[code],
|
||
-- opening по месяцу = LAG(closing) OVER (по SKU упорядочено по месяцу), для первого — opening_qty
|
||
CAST(
|
||
COALESCE(
|
||
LAG( closing_calc ) OVER (PARTITION BY p.[1c_id], p.[code] ORDER BY p.arrival_month),
|
||
p.opening_qty
|
||
)
|
||
AS DECIMAL(18,3)) AS opening_qty,
|
||
p.inbound_confirmed,
|
||
p.inbound_deficit,
|
||
p.forecast_demand,
|
||
-- closing = opening + inbound_confirmed + inbound_deficit - forecast
|
||
CAST(
|
||
(
|
||
COALESCE( LAG( closing_calc ) OVER (PARTITION BY p.[1c_id], p.[code] ORDER BY p.arrival_month), p.opening_qty)
|
||
+ p.inbound_confirmed
|
||
+ p.inbound_deficit
|
||
- p.forecast_demand
|
||
) AS DECIMAL(18,3)
|
||
) AS closing_qty,
|
||
SYSUTCDATETIME()
|
||
FROM (
|
||
-- промежуточное вычисление closing для использования в LAG
|
||
SELECT
|
||
scenario_id, [1c_id], [code], arrival_month, opening_qty, inbound_confirmed, inbound_deficit, forecast_demand,
|
||
CAST(opening_qty + inbound_confirmed + inbound_deficit - forecast_demand AS DECIMAL(18,3)) AS closing_calc
|
||
FROM projected
|
||
) p
|
||
OPTION (MAXRECURSION 0);
|
||
|
||
END
|
||
GO
|
||
|
||
/****** Object: StoredProcedure [analytics].[sp_recalc_roic] Script Date: 2026-02-22 12:07:01 ******/
|
||
SET ANSI_NULLS ON
|
||
GO
|
||
SET QUOTED_IDENTIFIER ON
|
||
GO
|
||
CREATE PROCEDURE [analytics].[sp_recalc_roic]
|
||
@manufacturer_id INT = NULL
|
||
AS
|
||
SET NOCOUNT ON;
|
||
|
||
;WITH stage_agg AS (
|
||
SELECT
|
||
manufacturer_id,
|
||
SUM([percent] / 100.0 * [days]) AS effective_deferral_days,
|
||
SUM([percent] / 100.0) AS total_percent
|
||
FROM [analytics].[manufacturer_payment_stage]
|
||
WHERE @manufacturer_id IS NULL OR manufacturer_id = @manufacturer_id
|
||
GROUP BY manufacturer_id
|
||
),
|
||
calc AS (
|
||
SELECT
|
||
man.id,
|
||
man.days_of_sales,
|
||
man.logistics_days,
|
||
COALESCE(s.effective_deferral_days, 0) AS effective_deferral_days,
|
||
COALESCE(s.total_percent, 0) AS total_percent,
|
||
(ISNULL(man.logistics_days, 120) + ISNULL(man.days_of_sales, 180) / 2.0) AS avg_return_day
|
||
FROM [analytics].[manufacturers] man
|
||
LEFT JOIN stage_agg s ON s.manufacturer_id = man.id
|
||
WHERE @manufacturer_id IS NULL OR man.id = @manufacturer_id
|
||
),
|
||
roic_calc AS (
|
||
SELECT
|
||
id,
|
||
CASE
|
||
WHEN total_percent <= 0 THEN NULL
|
||
WHEN (avg_return_day - effective_deferral_days) <= 0 THEN NULL
|
||
ELSE ROUND(12.0 / ((avg_return_day - effective_deferral_days) / 30.0) * 100.0, 2)
|
||
END AS new_roic
|
||
FROM calc
|
||
)
|
||
UPDATE man
|
||
SET man.roic_norm = r.new_roic
|
||
FROM [analytics].[manufacturers] man
|
||
JOIN roic_calc r ON r.id = man.id;
|
||
|
||
SELECT @@ROWCOUNT AS updated_count;
|
||
GO
|
||
|
||
/****** Object: StoredProcedure [analytics].[sp_report_ROI] Script Date: 2026-02-22 12:07:01 ******/
|
||
SET ANSI_NULLS ON
|
||
GO
|
||
SET QUOTED_IDENTIFIER ON
|
||
GO
|
||
CREATE PROCEDURE [analytics].[sp_report_ROI]
|
||
@scenario_id INT = 4,
|
||
@min_deficit_rub DECIMAL(18,2) = 2000,
|
||
@cutoff_month DATE = '2027-01-01',
|
||
@pathPattern NVARCHAR(400) = N'Кружево%'
|
||
AS
|
||
BEGIN
|
||
SET NOCOUNT ON;
|
||
|
||
;WITH stages_pivot AS (
|
||
SELECT manufacturer_id,
|
||
MAX(CASE WHEN rn = 1 THEN [percent] END) / 100.0 AS n_percent,
|
||
MAX(CASE WHEN rn = 1 THEN days END) AS n_days,
|
||
MAX(CASE WHEN rn = 2 THEN [percent] END) / 100.0 AS m_percent,
|
||
MAX(CASE WHEN rn = 2 THEN days END) AS m_days
|
||
FROM (
|
||
SELECT manufacturer_id, [percent], days,
|
||
ROW_NUMBER() OVER (PARTITION BY manufacturer_id ORDER BY sort_order) AS rn
|
||
FROM [analytics].[manufacturer_payment_stage]
|
||
) t WHERE rn <= 2
|
||
GROUP BY manufacturer_id
|
||
),
|
||
base AS (
|
||
SELECT
|
||
g.[path] AS grp
|
||
, n.[Производитель]
|
||
, d.place_month
|
||
, SUM(d.order_qty * ISNULL(n.[Цена учетная, руб], 0)) AS Deficit
|
||
, SUM(a.[%ТН год, руб.] * d.order_qty * ISNULL(n.[Цена учетная, руб], 0))
|
||
/ NULLIF(SUM(d.order_qty * ISNULL(n.[Цена учетная, руб], 0)), 0) AS [%ТН средн]
|
||
FROM [analytics].[deficit_proposal] d
|
||
INNER JOIN [mag_pbi].[pbi].[v_nomenclature_full] n ON n.[1c_id] = d.[1c_id]
|
||
INNER JOIN [analytics].[аналитика за 365 дн.] a ON a.[1c_id] = d.[1c_id]
|
||
INNER JOIN [mag_pbi].[pbi].[groups] g ON g.[1c_id] = n.[1c_group]
|
||
WHERE d.scenario_id = @scenario_id
|
||
AND ISNULL(n.[cenovaya_gruppa], N'') = N'Валютная'
|
||
AND g.[path] LIKE @pathPattern
|
||
GROUP BY g.[path], n.[Производитель], d.place_month
|
||
HAVING SUM(d.order_qty * ISNULL(n.[Цена учетная, руб], 0)) > @min_deficit_rub
|
||
)
|
||
SELECT
|
||
b.grp AS [g]
|
||
, b.[Производитель]
|
||
, b.place_month
|
||
, b.Deficit
|
||
, b.[%ТН средн]
|
||
, b.[%ТН средн] * ISNULL(m.roic_norm, 1.36) AS [%ROI заказа]
|
||
, ISNULL(m.roic_norm, 1.36) AS [ROI normalized]
|
||
, b.[%ТН средн] * ISNULL(m.roic_norm, 1.36) * b.Deficit AS [ROI руб]
|
||
, ISNULL(sp.n_percent, 0.30) AS n_percent
|
||
, ISNULL(sp.n_days, 1) AS n_days
|
||
, ISNULL(sp.m_percent, 0.70) AS m_percent
|
||
, ISNULL(sp.m_days, 60) AS m_days
|
||
FROM base b
|
||
LEFT JOIN [analytics].[manufacturers] m
|
||
ON LTRIM(RTRIM(ISNULL(b.[Производитель], N''))) = LTRIM(RTRIM(m.manufacturer))
|
||
AND LTRIM(RTRIM(ISNULL(b.[Производитель], N''))) <> N''
|
||
LEFT JOIN stages_pivot sp ON sp.manufacturer_id = m.id
|
||
ORDER BY [%ROI заказа] DESC, b.grp, b.Deficit DESC;
|
||
|
||
-- 2. Платежи по месяцам (из deficit_proposal по path — dmr не хранит path)
|
||
SELECT
|
||
FORMAT(d.arrival_month, 'yyyy-MM') AS [Дата платежа]
|
||
, SUM(d.order_qty * ISNULL(n.[Цена учетная, руб], 0)) AS [Сумма платежа]
|
||
FROM [analytics].[deficit_proposal] d
|
||
INNER JOIN [mag_pbi].[pbi].[v_nomenclature_full] n ON n.[1c_id] = d.[1c_id]
|
||
INNER JOIN [mag_pbi].[pbi].[groups] g ON g.[1c_id] = n.[1c_group]
|
||
WHERE d.scenario_id = @scenario_id
|
||
AND g.[path] LIKE @pathPattern
|
||
GROUP BY FORMAT(d.arrival_month, 'yyyy-MM')
|
||
ORDER BY FORMAT(d.arrival_month, 'yyyy-MM') ASC;
|
||
|
||
-- 3. Платежи до cutoff (из deficit_proposal по path)
|
||
SELECT
|
||
SUM(d.order_qty * ISNULL(n.[Цена учетная, руб], 0)) AS [Платежи в 2026]
|
||
FROM [analytics].[deficit_proposal] d
|
||
INNER JOIN [mag_pbi].[pbi].[v_nomenclature_full] n ON n.[1c_id] = d.[1c_id]
|
||
INNER JOIN [mag_pbi].[pbi].[groups] g ON g.[1c_id] = n.[1c_group]
|
||
WHERE d.scenario_id = @scenario_id
|
||
AND g.[path] LIKE @pathPattern
|
||
AND d.arrival_month < @cutoff_month;
|
||
|
||
-- 4. Прогноз выручки по месяцам
|
||
SELECT
|
||
t.[month]
|
||
, SUM(t.Выручка) AS revenue
|
||
, SUM(t.[Сумм. учет]) AS uchet
|
||
FROM (
|
||
SELECT
|
||
f.[month]
|
||
, f.[value] * ISNULL(n.[Цена учетная, руб], 0) AS [Сумм. учет]
|
||
, f.[value] * ISNULL(n.[Цена учетная, руб], 0) * (1 + a.[%ТН год, руб.]) AS Выручка
|
||
FROM [analytics].[forecast] f
|
||
INNER JOIN [analytics].[аналитика за 365 дн.] a ON a.[1c_id] = f.[1c_id]
|
||
INNER JOIN [mag_pbi].[pbi].[nomenclature] n ON n.[1c_id] = f.[1c_id]
|
||
INNER JOIN [mag_pbi].[pbi].[groups] g ON g.[1c_id] = n.[1c_group]
|
||
WHERE f.scenario_id = @scenario_id
|
||
AND a.[%ТН год, руб.] > 0
|
||
AND g.[path] LIKE @pathPattern
|
||
) t
|
||
GROUP BY t.[month]
|
||
ORDER BY t.[month];
|
||
|
||
-- 5. ROIC по группе (path pattern)
|
||
;WITH sku_in_group AS (
|
||
SELECT
|
||
a.[1c_id]
|
||
, a.[Code] AS code
|
||
, g.[path]
|
||
, a.[Оплаченный остаток] AS PaidCapital
|
||
, a.[Рентабельность по остатку / год] AS RoicPast
|
||
FROM [analytics].[аналитика за 365 дн.] a
|
||
INNER JOIN [mag_pbi].[pbi].[v_nomenclature_full] n ON n.[1c_id] = a.[1c_id]
|
||
INNER JOIN [mag_pbi].[pbi].[groups] g ON g.[1c_id] = n.[1c_group]
|
||
WHERE g.[path] LIKE @pathPattern
|
||
)
|
||
SELECT
|
||
@pathPattern AS [Фильтр path]
|
||
, SUM(PaidCapital) AS [Оплаченный остаток всего, руб]
|
||
, CASE
|
||
WHEN SUM(PaidCapital) > 0
|
||
THEN SUM(ISNULL(PaidCapital, 0) * ISNULL(RoicPast, 0)) / SUM(PaidCapital)
|
||
ELSE NULL
|
||
END AS [Рентабельность остатка / год назад, %]
|
||
FROM sku_in_group;
|
||
|
||
-- 6. Итоговые ROIC по всей аналитике
|
||
DECLARE @sumPaid DECIMAL(38,6);
|
||
DECLARE @sumWeightedPast DECIMAL(38,6);
|
||
DECLARE @sumWeightedFuture DECIMAL(38,6);
|
||
DECLARE @roicPastTotal DECIMAL(38,6);
|
||
DECLARE @roicFutureTotal DECIMAL(38,6);
|
||
|
||
SELECT
|
||
@sumPaid = SUM(CAST(ISNULL(a.[Оплаченный остаток], 0) AS DECIMAL(38,6)))
|
||
, @sumWeightedPast = SUM(
|
||
CAST(ISNULL(a.[Оплаченный остаток], 0) AS DECIMAL(38,6))
|
||
* CAST(ISNULL(a.[Рентабельность по остатку / год], 0) AS DECIMAL(38,6))
|
||
)
|
||
, @sumWeightedFuture = SUM(
|
||
CAST(ISNULL(a.[Оплаченный остаток], 0) AS DECIMAL(38,6))
|
||
* CAST(ISNULL(a.[Рентабельность / будущий год], 0) AS DECIMAL(38,6))
|
||
)
|
||
FROM [analytics].[аналитика за 365 дн.] a
|
||
INNER JOIN [mag_pbi].[pbi].[v_nomenclature_full] n ON n.[1c_id] = a.[1c_id]
|
||
INNER JOIN [mag_pbi].[pbi].[groups] g ON g.[1c_id] = n.[1c_group]
|
||
WHERE g.[path] LIKE @pathPattern;
|
||
|
||
SET @roicPastTotal = CASE WHEN @sumPaid > 0 THEN @sumWeightedPast / @sumPaid ELSE NULL END;
|
||
SET @roicFutureTotal = CASE WHEN @sumPaid > 0 THEN @sumWeightedFuture / @sumPaid ELSE NULL END;
|
||
|
||
SELECT
|
||
@sumPaid AS [Оплаченный остаток всего, руб]
|
||
, @roicPastTotal AS [Рентабельность остатка / год назад всего, %]
|
||
, @roicFutureTotal AS [Рентабельность остатка / будущий год всего, %];
|
||
END
|
||
GO
|
||
|
||
/****** Object: StoredProcedure [analytics].[sp_report_ROI_подробно] Script Date: 2026-02-22 12:07:01 ******/
|
||
SET ANSI_NULLS ON
|
||
GO
|
||
SET QUOTED_IDENTIFIER ON
|
||
GO
|
||
CREATE procedure [analytics].[sp_report_ROI_подробно] as BEGIN
|
||
---------------------------------------------------------
|
||
-- 0. Параметры дат: 12 полных месяцев НАЗАД
|
||
---------------------------------------------------------
|
||
DECLARE @today date = CAST(GETDATE() AS date);
|
||
DECLARE @lastFullMonth date;
|
||
DECLARE @firstMonthStart date;
|
||
DECLARE @curMonthStart date;
|
||
|
||
-- последний полный месяц (месяц до текущего дня)
|
||
SET @lastFullMonth = EOMONTH(DATEADD(month, -1, @today));
|
||
-- первый из 12 полных месяцев
|
||
SET @firstMonthStart = DATEADD(
|
||
month, -11,
|
||
DATEFROMPARTS(YEAR(@lastFullMonth), MONTH(@lastFullMonth), 1)
|
||
);
|
||
SET @curMonthStart = @firstMonthStart;
|
||
|
||
---------------------------------------------------------
|
||
-- 1. Список месяцев (#months)
|
||
---------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#months') IS NOT NULL DROP TABLE #months;
|
||
|
||
CREATE TABLE #months (
|
||
MonthStart date NOT NULL PRIMARY KEY
|
||
);
|
||
|
||
WHILE @curMonthStart
|
||
<= DATEFROMPARTS(YEAR(@lastFullMonth), MONTH(@lastFullMonth), 1)
|
||
BEGIN
|
||
INSERT INTO #months (MonthStart)
|
||
VALUES (@curMonthStart);
|
||
|
||
SET @curMonthStart = DATEADD(month, 1, @curMonthStart);
|
||
END;
|
||
|
||
---------------------------------------------------------
|
||
-- 2. SKU (#s) – здесь укажи нужный код товара
|
||
---------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#s') IS NOT NULL DROP TABLE #s;
|
||
|
||
CREATE TABLE #s (
|
||
[1c_id] binary(16) NOT NULL,
|
||
code nchar(11) NOT NULL
|
||
);
|
||
|
||
INSERT INTO #s ([1c_id], code)
|
||
SELECT n.[1c_id], n.code
|
||
FROM pbi.nomenclature n
|
||
WHERE n.code = N'УТ-00176242'; -- <=== поменяй при необходимости
|
||
|
||
---------------------------------------------------------
|
||
-- 3. Внешние остатки (#ext_stock) – срезы на конец месяца
|
||
---------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#ext_stock') IS NOT NULL DROP TABLE #ext_stock;
|
||
|
||
CREATE TABLE #ext_stock (
|
||
Dt date NOT NULL,
|
||
code nchar(11) NOT NULL,
|
||
qty numeric(18,3) NOT NULL,
|
||
CONSTRAINT PK_ext_stock PRIMARY KEY (Dt, code)
|
||
);
|
||
|
||
INSERT INTO #ext_stock (Dt, code, qty)
|
||
SELECT
|
||
CAST(o.[Дата обновления] AS date) AS Dt,
|
||
o.code,
|
||
SUM(o.[Количество]) AS qty
|
||
FROM analytics.[Внешние остатки] o
|
||
JOIN #s s
|
||
ON s.code = o.code
|
||
GROUP BY CAST(o.[Дата обновления] AS date), o.code;
|
||
|
||
---------------------------------------------------------
|
||
-- 4. Приходы (#incoming) – Закупка / Приход по месяцам
|
||
-- БЕРЁМ ТОЛЬКО ПРИХОДЫ ВНУТРИ НАШИХ 12 МЕСЯЦЕВ
|
||
---------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#incoming') IS NOT NULL DROP TABLE #incoming;
|
||
|
||
CREATE TABLE #incoming (
|
||
MonthStart date NOT NULL, -- месяц прихода (1-е число)
|
||
code nchar(11) NOT NULL,
|
||
qty numeric(18,3) NOT NULL,
|
||
CONSTRAINT PK_incoming PRIMARY KEY (MonthStart, code)
|
||
);
|
||
|
||
INSERT INTO #incoming (MonthStart, code, qty)
|
||
SELECT
|
||
DATEFROMPARTS(YEAR(s.[Период]), MONTH(s.[Период]), 1) AS MonthStart,
|
||
n.code,
|
||
SUM(s.[Количество]) AS qty
|
||
FROM pbiProd.[СводныйСебестоимость Для PBI] s
|
||
JOIN pbi.nomenclature n
|
||
ON n.[1c_id] = s.[1c_id]
|
||
JOIN #s sfilter
|
||
ON sfilter.code = n.code
|
||
WHERE s.[Статья] = N'Закупка'
|
||
AND s.[Вид операции] = N'Приход'
|
||
AND s.[Период] >= @firstMonthStart -- отключаем старые приходы
|
||
GROUP BY
|
||
DATEFROMPARTS(YEAR(s.[Период]), MONTH(s.[Период]), 1),
|
||
n.code;
|
||
|
||
---------------------------------------------------------
|
||
-- 5. Части платежей по каждому приходу (#pay_parts)
|
||
-- (1-й и 2-й платёж по производителю, проценты как ДОЛИ)
|
||
---------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#pay_parts') IS NOT NULL DROP TABLE #pay_parts;
|
||
|
||
CREATE TABLE #pay_parts (
|
||
MonthStart date NOT NULL, -- месяц прихода (ключ к #incoming)
|
||
code nchar(11) NOT NULL,
|
||
pay_date date NOT NULL, -- дата платежа
|
||
amount numeric(18,2) NOT NULL
|
||
);
|
||
|
||
-- первый платёж (n)
|
||
INSERT INTO #pay_parts (MonthStart, code, pay_date, amount)
|
||
SELECT
|
||
inc.MonthStart,
|
||
inc.code,
|
||
DATEADD(day, man.n_days - 90, EOMONTH(inc.MonthStart)) AS pay_date,
|
||
inc.qty * n.[Цена учетная, руб] * man.n_percent AS amount
|
||
FROM #incoming inc
|
||
JOIN pbi.nomenclature n
|
||
ON n.code = inc.code
|
||
JOIN analytics.manufacturers man
|
||
ON man.[manufacturer] = n.Производитель
|
||
WHERE man.n_percent IS NOT NULL
|
||
AND man.n_percent <> 0;
|
||
|
||
-- второй платёж (m)
|
||
INSERT INTO #pay_parts (MonthStart, code, pay_date, amount)
|
||
SELECT
|
||
inc.MonthStart,
|
||
inc.code,
|
||
DATEADD(day, man.m_days - 90, EOMONTH(inc.MonthStart)) AS pay_date,
|
||
inc.qty * n.[Цена учетная, руб] * man.m_percent AS amount
|
||
FROM #incoming inc
|
||
JOIN pbi.nomenclature n
|
||
ON n.code = inc.code
|
||
JOIN analytics.manufacturers man
|
||
ON man.[manufacturer] = n.Производитель
|
||
WHERE man.m_percent IS NOT NULL
|
||
AND man.m_percent <> 0;
|
||
|
||
---------------------------------------------------------
|
||
-- 6. Платежи по месяцам (#payments) – для колонки [платеж]
|
||
---------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#payments') IS NOT NULL DROP TABLE #payments;
|
||
|
||
CREATE TABLE #payments (
|
||
MonthStart date NOT NULL, -- месяц платежа (1-е число)
|
||
code nchar(11) NOT NULL,
|
||
amount numeric(18,2) NOT NULL,
|
||
CONSTRAINT PK_payments PRIMARY KEY (MonthStart, code)
|
||
);
|
||
|
||
INSERT INTO #payments (MonthStart, code, amount)
|
||
SELECT
|
||
DATEFROMPARTS(YEAR(p.pay_date), MONTH(p.pay_date), 1) AS MonthStart,
|
||
p.code,
|
||
SUM(p.amount) AS amount
|
||
FROM #pay_parts p
|
||
GROUP BY
|
||
DATEFROMPARTS(YEAR(p.pay_date), MONTH(p.pay_date), 1),
|
||
p.code;
|
||
|
||
---------------------------------------------------------
|
||
-- 7. Обязательства по месяцам (#obligations)
|
||
-- Считаем только по приходам (#incoming)
|
||
---------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#obligations') IS NOT NULL DROP TABLE #obligations;
|
||
|
||
CREATE TABLE #obligations (
|
||
MonthStart date NOT NULL, -- месяц, на который считаем долг
|
||
code nchar(11) NOT NULL,
|
||
obligation numeric(18,2) NOT NULL,
|
||
CONSTRAINT PK_obligations PRIMARY KEY (MonthStart, code)
|
||
);
|
||
|
||
;WITH ObligRows AS (
|
||
SELECT
|
||
m.MonthStart,
|
||
inc.code,
|
||
CASE
|
||
-- если месяц ещё до прихода товара – долга нет
|
||
WHEN EOMONTH(m.MonthStart) < EOMONTH(inc.MonthStart) THEN 0
|
||
ELSE
|
||
CASE
|
||
WHEN full_sum - paid_to_month > 0
|
||
THEN full_sum - paid_to_month
|
||
ELSE 0
|
||
END
|
||
END AS ObligationPerIncoming
|
||
FROM #months m
|
||
JOIN #incoming inc
|
||
ON 1 = 1 -- каждая пара (месяц, приход)
|
||
JOIN pbi.nomenclature n2
|
||
ON n2.code = inc.code
|
||
JOIN analytics.manufacturers man2
|
||
ON man2.[manufacturer] = n2.Производитель
|
||
CROSS APPLY (
|
||
SELECT
|
||
inc.qty * n2.[Цена учетная, руб] AS full_sum,
|
||
(
|
||
-- оплачено к концу месяца m.MonthStart (по обоим платежам)
|
||
(CASE
|
||
WHEN man2.n_percent IS NOT NULL
|
||
AND EOMONTH(m.MonthStart)
|
||
>= DATEADD(day, man2.n_days - 90,
|
||
EOMONTH(inc.MonthStart))
|
||
THEN inc.qty * n2.[Цена учетная, руб] * man2.n_percent
|
||
ELSE 0
|
||
END)
|
||
+
|
||
(CASE
|
||
WHEN man2.m_percent IS NOT NULL
|
||
AND EOMONTH(m.MonthStart)
|
||
>= DATEADD(day, man2.m_days - 90,
|
||
EOMONTH(inc.MonthStart))
|
||
THEN inc.qty * n2.[Цена учетная, руб] * man2.m_percent
|
||
ELSE 0
|
||
END)
|
||
) AS paid_to_month
|
||
) calc
|
||
)
|
||
INSERT INTO #obligations (MonthStart, code, obligation)
|
||
SELECT
|
||
MonthStart,
|
||
code,
|
||
SUM(ObligationPerIncoming) AS obligation
|
||
FROM ObligRows
|
||
GROUP BY
|
||
MonthStart,
|
||
code;
|
||
|
||
---------------------------------------------------------
|
||
-- 8. ТН по месяцам (#tn_monthly) из Продажи_Учёт_Маржа_по_дням
|
||
---------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#tn_monthly') IS NOT NULL DROP TABLE #tn_monthly;
|
||
|
||
CREATE TABLE #tn_monthly (
|
||
MonthStart date NOT NULL,
|
||
code nchar(11) NOT NULL,
|
||
tn_amount numeric(18,2) NOT NULL,
|
||
CONSTRAINT PK_tn_monthly PRIMARY KEY (MonthStart, code)
|
||
);
|
||
|
||
INSERT INTO #tn_monthly (MonthStart, code, tn_amount)
|
||
SELECT
|
||
DATEFROMPARTS(YEAR(p.[d]), MONTH(p.[d]), 1) AS MonthStart,
|
||
p.[Code],
|
||
SUM(p.[Торговая надбавка]) AS tn_amount
|
||
FROM [mag_pbi].[analytics].[Продажи_Учёт_Маржа_по_дням] p
|
||
JOIN #s s
|
||
ON s.code = p.[Code]
|
||
WHERE p.[d] >= @firstMonthStart
|
||
AND p.[d] <= @lastFullMonth
|
||
GROUP BY
|
||
DATEFROMPARTS(YEAR(p.[d]), MONTH(p.[d]), 1),
|
||
p.[Code];
|
||
|
||
---------------------------------------------------------
|
||
-- 9. Финальный SELECT ПО МЕСЯЦАМ (прошлый год)
|
||
---------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#month_plan') IS NOT NULL DROP TABLE #month_plan;
|
||
|
||
SELECT
|
||
s.[1c_id],
|
||
s.code,
|
||
n.Производитель,
|
||
n.[Цена учетная, руб],
|
||
n.[Цена учетная, руб]*(ISNULL(intStock.quantity, 0) + ISNULL(ext.qty, 0)) AS [Остаток учетка руб],
|
||
man.n_percent,
|
||
man.n_days,
|
||
man.m_percent,
|
||
man.m_days,
|
||
RIGHT('0' + CAST(MONTH(m.MonthStart) AS varchar(2)), 2)
|
||
+ '-' + CAST(YEAR(m.MonthStart) AS varchar(4)) AS [месяц], -- MM-YYYY (текст)
|
||
m.MonthStart AS [MonthStart], -- реальная дата месяца
|
||
ISNULL(intStock.quantity, 0) + ISNULL(ext.qty, 0) AS [остаток], -- шт
|
||
ISNULL(inc.qty, 0) AS [приход], -- шт
|
||
ISNULL(pay.amount, 0) AS [платеж], -- руб
|
||
ISNULL(ob.obligation, 0) AS [Обязательства],-- руб
|
||
CAST(
|
||
CASE
|
||
WHEN p.paid_raw < 0 THEN 0
|
||
ELSE p.paid_raw
|
||
END
|
||
AS numeric(18,2)
|
||
) AS [Оплаченный остаток], -- руб
|
||
ISNULL(tn.tn_amount, 0) AS [ТН], -- торговая надбавка за месяц, руб
|
||
CAST(
|
||
CASE
|
||
WHEN p.paid_raw < 0 THEN 0
|
||
ELSE p.paid_raw
|
||
END * DAY(EOMONTH(m.MonthStart))
|
||
AS numeric(18,2)
|
||
) AS [рубли-дни] -- оплаченный остаток × дней в месяце
|
||
INTO #month_plan
|
||
FROM #months m
|
||
CROSS JOIN #s s
|
||
|
||
OUTER APPLY (
|
||
SELECT TOP (1) w1.quantity
|
||
FROM pbi.[w_ostatok_da_net] w1
|
||
WHERE w1._IDRREF = s.[1c_id]
|
||
AND w1.dt <= EOMONTH(m.MonthStart)
|
||
ORDER BY w1.dt DESC
|
||
) AS intStock
|
||
|
||
LEFT JOIN #ext_stock ext
|
||
ON ext.code = s.code
|
||
AND ext.Dt = EOMONTH(m.MonthStart)
|
||
|
||
LEFT JOIN #incoming inc
|
||
ON inc.code = s.code
|
||
AND inc.MonthStart = m.MonthStart
|
||
|
||
LEFT JOIN pbi.nomenclature n
|
||
ON n.code = s.code
|
||
|
||
LEFT JOIN analytics.manufacturers man
|
||
ON man.[manufacturer] = n.Производитель
|
||
|
||
LEFT JOIN #payments pay
|
||
ON pay.code = s.code
|
||
AND pay.MonthStart = m.MonthStart
|
||
|
||
LEFT JOIN #obligations ob
|
||
ON ob.code = s.code
|
||
AND ob.MonthStart = m.MonthStart
|
||
|
||
LEFT JOIN #tn_monthly tn
|
||
ON tn.code = s.code
|
||
AND tn.MonthStart = m.MonthStart
|
||
|
||
CROSS APPLY ( -- сырые оплаченные рубли (до обрезки в 0)
|
||
SELECT
|
||
(ISNULL(intStock.quantity, 0) + ISNULL(ext.qty, 0))
|
||
* ISNULL(n.[Цена учетная, руб], 0)
|
||
- ISNULL(ob.obligation, 0) AS paid_raw
|
||
) p
|
||
|
||
ORDER BY m.MonthStart;
|
||
|
||
-- помесячная картина прошлого
|
||
SELECT * FROM #month_plan;
|
||
|
||
---------------------------------------------------------
|
||
-- 10. Рентабельность за прошлый год (ROIC как было)
|
||
---------------------------------------------------------
|
||
DECLARE @roic_past numeric(38,6);
|
||
DECLARE @tn_year numeric(38,6);
|
||
DECLARE @ruble_days_year numeric(38,6);
|
||
|
||
SELECT
|
||
@tn_year = SUM(t.[ТН]),
|
||
@ruble_days_year = SUM(t.[рубли-дни])
|
||
FROM #month_plan t;
|
||
|
||
SET @roic_past = CASE
|
||
WHEN @ruble_days_year > 0
|
||
THEN @tn_year * 365.0 / @ruble_days_year * 100.0
|
||
ELSE NULL
|
||
END;
|
||
|
||
---------------------------------------------------------
|
||
-- 10a. Рентабельность по остатку В РУБЛЯХ (год и квартал назад)
|
||
-- ТН / средний остаток учетка руб
|
||
---------------------------------------------------------
|
||
DECLARE @rent_cap_year numeric(38,6);
|
||
DECLARE @rent_cap_quarter numeric(38,6);
|
||
|
||
-- год: средний Остаток учетка руб по всем 12 месяцам
|
||
DECLARE @avg_cap_year numeric(38,6);
|
||
|
||
SELECT
|
||
@avg_cap_year = AVG(NULLIF(t.[Остаток учетка руб], 0.0))
|
||
FROM #month_plan t;
|
||
|
||
IF @avg_cap_year IS NOT NULL AND @avg_cap_year > 0 AND @tn_year IS NOT NULL
|
||
SET @rent_cap_year = @tn_year / @avg_cap_year;
|
||
ELSE
|
||
SET @rent_cap_year = NULL;
|
||
|
||
-- квартал: последние 3 месяца окна
|
||
DECLARE @q_start date = DATEADD(
|
||
month, -2,
|
||
DATEFROMPARTS(YEAR(@lastFullMonth), MONTH(@lastFullMonth), 1)
|
||
);
|
||
DECLARE @tn_q numeric(38,6);
|
||
DECLARE @avg_cap_q numeric(38,6);
|
||
|
||
SELECT
|
||
@tn_q = SUM(t.[ТН]),
|
||
@avg_cap_q = AVG(NULLIF(t.[Остаток учетка руб], 0.0))
|
||
FROM #month_plan t
|
||
WHERE t.MonthStart >= @q_start;
|
||
|
||
IF @avg_cap_q IS NOT NULL AND @avg_cap_q > 0 AND @tn_q IS NOT NULL
|
||
SET @rent_cap_quarter = @tn_q / @avg_cap_q;
|
||
ELSE
|
||
SET @rent_cap_quarter = NULL;
|
||
|
||
---------------------------------------------------------
|
||
-- 11. Рентабельность остатка на будущий год (ROIC вперёд)
|
||
---------------------------------------------------------
|
||
|
||
-- 11.1. Текущий SKU и его продажи / день
|
||
DECLARE @sku_1c_id binary(16);
|
||
SELECT TOP(1) @sku_1c_id = [1c_id] FROM #s;
|
||
|
||
DECLARE @sales_per_day numeric(38,6); -- Продажи шт / день
|
||
DECLARE @sold_year numeric(38,6); -- Продано шт за последние 12 мес
|
||
|
||
-- берём скорость продаж из аналитики за 365 дней (если есть)
|
||
SELECT
|
||
@sales_per_day = a.[Продажи шт / день]
|
||
FROM [mag_pbi].[analytics].[аналитика за 365 дн.] a
|
||
WHERE a.[1c_id] = @sku_1c_id;
|
||
|
||
-- продано шт за наши 12 месяцев (по себестоимости)
|
||
SELECT
|
||
@sold_year = SUM(s.[Количество])
|
||
FROM pbiProd.[СводныйСебестоимость Для PBI] s
|
||
WHERE s.[1c_id] = @sku_1c_id
|
||
AND s.[Статья] = N'Реализация'
|
||
AND s.[Период] >= @firstMonthStart
|
||
AND s.[Период] <= @lastFullMonth;
|
||
|
||
IF (@sales_per_day IS NULL OR @sales_per_day = 0) AND @sold_year IS NOT NULL
|
||
BEGIN
|
||
-- fallback, если ещё не прогоняли sp_create_analytics_365
|
||
SET @sales_per_day = @sold_year / 365.0;
|
||
END;
|
||
|
||
-- 11.2. Маржа на штуку по истории
|
||
DECLARE @tn_per_unit numeric(38,6);
|
||
|
||
SET @tn_per_unit = CASE
|
||
WHEN @sold_year IS NOT NULL AND @sold_year > 0
|
||
THEN @tn_year / @sold_year
|
||
ELSE NULL
|
||
END;
|
||
|
||
-- 11.3. Текущий оплаченный остаток, остаток шт и остаток учетка руб (последний месяц окна)
|
||
DECLARE @q_stock numeric(18,6); -- текущий остаток шт
|
||
DECLARE @paid_stock_today numeric(18,6); -- оплаченный остаток в руб
|
||
DECLARE @stock_rub_today numeric(18,6); -- остаток учетка руб на конец окна
|
||
|
||
SELECT TOP(1)
|
||
@q_stock = [остаток],
|
||
@paid_stock_today = [Оплаченный остаток],
|
||
@stock_rub_today = [Остаток учетка руб]
|
||
FROM #month_plan
|
||
ORDER BY MonthStart DESC; -- последний из 12 месяцев = @lastFullMonth
|
||
|
||
-- 11.4. Сколько успеем продать за будущий год и какая будет ТН
|
||
|
||
DECLARE @q_future numeric(18,6); -- сколько штук реально успеем продать за год
|
||
DECLARE @tn_future numeric(18,6); -- ожидаемая ТН за будущий год
|
||
|
||
IF @sales_per_day IS NOT NULL AND @sales_per_day > 0
|
||
BEGIN
|
||
DECLARE @max_sell_1y numeric(18,6);
|
||
SET @max_sell_1y = @sales_per_day * 365.0;
|
||
|
||
SET @q_future = CASE
|
||
WHEN @q_stock IS NULL THEN 0
|
||
WHEN @q_stock <= @max_sell_1y THEN @q_stock
|
||
ELSE @max_sell_1y
|
||
END;
|
||
END
|
||
ELSE
|
||
BEGIN
|
||
SET @q_future = 0;
|
||
END;
|
||
|
||
IF @tn_per_unit IS NOT NULL
|
||
SET @tn_future = @tn_per_unit * @q_future;
|
||
ELSE
|
||
SET @tn_future = NULL;
|
||
|
||
-- 11.5. Будущие рубли-дни и ROIC вперёд
|
||
|
||
DECLARE @days_to_sell numeric(18,6);
|
||
DECLARE @ruble_days_future numeric(18,6);
|
||
DECLARE @roic_future numeric(38,6);
|
||
|
||
IF @sales_per_day IS NOT NULL AND @sales_per_day > 0 AND @q_stock IS NOT NULL
|
||
SET @days_to_sell = @q_stock / @sales_per_day;
|
||
ELSE
|
||
SET @days_to_sell = NULL;
|
||
|
||
IF @days_to_sell IS NULL OR @paid_stock_today IS NULL OR @paid_stock_today <= 0 OR @tn_future IS NULL
|
||
BEGIN
|
||
SET @roic_future = NULL;
|
||
END
|
||
ELSE
|
||
BEGIN
|
||
IF @days_to_sell >= 365.0
|
||
-- остаток будет лежать весь год
|
||
SET @ruble_days_future = @paid_stock_today * 365.0;
|
||
ELSE
|
||
-- остаток линейно уходит до нуля за days_to_sell
|
||
SET @ruble_days_future = @paid_stock_today * @days_to_sell / 2.0;
|
||
|
||
IF @ruble_days_future > 0
|
||
SET @roic_future = @tn_future * 365.0 / @ruble_days_future * 100.0;
|
||
ELSE
|
||
SET @roic_future = NULL;
|
||
END;
|
||
|
||
---------------------------------------------------------
|
||
-- 11.6. Рентабельность по остатку В РУБЛЯХ на будущий год
|
||
-- (ТН_future / средний остаток учетка руб в горизонте)
|
||
---------------------------------------------------------
|
||
DECLARE @rent_cap_future numeric(38,6);
|
||
|
||
IF @stock_rub_today IS NULL OR @stock_rub_today <= 0
|
||
OR @q_stock IS NULL OR @q_stock <= 0
|
||
OR @q_future IS NULL OR @q_future <= 0
|
||
OR @tn_future IS NULL
|
||
BEGIN
|
||
SET @rent_cap_future = NULL;
|
||
END
|
||
ELSE
|
||
BEGIN
|
||
DECLARE @avg_cap_future numeric(38,6);
|
||
|
||
-- считаем, что капитал уходит линейно пропорционально количеству:
|
||
-- капитал "на продажу" = stock_rub_today * (q_future / q_stock)
|
||
-- средний капитал = stock_rub_today - 0.5 * капитал_на_продажу
|
||
SET @avg_cap_future =
|
||
@stock_rub_today * (1.0 - (@q_future / @q_stock) / 2.0);
|
||
|
||
IF @avg_cap_future > 0
|
||
SET @rent_cap_future = @tn_future / @avg_cap_future;
|
||
ELSE
|
||
SET @rent_cap_future = NULL;
|
||
END;
|
||
|
||
---------------------------------------------------------
|
||
-- 11.7. Таблица будущего по месяцам (#future_plan) — как было
|
||
---------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#future_plan') IS NOT NULL DROP TABLE #future_plan;
|
||
|
||
CREATE TABLE #future_plan (
|
||
PeriodStart date,
|
||
PeriodEnd date,
|
||
DaysInPeriod int,
|
||
QtyStart numeric(18,6),
|
||
Sold numeric(18,6),
|
||
QtyEnd numeric(18,6),
|
||
PaidCapitalAvg numeric(18,6),
|
||
RubleDays numeric(18,6),
|
||
TN_period numeric(18,6),
|
||
ROIC_period numeric(38,6)
|
||
);
|
||
|
||
DECLARE
|
||
@f_start date,
|
||
@f_end date,
|
||
@days int,
|
||
@rem_qty numeric(18,6),
|
||
@qty_start numeric(18,6),
|
||
@qty_end numeric(18,6),
|
||
@sold numeric(18,6),
|
||
@capital_start numeric(18,6),
|
||
@capital_end numeric(18,6),
|
||
@capital_avg numeric(18,6),
|
||
@tn_period numeric(18,6),
|
||
@ruble_days_m numeric(18,6),
|
||
@roic_m numeric(38,6),
|
||
@cum_days int;
|
||
|
||
SET @rem_qty = ISNULL(@q_stock, 0);
|
||
SET @f_start = @today;
|
||
SET @cum_days = 0;
|
||
|
||
WHILE @cum_days < 365 AND @rem_qty > 0 AND @sales_per_day IS NOT NULL AND @sales_per_day > 0
|
||
BEGIN
|
||
-- конец периода = конец месяца, в котором f_start
|
||
SET @f_end = EOMONTH(@f_start);
|
||
SET @days = DATEDIFF(day, @f_start, @f_end) + 1;
|
||
|
||
-- не выходим за 365 дней горизонта
|
||
IF @cum_days + @days > 365
|
||
BEGIN
|
||
SET @days = 365 - @cum_days;
|
||
SET @f_end = DATEADD(day, @days - 1, @f_start);
|
||
END;
|
||
|
||
SET @qty_start = @rem_qty;
|
||
|
||
DECLARE @can_sell numeric(18,6);
|
||
SET @can_sell = @sales_per_day * @days;
|
||
|
||
SET @sold = CASE
|
||
WHEN @rem_qty <= @can_sell THEN @rem_qty
|
||
ELSE @can_sell
|
||
END;
|
||
|
||
SET @qty_end = @qty_start - @sold;
|
||
|
||
IF @q_stock IS NOT NULL AND @q_stock > 0 AND @paid_stock_today IS NOT NULL
|
||
BEGIN
|
||
SET @capital_start = @paid_stock_today * (@qty_start / @q_stock);
|
||
SET @capital_end = @paid_stock_today * (@qty_end / @q_stock);
|
||
END
|
||
ELSE
|
||
BEGIN
|
||
SET @capital_start = 0;
|
||
SET @capital_end = 0;
|
||
END;
|
||
|
||
SET @capital_avg = (@capital_start + @capital_end) / 2.0;
|
||
|
||
IF @tn_per_unit IS NOT NULL
|
||
SET @tn_period = @tn_per_unit * @sold;
|
||
ELSE
|
||
SET @tn_period = NULL;
|
||
|
||
SET @ruble_days_m = @capital_avg * @days;
|
||
|
||
IF @ruble_days_m > 0 AND @tn_period IS NOT NULL
|
||
SET @roic_m = @tn_period * 365.0 / @ruble_days_m * 100.0;
|
||
ELSE
|
||
SET @roic_m = NULL;
|
||
|
||
INSERT INTO #future_plan (
|
||
PeriodStart, PeriodEnd, DaysInPeriod,
|
||
QtyStart, Sold, QtyEnd,
|
||
PaidCapitalAvg, RubleDays, TN_period, ROIC_period
|
||
)
|
||
VALUES (
|
||
@f_start, @f_end, @days,
|
||
@qty_start, @sold, @qty_end,
|
||
@capital_avg, @ruble_days_m, @tn_period, @roic_m
|
||
);
|
||
|
||
SET @rem_qty = @qty_end;
|
||
SET @cum_days = @cum_days + @days;
|
||
SET @f_start = DATEADD(day, 1, @f_end);
|
||
|
||
IF @cum_days >= 365 BREAK;
|
||
END;
|
||
|
||
---------------------------------------------------------
|
||
-- 12. Итог: прошлое и будущее + рентабельность по остатку (руб)
|
||
---------------------------------------------------------
|
||
SELECT
|
||
@roic_past AS [ROIC за год назад],
|
||
@roic_future AS [ROIC на будущий год],
|
||
@rent_cap_year AS [Рентабельность по остатку (руб) / год назад],
|
||
@rent_cap_quarter AS [Рентабельность по остатку (руб) / квартал назад],
|
||
@rent_cap_future AS [Рентабельность по остатку (руб) / будущий год];
|
||
|
||
-- Таблица будущего по месяцам / периодам (как было)
|
||
SELECT * FROM #future_plan ORDER BY PeriodStart;
|
||
|
||
END
|
||
GO
|
||
|
||
/****** Object: StoredProcedure [analytics].[sp_run_deficit_all_skus] Script Date: 2026-02-22 12:07:01 ******/
|
||
SET ANSI_NULLS ON
|
||
GO
|
||
SET QUOTED_IDENTIFIER ON
|
||
GO
|
||
CREATE PROCEDURE [analytics].[sp_run_deficit_all_skus]
|
||
@scenario_id INT
|
||
as BEGIN
|
||
DELETE FROM [analytics].[deficit_proposal] where scenario_id = @scenario_id
|
||
|
||
EXEC [analytics].[sp_build_deficit_proposal]
|
||
@scenario_id = @scenario_id,
|
||
@group_path = N'', -- пусто = все группы
|
||
@lead_time_m = 4,
|
||
@cover_months = 6,
|
||
@from_month = '2025-10-01',
|
||
@to_month_excl = '2028-01-01',
|
||
@debug = 0; -- чтобы не заспамить Messages
|
||
END
|
||
GO
|
||
|
||
/****** Object: StoredProcedure [analytics].[sp_загрузка_прогноза_закупки] Script Date: 2026-02-22 12:07:01 ******/
|
||
SET ANSI_NULLS ON
|
||
GO
|
||
SET QUOTED_IDENTIFIER ON
|
||
GO
|
||
/****** Script for SelectTopNRows command from SSMS ******/
|
||
|
||
CREATE PROCEDURE [analytics].[sp_загрузка_прогноза_закупки] AS BEGIN
|
||
|
||
SET NOCOUNT ON;
|
||
|
||
IF OBJECT_ID('tempdb..#plan2026') IS NOT NULL
|
||
DROP TABLE #plan2026;
|
||
|
||
CREATE TABLE #plan2026 (
|
||
code nvarchar(50) NOT NULL,
|
||
opt decimal(18,2) NULL,
|
||
mp decimal(18,2) NULL,
|
||
updated_by nvarchar(100) NULL
|
||
);
|
||
|
||
-- уникальный индекс по коду, дубли игнорируем
|
||
CREATE UNIQUE INDEX UX_plan2026_code
|
||
ON #plan2026(code)
|
||
WITH (IGNORE_DUP_KEY = ON);
|
||
|
||
BULK INSERT #plan2026
|
||
FROM '\\192.168.35.3\admin3\Обмен\powerbi\plan2026.csv'
|
||
WITH (
|
||
FIRSTROW = 2, -- пропускаем заголовок
|
||
FIELDTERMINATOR = ';',
|
||
ROWTERMINATOR = '\n',
|
||
CODEPAGE = '1251', -- или 1251, если файл не в UTF-8
|
||
TABLOCK
|
||
);
|
||
|
||
-- Проверка
|
||
SELECT COUNT(*) AS total_rows,
|
||
COUNT(DISTINCT code) AS distinct_codes
|
||
FROM #plan2026;
|
||
|
||
-- Проверка
|
||
SELECT TOP (20) *
|
||
FROM #plan2026
|
||
|
||
|
||
SELECT
|
||
month
|
||
, avg([koef])
|
||
FROM [mag_pbi].[analytics].[seasonality_groups_summ_1]
|
||
GROUP by month
|
||
order by month
|
||
|
||
|
||
|
||
|
||
------------------------------------------------------------
|
||
-- 1. Средняя сезонность по месяцам → #Seasonality
|
||
------------------------------------------------------------
|
||
IF OBJECT_ID('tempdb..#Seasonality') IS NOT NULL
|
||
DROP TABLE #Seasonality;
|
||
|
||
SELECT
|
||
[month]
|
||
, koef = AVG([koef])
|
||
INTO #Seasonality
|
||
FROM [mag_pbi].[analytics].[seasonality_groups_summ_1]
|
||
GROUP BY [month];
|
||
|
||
------------------------------------------------------------
|
||
-- 2. Почистить сценарий 8 (если нужно пересчитать)
|
||
------------------------------------------------------------
|
||
DELETE FROM [mag_pbi].[analytics].[forecast]
|
||
WHERE scenario_id = 8;
|
||
|
||
------------------------------------------------------------
|
||
-- 3. Записать прогноз в forecast для scenario_id = 8
|
||
-- value = opt_месяц + mp_месяц
|
||
------------------------------------------------------------
|
||
INSERT INTO [mag_pbi].[analytics].[forecast] (
|
||
scenario_id
|
||
, [1c_id]
|
||
, [code]
|
||
, [month]
|
||
, [value]
|
||
, [updated_at]
|
||
, [updated_by]
|
||
, [opt]
|
||
, [mp]
|
||
)
|
||
SELECT
|
||
8 AS scenario_id
|
||
, n.[1c_id]
|
||
, p.[code]
|
||
, DATEFROMPARTS(2026, s.[month], 1) AS [month]
|
||
, CAST( (p.opt * s.koef) + (p.mp * s.koef) AS decimal(18,3)) AS [value]
|
||
, GETDATE() AS updated_at
|
||
, p.[updated_by] AS updated_by
|
||
, CAST(p.opt * s.koef AS decimal(18,3)) AS [opt]
|
||
, CAST(p.mp * s.koef AS decimal(18,3)) AS [mp]
|
||
FROM #plan2026 AS p
|
||
INNER JOIN [mag_pbi].[pbi].[nomenclature] AS n
|
||
ON n.[code] = p.[code]
|
||
CROSS JOIN #Seasonality AS s;
|
||
-- всего строк и разных кодов
|
||
|
||
|
||
--EXEC [analytics].[sp_build_deficit_proposal] @scenario_id = 8
|
||
/*
|
||
/****** Script for SelectTopNRows command from SSMS ******/
|
||
INSERT INTO [mag_pbi].[analytics].[forecast] (
|
||
scenario_id
|
||
, [1c_id]
|
||
, [code]
|
||
, [month]
|
||
, [value]
|
||
, [updated_at]
|
||
, [updated_by]
|
||
, [opt]
|
||
, [mp]
|
||
)
|
||
SELECT
|
||
8 AS scenario_id
|
||
,[1c_id]
|
||
,[code]
|
||
,[month]
|
||
,[value]
|
||
,[updated_at]
|
||
,[updated_by]
|
||
,[opt]
|
||
,[mp]
|
||
FROM [mag_pbi].[analytics].[forecast]
|
||
WHERE scenario_id = 5 AND code not in (SELECT code FROM [mag_pbi].[analytics].[forecast] WHERE scenario_id = 8 )
|
||
*/
|
||
|
||
--EXEC [analytics].[sp_fill_deficit_money_request] @scenario_id = 8
|
||
|
||
END
|
||
GO
|
||
|
||
/****** Object: StoredProcedure [analytics].[usp_CreateForecastBasesKs] Script Date: 2026-02-22 12:07:01 ******/
|
||
SET ANSI_NULLS ON
|
||
GO
|
||
SET QUOTED_IDENTIFIER ON
|
||
GO
|
||
CREATE PROCEDURE analytics.usp_CreateForecastBasesKs
|
||
AS
|
||
BEGIN
|
||
SET NOCOUNT ON;
|
||
|
||
-- Удаляем вьюху, если существует
|
||
IF OBJECT_ID('analytics.ForecastBasesKs', 'V') IS NOT NULL
|
||
DROP VIEW analytics.ForecastBasesKs;
|
||
|
||
-- Создаём заново
|
||
EXEC('
|
||
CREATE VIEW [analytics].[ForecastBasesKs] AS
|
||
WITH MaxDate AS (
|
||
SELECT MAX(Период) AS max_date
|
||
FROM [pbi].[Себестоимость]
|
||
WHERE Статья = ''реализация''
|
||
),
|
||
LastFullMonth AS (
|
||
SELECT
|
||
YEAR(DATEADD(MONTH, -1, max_date)) AS last_year,
|
||
MONTH(DATEADD(MONTH, -1, max_date)) AS last_month
|
||
FROM MaxDate
|
||
),
|
||
-- 1. Продажи по SKU × месяц
|
||
Sales AS (
|
||
SELECT
|
||
artic_id,
|
||
YEAR(Период) AS Год,
|
||
MONTH(Период) AS Месяц,
|
||
SUM(Количество) AS total_sales
|
||
FROM [pbi].[Себестоимость]
|
||
WHERE Статья = ''реализация''
|
||
GROUP BY artic_id, YEAR(Период), MONTH(Период)
|
||
),
|
||
-- 2. Дни в продаже
|
||
Stock AS (
|
||
SELECT
|
||
artic_id,
|
||
YEAR(dt) AS Год,
|
||
MONTH(dt) AS Месяц,
|
||
SUM(CASE WHEN ostatok = 1 THEN 1 ELSE 0 END) AS days_available
|
||
FROM [pbi].[w_ostatok_da_net]
|
||
GROUP BY artic_id, YEAR(dt), MONTH(dt)
|
||
),
|
||
-- 3. База
|
||
Base AS (
|
||
SELECT
|
||
s.artic_id,
|
||
n.code,
|
||
n.description,
|
||
s.Год,
|
||
s.Месяц,
|
||
s.total_sales,
|
||
st.days_available,
|
||
sg.seasonal_koef,
|
||
(s.total_sales / NULLIF(sg.seasonal_koef,0)) AS Normalized_sales,
|
||
CASE WHEN st.days_available > 19 THEN 1 ELSE 0 END AS valid_month,
|
||
CASE WHEN st.days_available > 19
|
||
THEN (s.total_sales / NULLIF(sg.seasonal_koef,0))
|
||
ELSE NULL END AS normalized_valid_sales
|
||
FROM Sales s
|
||
LEFT JOIN Stock st
|
||
ON s.artic_id = st.artic_id
|
||
AND s.Год = st.Год
|
||
AND s.Месяц = st.Месяц
|
||
JOIN [pbi].[nomenclature] n
|
||
ON s.artic_id = n.artic_id
|
||
JOIN [analytics].[seasonality_groups] sg
|
||
ON n.[1c_group] = sg.group_1c_id
|
||
AND s.Месяц = sg.month
|
||
),
|
||
Windowed AS (
|
||
SELECT
|
||
b.*,
|
||
ROW_NUMBER() OVER (PARTITION BY b.artic_id ORDER BY (b.Год*100 + b.Месяц) DESC) AS rn_desc
|
||
FROM Base b
|
||
),
|
||
Aggregates AS (
|
||
SELECT
|
||
w.artic_id,
|
||
AVG(CASE WHEN rn_desc <= 12 THEN normalized_valid_sales END) AS Base_12M,
|
||
CASE
|
||
WHEN MIN(CASE WHEN rn_desc <= 3 THEN valid_month END) = 1
|
||
THEN AVG(CASE WHEN rn_desc <= 3 THEN normalized_valid_sales END)
|
||
ELSE NULL
|
||
END AS Base_3M
|
||
FROM Windowed w
|
||
CROSS JOIN LastFullMonth lm
|
||
WHERE (w.Год*100 + w.Месяц) <= (lm.last_year*100 + lm.last_month)
|
||
GROUP BY w.artic_id
|
||
),
|
||
TrendData AS (
|
||
SELECT
|
||
w.artic_id,
|
||
ROW_NUMBER() OVER (PARTITION BY w.artic_id ORDER BY (w.Год*100 + w.Месяц)) - 1 AS t,
|
||
LOG(w.normalized_valid_sales) AS ln_sales
|
||
FROM Windowed w
|
||
CROSS JOIN LastFullMonth lm
|
||
WHERE (w.Год*100 + w.Месяц) > (lm.last_year*100 + lm.last_month - 200)
|
||
AND (w.Год*100 + w.Месяц) <= (lm.last_year*100 + lm.last_month)
|
||
AND w.normalized_valid_sales > 0
|
||
),
|
||
TrendAgg AS (
|
||
SELECT
|
||
artic_id,
|
||
COUNT(*) AS n_obs,
|
||
EXP( (AVG(t*ln_sales) - AVG(t)*AVG(ln_sales)) / NULLIF(AVG(t*t) - AVG(t)*AVG(t),0) ) AS g_raw
|
||
FROM TrendData
|
||
GROUP BY artic_id
|
||
HAVING COUNT(*) >= 6
|
||
),
|
||
TrendFinal AS (
|
||
SELECT
|
||
artic_id,
|
||
POWER(
|
||
CASE
|
||
WHEN POWER(g_raw, 12) < 0.7 THEN 0.7
|
||
WHEN POWER(g_raw, 12) > 1.5 THEN 1.5
|
||
ELSE POWER(g_raw, 12)
|
||
END,
|
||
1.0/12
|
||
) AS Ktrend
|
||
FROM TrendAgg
|
||
),
|
||
YoYpairs AS (
|
||
SELECT
|
||
w.artic_id,
|
||
(w.Год*100 + w.Месяц) AS ym,
|
||
w.normalized_valid_sales AS v2,
|
||
LAG(w.normalized_valid_sales, 12) OVER (
|
||
PARTITION BY w.artic_id ORDER BY (w.Год*100 + w.Месяц)
|
||
) AS v1
|
||
FROM Windowed w
|
||
CROSS JOIN LastFullMonth lm
|
||
WHERE (w.Год*100 + w.Месяц) <= (lm.last_year*100 + lm.last_month)
|
||
),
|
||
YoYratios AS (
|
||
SELECT artic_id, (v2 / v1) AS ratio
|
||
FROM YoYpairs
|
||
WHERE v1 > 0 AND v2 > 0
|
||
),
|
||
YoYFinal AS (
|
||
SELECT DISTINCT
|
||
artic_id,
|
||
CASE
|
||
WHEN PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY ratio)
|
||
OVER (PARTITION BY artic_id) < 0.7 THEN 0.7
|
||
WHEN PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY ratio)
|
||
OVER (PARTITION BY artic_id) > 1.5 THEN 1.5
|
||
ELSE PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY ratio)
|
||
OVER (PARTITION BY artic_id)
|
||
END AS Kgrowth_YoY,
|
||
COUNT(ratio) OVER (PARTITION BY artic_id) AS cnt_pairs
|
||
FROM YoYratios
|
||
),
|
||
YoYFiltered AS (
|
||
SELECT artic_id, Kgrowth_YoY
|
||
FROM YoYFinal
|
||
WHERE cnt_pairs >= 3
|
||
)
|
||
SELECT
|
||
n.artic_id,
|
||
n.code,
|
||
n.description,
|
||
a.Base_12M,
|
||
a.Base_3M,
|
||
CASE
|
||
WHEN a.Base_3M IS NOT NULL THEN a.Base_3M
|
||
ELSE a.Base_12M
|
||
END AS Base_Selected,
|
||
tf.Ktrend,
|
||
yf.Kgrowth_YoY
|
||
FROM pbi.nomenclature n
|
||
LEFT JOIN Aggregates a ON n.artic_id = a.artic_id
|
||
LEFT JOIN TrendFinal tf ON n.artic_id = tf.artic_id
|
||
LEFT JOIN YoYFiltered yf ON n.artic_id = yf.artic_id;
|
||
');
|
||
|
||
END;
|
||
GO
|
||
|
||
/****** Object: StoredProcedure [analytics].[usp_InsertForecasts] Script Date: 2026-02-22 12:07:01 ******/
|
||
SET ANSI_NULLS ON
|
||
GO
|
||
SET QUOTED_IDENTIFIER ON
|
||
GO
|
||
CREATE PROCEDURE analytics.usp_InsertForecasts
|
||
AS
|
||
BEGIN
|
||
SET NOCOUNT ON;
|
||
|
||
-- Удаляем старые прогнозы
|
||
DELETE FROM analytics.forecast
|
||
WHERE scenario_id IN (5, 6);
|
||
|
||
-- Чистим временные таблицы
|
||
IF OBJECT_ID('tempdb..#Params') IS NOT NULL DROP TABLE #Params;
|
||
IF OBJECT_ID('tempdb..#Calendar') IS NOT NULL DROP TABLE #Calendar;
|
||
|
||
-- #Params
|
||
SELECT DATEFROMPARTS(
|
||
YEAR(DATEADD(MONTH, -1, MAX(Период))),
|
||
MONTH(DATEADD(MONTH, -1, MAX(Период))),
|
||
1
|
||
) AS LastMonth
|
||
INTO #Params
|
||
FROM pbi.Себестоимость
|
||
WHERE Статья = 'реализация';
|
||
|
||
-- #Calendar
|
||
SELECT DATEFROMPARTS(YEAR(d), MONTH(d), 1) AS MonthStart
|
||
INTO #Calendar
|
||
FROM (
|
||
SELECT TOP (1000)
|
||
DATEADD(MONTH, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
|
||
(SELECT LastMonth FROM #Params)) AS d
|
||
FROM master..spt_values
|
||
) x
|
||
WHERE d <= '2026-12-01';
|
||
|
||
-- Вставка Trend (5)
|
||
INSERT INTO analytics.forecast (scenario_id, [1c_id], code, [month], value, updated_at, updated_by)
|
||
SELECT
|
||
5,
|
||
n.[1c_id],
|
||
f.code,
|
||
c.MonthStart,
|
||
CAST(
|
||
ROUND(
|
||
f.Base_Selected * sg.seasonal_koef *
|
||
POWER(f.Ktrend, DATEDIFF(MONTH, p.LastMonth, c.MonthStart)),
|
||
0
|
||
) AS numeric(18,0)
|
||
),
|
||
GETDATE(),
|
||
SUSER_SNAME()
|
||
FROM analytics.ForecastBasesKs f
|
||
JOIN pbi.nomenclature n ON f.artic_id = n.artic_id
|
||
CROSS JOIN #Params p
|
||
JOIN #Calendar c ON c.MonthStart > p.LastMonth
|
||
JOIN analytics.seasonality_groups sg
|
||
ON n.[1c_group] = sg.group_1c_id
|
||
AND sg.[month] = MONTH(c.MonthStart)
|
||
WHERE f.Base_Selected IS NOT NULL
|
||
AND f.Ktrend IS NOT NULL;
|
||
|
||
-- Вставка YoY (6)
|
||
INSERT INTO analytics.forecast (scenario_id, [1c_id], code, [month], value, updated_at, updated_by)
|
||
SELECT
|
||
6,
|
||
n.[1c_id],
|
||
f.code,
|
||
c.MonthStart,
|
||
CAST(
|
||
ROUND(
|
||
f.Base_Selected * sg.seasonal_koef * f.Kgrowth_YoY,
|
||
0
|
||
) AS numeric(18,0)
|
||
),
|
||
GETDATE(),
|
||
SUSER_SNAME()
|
||
FROM analytics.ForecastBasesKs f
|
||
JOIN pbi.nomenclature n ON f.artic_id = n.artic_id
|
||
CROSS JOIN #Params p
|
||
JOIN #Calendar c ON c.MonthStart > p.LastMonth
|
||
JOIN analytics.seasonality_groups sg
|
||
ON n.[1c_group] = sg.group_1c_id
|
||
AND sg.[month] = MONTH(c.MonthStart)
|
||
WHERE f.Base_Selected IS NOT NULL
|
||
AND f.Kgrowth_YoY IS NOT NULL;
|
||
|
||
END;
|
||
GO
|
||
|
||
/****** Object: StoredProcedure [analytics].[Подготовка таблицы продаж к прогнозу] Script Date: 2026-02-22 12:07:01 ******/
|
||
SET ANSI_NULLS ON
|
||
GO
|
||
SET QUOTED_IDENTIFIER ON
|
||
GO
|
||
CREATE PROCEDURE analytics.[Подготовка таблицы продаж к прогнозу] as
|
||
BEGIN
|
||
/* ========= ПАРАМЕТРЫ ========= */
|
||
DECLARE @FromDate date = '2023-01-01';
|
||
DECLARE @ToDate date = CAST(GETDATE() AS date);
|
||
|
||
/* ========= ВСПОМОГАТЕЛЬНЫЕ: НЕДЕЛЯ С ПОНЕДЕЛЬНИКА ========= */
|
||
IF OBJECT_ID('tempdb..#dates') IS NOT NULL DROP TABLE #dates;
|
||
WITH d AS (
|
||
SELECT @FromDate AS d
|
||
UNION ALL SELECT DATEADD(day,1,d) FROM d WHERE d < @ToDate
|
||
)
|
||
SELECT d AS [date],
|
||
DATEADD(day, - (DATEPART(weekday,d) + @@DATEFIRST + 5) % 7, d) AS week_start
|
||
INTO #dates
|
||
FROM d
|
||
OPTION (MAXRECURSION 0);
|
||
|
||
CREATE INDEX IX_dates_week ON #dates(week_start);
|
||
|
||
/* ========= 1) ПРОДАЖИ (только положительные) ========= */
|
||
IF OBJECT_ID('tempdb..#sales_raw') IS NOT NULL DROP TABLE #sales_raw;
|
||
|
||
SELECT
|
||
CAST(s.[Период] AS date) AS [date],
|
||
s.[1c_id] AS sku_id,
|
||
CAST(s.[КоличествоУпаковок] AS decimal(18,4)) AS qty
|
||
INTO #sales_raw
|
||
FROM [pbiProd].[СводныйСебестоимость Для PBI] s
|
||
WHERE
|
||
s.[Статья] = N'Реализация'
|
||
AND s.[Вид операции] = N'Расход'
|
||
AND s.[КоличествоУпаковок] > 0
|
||
AND s.[Период] >= @FromDate
|
||
AND s.[Период] <= @ToDate;
|
||
|
||
CREATE INDEX IX_sales_raw ON #sales_raw(sku_id, [date]);
|
||
|
||
/* ========= 2) ФИЛЬТРУЕМ ПРОДАЖИ ПО НАЛИЧИЮ (ostatok=1 из pbi.w_ostatok_da_net) ========= */
|
||
IF OBJECT_ID('tempdb..#sales_clean_daily') IS NOT NULL DROP TABLE #sales_clean_daily;
|
||
|
||
SELECT
|
||
sr.sku_id,
|
||
sr.[date],
|
||
sr.qty
|
||
INTO #sales_clean_daily
|
||
FROM #sales_raw sr
|
||
JOIN [pbi].[w_ostatok_da_net] a
|
||
ON a.[_IDRREF] = sr.sku_id
|
||
AND a.dt = sr.[date]
|
||
AND a.ostatok = 1;
|
||
|
||
CREATE INDEX IX_sales_clean_daily ON #sales_clean_daily(sku_id, [date]);
|
||
|
||
/* ========= 3) АГРЕГАЦИЯ В НЕДЕЛИ ========= */
|
||
/* 3.1 Продажи по неделям */
|
||
IF OBJECT_ID('tempdb..#sales_weekly') IS NOT NULL DROP TABLE #sales_weekly;
|
||
|
||
SELECT
|
||
scd.sku_id,
|
||
d.week_start,
|
||
SUM(scd.qty) AS qty_week
|
||
INTO #sales_weekly
|
||
FROM #sales_clean_daily scd
|
||
JOIN #dates d
|
||
ON d.[date] = scd.[date]
|
||
GROUP BY scd.sku_id, d.week_start;
|
||
|
||
CREATE INDEX IX_sales_weekly ON #sales_weekly(sku_id, week_start);
|
||
|
||
/* 3.2 Доля доступности по неделям (из pbi.w_ostatok_da_net) */
|
||
IF OBJECT_ID('tempdb..#avail_weekly') IS NOT NULL DROP TABLE #avail_weekly;
|
||
|
||
SELECT
|
||
a.[_IDRREF] AS sku_id,
|
||
d.week_start,
|
||
SUM(a.ostatok) AS days_available,
|
||
COUNT(*) AS days_total,
|
||
CAST(SUM(a.ostatok) * 1.0 / NULLIF(COUNT(*),0) AS decimal(6,4)) AS availability_rate
|
||
INTO #avail_weekly
|
||
FROM [pbi].[w_ostatok_da_net] a
|
||
JOIN #dates d
|
||
ON d.[date] = a.dt
|
||
WHERE a.dt BETWEEN @FromDate AND @ToDate
|
||
GROUP BY a.[_IDRREF], d.week_start;
|
||
|
||
CREATE INDEX IX_avail_weekly ON #avail_weekly(sku_id, week_start);
|
||
|
||
/* ========= 4) СКЛЕЙКА: оставляем недели без продаж, если товар был доступен ========= */
|
||
IF OBJECT_ID('tempdb..#weekly_base') IS NOT NULL DROP TABLE #weekly_base;
|
||
|
||
SELECT
|
||
COALESCE(sw.sku_id, aw.sku_id) AS sku_id,
|
||
COALESCE(sw.week_start, aw.week_start) AS week_start,
|
||
COALESCE(sw.qty_week, 0) AS qty_week,
|
||
COALESCE(aw.availability_rate, 0) AS availability_rate
|
||
INTO #weekly_base
|
||
FROM #sales_weekly sw
|
||
FULL JOIN #avail_weekly aw
|
||
ON aw.sku_id = sw.sku_id
|
||
AND aw.week_start = sw.week_start;
|
||
|
||
CREATE INDEX IX_weekly_base ON #weekly_base(sku_id, week_start);
|
||
|
||
/* ========= 5) МЕТАДАННЫЕ SKU (cat L1, minAvailableQty) ========= */
|
||
IF OBJECT_ID('tempdb..#sku_meta') IS NOT NULL DROP TABLE #sku_meta;
|
||
|
||
SELECT
|
||
n.[1c_id] AS sku_id,
|
||
n.minAvailableQty,
|
||
CAST(CASE
|
||
WHEN CHARINDEX(N' | ', g.path) > 0
|
||
THEN LEFT(g.path, CHARINDEX(N' | ', g.path) - 1)
|
||
ELSE g.path
|
||
END AS nvarchar(200)) AS category_l1
|
||
INTO #sku_meta
|
||
FROM [mag_pbi].[pbi].[nomenclature] n
|
||
LEFT JOIN [mag_pbi].[pbi].[groups] g
|
||
ON g.group_id = n.group_id;
|
||
|
||
CREATE INDEX IX_sku_meta ON #sku_meta(sku_id);
|
||
|
||
/* ========= 6) ВИТРИНА С ЛАГАМИ И ФИЧАМИ (analytics) ========= */
|
||
IF OBJECT_ID('analytics.sales_weekly_features', 'U') IS NOT NULL
|
||
DROP TABLE analytics.sales_weekly_features;
|
||
|
||
CREATE TABLE analytics.sales_weekly_features (
|
||
sku_id varbinary(16) NOT NULL,
|
||
week_start date NOT NULL,
|
||
qty_week decimal(18,4) NOT NULL,
|
||
availability_rate decimal(6,4) NOT NULL,
|
||
-- календарные
|
||
year_num int NOT NULL,
|
||
month_num tinyint NOT NULL,
|
||
iso_week tinyint NOT NULL,
|
||
quarter_num tinyint NOT NULL,
|
||
dow_monday1 tinyint NOT NULL,
|
||
-- справочники
|
||
category_l1 nvarchar(200) NULL,
|
||
minAvailableQty decimal(18,4) NULL,
|
||
-- лаги
|
||
lag_w1 decimal(18,4) NULL,
|
||
lag_w2 decimal(18,4) NULL,
|
||
lag_w4 decimal(18,4) NULL,
|
||
lag_w12 decimal(18,4) NULL,
|
||
lag_w26 decimal(18,4) NULL,
|
||
lag_w52 decimal(18,4) NULL,
|
||
-- скользящие средние
|
||
ma4 decimal(18,4) NULL,
|
||
ma12 decimal(18,4) NULL,
|
||
created_at datetime2 NOT NULL DEFAULT sysutcdatetime(),
|
||
CONSTRAINT PK_sales_weekly_features PRIMARY KEY (sku_id, week_start)
|
||
);
|
||
|
||
WITH base AS (
|
||
SELECT wb.sku_id,
|
||
wb.week_start,
|
||
wb.qty_week,
|
||
wb.availability_rate,
|
||
YEAR(wb.week_start) AS year_num,
|
||
MONTH(wb.week_start) AS month_num,
|
||
DATEPART(ISO_WEEK, wb.week_start) AS iso_week,
|
||
DATEPART(QUARTER, wb.week_start) AS quarter_num,
|
||
1 AS dow_monday1
|
||
FROM #weekly_base wb
|
||
),
|
||
enriched AS (
|
||
SELECT b.*,
|
||
m.category_l1,
|
||
m.minAvailableQty
|
||
FROM base b
|
||
LEFT JOIN #sku_meta m
|
||
ON m.sku_id = b.sku_id
|
||
),
|
||
lags AS (
|
||
SELECT
|
||
e.*,
|
||
LAG(e.qty_week, 1) OVER (PARTITION BY e.sku_id ORDER BY e.week_start) AS lag_w1,
|
||
LAG(e.qty_week, 2) OVER (PARTITION BY e.sku_id ORDER BY e.week_start) AS lag_w2,
|
||
LAG(e.qty_week, 4) OVER (PARTITION BY e.sku_id ORDER BY e.week_start) AS lag_w4,
|
||
LAG(e.qty_week, 12) OVER (PARTITION BY e.sku_id ORDER BY e.week_start) AS lag_w12,
|
||
LAG(e.qty_week, 26) OVER (PARTITION BY e.sku_id ORDER BY e.week_start) AS lag_w26,
|
||
LAG(e.qty_week, 52) OVER (PARTITION BY e.sku_id ORDER BY e.week_start) AS lag_w52,
|
||
CAST(AVG(e.qty_week) OVER (
|
||
PARTITION BY e.sku_id
|
||
ORDER BY e.week_start
|
||
ROWS BETWEEN 4 PRECEDING AND 1 PRECEDING
|
||
) AS decimal(18,4)) AS ma4,
|
||
CAST(AVG(e.qty_week) OVER (
|
||
PARTITION BY e.sku_id
|
||
ORDER BY e.week_start
|
||
ROWS BETWEEN 12 PRECEDING AND 1 PRECEDING
|
||
) AS decimal(18,4)) AS ma12
|
||
FROM enriched e
|
||
)
|
||
INSERT INTO analytics.sales_weekly_features (
|
||
sku_id, week_start, qty_week, availability_rate,
|
||
year_num, month_num, iso_week, quarter_num, dow_monday1,
|
||
category_l1, minAvailableQty,
|
||
lag_w1, lag_w2, lag_w4, lag_w12, lag_w26, lag_w52,
|
||
ma4, ma12
|
||
)
|
||
SELECT
|
||
sku_id, week_start, qty_week, availability_rate,
|
||
year_num, month_num, iso_week, quarter_num, dow_monday1,
|
||
category_l1, minAvailableQty,
|
||
lag_w1, lag_w2, lag_w4, lag_w12, lag_w26, lag_w52,
|
||
ma4, ma12
|
||
FROM lags;
|
||
|
||
CREATE INDEX IX_sales_weekly_features_sku ON analytics.sales_weekly_features(sku_id, week_start);
|
||
CREATE INDEX IX_sales_weekly_features_cat ON analytics.sales_weekly_features(category_l1, week_start);
|
||
END
|
||
GO
|