Yii2 DB Expression in ActiveQuery::andFilterWhere() - yii2

I have the following code which filters an ActiveQuery
// Start date condition
if ($this->start_date) {
$query->andWhere('event_datetime >= :start_date', [
'start_date' => $this->start_date,
]);
}
// End date condition
if ($this->end_date) {
$query->andWhere('event_datetime < :end_date + INTERVAL 1 DAY', [
'end_date' => $this->end_date,
]);
}
where $this->start_date and $this->end_date are either null, '', or in the form Y-m-d, and event_datetime is an indexed DATETIME column
Provided that both date properties are true, this code produces the following SQL
WHERE event_datetime >= :start_date
AND event_datetime < :end_date + INTERVAL 1 DAY
I can rewrite the start date condition as
$query->andFilterWhere(['>=', 'event_datetime', $this->start_date);
Is it possible to rewrite the end date condition simlarly, perhaps using some kind of DB expression?
I'd rather keep the '+ INTERVAL 1 DAY' in SQL, if possible, and still be able to use an index on event_datetime

Apart from your date is null or empty '', what I am trying to understand is that why are you not using DateTime class along with DateInterval to set the $this->end_date to the final date after adding the interval and compare it with your column event_datetime rather than getting confused about it.
$endDate = $this->end_date;
if ($this->end_date !==null && $this->end_date!=='') {
$endDateObj = new \DateTime($this->end_date);
$endDateObj->add(new \DateInterval('P1D'));
$endDate = $endDateObj->format('Y-m-d');
}
$query->andFilterWhere(['<','event_datetime', $endDate]);
BUT, if you are still feeling sleepy head and dizzy and not feel like doing it in PHP for having personal reasons like increase in Blood Pressure, insomnia or your body start shivering thinking of PHP :P you can do it via \yii\db\Expression() in the following way which will produce the query like WHERE created_at < '2019-10-22' + INTERVAL 1 DAY, i have tested it but not sure how accurate it will be so test it before you go for it.
$query->andFilterWhere(
[
'<',
'event_datetime',
new \yii\db\Expression("'$this->end_date' + INTERVAL 1 DAY")
]
);
EDIT
You have to check manually the value for null and empty before you use any of the methods above and that is the only way you can do that. A little addition is needed to control the blank or null value which wont be controlled in the Expression automatically and will give incorrect results so change to the following
$expression = null;
$isValidDate = null !== $this->end_date && $this->end_date !== '';
//prepare the expression if the date is not null or empty
if ($isValidDate) {
$expression = new Expression(
":my_date + INTERVAL 1 DAY",
[
':my_date' => $this->end_date,
]
);
}
$query->andFilterWhere(
[
'>',
'end_date',
$expression,
]
);

A supplemental answer to #Muhammad's, and supported by #Michal's comment on my original question
The ActiveQuery::andFilterWhere() function ignores empty operands, so we can set up our expression or manipulated value, $endDate, to remain empty when the original value is empty
either using PHP's DateTime
$endDate = $this->end_date ?
(new DateTime($this->end_date . ' + 1 day'))->format('Y-m-d') :
null;
or using Yii's Expression
$endDate = $this->end_date ?
new Expression(':end_date + INTERVAL 1 DAY', ['end_date' => $this->end_date]) :
null;
Then ActiveQuery::andFilterWhere() can be used as follows
$query->andFilterWhere(['<','end_date', $endDate]);

Related

How to select DB Expression as value using knex/Bookshelf

I'm trying to execute the following query using knex.js and MySql
SELECT
m.id,
TIME(date_created) AS `timestamp`,
u.username,
m.`message`
FROM
`messages` AS m
INNER JOIN users AS u ON u.id = m.user_id
WHERE
m.game_id IS NULL
AND m.`date_created` > DATE_SUB(
CURRENT_TIMESTAMP (),
INTERVAL 12 HOUR
)
ORDER BY
m.`date_created` ASC
LIMIT 50
For proper handling expressions in where closure such as DATE_SUB(CURRENT_TIMESTAMP(), INTERVAL 12 HOUR) according to documentation there is whereRow() method in knex.
I tried to use select() method as
select('messages.id', 'TIME(date_created) AS timestamp', 'users.username', 'messages.message')
But knex masks TIME(date_created) expression in a way it should to be a column name. Does anybody know a way to use a custom expressions in selects?
I did not found exact answer on my question but I've found a solution which allows me to move forward. I created separate model which uses standard Bookshelf(knex) export:
var Bookshelf = require('bookshelf')(knex);
module.exports.DB = Bookshelf;
And created separate method in that model where I could use DB.knex.raw() for masking DB expressions in SELECT. So I became able to write the query above in the following way:
var DB = require('./../db').DB;
var Messages = DB.Model.extend({
tableName: 'messages',
hasTimestamps: false,
idAttribute: 'id'
},
{
getMessagesHistory: function (gameId) {
return this.query().select('messages.id', DB.knex.raw('TIME(date_created) AS timestamp'), 'users.username', 'messages.message')
.innerJoin('users', 'messages.user_id', 'users.id')
.whereRaw("messages.game_id " + (gameId == 0 ? "IS NULL" : "= " + gameId))
.andWhereRaw("messages.date_created > DATE_SUB(CURRENT_TIMESTAMP(), INTERVAL 12 HOUR)")
.orderBy('messages.date_created', 'ASC')
.limit(50);
}
}
);
module.exports = Messages;
You can wrap any argument with knex.raw() to tell the framework that it's a raw piece of SQL:
select(
'messages.id',
knex.raw('TIME(date_created) AS timestamp'),
'users.username',
'messages.message',
)
See http://knexjs.org/#Raw-Bindings

Search data between two dates

I want to select the data between two dates and I wrote the query as follows:
SELECT *
FROM hospital_details
WHERE expirydate BETWEEN '03/13/2015' AND '03/18/2015'
But it also displays the results of 03/17/2016
How can I solve it?
You could write single syntax.
SELECT * FROM hospital_details WHERE ExpiryDate BETWEEN '$Date' AND '$b';
The active record where function will take an associative array;
$array = array('expirydate >= ' => $Date, 'expirydate <= ' => $b);
$this->db->where($array);
or a custome string;
$where = "expirydate > ='$Date' AND expirydate < ='$b'";
$this->db->where($where);
Obviously, make sure your variables are in the right format.
Active record docs here

Illuminate database query with date_sub

I'm struggling with a query using the Illuminate database query builder.
When I use the query the result is not as I expected.
When using the query from the querylog directly with mysql cli, I get the expected result.
With query builder:
->table('CompanyTools')
->select(
'CompanyTools.toolId',
$db->raw('COUNT(CompanyTools.toolId) as count')
)
->whereYear('CompanyTools.date', '>', 'YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))')
->groupBy('CompanyTools.toolId')
->orderBy('count', 'DESC')
->take(1)
->get();
Result:
Array ( [toolId] => 88 [count] => 55 )
With mysql cli:
select `CompanyTools`.`toolId`, COUNT(CompanyTools.toolId) as count from `CompanyTools`
where year(`CompanyTools`.`date`) > YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))
group by `CompanyTools`.`toolId`
order by `count` desc
limit 1
Result:
ToolId: 88
count: 17
If I (in the query builder) replace 'YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))'with 2013 I get:
Array ( [toolId] => 88 [count] => 17 )
Somehow the date_sub get ignored so the result includes all years
I tried with ->whereYear('CompanyTools.date', '>', $db->raw('YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))')) without any luck.
I guess I could use php to calculate the desired year, but I would rather get the query right.
Thx in advance
/ j
UPDATE
Replacing
->whereYear('CompanyTools.date', '>', 'YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))')
with
->where($db->raw('YEAR(CompanyTools.date)'), '>', $db->raw('YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))'))
solves it. Not clever enough to figure out why, but perhaps the whereYear function is supposed to be used diffently
As you already found out using
->where($db->raw('YEAR(CompanyTools.date)'), '>', $db->raw('YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))'))
Or alternatively
->whereRaw('YEAR(CompanyTools.date) > YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))')
solves the problem.
But why is that?
For every "normal" query, Laravel uses bindings. Obviously SQL functions like YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR)) don't work with bindings.
Normally, you can use DB::raw('YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))') and the Laravel won't use bindings. For example in where() (Expression is the class DB::raw() returns)
if ( ! $value instanceof Expression)
{
$this->addBinding($value, 'where');
}
But the whereYear() function doesn't do such a thing. It uses addDateBasedWhere() and just adds a binding without checking if the value is an instance of Expression
protected function addDateBasedWhere($type, $column, $operator, $value, $boolean = 'and')
{
$this->wheres[] = compact('column', 'type', 'boolean', 'operator', 'value');
$this->addBinding($value, 'where');
return $this;
}
This means the query will use bindings and therefore NOT execute the date calculation at all.

