DBIx :: Class / SQL :: Abstract将SQL-Function应用于WHERE子句中的列

时间:2016-07-04 09:01:02

标签: postgresql perl dbix-class

我只是摆弄了使用DBIx::Class生成的复杂SQL查询(对于WHERE子句,它使用了SQL::Abstract。)

我正在使用BETWEEN运算符来过滤条目:

# SELECT title FROM tracks WHERE begin BETWEEN '2016-07-01' AND '2016-07-02'
$searchArgs->{'begin'} = { BETWEEN => ['2016-07-01', '2016-07-02' ] };

这将在2016-07-02实际上跳过开始日期。可能是因为这些开始值已经过了2016-07-02的午夜(00:00),因此不在BETWEEN范围内(参见最后的示例数据)。

在SQL端,这很容易解决:

SELECT title FROM tracks WHERE DATE(begin) BETWEEN '2016-07-01' AND '2016-07-02'
# Note the DATE()-cast of begin

有没有办法用SQL::Abstract语法实现这一目标?我尝试将列名称作为标量引用($searchArgs->{\"DATE(begin)"})以使其不受影响地传递,但它没有用完。

仅使用$searchArgs->{'DATE(begin)'}会触发SQL-Error,因为没有这样的列“DATE(begin)”。

# Minimal example data
CREATE TABLE test ( title VARCHAR(255), begin timestamp );
INSERT INTO test (title, begin) VALUES ( 'Track 1', '2016-07-01T12:00:00'::timestamp), ( 'Track 2', '2016-07-02 12:00:00'::timestamp );
SELECT title FROM test WHERE begin BETWEEN '2016-07-01' AND '2016-07-02';
SELECT title FROM test WHERE DATE(begin) BETWEEN '2016-07-01' AND '2016-07-02';

1 个答案:

答案 0 :(得分:4)

问题似乎是'2016-07-02'之类的日期等同于'2016-07-02T00:00:00',而'2016-07-02T12:00:00' 大于。调用DATE()只会截断时间值,以便丢弃额外的12小时

我建议您放弃BETWEEN,并使用等效的>=<=运算符。您实际上希望所有记录最多但不包括 '2016-07-03T0:00:00'(即,包括2016-07-02T23:59:59在内的所有内容以及您的数据库可能支持的任何分数)所以您应该写

SELECT title FROM test
WHERE begin BETWEEN >= '2016-07-01' AND begin < '2016-07-03';

或,在DBIx::Class

$searchArgs->{begin} = {
    '>=' => '2016-07-01',
    '<'  => '2016-07-03',
};

或者如果您只能访问结束日期并希望避免在Perl代码中使用日期算术,那么只需让数据库为您执行此操作

$searchArgs->{begin} = {
    '>=' => '2016-07-01',
    '<'  => \[ 'date(?) + 1', '2016-07-02' ],
};


如果你坚持在WHERE比较中使用函数而不是简单的表名,那么可以使用文字SQL,但它是不可取的,因为你将绕过数据库可以提供的所有优化,它将不得不诉诸线性搜索

DBIx::Class::Manual::Cookbook文档在Using SQL functions on the left hand side of a comparison

下有此内容
  

在比较的左侧使用SQL函数通常不是一个好主意,因为它需要扫描整个表。 (除非您的RDBMS支持表达式的索引 - 包括函数的返回值 - 并且您在相关函数的返回值上创建索引。)但是,必要时可以使用DBIx::Class通过使用文字SQL来完成