我必须为日期范围生成销售报告,用户将在该日期范围内选择日 - 开始时和结束时。
以下查询将返回日期时间和销售额
select s.StartDate ,
CONVERT(DECIMAL(10,2),sum(OrigionalSubTotal)/100.0) Amt from Sale s
where
s.StartDate
BETWEEN '2016-06-12 04:00:01'
and '2016-06-18 04:00:00'
and s.IsSuspend = 0 and s.IsTrainMode = 0 and wasrefunded=0
and IsCancelled = 0
group by S.StartDate
order by s.StartDate
O / P
StartDate Amt
2016-06-12 10:01:15.780 10.00
2016-06-12 10:15:57.360 20.00
2016-06-12 12:48:41.250 50.00
2016-06-13 11:02:50.850 5.00
2016-06-13 12:04:45.090 15.00
2016-06-14 14:18:38.960 10.00
由此我需要按照以下每小时的销售情况,在6月6日至16日至6月18日期间进行小时销售
From -To Sun-12 Mon-13 Tue-14 Wed-15 Thu-16 Fri-17 Sat-18
04:00-05:00 0.00 0.00 0.00 0.00 0.00 0.00 0.00--nosale
05:00-06:00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 "
06:00-07:00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 "
07:00-08:00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 "
08:00-09:00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 "
09:00-10:00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 "
10:00-11:00 30.00 0.00 0.00 0.00 0.00 0.00 0.00
11:00-12:00 0.00 5.00 0.00 0.00 0.00 0.00 0.00
12:00-13:00 50.00 15.00 0.00 0.00 0.00 0.00 0.00
13:00-14:00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
14:00-15:00 0.00 0.00 10.00 0.00 0.00 0.00 0.00
..
..
..
23:00-00:00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
..
..
03:00-04:00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
如何从上表中获得相同的结果。请说明一下。我完全坚持这个。
答案 0 :(得分:1)
销售数据没有给出每个时段的每小时的记录。因此,为了获得所有“交易日”(或任何你称之为)的所需结构,小时和每个日期的日期,你必须从其他地方提供你的脚本的时间间隔和工作日列表(因为销售只存储一些数据) )。
因此,您需要时间间隔列表和日期列表(工作日)。
之后,如果没有合适的数据,您将能够生成LEFT JOIN
并从Sales 获取数据或NULL。
您的方法基于简单的GROUP BY
- 这就是为什么除dbo.Sales
之外的其他行外,您无法获得任何其他行。
如果我理解你的请求是正确的 - 你正在构建类似“每周报告”的东西,我的解决方案基于这个假设(你总是需要7天)。
现在,要生成所需的输出,您需要从行开始的日期成为列。这可以通过PIVOT
(或SUM(CASE WHEN ...)
技巧)来完成。
没有转动time+date left join sales
的输出看起来像:
[From-To] [Date] [Amt]
10:00-11:00 12.06 (Sun) 11
10:00-11:00 13.06 (Mon) 5
在PIVOT
之后,您会将日期(工作日)作为列,但在同一时间段内有很多行 - 来自不同日期:
[From-To] [Sun] [Mon]
10:00-11:00 11 0
10:00-11:00 0 5
所以你必须聚合它
[From-To] [Sun] [Mon]
10:00-11:00 11 5
可以通过简单的GROUP BY
这里使用递归CTE来为@Timing
和@WeekDays
表填充顺序数据(注意+1
和dateadd
)。
使用诸如时间段或日历日期列表等数据的最简单且经常建议的方法之一是使用您需要的所有内容填充持久Calendar
表。然后 - 只需从中选择即可获得给定的所有日期。
另一方面注意:您的交易日不是在0:00开始,所以您可能需要了解4:00中哪一个属于这个天文日,哪个属于下一个天文日。在列表中,您需要按正确的顺序获取这些行。请参阅下面代码中的hrs
列,其中包含4到27小时的小时数。
如果我对“每周报告”的假设是错误的,并且您需要一个包含动态列数的报告,那么获取它的唯一方法是使用动态SQL。
declare
@DateBegin datetime = '20160612',
@DateEnd datetime = '20160615'
declare @Sales table
(
StartDate datetime,
OrigionalSubTotal decimal(10, 2)
)
insert into @Sales(StartDate, OrigionalSubTotal)
values
('2016-06-12 10:01:15.780', 10.00),
('2016-06-12 10:15:57.360', 20.00),
('2016-06-12 12:48:41.250', 50.00),
('2016-06-13 11:02:50.850', 5.00),
('2016-06-13 12:04:45.090', 15.00),
('2016-06-14 14:18:38.960', 10.00)
/* DEBUG: * /
select * from @Sales
/ * :DEBUG */
declare @Timing table
(
hrs smallint,
hours_start smallint,
hours_end smallint,
period varchar(11)
)
;with cteTiming as
(
select 4 as hrs
union all
select t.hrs+1
from cteTiming t
where t.hrs < 27
)
insert into @Timing (hrs, hours_start, hours_end, period)
select t.hrs, hrs % 24, (hrs + 1) % 24,
cast(hrs % 24 as varchar(10)) + ':00-' + cast((hrs+1) % 24 as varchar(10)) + ':00'
from cteTiming t
/* DEBUG: * /
select * from @Timing
/ * :DEBUG */
declare @WeekDays table
(
wd smallint,
wd_name varchar(3),
wd_date date
)
;with cteWeekDays as
(
select
1 wd,
dateadd(day, 1-datepart(weekday, @DateBegin), cast(@DateBegin as date)) wd_date
union all
select
wd.wd+1,
dateadd(day, 1, wd.wd_date) wd_date
from cteWeekDays wd
where wd.wd < 7
)
insert into @WeekDays (wd, wd_name, wd_date)
select wd.wd, left(datename(weekday, wd.wd_date), 3), wd.wd_date
from cteWeekDays wd
/* DEBUG: * /
select * from @WeekDays
/ * :DEBUG */
;with cteSales as
(
select
datepart(weekday, s.StartDate) as SalesWeekDay,
datediff(hour, cast(s.StartDate as date), s.StartDate) DayTime,
cast(s.StartDate as date) SalesDate,
s.OrigionalSubTotal
from @Sales s
where s.StartDate >= @DateBegin and s.StartDate < @DateEnd
),
cteSalesPerWeekDays as
(
select
p.hrs,
p.period,
p.[Mon], p.[Tue], p.[Wed], p.[Thu], p.[Fri], p.[Sat], p.[Sun]
from @Timing t
cross join @WeekDays w
left join cteSales s on s.DayTime >= t.hours_start and s.DayTime < t.hours_end
and s.SalesDate = w.wd_date
pivot
(
Sum(s.OrigionalSubTotal)
for w.wd_name in ([Mon], [Tue], [Wed], [Thu], [Fri], [Sat], [Sun])
) p
)
select
spd.hrs,
spd.period as [From-To],
Sum(IsNull(spd.[Sun], 0)) as [Sun],
Sum(IsNull(spd.[Mon], 0)) as [Mon],
Sum(IsNull(spd.[Tue], 0)) as [Tue],
Sum(IsNull(spd.[Wed], 0)) as [Wed],
Sum(IsNull(spd.[Thu], 0)) as [Thu],
Sum(IsNull(spd.[Fri], 0)) as [Fri],
Sum(IsNull(spd.[Sat], 0)) as [Sat]
from cteSalesPerWeekDays spd
group by spd.hrs, spd.period
order by spd.hrs
要从示例@Sales
表转移到真实源,您需要在第一个cte的最终选择中替换它(脚本的一部分):
;with cteSales as
(
select
datepart(weekday, s.StartDate) as SalesWeekDay,
datediff(hour, cast(s.StartDate as date), s.StartDate) DayTime,
cast(s.StartDate as date) SalesDate,
CONVERT(DECIMAL(10,2),sum(OrigionalSubTotal)/100.0) as OrigionalSubTotal
from dbo.Sales s
where s.StartDate >= @DateBegin and s.StartDate < @DateEnd
and s.IsSuspend = 0 and s.IsTrainMode = 0
and s.IsCancelled = 0 and s.wasrefunded=0
)
...
但@WeekDays和@Timing将留下来!您需要它来生成所需的报告结构。
注意,s.StartDate
包含时间,因此between
之类的简单日期过滤器在某些情况下不起作用(对于上限日期)。建议的方法是在开始日期过滤>=
,并在给定期间上限零时间后过滤<
下一个日期。这样你就可以找到所有'23:59:59'。
请注意,我已更改了工作日中枢轴的顺序。实际上,在PIVOT
之后,列顺序没有区别 - 它应该由客户端应用程序管理。或者通过上面示例中的最终选择语句。所有其他代码都知道您的一周是从星期日还是星期一开始。
固定日期过滤器为下一个天文日00:00-04:00期间。我添加了一些行来演示它。
if object_id('tempdb..#Sales', 'U') is not NULL
exec('drop table #Sales')
GO
if object_id('tempdb..#Timing', 'U') is not NULL
exec('drop table #Timing')
GO
if object_id('tempdb..#WeekDays', 'U') is not NULL
exec('drop table #WeekDays')
GO
declare
@DateBegin datetime = '20160612',
@DateEnd datetime = '20160715',
@TradeDateEnd datetime
/* to enable "<" filter */
set @TradeDateEnd = dateadd(hour, 4, dateadd(day, 1, @DateEnd))
declare
@sql nvarchar(max),
@col_per_day_list nvarchar(max),
@pivot_val_per_day_list nvarchar(max),
@sum_cols_per_day_list nvarchar(max)
create table #Sales
(
StartDate datetime,
OrigionalSubTotal decimal(10, 2)
)
insert into #Sales(StartDate, OrigionalSubTotal)
values
('20160612 03:55:00.000', 77.00),
('20160612 10:01:15.780', 10.00),
('20160612 10:15:57.360', 20.00),
('20160612 12:48:41.250', 50.00),
('20160613 11:02:50.850', 5.00),
('20160613 12:04:45.090', 15.00),
('20160614 14:18:38.960', 10.00),
('20160715 17:22:00.000', 11.00),
('20160716 03:55:00.000', 99.00)
/* DEBUG: * /
select * from #Sales
/ * :DEBUG */
create table #Timing
(
hrs smallint,
hours_start smallint,
hours_end smallint,
period varchar(11)
)
;with cteTiming as
(
select 4 as hrs
union all
select t.hrs+1
from cteTiming t
where t.hrs < 27
)
insert into #Timing (hrs, hours_start, hours_end, period)
select t.hrs, hrs % 24, (hrs + 1) % 24,
cast(hrs % 24 as varchar(10)) + ':00-' + cast((hrs+1) % 24 as varchar(10)) + ':00'
from cteTiming t
/* DEBUG: * /
select * from #Timing
/ * :DEBUG */
create table #WeekDays
(
wd smallint,
wd_date datetime,
wd_name varchar(12)
)
;with cteWeekDays as
(
select
datepart(weekday, @DateBegin) wd,
cast(@DateBegin as date) wd_date
union all
select
wd.wd+1,
dateadd(day, 1, wd.wd_date) wd_date
from cteWeekDays wd
where wd_date < cast(@DateEnd as date)
)
insert into #WeekDays (wd, wd_date, wd_name)
select wd.wd, wd.wd_date,
left(datename(weekday, wd.wd_date), 3) + '-' + convert(varchar(8), wd.wd_date, 112)
from cteWeekDays wd
/* DEBUG: * /
select * from #WeekDays
/ * :DEBUG */
select
@sum_cols_per_day_list = stuff(wd.value('.', 'varchar(max)'), 1, 2, '')
from
(
select
',
Sum(IsNull(r.' + quotename(wd.wd_name) + ', 0)) as ' + quotename(wd.wd_name)
from #WeekDays wd
order by wd.wd_date
for xml path(''), type
) x(wd)
select
@col_per_day_list = stuff(wd.value('.', 'varchar(max)'), 1, 2, '')
from
(
select
',
p.' + quotename(wd.wd_name)
from #WeekDays wd
order by wd.wd_date
for xml path(''), type
) x(wd)
set @pivot_val_per_day_list = replace(@col_per_day_list, 'p.[', ' [')
/* DEBUG: * /
print @col_per_day_list
print @pivot_val_per_day_list
print @sum_cols_per_day_list
/ * :DEBUG */
set @sql = cast(N'
;with cteSales as
(
select
datepart(weekday, s.StartDate) as SalesWeekDay,
datediff(hour, cast(s.StartDate as date), s.StartDate) DayTime,
s.StartDate SalesDate,
s.OrigionalSubTotal
from #Sales s
where s.StartDate >= @DateBegin and s.StartDate < @DateEnd
),
cteSalesPerWeekDays as
(
select
p.hrs,
p.period,' as nvarchar(max)) + @col_per_day_list + N'
from #Timing t
cross join #WeekDays w
left join cteSales s on datediff(hour, w.wd_date, s.SalesDate) = t.hrs
pivot
(
Sum(s.OrigionalSubTotal)
for w.wd_name in (' + @pivot_val_per_day_list + N')
) p
)
select
r.hrs,
r.period as [From-To],' + @sum_cols_per_day_list + N'
from cteSalesPerWeekDays r
group by r.hrs, r.period
order by r.hrs
'
/* DEBUG: */
print left(@sql, 4000)
print substring(@sql, 4000, 4000)
print datalength(@sql) / 2
/* :DEBUG */
exec sp_executesql @sql, N'@DateBegin datetime, @DateEnd datetime',
@DateBegin = @DateBegin,
@DateEnd = @TradeDateEnd
GO
if object_id('tempdb..#Sales', 'U') is not NULL
exec('drop table #Sales')
GO
if object_id('tempdb..#Timing', 'U') is not NULL
exec('drop table #Timing')
GO
if object_id('tempdb..#WeekDays', 'U') is not NULL
exec('drop table #WeekDays')
GO
解决方案几乎相同,除非您需要在构建最终选择之前收集列列表,并且不能使用@
表,因为它们的范围仅限于当前批次。
FOR XML
东西保证生成列列表的正确顺序。
对于动态天数,您还需要列名称中的月份和年份(例如,如果想要建立从12月的最后几天到明年1月的报告)。
@DateBegin
给出@TradeDateEnd
以保持@DateEnd
不变还原
答案 1 :(得分:0)
Unpivot可能就是你想要的。例如
declare @s table(FromTo varchar(11),Sun12 decimal(10,2), Mon13 decimal(10,2), Tue14 decimal(10,2), Wed15 decimal(10,2) ,Thu16 decimal(10,2), Fri17 decimal(10,2), Sat18 decimal(10,2))
insert into @s
values
('04:00-05:00' , 0.00 , 0.00 , 0.00, 0.00, 0.00, 0.00, 0.00),
('05:00-06:00' , 0.00 , 0.00 , 0.00, 0.00, 0.00, 0.00, 0.00),
('06:00-07:00' , 0.00 , 0.00 , 0.00, 0.00, 0.00, 0.00, 0.00),
('07:00-08:00' , 0.00 , 0.00 , 0.00, 0.00, 0.00, 0.00, 0.00),
('08:00-09:00' , 0.00 , 0.00 , 0.00, 0.00, 0.00, 0.00, 0.00),
('09:00-10:00' , 0.00 , 0.00 , 0.00, 0.00, 0.00, 0.00, 0.00),
('10:00-11:00' , 30.00 , 0.00 , 0.00, 0.00, 0.00, 0.00, 0.00),
('11:00-12:00' , 0.00 , 5.00 , 0.00, 0.00, 0.00, 0.00, 0.00),
('12:00-13:00' , 50.00 , 15.00, 0.00, 0.00, 0.00, 0.00, 0.00),
('13:00-14:00' , 0.00 , 0.00 , 0.00, 0.00, 0.00, 0.00, 0.00),
('14:00-15:00' , 0.00 , 0.00 , 10.00, 0.00, 0.00, 0.00, 0.00)
;WITH CTE AS
(
select UPVT.fromto,UPVT.DD,UPVT.sales
from
( SELECT FROMTO,Sun12, Mon13, Tue14, Wed15 ,Thu16, Fri17, Sat18 FROM @S) P
unpivot (sales for DD in (Sun12, Mon13, Tue14, Wed15 ,Thu16, Fri17, Sat18)
) UPVT
)
SELECT DD,CTE.FROMTO, SUM(CTE.SALES) SALES
FROM CTE
GROUP BY DD,CTE.FROMTO HAVING SUM(CTE.SALES) > 0
order by cast(right(DD,2) as int),CTE.FROMTO