Zend Db Sql Where

Hi how can I do a query like this in zf2 with zend\db\sql?
Query:
SELECT * FROM table WHERE field = $field AND data > SUBDATE(NOW(), INTERVAL 1 DAY)
In ZF2
$select = $this->sql->select();
$select->from(self::MYTABLE)
->where(array('fiels' => $field))
->where(array('data > ' => 'SUBDATE(NOW(), INTERVAL '.$lifetime.' SECOND'));
$statement = $this->sql->prepareStatementForSqlObject($select);
return $statement->execute()->current();
change the line
->where(array('data > ' => 'SUBDATE(NOW(), INTERVAL '.$lifetime.' SECOND'));
to
->where(array('data > ?' => 'SUBDATE(NOW(), INTERVAL '.$lifetime.' SECOND'));
From the code snippet, it's seen you had missed the place holder for the parameter(?), include a question mark, I had mentioned the existing line of code and the modified code for quick reference
There's no parameter there so it doesn't need to be an array. Assuming you know $lifetime is a safe, integer value, try:
->where('data > SUBDATE(NOW(), INTERVAL '.$lifetime.' SECOND)');

Codeigniter And MYSQL DateTime

I Have a query as follows:
$query = $this->db->get_where('Bookings', array('Status' => 0, 'Driver_ID' => null, 'Date'=> 'NOW()'));
The Date field is a datetime type, and I want to be garbing records where their date is the same as today, however the above, and everything else I have tired does not work.
Can anyone one show me how to correctly pull records that date it today, ignoring the time part of datetime.
Thanks for any help
UPDATE
I Have now converted the query to the following
$start = date('Y-m-d 00:00:00');
$end = date('Y-m-d 23:59:59');
$query = $this->db->get_where('Bookings', array('Status' => 0, 'Driver_ID' => null, 'Date' => 'BETWEEN '.$start.' AND '.$end));
However still no luck, just retuning no results!
I would try
$this->db->where('Status', 0);
$this->db->where('Driver_ID', null);
$this->db->where('DATE(Date)', date('Y-m-d'), FALSE);
$query = $this->db->get('Bookings');
$params = array('Status' => 0, 'Booking_Date'=> date('Y-m-d'));
$this->db->query('SELECT * FROM Bookings WHERE Status=? AND Driver_ID Is Null AND Date(Booking_Date) = ?',$params);
Think that will work
In your code, you are saying you want a DateTime field to be NOW(). The problem is that NOW() gives a DateTime value, that is, a date in the form "YYYY-MM-DD" followed by a time in the form "HH:MM:SS".
What your query is doing is saying "Give me records where the Date is today, at this exact second". What you want is "Give me records where the Date is today".
This is why using DateTime fields in a database is usually cumbersome. You will have to convert your Date field to be just the date, without the time, using the MySQL function DATE(), and instead of NOW() which returns a DateTime value, you will want to use CURDATE() which returns only the Date value. I am not experienced with CodeIgniter specifically, but try:
$query = $this->db->get_where('Bookings', array('Status' => 0, 'Driver_ID' => null, 'DATE(Date)'=> 'CURDATE()'));
(I don't know if you can apply MySQL functions to fields with $this->db->get_where).
MySql standard datetime format is date("Y-m-d H:i:s").
So you query will need to get everything for the current day, so a where clause someything like;
SELECT * FROM `Bookings` WHERE `Date` BETWEEN date("Y-m-d 00:00:00") AND date("Y-m-d 23:59:59")