smalldatetime的MSSQL PreparedStatement返回错误结果

时间:2018-08-07 04:51:34

标签: sql-server jdbc mssql-jdbc smalldatetime

我的mssql数据库中有一个表,其中一列为smalldatetime数据类型。

smalldatetime数据类型不具有秒精度。按照其documentation

  

ss是两位数字,范围从00到59,代表第二位。小于等于29.998秒的值将四舍五入到最接近的分钟,大于等于29.999秒的值将四舍五入到最接近的分钟。“

我有一个表em_test,其中包含列description(varchar)和startTime(smalldatetime)。

I inserted following entries in table :
    insert into em_test values('e1-2018-08-01 23:59:59', '2018-08-01 23:59:59');
    insert into em_test values('e2-2018-08-02 00:00:00', '2018-08-02 00:00:00');
    insert into em_test values('e3-2018-08-02 01:00:00', '2018-08-02 01:00:00');
    insert into em_test values('e4-2018-08-02 23:59:59', '2018-08-02 23:59:59');
    insert into em_test values('e5-2018-08-03 00:00:00', '2018-08-03 00:00:00');

如果我运行查询:select * from em_test where startTime between '2018-08-02 00:00:00' and '2018-08-02 11:59:59',则会获得以下结果:

name                    startTime
e1-2018-08-01 23:59:59  2018-08-02 00:00:00 (Rounded up)
e2-2018-08-02 00:00:00  2018-08-02 00:00:00
e3-2018-08-02 01:00:00  2018-08-02 01:00:00
e4-2018-08-02 23:59:59  2018-08-03 00:00:00 (Rounded up)
e5-2018-08-03 00:00:00  2018-08-03 00:00:00

使用prepared语句的同一查询将返回不同结果

name                    startTime
e1-2018-08-01 23:59:59  2018-08-02 00:00:00 (Rounded up)
e2-2018-08-02 00:00:00  2018-08-02 00:00:00
e3-2018-08-02 01:00:00  2018-08-02 01:00:00

我调试了mssql-jdbc-6.4.0-jre7.jar源代码,以了解为什么普通sql查询和sql prepare语句返回的结果不同。     下面是分析:

  1. 无论何时必须执行准备好的语句,都会在内部创建包裹查询的运行时存储过程。查询输入参数变为存储过程输入参数。
  2. 在形成准备好的语句时,我们将smalldatetime值设置为ps.setTimeStamp('2018-08-01 23:59:59')作为时间戳。
  3. 形成运行时存储过程时,将确定输入params数据类型。 TimeStamp 映射到mssql datetime2
  4. 因此,对于datetime2,参数'2018-08-02 00:00:00' and '2018-08-02 11:59:59'不会四舍五入,并且不会返回最后两个结果。
  5. 试图以很少的日志来模拟运行时过程:
    create procedure smalldatetimetest @startDate datetime2, @endDate datetime2
    AS
        print 'Start Date in Datetime2 format : ' + Convert(varchar(50), @startDate);
        print 'End Date in Datetime2 format: ' + Convert(varchar(50), @endDate);
        print 'Start Date in smalldatetime format: ' + Convert(varchar(50), cast(@startDate as smalldatetime));
        print 'End Date in smalldatetime format: ' + Convert(varchar(50), cast(@endDate as smalldatetime));
        select * from em_test where datetime1 between @startDate and @endDate;
    GO 
    

执行exec smalldatetimetest '2018-08-02 00:00:00','2018-08-02 23:59:59'在日志下方显示:

Start Date in Datetime2 format : 2018-08-02 00:00:00.0000000
End Date in Datetime2 format: 2018-08-02 23:59:59.0000000
Start Date in smalldatetime format: Aug  2 2018 12:00AM
End Date in smalldatetime format: Aug  3 2018 12:00AM

我的理解正确吗?如果准备了陈述,是否有任何解决方法来解决差异?

1 个答案:

答案 0 :(得分:0)

以下变通办法似乎对我有用:

代替提供java.sql.Timestamp参数值

ps.setTimestamp(1, ts);

以字符串形式提供

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
ps.setString(1, sdf.format(ts));

这导致JDBC生成的查询使用nvarchar(4000)参数而不是datetime2参数

exec sp_executesql N'SELECT COUNT(*) AS n FROM #tmp WHERE foo<=@P0        ',N'@P0 nvarchar(4000)',N'2018-08-11 02:59:59.001'

并且结果似乎与具有字符串文字日期/时间值的纯文本查询的结果相匹配,就像您所提问的一样。