我正在开发一个项目,需要计算 SQL Server(使用 Synapse)和 Oracle 中两个日期之间的月数。我在 SQL Server 中创建了一个用户定义的函数来复制 Oracle 的months_ Between 函数,但我在输出中遇到了轻微的差异,这些差异随着时间的推移而累积,并通过扩展在计算指标时产生巨大的差异。
我的尝试:
CREATE FUNCTION [TFM].[UDF_MONTHS_BETWEEN] (
@start_date_p [DATE]
,@end_date_p [DATE]
)
RETURNS FLOAT
AS
BEGIN
DECLARE @count_v FLOAT;
DECLARE @daysinmonth_v FLOAT;
DECLARE @dayofmonth_v FLOAT;
-- Count whole months
SET @count_v = DATEDIFF(MONTH, DATEADD(MONTH, 1, DATEFROMPARTS(YEAR(@start_date_p), MONTH(@start_date_p), 1)), DATEFROMPARTS(YEAR(@end_date_p), MONTH(@end_date_p), 1));
-- Days in start month
SET @daysinmonth_v = DAY(EOMONTH(@start_date_p));
-- Get fractional start month
SET @dayofmonth_v = DAY(@start_date_p);
SET @count_v = @count_v + ((@daysinmonth_v - (@dayofmonth_v - 1)) * (1.0 / @daysinmonth_v));
-- Days in end month
SET @daysinmonth_v = DAY(EOMONTH(@end_date_p));
-- Get fractional end month
SET @dayofmonth_v = DAY(@end_date_p);
SET @count_v = @count_v + ((@dayofmonth_v - 1) * (1.0 / @daysinmonth_v));
RETURN ABS(@count_v);
END
测试用例 | 输入日期 | Sql 服务器输出 | Oracle 输出 |
---|---|---|---|
测试1 | '2024年6月28日', '2024年7月27日' | 0.938 | 0.9677419354838709677419354838709677419355 |
测试2 | “2024 年 4 月 28 日”、“2024 年 7 月 27 日” | 2.9387 | 2.96774193548387096774193548387096774194 |
我需要它们尽可能接近。
要测试的代码:
sql server : SELECT TFM.UDF_MONTHS_BETWEEN('4/28/2024','7/27/2024')
oracle : SELECT ABS(MONTHS_BETWEEN('28-APR-24','27-JUL-24')) FROM DUAL;
正如 @AlexPoole 所指出的,MONTHS_BETWEEN
的 Oracle文档指出:
因此,您需要将所有月份的天数设置为 31(而不是该月的实际天数),并删除
MONTHS_BETWEEN
返回日期date1
和date2
之间的月数。月份和该月的最后一天由参数NLS_CALENDAR
定义。如果date1
晚于date2
,则结果为正。如果date1
早于date2
,则结果为负。如果date1
和date2
是该月的同一天或都是该月的最后一天,则结果始终是整数。否则,Oracle 数据库将基于 31 天的月份计算结果的小数部分,并考虑时间分量date1
和date2
的差异。
ABS
,因为当
date1
在
date2
之后时,结果应该为负数:
CREATE FUNCTION [TFM].[UDF_MONTHS_BETWEEN] (
@start_date_p [DATE]
,@end_date_p [DATE]
)
RETURNS FLOAT
AS
BEGIN
DECLARE @count_v FLOAT;
DECLARE @daysinmonth_v FLOAT;
DECLARE @dayofmonth_v FLOAT;
SET @daysinmonth_v = 31;
-- Count whole months
SET @count_v = DATEDIFF(MONTH, DATEADD(MONTH, 1, DATEFROMPARTS(YEAR(@start_date_p), MONTH(@start_date_p), 1)), DATEFROMPARTS(YEAR(@end_date_p), MONTH(@end_date_p), 1));
-- Days in start month
-- Get fractional start month
SET @dayofmonth_v = DAY(@start_date_p);
SET @count_v = @count_v + ((@daysinmonth_v - (@dayofmonth_v - 1)) * (1.0 / @daysinmonth_v));
-- Days in end month
-- Get fractional end month
SET @dayofmonth_v = DAY(@end_date_p);
SET @count_v = @count_v + ((@dayofmonth_v - 1) * (1.0 / @daysinmonth_v));
RETURN @count_v;
END
对于样本数据:
CREATE TABLE tests (
test_case VARCHAR(20),
start_date DATE,
end_date DATE,
oracle_output NUMERIC(30,26)
);
INSERT INTO tests (test_case, start_date, end_date, oracle_output)
VALUES (
'Test 1',
'2024-06-28',
'2024-07-27',
0.967741935483870967741935
),
(
'Test 2',
'2024-04-28',
'2024-07-27',
2.967741935483870967741935
),
(
'Test 3',
'2024-07-27',
'2024-04-28',
-2.967741935483870967741935
),
(
'Test 4',
'2024-07-31',
'2024-04-30',
-3
);
然后:
SELECT test_case,
start_date,
end_date,
[TFM].[UDF_MONTHS_BETWEEN](start_date, end_date) AS sql_server_output,
oracle_output
FROM tests;
输出:
注意:测试 4 显示当开始日期和结束日期都是该月的最后一天时 Oracle 的行为,然后 Oracle MONTHS_BETWEEN
将始终返回整数值。 OP 的代码中没有捕获此行为,并且实现它作为读者的练习。其他情况下的行为应该匹配。