#sql #sql-server
#sql #sql-сервер
Вопрос:
Я работаю над Microsoft SQL Server 2017. Я пытаюсь заполнить недостающие месяцы в запросе данных.
Запрос:
SELECT Concat(Datename(mm, temp1.InvoiceDate), '-', Datepart(yy, Getdate())) AS 'Date' , temp1.[Partner], Count(temp1.issues) AS 'Total'
FROM temp1
GROUP BY temp1.InvoiceDate, temp1.[partner]
ORDER BY Datepart(mm, temp1.InvoiceDate), temp1.[partner];
Не обращайте внимания на getdate, поскольку я всегда буду работать с фактическими данными за год.
И это показывает мне этот результат:
Date Partner Total
April-2020 Enterprise1, S.A. 1
May-2020 Enterprise2, S.A. 1
July-2020 Enterprise2, S.A. 2
Это правильно, но мне также нужны недостающие месяцы до фактического (фактический месяц getdate), где нет данных.
Что-то вроде:
Date Partner Total
January-2020 N/A 0
February-2020 N/A 0
March-2020 N/A 0
April-2020 Enterprise1, S.A. 1
May-2020 Enterprise2, S.A. 1
June-2020 N/A 0
July-2020 Enterprise2, S.A. 2
August-2020 N/A 0
September-2020 N/A 0
Кроме того, я не хочу, чтобы отображались месяцы из фактического.
PS: я не могу использовать код T-SQL (объявить и т.д.) В своей среде разработки.
Заранее спасибо.
Ответ №1:
Я бы использовал рекурсивный CTE для получения дат за год:
with dates as (
select datefromparts(year(getdate()), 1, 1) as yyyymm
union all
select dateadd(month, 1, yyyymm)
from dates
where yyyymm < getdate()
)
select d.yyyymm, t1.partner, count(t1.issues)
from dates d left join
temp1 t1
ON t1.InvoiceDate >= d.yyyymm AND
t1.InvoiceDate < dateadd(month, 1, d.yyyymm)
group by d.yyyymm, t1.partner
order by d.yyyymm, t1.partner;
Обратите внимание на некоторые изменения:
- Это возвращает дату. Он не форматирует его так, как у вас. Вы можете отформатировать его так, как вам нравится.
- Он возвращает
0
вместо «N / A».0
кажется более подходящим.
На самом деле вышеприведенное будет работать только для одного партнера (так что это работает для примера в вопросе). Я подозреваю, что вам нужны все месяцы для всех партнеров. Если это так, используйте cross join
для генерации строк, а затем left join
и group by
:
with dates as (
select datefromparts(year(getdate()), 1, 1) as yyyymm
union all
select dateadd(month, 1, yyyymm)
from dates
where yyyymm < getdate()
)
select d.yyyymm, p.partner, count(t1.issues)
from dates d cross join
(select distinct partner from temp1) p left join
temp1 t1
on t1.partner = p.partner and
t1.InvoiceDate >= d.yyyymm and
t1.InvoiceDate < dateadd(month, 1, d.yyyymm)
group by d.yyyymm, p.partner
order by d.yyyymm, p.partner;
Комментарии:
1. Я попробовал ваш код, но SQL выдает мне ошибку: DATEFROMPARTS не является распознанным именем встроенной функции. Как я мог заставить это работать? Я использую версию 14.0.2027.2, поэтому она должна быть совместима… Спасибо.
2. @operamaxi . . . Это доступно во всех поддерживаемых версиях SQL Server: learn.microsoft.com/en-us/sql/t-sql/functions /… .
3. Я обнаружил ошибку, первая скобка никогда не закрывается. Не могли бы вы отредактировать свой ответ, закрыв его? Бьюсь об заклад, это изменение может заставить его работать. Спасибо.
4. Найдено, оно должно быть добавлено после getdate: выберите datefromparts(год(getdate()), 1, 1) как гггг . Спасибо!
5. Я пробовал, но он показывает все месяцы только до октября. Может ли это отображаться до декабря или сентября (фактический месяц)? Спасибо.
Ответ №2:
Альтернативой рекурсивному CTE может быть запрос с таблицей чисел (иногда называемой ‘tally’). Есть только 12 месяцев, поэтому их можно просто перечислить в коде, и тогда рекурсия не требуется. Кроме того, для ввода лет я добавил переменную, которая принимает список, разделенный запятыми. Пример в этом случае имеет 2 года, 2020 и 2021
Что-то вроде этого
declare
@report_yrs varchar(100)='2020,2021';
with ent_cte(InvoiceDate, [Partner], [issues]) as
(select '2020-03-01', 'Enterprise1, S.A.', 'x'
union all
select '2020-04-01', 'Enterprise1, S.A.', 'x'
union all
select '2020-05-01', 'Enterprise1, S.A.', 'x'
union all
select '2020-12-01', 'Enterprise1, S.A.', 'x')
SELECT Concat(Datename(mm, dfp.dfp), '-', Datepart(yy, dfp.dfp)) AS [Date],
isnull(t.[Partner], 'N/A') [Partner], Count(t.issues) AS [Total]
FROM string_split(@report_yrs, ',') sp
cross join
(values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) v(mo)
cross apply
(select datefromparts(cast(sp.[value] as int), v.mo, 1) dfp) dfp
left join
ent_cte t on dfp.dfp=t.InvoiceDate
GROUP BY dfp.dfp, t.[partner]
ORDER BY dfp.dfp, t.[partner];
Вывод
Date Partner Total
January-2020 N/A 0
February-2020 N/A 0
March-2020 Enterprise1, S.A. 1
April-2020 Enterprise1, S.A. 1
May-2020 Enterprise1, S.A. 1
June-2020 N/A 0
July-2020 N/A 0
August-2020 N/A 0
September-2020 N/A 0
October-2020 N/A 0
November-2020 N/A 0
December-2020 Enterprise1, S.A. 1
January-2021 N/A 0
February-2021 N/A 0
March-2021 N/A 0
April-2021 N/A 0
May-2021 N/A 0
June-2021 N/A 0
July-2021 N/A 0
August-2021 N/A 0
September-2021 N/A 0
October-2021 N/A 0
November-2021 N/A 0
December-2021 N/A 0
Ответ №3:
Вы найдете в базе данных только те данные, которые вы в нее ввели. В таком случае рекомендуется добавить некоторую таблицу даты и времени, такую как таблица календаря (одна строка в день) и некоторые другие таблицы, такие как месяц, дни, недели…
Когда это будет создано в вашей базе данных, запрос будет простым и эффективным, вместо того, чтобы использовать рекурсивные запросы функции с табличным значением, все вещи, которые отнимают много времени и обрабатываются.
В вашем случае это может быть :
CREATE TABLE T_MONTH
(
MONTH_NUM TINYINT PRIMARY KEY,
MONTH_NAME VARCHAR(16) NOT NULL UNIQUE
);
DECLARE @I INT = 1, @D DATE = '2000-01-01'
WHILE @I <= 12
BEGIN
INSERT INTO T_MONTH
SELECT @I, DATENAME(month, DATEADD(month, @I - 1, @D));
SET @I = @I 1;
END
И запрос что-то вроде :
SELECT Concat(MONTH_NAME, '-', Datepart(yy, Getdate())) AS 'Date' ,
COALESCE(temp1.[Partner], 'N/A'),
COALESCE(Count(temp1.issues), 0) AS 'Total'
FROM temp1
RIGHT OUTER JOIN T_MONTH ON MONTH_NAME = Datename(mm, temp1.InvoiceDate)
GROUP BY MONTH_NAME, temp1.[partner]
ORDER BY MONTH_NAME, temp1.[partner];