I`m really stuck on this one.
I have a table with contracts, and one with items.
What Im trying to achieve is to see the total quantity of an item on contracts in a date range for every day.
For example, item1 appears on 3 contracts on Monday and the total quantity is 30.
Currently, Im doing a while loop for each day in the selected date range and using this query
$itemQty = ContItems::find()
->where(['ITEMNO' => $itemNo])
->andWhere(['<=' , 'HIREDATE' , $today])
->andWhere(['>=' , 'ESTRETD' , $today])
->andWhere(['or',
['STATUS' => 0],
['STATUS' => 1]
])
->SUM('QTY');
It is working fine, but I want to check for every item I have for each day in the selected date range, and the number of queries goes up to 50k+ if I do that.
What I tried to do is selected every contract in a date range and stored in an array
Every contract has a start and end date and I want to select every contract where the start or end date is in the date range I specified.
$ContItemsArray = ContItems::find()
->where(['or',
['<=' , 'HIREDATE' , $dateFrom],
['<=' , 'HIREDATE' , $dateTo],
['>=' , 'HIREDATE' , $dateFrom],
['>=' , 'ESTRETD' , $dateTo],
['>=' , 'ESTRETD' , $dateFrom]
])
->andWhere(['or',
['STATUS' => 0],
['STATUS' => 1]
])
->asArray()
->all();
And use this code to count the quantities
$theQty = 0;
foreach ($ContItemsArray as $subArray)
{
if ($subArray['ITEMNO'] == $itemNo && ($subArray['HIREDATE'] <= $today && $subArray['ESTRETD'] >= $today) && ($subArray['STATUS'] <= 1))
{
$theQty += $subArray['QTY'];
}
else
{
$theQty += 0;
}
}
But it is returning incorrect numbers, contracts are missing for some reason and I can`t figure it out why.
I tried to change around the query for the $ContItemsArray but with no luck.
I hope I was clear enough with the question.
I would appreciate any help.
Edit:
ContItems table has all the contracts with the item number 'ITEMNO' start date 'HIREDATE' end date 'ESTRETD' and the quantity 'QTY' fields.
And I have an array with all the item numbers what I want to check.
The expected result would be, if the day I`m checking is between the contract start date and end date, sum the qty of the items.
I solved the problem, had to use date('Y-m-d') and strtotime on the dates coming from the table (HIREDATE, ESTRETD) and now it is working fine
Related
I have a Model Charter that hasMany BlackoutRanges. I am trying to setup a scope to return charters that do not have a blackoutRange created for 3 dates. In our system a blackoutRange is a single day.
Pseudocode would look something like this:
// query where doesnt have blackoutDates on all three of the following dates:
// start date === date passed in
// start date === date passed in plus 1 days
// start date === date passed in plus 2 days
I tried to do some logical grouping and came up with this and I also logged my raw sql query to see what it looks like:
$dayTwo = $date->copy()->addDays(1);
$dayThree = $date->copy()->addDays(2);
return $query->whereDoesntHave("blackoutRanges", function(Builder $subQuery) use($date, $dayTwo, $dayThree){
$temp = $subQuery->where(function($queryOne) use($date) {
return $queryOne->whereDate('start', $date)->where('trip_id', NULL);
})->where(function($queryTwo) use($dayTwo) {
return $queryTwo->whereDate('start', $dayTwo)->where('trip_id', NULL);
})->where(function($queryThree) use($dayThree) {
return $queryThree->whereDate('start', $dayThree)->where('trip_id', NULL);
});
logger($temp->toSql());
return $temp;
});
select * from `blackout_ranges` where `charters`.`id` = `blackout_ranges`.`charter_id` and (date(`start`) = ? and `trip_id` is null) and (date(`start`) = ? and `trip_id` is null) and (date(`start`) = ? and `trip_id` is null)
I've also logged the dates passed in and they are:
[2022-06-07 19:00:58] local.DEBUG: 2022-06-09 00:00:00
[2022-06-07 19:00:58] local.DEBUG: 2022-06-10 00:00:00
[2022-06-07 19:00:58] local.DEBUG: 2022-06-11 00:00:00
An example of the start column of a BlackoutRange would be: 2022-07-21 04:00:00
Would the fact that they are not matching timestamps be a reason for this scope not working?
And how do I know it's not working? I added blackoutRanges for a specific charter for the entire month of June and it's still being returned.
I am looking for any advice on this one as I've been plugging away now for a day and a half and have gotten no where.
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]);
I'm in the process of evaluating the proposed solutions on SO related to the sum of overlapping datetimes in MySQL. I wasn't able to find out a silver-bullet solution, so would like to know if any classic/industrial-grade algorithmic procedure is available or if a custom-made needs to be developed.
Total should be 8 hours (4+4).
Proposed solution through MySQL
function final_balance($teacher_id, $aa, $teaching_id=0) {
$dbo = $this->Attendance->getDataSource();
$years=$this->Attendance->Student->Year->find('list', array('fields' => array('anno', 'data_from')));
$filteraa='attendances.start>="'.$years[$aa].'"';
$this->query('SET #interval_id = 0');
$this->query('SET #interval_end = \'1970-01-01 00:00:00\'');
$sql='SELECT
MIN(start) as start,
MAX(end) as end
FROM (
SELECT
#interval_id := IF(attendances.start > #interval_end, #interval_id + 1, #interval_id) AS interval_id,
#interval_end := IF(attendances.start < #interval_end, GREATEST(#interval_end, attendances.end), attendances.end) AS interval_end,
attendances.start,
attendances.end
FROM attendances
INNER JOIN attendance_sheets ON (
attendance_sheet_id = attendance_sheets.id AND
attendance_sheets.teacher_id='.$teacher_id.' AND '.$filteraa.' AND
attendance_sheet_status_id = 2 AND
attendance_status_id!=3'.
($teaching_id?' AND attendances.teaching_id IN ('.$teaching_id.')':'').'
)
ORDER BY attendances.start,attendances.end
) intervals GROUP BY interval_id';
// final query to sum in the temp table
$finalStatement =array(
'table' => $dbo->expression('('.$sql.')')->value,
'alias' => 'Attendance',
'fields' => array(
'DATE_FORMAT(start, \'%d/%m/%Y\') as data',
'DATE_FORMAT(start, \'%m-%Y\') as datamese',
'DATE(start) as datasql',
$teacher_id.' AS teacher_id',
'DAY(start) as giorno',
'MONTH(start) as mese',
'YEAR(start) as anno',
'SEC_TO_TIME(SUM((TIME_TO_SEC(end) - TIME_TO_SEC(start)))) as ore',
),
'conditions' => array(),
'limit' => null,
'group' => array('CONCAT(YEAR(start),MONTH(start))', 'DATE(start) WITH ROLLUP'),
'order' => null
);
$finalQuery= $dbo->buildStatement($finalStatement, $this->Attendance);
return $this->Attendance->query($dbo->expression($finalQuery)->value);
}
References
Sum amount of overlapping datetime ranges in MySQL
performs a different task
MySQL: sum time ranges exluding overlapping ones
and
MySQL: sum datetimes without counting overlapping periods twice
both seems to me like not considering all the cases
GeeksForCode: Merge Overlapping Intervals
Depending on the circumstances, the following might be useful and efficient.
Create another table that has one row per hour. Inner join that table with your table while selecting only the new column and dedupe the rows.
You can keep increasing the resolution (eg. to minutes or seconds), but that might make your code run slow.
Here's what I'm trying to do. I'm trying to select from a forum views table all of the user_ids where there are 5 or more records. That's fairly easy (this is Zend):
$objCountSelect = $db->select()
->from(array('v' =>'tbl_forum_views'), 'COUNT(*) AS count')
->where('u.id = v.user_id')
->having('COUNT(user_id) >= ?', 5)
;
But I need to somehow connect this to my users table. I don't want to return a result if the count is greater than 5. I tried this:
$objSelect = $db->select()
->from(array('u' => 'tbl_users'), array(
'id as u_id',
'count' => new Zend_Db_Expr('(' . $objCountSelect . ')'),
))
;
But that returns a record for every user, leaving blank the count if it's less than or equal to 5. How do I exclude the rows where the count is less than or equal to 5?
I figured it out, but wanted to post the answer in case someone else had the same issue. I added:
->having('count > 0')
to the second select and now it works.
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")