2

在我非常简单的查询中查找日期范围内的存款对象:

    String sqlQuery ="select d from Deposit d where status in('PENDING', 'SKIPPED') and  d.depositDate <= '${endDateString}'"

    def allUnprocessedDeposits = Deposit.executeQuery(sqlQuery)

不返回 depositDate 等于 endDateString 的行。(?!?) 例如,如果我将所有 d.depositDate 行更新为相同的日期,并将该日期作为 endDateString 提供,则不会返回任何行。

使用 grails 2.0.3 和 MySql 5.1...

感谢任何有答案的人。这看起来非常简单,但令人讨厌地失败了。

4

3 回答 3

3

当您只为 DateTime 字段指定一天时,mySQL 假定为午夜,因此午夜之后的任何时间都不会包含在您的结果中。你最好说 date < [end date + 1]

于 2012-07-20T19:24:26.887 回答
2

鉴于您提供的信息,最可能的解释是该depositDate列被定义为 DATETIME 或 TIMESTAMP。请注意,除了日期之外,这些数据类型还存储时间值(分辨率低至一秒)。例如

'2012-07-20 11:35:46'

DATETIME 值的比较 所以一个 DATE 文字(没有时间分量),例如

'2012-07-20 11:35:46' <= '2012-07-20'

相当于与时间值为午夜的 DATETIME 文字进行比较:

'2012-07-20 11:35:46' <= '2012-07-20 00:00:00'

这显然会返回 FALSE。这就是为什么您的查询没有返回您期望的行的最可能的解释。


建议修复:

对于 DATETIME 和 TIMESTAMP 列上的谓词,获取“天”的正常模式是从给定日期的午夜到第二天的午夜进行范围扫描。我们要检查的是该列是否小于第二天的午夜:

'2012-07-20 11:35:46' < DATE_ADD('2012-07-20', INTERVAL 1 DAY)

这相当于与 21 日午夜进行比较。

'2012-07-20 11:35:46' < '2012-07-21 00:00:00'

在您的情况下,由于您已经有了“结束日期”值,因此最简单的更改是简单地更改查询文本。

只需将一天添加到您传入的日期值,并将比较运算符从 更改<=<。(我在这里假设您只提供日期部分,并允许时间部分默认为午夜。)

... d.depositDate < DATE_ADD('${endDateString}',INTERVAL 1 DAY)"

我们更喜欢这种模式,因为它同样适用于DATE和。DATETIMETIMESTAMP

(注意:我们更喜欢这种模式的另一个原因是因为它适用于具有更精细分辨率的“时间点”值(例如 Microsoft SQL Server DATETIME,其精度低至 3 毫秒,并检查 <= 23:59.59结果是不够的。)

您的代码可以确保参数值仅是日期,或者是具有午夜时间分量的日期,但是通过将参数包装在 aa 中,很容易让 SQL 查询执行此操作CAST( AS DATE)

注意:您要避免将列引用包装d.depositDate在任何函数中,因为这将禁用 MySQL 执行索引范围扫描操作(以满足谓词)的能力。


注意:所有关于 SQL 注入漏洞的常见警告都适用于此处,如果参数值是由用户提供的,您希望使用绑定参数或转义提供的 .... 值

考虑当恶意用户提供如下值时会发生什么:

2012-07-20'; DELETE FROM Deposit ; SELECT '1

考虑哪些语句将被传递到数据库。有几种方法可以解决这个问题,以阻止此类攻击。

于 2012-07-20T20:16:38.170 回答
2

顺便说一句,您应该使用带参数的查询,它更安全:

String sqlQuery ="select d from Deposit d where status in('PENDING', 'SKIPPED') and  d.depositDate <= :endDate"

def allUnprocessedDeposits = Deposit.executeQuery(sqlQuery,[endDate:endDateVariable])
于 2012-07-20T19:34:06.640 回答