我有这个非常棘手的问题,我现在试图解决它,
我有这个查询结果集
SELECT * FROM Orders
OrderID | OrderAmount | OrderDate | Expiry Date
1 $100 2008-01-01 2009-12-31
2 $200 2009-01-01 2010-12-31
3 $300 2010-01-01 2011-12-31
4 $3 2010-01-01 2010-06-31
5 $400 2007-01-01 2009-05-31
现在,我如何通过OrderDate细分每个订单 - 每年的ExpiryDate日期范围
我想在RDLC报告中得到类似的结果
ORDERS CONSUMED PER YEAR
OrderID | YEAR | Consumed Amount
1 2008 $50
1 2009 $50
2 2009 $100
2 2010 $100
3 2010 $150
3 2011 $150
4 2010 $3
5 2007 $160
5 2008 $160
5 2009 $80 <---- another tricky part
计算基于术语,eq。 (2年期300美元意味着每年150美元)
如何在MS-SQL查询中执行此操作?
**我知道标题似乎不正确^^,我只是找不到合适的标题
编辑:添加更多样本和解释
答案 0 :(得分:2)
如果这是SQL Server 2005或更高版本,则可以使用公用表表达式递归构建月份表,并使用它来计算每年消耗的订单量。
一些注意事项:
此解决方案假设时间段基于整月。方法是确定每月消费的订单金额,然后乘以该年度的月数。如果需要,您可以修改此值以使用每天消耗的金额。
在最后的select
中,我从money
投射到decimal
,精度更高,以避免舍入问题。您可能需要根据自己的需要进行调整,但我不确定您是否能完全避免将问题四舍五入。
OrderID 5的结果与您的样本结果不符。这是因为这个例子在2009年有5个月,而不是6个月。
create table #Orders
(
OrderID int,
OrderAmount money,
OrderDate datetime,
ExpiryDate datetime
)
insert into #Orders values(1, 100, '2008-01-01', '2009-12-31'),
(2, 200, '2009-01-01', '2010-12-31'),
(3, 300, '2010-01-01', '2011-12-31'),
(4, 3, '2010-01-01', '2010-06-30'),
(5, 400, '2007-01-01', '2009-05-31')
;with cte_months
as
(
select OrderID, OrderDate, year(OrderDate) OrderYear
from #Orders
union all
select m.OrderID, dateadd(month, 1, m.OrderDate), year(dateadd(month, 1, m.OrderDate))
from cte_months m
inner join #Orders o on m.OrderID = o.OrderID
where dateadd(month, 1, m.OrderDate) <= o.ExpiryDate
)
select m.OrderId, m.OrderYear, cast(sum(o.MonthlyAmount) as money) as ConsumedAmount
from
(
select OrderID, cast(OrderAmount as decimal(12,6)) / (datediff(month, orderdate, ExpiryDate) + 1) as MonthlyAmount
from #Orders
) o inner join cte_months m on o.OrderID = m.OrderID
group by m.OrderID, m.OrderYear
drop table #Orders
结果:
OrderId OrderYear ConsumedAmount
----------- ----------- ---------------------
1 2008 50.00
1 2009 50.00
2 2009 100.00
2 2010 100.00
3 2010 150.00
3 2011 150.00
4 2010 3.00
5 2007 165.5172
5 2008 165.5172
5 2009 68.9655
答案 1 :(得分:1)
从您的有限数据集中,每个订单产生两年的时间,您可以这样做:
SELECT orderId, Year(OrderDate) AS Year, orderamount/2 AS amount FROM orders
UNION
SELECT orderId, Year(Expiry Date) AS Year, orderamount/2 AS amount
假设您的产品最多两年(但可能只有一个)产生数据:
SELECT orderId, year, sum(amount)
FROM (
SELECT orderId, Year(OrderDate) year, orderamount/2 amount FROM orders
UNION
SELECT orderId, Year(Expiry Date) year, orderamount/2 amount FROM orders
) GROUP By orderId, year
如果你想要产生任意数年,它确实变得棘手。以下是一些适用于DB2 / MSSQL的递归SQL:
WITH expanded_orders (orderid, year)
AS
(
SELECT orderid, year(OrderDate) FROM orders
UNION ALL
SELECT orderid, year + 1 FROM expanded_orders
WHERE year + 1 <= (SELECT year(expiry date) FROM orders WHERE orders.orderid = exanded_orders.orderid)
)
SELECT exp.orderid, exp.year,
orders.amount / (SELECT count(*) FROM expanded_orders this WHERE this.orderid = exp.orderid)
FROM expanded_orders exp, orders
WHERE exp.orderid = orders.orderid
我没有测试查询,所以它可能有语法错误,但一般方法应该是合理的。
更新:我刚看到你添加的例子。这种方法在未完全使用的年份将不起作用,在这种情况下,第二选择必须适应于不均匀地划分条纹。但我认为这应该可以使用'传统'sql解决。
答案 2 :(得分:1)
正如inflagranti指出,如果Orders表所涵盖的年份中存在漏洞,我的第一个解决方案会遇到问题。我试图动态创建所需的年份。
更直接的解决方案是使用numbers or tally table来提供年份。我的计数表名为Number,并且有一个int类型的N列,其数字为0到1,000,000。使用计数表提供连接年份以获取跨越多年的订单的多行。对于ConsumedAmount,我正在计算OrderAmount *(订单的订单/天的年份中的天数。)如果您想要不同的东西,例如几个月或几周,或财政上的任何东西,您将需要修改。
注意我也使用SQL Server 2008功能,特别是2008年新增的date
数据类型。从数字计数表中获取YearNumber,YearStartDate和YearEndDate的方法在2008之前会有所不同
请注意,由于闰年的额外日期,这些数字与您的示例略有不同。
; With Y as (select N as YearNumber
, dateadd(year, N-1, cast('0001-01-01' as date)) as YearStartDate
, dateadd(day, -1, dateadd(year, N, cast('0001-01-01' as date)))
as YearEndDate
from Number)
select O.OrderId
, Y.YearNumber
-- Next column is the OrderAmount * the ratio of
-- number of days in this year for this order
-- divided by the number of days of the order.
, OrderAmount * -- multiply the Order amount by the ratio of
((case when year(OrderDate) = year(Expirydate)
then datediff(day, OrderDate, ExpiryDate)
when YearNumber = year(OrderDate)
then datediff(day, OrderDate, YearEndDate)
when YearNumber = year(ExpiryDate)
then datediff(day, YearStartDate, ExpiryDate)
else datediff(day, YearStartDate, YearEndDate) end + 1)
/ cast(datediff(day, OrderDate, ExpiryDate) + 1 as float))
from Orders O
inner join Y on Y.YearNumber between year(OrderDate) and year(ExpiryDate)
order by O.OrderId, YearNumber
测试数据:
create table Orders (OrderId int not null constraint Orders_PK primary key
, OrderAmount money
, OrderDate date
, ExpiryDate date)
go
insert into Orders
values (1, 100, '2008-01-01', '2009-12-31')
, (2, 200, '2009-01-01', '2010-12-31')
, (3, 300, '2010-01-01', '2011-12-31')
, (4, 3, '2010-01-01', '2010-06-30')
, (5, 400, '2007-01-01', '2009-05-31')
, (100, 1000, '2010-01-01', '2019-12-31')
查询结果:
OrderId YearNumber
----------- ----------- ----------------------
1 2008 50.0683994528044
1 2009 49.9316005471956
2 2009 100
2 2010 100
3 2010 150
3 2011 150
4 2010 3
5 2007 165.532879818594
5 2008 165.986394557823
5 2009 68.4807256235828
100 2010 99.9452354874042
100 2011 99.9452354874042
100 2012 100.219058050383
100 2013 99.9452354874042
100 2014 99.9452354874042
100 2015 99.9452354874042
100 2016 100.219058050383
100 2017 99.9452354874042
100 2018 99.9452354874042
100 2019 99.9452354874042
答案 3 :(得分:1)
另一种方法是使用它 - 这将在未来长达2047年使用,如果需要更多,某些数字表将是有用的,虽然我不认为这是有效的adrift的解决方案!
create table #Orders (OrderID int, OrderAmount money, OrderDate datetime, ExpiryDate datetime )
insert into #Orders
SELECT 1 as orderid, 100 as orderamount, '2008-01-01' as orderdate, '2009-12-31' as expirydate UNION ALL
SELECT 2, 200, '2009-01-01', '2010-12-31' UNION ALL
SELECT 3, 300, '2010-01-01', '2011-12-31' UNION ALL
SELECT 4, 3, '2010-01-01', '2010-06-30' UNION ALL
SELECT 5, 400, '2007-01-01', '2009-05-31'
SELECT
orderid
,orderamount as total_order_amount
,YEAR(orderdate) + number as orderyear
,CASE WHEN
DATEDIFF(MM,orderdate,expirydate) - (12 * (YEAR(orderdate) - YEAR(orderdate) + number))
> 12 THEN 12 * CAST(orderamount / DATEDIFF(MM,orderdate,expirydate) AS DECIMAL(18,5))
ELSE
(DATEDIFF(MM,orderdate,expirydate) % 12 *
CAST(orderamount / DATEDIFF(MM,orderdate,expirydate) AS DECIMAL(18,5))) END as amount
,CAST(orderamount / DATEDIFF(MM,orderdate,expirydate) AS DECIMAL(18,5)) as monthly_value
from #orders
LEFT OUTER JOIN
(
select number from spt_values
where type = 'p'
) numbers on numbers.number <= DATEDIFF(YY,orderdate,expirydate)