问题描述
以下是 Navision CALCDATE function 的定义:
NewDate := CALCDATE(DateExpression [,Date])
需要两个参数:
- DateExpression 在 Navision 中的类型为 DateFormula,在 sql 中存储为 varchar(32)
- Date,在 sql 中表示日期时间。为了简化问题,我假设它不是可选的(在 Navsion 中是可选的)
我需要从 sql 访问 Navision 数据并根据存储在那里的 DateFormulas 进行一些计算。 我知道如何在 sql CLR 中编写此类函数,因此请不要在您的答案中包含此内容。
- 如何在 Tsql 中编写 CALCDATE 函数?
- [可选] 是否可以仅在 sql 查询中使用一些(辅助)CTE(这将允许我不向 Navsion 数据库添加函数,因此不修改它)?
- sql CLR 是我唯一的选择吗?
我对这个功能的主要问题是它似乎必须按以下步骤完成:
- 解析日期表达式
- 将解析结果应用于日期参数并生成新日期。
但是解析部分怎么做呢?
例如 DateExpression = '-CW+1W+1D' 意味着我们必须按照这个顺序对我们的 Date 参数做 3 件事:
它可以写成更短的形式“+CW+1D”,这意味着下周一和 1 天。 通常这个 varchar(32) 可以包含 1 个或多个这样的表达式,并且它们必须按提供的顺序应用于 Date 参数。
如何在sql中解析这种varchar(32)?
在 C# 中,我会在循环中或递归中执行此操作,但是如何在 sql(集合)中执行此操作?
如何将这种结构化的 varchar(32) 转换为一组公式。
也许我需要以某种方式将它转换为 XML 并使用 sql Server XML 函数查询它 - 但如何?
编辑: 我想如何使用它?
select ...
from [Issued Reminder Header] as reminder
join [Reminder Terms] as terms on ...
join [Reminder Level] as level on ...
cross apply dbo.CalcDate(level.[Grace Period],reminder.[Posting Date]) as calculated
我的意思是,那个级别。[宽限期]在表格中,可以由用户编辑并且可以随时更改。所以我不能走任何捷径并预先计算几个值。
解决方法
这个答案在这里主要是作为一个例子,说明在纯 SQL 中尝试和实现是多么愚蠢(也因为我认为这是一个有趣的挑战)。
它不打算用于任何类型的生产能力,我什至不确定它是否正确实现了 calcdate
的功能或是否适用于 calcdate
允许的所有可能场景。
SQL
declare @d date = '19960521';
declare @t table(Expression varchar(32),ExpectedValue date);
insert into @t values
('-CW+1W+1D','19960528'),('CQ+1M-10D','19960720'),('+CW+1D',('+CW+1WD',('CM+30D','19960630'),('-WD2','19960514'),('WD3','19960522'),('-D3','19960503'),('-D27','19960427'),('D3','19960603'),('D27','19960527'),('10D','19960531'),('1W',('W2','19970106'),('-W2','19960108'),('M12','19961201'),('M2','19970201'),('-M2','19960201'),('Q4','19961001'),('Q2','19970401'),('-Q2','19960401')
;
with v as
(
select Expression,ExpectedValue,stuff(replace(replace(case when left(Expression,1) not in('+','-') then '+' else '' end + Expression,'+','|+'
),'-','|-'
),1,''
) + '|||' as Fmt
from @t
),p as
(
select Expression,Fmt,left(v.Fmt,charindex('|',v.Fmt,0)-1) as p1,substring(v.Fmt,0)+1,(charindex('|',0)+1)-1) - (charindex('|',0))
) as p2,replace(substring(v.Fmt,0)+1)+1,999
),'|',''
) as p3
from v
)
select @d as GivenDate,p.Expression,p.ExpectedValue,cast(v3.retp3 as date) as ReturnedValue,case when p.ExpectedValue = v3.retp3 then 'Match' else 'No Match' end as ValueCheck
from p
outer apply(values(case when substring(p1,2,1) = 'C' -- <Prefix><Unit>
then case substring(p1,3,2)
when 'D' then @d
when 'WD' then @d
when 'W' then dateadd(week,datediff(week,@d) + case when left(p1,1) = '+' then 1 else 0 end,0)
when 'M' then case when left(p1,1) = '+' then eomonth(@d) else dateadd(day,eomonth(@d,-1)) end
when 'Q' then dateadd(day,case when left(p1,1) = '+' then -1 else 0 end,dateadd(quarter,datediff(quarter,0))
when 'Y' then dateadd(year,datediff(year,0)
else ''
end
when isnumeric(substring(p1,1)) = 1 -- <Number><Unit>
then case when right(p1,2) = 'WD'
then dateadd(day,cast(replace(p1,'WD','') as int),@d)
else case right(p1,1)
when 'D' then dateadd(day,'D',@d)
when 'W' then dateadd(week,'W',@d)
when 'M' then dateadd(month,'M',@d)
when 'Q' then dateadd(quarter,'Q',@d)
when 'Y' then dateadd(year,'Y',@d)
end
end
when isnumeric(substring(p1,1)) = 0 -- <Unit><Number>
then case when substring(p1,right(p1,1)-1,dateadd(week,@d) - case when left(p1,1) = '-' then 1 else 0 end,0))
else case substring(p1,abs(cast(replace(p1,'') as int))-1,dateadd(month,datediff(month,@d) + case when abs(cast(replace(p1,'') as int)) < day(@d) then 1 else 0 end + case when sign(cast(replace(p1,'') as int)) = -1 then -1 else 0 end,0))
when 'W' then dateadd(week,datefromparts(year(@d) + case when abs(cast(replace(p1,'') as int)) <= datepart(week,@d) then 1 else 0 end + case when sign(cast(replace(p1,1))),0)
when 'M' then datefromparts(year(@d) + case when abs(cast(replace(p1,'') as int)) <= month(@d) then 1 else 0 end + case when sign(cast(replace(p1,'') as int)),1)
when 'Q' then datefromparts(year(@d) + case when abs(cast(replace(p1,'') as int)) <= datepart(quarter,((abs(cast(replace(p1,'') as int))-1)*3)+1,1)
when 'Y' then datefromparts(abs(cast(replace(p1,1)
end
end
else ''
end
)
) as v1(retp1)
outer apply(values(case right(p2,1)
when 'D' then dateadd(day,cast(replace(replace(p2,''),retp1)
when 'W' then dateadd(day,cast(replace(p2,'') as int) * 7,retp1)
when 'M' then dateadd(month,retp1)
when 'Q' then dateadd(quarter,retp1)
when 'Y' then dateadd(year,retp1)
else retp1
end
)
) as v2(retp2)
outer apply(values(case right(p3,1)
when 'D' then dateadd(day,cast(replace(replace(p3,retp2)
when 'W' then dateadd(day,cast(replace(p3,retp2)
when 'M' then dateadd(month,retp2)
when 'Q' then dateadd(quarter,retp2)
when 'Y' then dateadd(year,retp2)
else retp2
end
)
) as v3(retp3);
输出
给定日期 | 表达 | 预期价值 | 返回值 | 值检查 |
---|---|---|---|---|
1996-05-21 | -CW+1W+1D | 1996-05-28 | 1996-05-28 | 匹配 |
1996-05-21 | CQ+1M-10D | 1996-07-20 | 1996-07-20 | 匹配 |
1996-05-21 | +CW+1D | 1996-05-28 | 1996-05-28 | 匹配 |
1996-05-21 | +CW+1WD | 1996-05-28 | 1996-05-28 | 匹配 |
1996-05-21 | CM+30D | 1996-06-30 | 1996-06-30 | 匹配 |
1996-05-21 | -WD2 | 1996-05-14 | 1996-05-14 | 匹配 |
1996-05-21 | WD3 | 1996-05-22 | 1996-05-22 | 匹配 |
1996-05-21 | -D3 | 1996-05-03 | 1996-05-03 | 匹配 |
1996-05-21 | -D27 | 1996-04-27 | 1996-04-27 | 匹配 |
1996-05-21 | D3 | 1996-06-03 | 1996-06-03 | 匹配 |
1996-05-21 | D27 | 1996-05-27 | 1996-05-27 | 匹配 |
1996-05-21 | 10D | 1996-05-31 | 1996-05-31 | 匹配 |
1996-05-21 | 1W | 1996-05-28 | 1996-05-28 | 匹配 |
1996-05-21 | W2 | 1997-01-06 | 1997-01-06 | 匹配 |
1996-05-21 | -W2 | 1996-01-08 | 1996-01-08 | 匹配 |
1996-05-21 | M12 | 1996-12-01 | 1996-12-01 | 匹配 |
1996-05-21 | M2 | 1997-02-01 | 1997-02-01 | 匹配 |
1996-05-21 | -M2 | 1996-02-01 | 1996-02-01 | 匹配 |
1996-05-21 | 第四季度 | 1996-10-01 | 1996-10-01 | 匹配 |
1996-05-21 | 第二季度 | 1997-04-01 | 1997-04-01 | 匹配 |
1996-05-21 | -Q2 | 1996-04-01 | 1996-04-01 | 匹配 |