【问题标题】:Using SQL to prorate based on DateTime values使用 SQL 根据 DateTime 值按比例分配
【发布时间】:2011-07-08 16:27:18
【问题描述】:

这个问题的背景是存储小部件的仓库的计费系统。存储费基于小部件的大小(例如 $x * 高度 * 长度 * 宽度)。这部分很容易。我使用以下 SQL 来计算应付金额并按用户 ID 汇总。

Select UserID, Sum((Height * Length * Width * 5) as AmtDue From Widget
Where StatusID=2
Group By UserID

5 目前是一个任意值。最终我会将它作为参数传递或从数据库中的表中获取。假设现在总是 5。

这是我正在努力解决的部分。费用在月底计算,并根据该月小部件存储在仓库中的天数按比例分配(例如,如果小部件在 30 天的月份中有 15 天在仓库中存放,则应支付的费用为金额的 50%)。在我的 Widget 表中,我有一列 DateReceived 和 DateShipped。 DateReceived 始终具有日期时间值。 DateShipped 有时为空(例如,如果小部件仍在存储中)。我有可用于 CycleStart 和 CycleEnd 的参数,它们代表相关计费月份的开始和结束。我正在尝试修改我的 SQL 查询以在选择表达式中包含一个比例因子,该因子等于(存储天数/月天数)。换句话说,AmtDue 变为 (height * length * width * 5 * prorate factor) 我该怎么做?

这里有一个例子来帮助说明如何计算比例因子。

如果@CycleStart = 7/1/11 和 @CycleEnd = 7/31/11 mm/dd/yy

DateReceived  |  DateShipped  |  Prorate Factor

6/5/11              Null           100%
6/5/11              7/15/11      48.38%
7/30/11             8/5/11        6.45%
7/15/11             7/25/11      35.48%        

我真的在为这种条件的性质以及我们正在处理日期时间值的事实而苦苦挣扎。

【问题讨论】:

  • 我们可以假设每个月都是 30 天吗?
  • 我们可以大大简化事情。否则我宁愿准确并使用相关月份的实际天数。

标签: sql sql-server sql-server-2008


【解决方案1】:

给你。这个

drop table #work
go
create table #work
(
  id           int      not null identity(1,1) primary key clustered ,
  DateReceived datetime not null ,
  DateShipped  datetime     null ,
)
go

insert #work ( DateReceived , DateShipped ) values ( '06/05/2011' , null         )
insert #work ( DateReceived , DateShipped ) values ( '06/05/2011' , '07/15/2011' )
insert #work ( DateReceived , DateShipped ) values ( '07/30/2011' , '08/05/2011' )
insert #work ( DateReceived , DateShipped ) values ( '07/15/2011' , '07/25/2011' )
go

declare
  @CycleStart   datetime ,
  @CycleEnd     datetime ,
  @DaysInPeriod money

set @CycleStart = '07/01/2011'
set @CycleEnd   = '07/31/2011'
set @DaysInPeriod = datediff(day,@CycleStart,dateadd(day,1,@CycleEnd))

select * ,
       DaysInStorage = datediff( day ,
                         case when DateReceived < @CycleStart
                           then @CycleStart
                           else DateReceived
                         end ,
                         case when DateShipped > @CycleEnd
                           then dateadd(day,1,@CycleEnd)
                           else dateadd(day,1,coalesce(DateShipped,@CycleEnd))
                         end
                         ) ,
       DaysInPeriod  = @DaysInPeriod ,
       ProrateFactor = datediff( day ,
                         case when DateReceived < @CycleStart
                           then @CycleStart
                           else DateReceived
                         end ,
                         case when DateShipped > @CycleEnd
                           then dateadd(day,1,@CycleEnd)
                           else dateadd(day,1,coalesce(DateShipped,@CycleEnd))
                         end
                         ) / @DaysInPeriod
from #work

产生以下结果

id DateReceived            DateShipped             DaysInStorage DaysInPeriod ProrateFactor
-- ----------------------- ----------------------- ------------- ------------ -------------
 1 2011-06-05 00:00:00.000 NULL                               30        31.00        1.00
 2 2011-06-05 00:00:00.000 2011-07-15 00:00:00.000            14        31.00        0.4838
 3 2011-07-30 00:00:00.000 2011-08-05 00:00:00.000             1        31.00        0.0645
 4 2011-07-15 00:00:00.000 2011-07-25 00:00:00.000            10        31.00        0.3548

【讨论】:

  • 太棒了。我假设您打算将 DaysInPeriod 声明为 int 而不是金钱。
  • 是的。作为int,它将自动向上转换(扩大转换)。不过,如果您愿意,没有什么可以阻止您将 @DaysInPeriod 声明为 money 值。
【解决方案2】:

试试这个:

SELECT CAST(DATEPART( dd,DateShipped) as decimal)/
CAST(DATEPART(dd,DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE())+1,0))) as decimal) as ProrateFactor

如果你想得到百分比,你可以乘以 100,但出于计算的原因,我假设你想要小数。

你可以运行这个来查看今天的比例因子:

SELECT CAST(DATEPART( dd,GETDATE()) as decimal)/
CAST(DATEPART(dd,DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE())+1,0))) as decimal) 

要将它与您当前的计算结合起来,您可以这样做(警告 - 这很可怕):

Select UserID, Sum((Height * Length * Width * 5 * 
               SELECT CAST(DATEPART( dd,DateShipped) as decimal)/
               CAST(DATEPART(dd,DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE())+1,0))) as decimal) ) as AmtDue From Widget
Where StatusID=2
Group By UserID

【讨论】:

  • 谢谢,这看起来是在正确的轨道上。但是,按照指示修改我的 select 语句会导致我的 Group By 语句出现问题。具体来说,SQL 期望对这些列进行某种聚合。你有什么建议?
  • 为了使这项工作在当前月份以外的某个月份工作,将 GETDATE() 替换为 @CycleEnd 是否正确?
  • 如果你使用 GETDATE() 它只适用于当前月份。如果您想要当前月份以外的内容,您应该可以用 @CycleStart/@CycleEnd 替换。您需要在 GROUP BY 语句中包含所有选择的新列才能使其正常工作。
猜你喜欢
  • 1970-01-01
  • 2010-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-08
  • 2023-03-11
  • 2013-08-16
  • 1970-01-01
相关资源
最近更新 更多