У меня есть таблица продуктов и их количество продаж в месяцах.
Product Month Qty A 2018-01-01 5 A 2018-02-01 3 A 2018-05-01 5 B 2018-08-01 10 B 2018-10-01 12 ...
Я хотел бы сначала заполнить пробел в данных между минимальной и максимальной датами каждого продукта, как показано ниже:
Product Month Qty A 2018-01-01 5 A 2018-02-01 3 A 2018-03-01 0 A 2018-04-01 0 A 2018-05-01 5 B 2018-08-01 10 B 2018-09-01 0 B 2018-10-01 12 ...
Затем мне нужно было бы выполнить накопление количества продаж каждого продукта по месяцам.
Product Month total_Qty A 2018-01-01 5 A 2018-02-01 8 A 2018-03-01 8 A 2018-04-01 8 A 2018-05-01 13 B 2018-08-01 10 B 2018-09-01 10 B 2018-10-01 22 ...
Я возился с пунктом «перекрестное соединение», однако, похоже, он дает некоторые неожиданные результаты для меня. Может ли кто-нибудь подсказать, как я могу добиться этого в SQL?
Заранее большое спасибо.
Привет, я думаю, вам нужно увидеть CTE (предложение WITH) AND OVER PARTITION BY. Какая у вас версия SQL Server?
@john McTighe Спасибо за комментарий. Я мог бы добавить таблицу «календарь». Однако проблема в том, что у меня разные минимальные и максимальные даты для разных продуктов. Я не хочу, чтобы все они использовали один и тот же «календарь».
@ got2nosth привет, вы должны работать с определенной таблицей календаря продукта, такой как Product, DateMonth
@pascal sanchez Спасибо за мысли. Проблема с PARTITION BY заключается в том, что я пропущу сумму за несуществующие месяцы, например 2018-03-01 для моего продукта A. Я использую SQL Server 2017.
@ got2nosth взгляните на ответ, у меня есть ошибка обновления :) я думаю, что у вас есть обходной путь и вы можете построить запрос с помощью этого подхода (вы также можете создать таблицу продуктов временного календаря с помощью динамического SQL «EXECUTE»)
@ got2nosth «Однако проблема в том, что у меня разные минимальные и максимальные даты для разных продуктов». Эта проблема должна быть объяснена как часть вопроса ... в любом случае это не меняет того факта, что вам нужна таблица календаря (не обязательно физическая таблица), что она может изменить, так это способ присоединиться к ней.


