2011-10-14 38 views
3

我有這個非常棘手的問題,我試着去弄清楚,現在一段時間切片日期範圍的數據,SQL:在今年(整蠱)

我有這樣的查詢結果集

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 

現在,我怎麼能分解每一個訂單的訂購日期 - 每年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查詢中做到這一點?

**我知道標題似乎不正確^^,我只是不能找到合適的標題

編輯:增加了更多的樣本和解釋

+1

爲什麼orderId 2的金額僅限於2009年且未拆分? –

+0

編輯,謝謝! – dotnetlinc

+0

每年總是會獲得相同的「CREDIT」,例如Order Date = 2010年7月1日和ExpiryDate = 2011年12月31日,訂單金額爲$ 150。每年有沒有75美元,等量?或者2010年獲得50美元,2011年獲得100美元,與每年的保有量成比例? –

回答

2

如果這是SQL Server 2005或更高版本,可以使用公用表表達式遞歸建立個月的表並用它來計算每年消費的訂單數量。

的幾個注意事項:

  1. 該解決方案假定時間段是基於整月。方法是確定每月消費的訂單數量,然後再乘以當年的月數。如果需要,您可以修改此設置以使用每天消耗的金額。

  2. 在最終的select中,我從money轉換爲decimal以更高的精度嘗試避免舍入問題。您可能需要調整以適應您的需求,但我不確定您能否完全避免將問題舍入。

  3. OrderID 5的結果與您的樣品結果不符。這是因爲,例如有5個月的2009年,不是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 
+0

恰恰是我需要的,+1和v/ – dotnetlinc

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 

如果你想產卵數年,它的確會變得棘手。以下是一些遞歸SQL應爲DB2/MSSQL工作:

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來解決。

+0

它不總是完全2年,訂單日期-expir吃了可能從1個月範圍到10年不等,我只是用它作爲例子,以便於切片^^ – dotnetlinc

+0

你應該改變你的例子多一點,那麼它很明顯:)你還應該指出你正在使用哪個數據庫,這樣人們可以像往常一樣,在需要這種棘手的東西時爲你提供特定的SQL,而通用標準SQL並不會削減它。 –

+0

我正在使用SQL Server數據庫(T-SQL)。感謝您的投入。 – dotnetlinc

1

由於inflagranti指出我的第一個解決方案遇到了Orders表所涵蓋年份出現漏洞的問題。我試圖動態創建所需年份的集合。

一個更直接的解決方案是使用一個numbers or tally table供應年。我的計數表叫做Number,並且有一個int類型的N列,數字爲0到1,000,000(含)。使用計數表可以提供多年加入的時間,以獲得跨越多年的訂單的多個行。對於ConsumedAmount,我正在計算OrderAmount *(訂單/訂單日期的一年中的幾天)。如果您想要某些不同的東西,如月份,周或財務上的任何東西,則需要修改。

注意我也使用SQL Server 2008點的功能,特別是date數據類型,這在2008年是新從數相符表中獲取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 
+1

這不會超過2年的範圍。 –

+0

是的,沒有。如果記錄的多年範圍由其他記錄填充,它將起作用,但可能會失敗。稍後再糾正。右側應該是一個理貨表。 –

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) 
+0

我需要什麼,+1和一個v/^^ – dotnetlinc