Привет, я думаю, что этот пример может помочь вам и выполнить то, что вы исключили:
CREATE TABLE #MyTable
(Product varchar(10),
ProductMonth DATETIME,
Qty int
);
GO
CREATE TABLE #MyTableTempDate
(
FullMonth DATETIME
);
GO
INSERT INTO #MyTable
SELECT 'A', '2019-01-01', 214
UNION
SELECT 'A', '2019-02-01', 4
UNION
SELECT 'A', '2019-03-01', 50
UNION
SELECT 'B', '2019-01-01', 214
UNION
SELECT 'B', '2019-02-01', 10
UNION
SELECT 'C', '2019-04-01', 150
INSERT INTO #MyTableTempDate
SELECT '2019-01-01'
UNION
SELECT '2019-02-01'
UNION
SELECT '2019-03-01'
UNION
SELECT '2019-04-01'
UNION
SELECT '2019-05-01'
UNION
SELECT '2019-06-01'
UNION
SELECT '2019-07-01';
------------- FOR NEWER SQL SERVER VERSION > 2005
WITH MyCTE AS
(
SELECT T.Product, T.ProductMonth AS 'MMonth', T.Qty
FROM #MyTable T
UNION
SELECT T.Product, TD.FullMonth AS 'MMonth', 0 AS 'Qty'
FROM #MyTable T, #MyTableTempDate TD
WHERE NOT EXISTS (SELECT 1 FROM #MyTable TT WHERE TT.Product = T.Product AND TD.FullMonth = TT.ProductMonth)
)
-- SELECT * FROM MyCTE;
SELECT Product, MMonth, Qty, SUM( Qty) OVER(PARTITION BY Product ORDER BY Product
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as 'TotalQty'
FROM MyCTE
ORDER BY Product, MMonth ASC;
DROP TABLE #MyTable
DROP TABLE #MyTableTempDate
У меня есть другой способ выполнить это в более низкой версии SQL Server (например, 2005 и ниже) Это SELECT на SELECT, если это ваш случай, дайте мне знать, и я приведу другой пример.
Спасибо за ответ. Любым другим способом я могу создать эту временную таблицу на дату? На самом деле у меня есть тысячи рядов продуктов и дат в разных комбинациях.
@ got2nosth Я думаю, вы можете создать временную таблицу для конкретного продукта, но я не знал другого способа ... или вам нужно вставить в свою таблицу строку с номером 0 для продукта за месяц, и это может быть (запланированный ежемесячный рабочий "planified задача")
@Pascal, как только вы заполняете таблицу календаря вручную, вам не нужны CTE или UNIONS ... просто LEFT JOIN сделает то же самое и совместимо со всеми версиями .....
@DanielBrughera да, но кто связывает ваш продукт с левым присоединением?
В моем случае я использовал подзапрос, чтобы получить также максимальную и минимальную даты для каждого продукта, я прочитал в комментариях, что заказ на покупку требует разных дат начала и окончания для каждого продукта...
@DanielBrughera да, я ищу ваш ответ, но я думаю, что запрос неясен, привет, пропустите некоторую информацию, я думаю, что у них есть другая таблица, чтобы найти минимальную дату и максимальную дату по продукту
Затем можно заменить эту часть запроса предоставленной схемой - это способ, которым я нашел эти значения.
Прежде чем увидеть этот комментарий, я использовал SELECT DISTINCT ProductID в подзапросе, присоединяющемся к 1 = 1, чтобы получить все даты для всех продуктов.... получив тот же результат, что и у вас.
Давайте продолжить обсуждение в чате.
Прежде всего, я бы разделил месяц и год, чтобы упростить статистику.
Я дам вам пример запроса, не основанный на вашей таблице, но все же полезный.
--here i create the table that will be used as calendar
Create Table MA_MonthYears (
Month int not null ,
year int not null
PRIMARY KEY ( month, year) )
--/////////////////
-- here i'm creating a procedure to fill the ma_monthyears table
declare @month as int
declare @year as int
set @month = 1
set @year = 2015
while ( @year != 2099 )
begin
insert into MA_MonthYears(Month, year)
select @month, @year
if @month < 12
set @month=@month+1
else
set @month=1
if @month = 1
set @year = @year + 1
end
--/////////////////
--here you are the possible result you are looking for
select SUM(Ma_saledocdetail.taxableamount) as Sold, MA_MonthYears.month , MA_MonthYears.year , item
from MA_MonthYears left outer join MA_SaleDocDetail on year(MA_SaleDocDetail.DocumentDate) = MA_MonthYears.year
and Month(ma_saledocdetail.documentdate) = MA_MonthYears.Month
group by MA_SaleDocDetail.Item, MA_MonthYears.year , MA_MonthYears.month
order by MA_MonthYears.year , MA_MonthYears.month
Это не соответствует требованию
скажите почему, а не только критикуйте, чтобы всем помогли ваши предложения
Это не критика, это факт, вы проверили свой запрос?? Он возвращает нулевые значения, когда PO публикует желаемый результат.
Я не сужу, как вы получаете результат
Если это сработало для вас для другой цели, это не значит, что это сработает для этой... смоделируйте его с предоставленным вводом, и вы увидите существенные различия.
Вы можете создать месяцы с рекурсивным CTE
DECLARE @MyTable TABLE
(
ProductID CHAR(1),
Date DATE,
Amount INT
)
INSERT INTO @MyTable
VALUES
('A','2018-01-01', 5),
('A','2018-02-01', 3),
('A','2018-05-01', 5),
('B','2018-08-01', 10),
('B','2018-10-01', 12)
DECLARE @StartDate DATE
DECLARE @EndDate DATE
SELECT @StartDate = MIN(Date), @EndDate = MAX(Date) FROM @MyTable
;WITH dates AS (
SELECT @StartDate AS Date
UNION ALL
SELECT DATEADD(Month, 1, Date)
FROM dates
WHERE Date < @EndDate
)
SELECT A.ProductID, d.Date, COALESCE(Amount,0) AS Amount, COALESCE(SUM(Amount) OVER(PARTITION BY A.ProductID ORDER BY A.ProductID, d.Date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),0) AS Total
FROM
(
SELECT ProductID, MIN(date) as DateStart, MAX(date) as DateEnd
FROM @MyTable
GROUP BY ProductID -- As I read in your comments that you need different min and max dates per product
) A
JOIN dates d ON d.Date >= A.DateStart AND d.Date <= A.DateEnd
LEFT JOIN @MyTable T ON A.ProductID = T.ProductID AND T.Date = d.Date
ORDER BY A.ProductID, d.Date
Попробуйте это ниже
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
;WITH CTE(Product,[Month],Qty)
AS
(
SELECT 'A','2018-01-01', 5 UNION ALL
SELECT 'A','2018-02-01', 3 UNION ALL
SELECT 'A','2018-05-01', 5 UNION ALL
SELECT 'B','2018-08-01', 10 UNION ALL
SELECT 'D','2018-10-01', 12
)
SELECT ct.Product,[MonthDays],ct.Qty
INTO #Temp
FROM
(
SELECT c.Product,[Month],
ISNULL(Qty,0) AS Qty
FROM CTE c
)ct
RIGHT JOIN
(
SELECT -- This code is to get month data
CONVERT(VARCHAR(10),'2018-'+ RIGHT('00'+CAST(MONTH(DATEADD(MM, s.number, CONVERT(DATETIME, 0)))AS VARCHAR),2) +'-01',120) AS [MonthDays]
FROM master.dbo.spt_values s
WHERE [type] = 'P' AND s.number BETWEEN 0 AND 11
)DT
ON dt.[MonthDays] = ct.[Month]
SELECT
MAX(Product)OVER(ORDER BY [MonthDays])AS Product,
[MonthDays],
ISNULL(Qty,0) Qty,
SUM(ISNULL(Qty,0))OVER(ORDER BY [MonthDays]) As SumQty
FROM #Temp
Результат
Product MonthDays Qty SumQty
------------------------------
A 2018-01-01 5 5
A 2018-02-01 3 8
A 2018-03-01 0 8
A 2018-04-01 0 8
A 2018-05-01 5 13
A 2018-06-01 0 13
A 2018-07-01 0 13
B 2018-08-01 10 23
B 2018-09-01 0 23
D 2018-10-01 12 35
D 2018-11-01 0 35
D 2018-12-01 0 35
Вопрос был неполным, поэтому я несколько дней думал, значит, решение неверно.
Формат даты неверен, и, если предположить, что это дни.... он работает только для января.
Я думаю, что в вопросе есть некоторая неясность, но вы делаете предположения, которые не соответствуют ожидаемым результатам, например, что делать с датами 6 и 7.... это уточняется в комментариях к вопросу. .. не то место, я знаю, но есть ли там...
Я думаю, что рекурсивный CTE - это простой способ сделать это. Код просто:
with cte as (
select product, min(mon) as mon, max(mon) as end_mon
from t
group by product
union all
select product, dateadd(month, 1, mon), end_mon
from cte
where mon < end_mon
)
select cte.product, cte.mon, coalesce(qty, 0) as qty
from cte left join
t
on t.product = cte.product and t.mon = cte.mon;
Здесь — это рабочий пример db<>.
Не слишком ли дорого для сервера это делать с большим списком товаров за несколько месяцев??
@ДэниэлБругера . . . Я так не думаю. Вы должны агрегировать исходные данные, чтобы получить ограничения, в любом случае сгенерировать все строки, а затем соединиться, чтобы получить исходные данные, независимо от подхода. Рекурсивные CTE могут добавить немного накладных расходов к первой части проблемы (по сравнению с другими методами), но вам нужно будет протестировать решения, чтобы увидеть, имеет ли это значительный эффект. Я не думаю, что это было бы.
Я сравнил планы выполнения... мой способ требует меньше памяти, но ваш быстрее, конечная стоимость очень похожа.... но противоречит моему оригиналу, хотя ваш использует меньше процессора
@Гордон Линофф Большое спасибо! Никогда не думал, что рекурсивный cte может работать так хорошо! Кстати, мне нужно уточнить одну небольшую деталь реализации. Почему mon < end_mon вместо mon <= end_mon? Я проверил, что "<" является правильным условием, но почему диапазон дат может охватывать end_mon, но не быть равным максимуму?
@got2nosth . . . Потому что select добавляет к дате один месяц. Если вы используете <=, вы получите одно значение после окончания месяца.
Вам понадобится таблица «Календарь», чтобы указать отсутствующую дату, и вы оставили соединение с ней.... Либо используйте существующую (если она доступна), либо сгенерируйте ее через sql CTE...