Improve the speed of this MYSQL query - php

I am using PHP and MYSQL to graph call concurenncy from an Asterisk CDR database,
I currently use the following prepared statement:
$query=$cdrdb->prepare('select count(acctid) from cdr where calldate between ? and ? or DATE_ADD(calldate, INTERVAL duration SECOND) between ? and ?');
and then the following foreach loop to enter the variables:
foreach ($timerange as $startdatetime){
$start=$startdatetime->format("Y-m-d H:i:s");
$enddatetime=new DateTime($start);
$enddatetime->Add($interval);
$end=$enddatetime->format("Y-m-d H:i:s");
if(!$query->execute(array($start, $end, $start, $end))){
echo "Execute failed: (" . $stmt->errno . ") " . $stmt->error;
}
if (!($res = $query->fetchall())) {
echo "Getting result set failed: ";
}
array_push($callsperinterval,$res[0][0]);
}
Timerange can be every hour for a day, every day for a month or every week for a year.
the calldate column is marked as an index column.
The table currently holds 122000 records.
the result of running an EXPLAIN on the query:
mysql> explain select count(acctid) from cdr where calldate between '2014-10-02 23:30:00' and '2014-11-03 00:00:00' or DATE_ADD(calldate, INTERVAL duration SECOND) between '2014-10-02 23:30:00' and '2014-11-03 00:00:00';
+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+
| 1 | SIMPLE | cdr | ALL | calldate | NULL | NULL | NULL | 123152 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+
A single run of the query takes around 0.14s so for a 24 hour period with an hourly interval the script should finish in about 3.36 seconds, but it ends up taking about 12 seconds
Currently the whole process can take up to 20 seconds to run for a 24 hour period,can anyone please help me to improve the speed of this query?

This part is the bottleneck in your query:
DATE_ADD(calldate, INTERVAL duration SECOND)
This is because MySQL is performing "math" on each row of the first subset determined from your first WHERE condition every row on your entire table that didn't match the first part of your WHERE statement since you are using WHERE OR, not WHERE AND.
I assumed your table looks something a little like:
acctid | calldate | duration
========================================
1 | 2014-12-01 17:55:00 | 300
... etc.
Consider rewriting your schema such that you are not using intervals that MySQL must calculate for each row, but full DateTime columns that MySQL can perform immediate comparisons on:
acctid | calldate | duration_end
==================================================
1 | 2014-12-01 17:55:00 | 2014-12-01 18:00:00
To rewrite this schema, you can make that new column and then do (this may take a while to process but will serve you well in the long run):
UPDATE cdr SET duration_end = DATE_ADD(calldate, INTERVAL duration SECOND);
Then scrap the duration column and rewrite your application to save into the new column!
Your resulting query will be:
select count(acctid) from cdr where calldate > ? and (calldate < ? or duration_end between ? and ?)
Assuming that nothing can change in the schema, then you're stuck with that function. However, you can try having MySQL work with subsets so that it's not doing math on so many rows:
select
count(acctid)
from
cdr
where
calldate > ? and
(calldate < ? or DATE_ADD(calldate, INTERVAL duration SECOND) between ? and ?)
I can't guarantee much of a performance increase from this solution although it may be a noticeable one depending on your data set.

For asterisk cdrs you can just do like this
Let say you used:
$query=$cdrdb->prepare('select count(acctid) from cdr where calldate between ? and ? or DATE_ADD(calldate, INTERVAL duration SECOND) between ? and ?');
$query->execute(array($start, $end, $start, $end))
You have use like this
$query=$cdrdb->prepare('select count(acctid) from cdr where calldate between ? and DATE_ADD(?, interval ? SECOND) and (calldate between ? and ? or DATE_ADD(calldate, INTERVAL duration SECOND) between ? and ?)
');
$MAX_CALL_LENGHT_POSIBLE = 60*60*10; # usualy 10 hr is not reachable on most calls. If you limit it in call, you can decrease to even less values
$query->execute(array($start, $end,$MAX_CALL_LENGHT_POSIBLE,$start,$end $start, $end))
So just first limit query to interval where that stop_time can be.
But much simple will be add column call_end_time and create trigger
DROP TRIGGER IF EXISTS cdr_insert_trigger;
DELIMITER //
CREATE TRIGGER cdr_insert_trigger BEFORE INSERT ON cdr
FOR EACH ROW BEGIN
Set NEW.call_end_time=DATE_ADD(OLD.calldate,interval OLD.duration second);
END//
DELIMITER ;
Sure you need create index on BOTH calldate and call_end_time column and use Union instead of OR(otherwise one part will not use index)

If disk space is less important than speed, try:
ALTER TABLE cdr ROW_FORMAT = FIXED;

Related

Find Records form date field and time field in one day before in mysql

I have the following structure as a table, I want to select the record from one day before, I have tried the following query for selecting the record.
SELECT slot_date, slot_time FROM slot_booking WHERE slot_date =
DATE_SUB(CURDATE(), INTERVAL 24 HOUR)
assume if today is (2018-04-03) time is 09 (24hr Format) I want to select a record from DB 2018-04-04 TIME between 09:00:00 to 9.30:00
i am struggling with compare time field, if you have any suggestion or solution post you answer
Concat date and time fields, use str_to_date to convert to datetime then use date_add or date_sub as required
SET #DT = '2018-04-14';
SET #T = '09:00:00';
set #now = '2018-04-13 09:00:00';
SELECT STR_TO_DATE(CONCAT(#DT,#T),'%Y-%m-%d%H:%i:%s') dt,
case when STR_TO_DATE(CONCAT(#DT,#T),'%Y-%m-%d%H:%i:%s')
between date_add(#now, interval 24 hour) and date_add(date_add(#now, interval 24 hour),interval 30 minute) then 'true'
else 'false'
end as inrange;
+---------------------+---------+
| dt | inrange |
+---------------------+---------+
| 2018-04-14 09:00:00 | true |
+---------------------+---------+
1 row in set (0.00 sec)

How to determine if a giving start and end time has expire in php

Please i want to create a php code to check if time has expired or still active based on the start date and end time that users specified.
Below is the database structure and sample php code which i tried.
game_config
game_id | start_date | start_t | start_h | end_time | end_h | status
--------|------------|---------|---------|----------|-------|---------
100 | 03/26/2018 | 10:45 | PM | 12:30 | AM | 0
101 | 03/27/2018 | 09:23 | AM | 11:10 | AM | 0
Php code sample
<?php
$conf_execution_date = date('m/d/Y'); /*Current date*/
$conf_execution_meridiem = date('A'); /*Current meridiem*/
$conf_execution_timer = date('h:m'); /*Current time*/
$conf_execution_proccess = false; /*Proceed or not*/
$conf_handler = $appconn->prepare("
SELECT * FROM game_config WHERE game_id = :game_id AND start_date = :start_dateAND start_t = :start_t AND status = 0 LIMIT 1
");
$conf_handler->bindParam(":game_id", 100);
$conf_handler->bindParam(":start_date", $conf_execution_date);
$conf_handler->bindParam(":start_t", $conf_execution_meridiem);
$conf_handler->execute();
$appconf = $conf_handler->fetch(PDO::FETCH_OBJ);
if($appconf){
$config_start_time = strtotime($appconf->start_t);
$config_end_time = strtotime($appconf->end_time);
$current_time = strtotime($conf_execution_timer);
if($appconf->start_h == $conf_execution_meridiem){ /*AM-PM*/
if($config_start_time >= $current_time){ /*Check if the start time is now o still active*/
$conf_execution_proccess = true;
}
}
if($appconf->end_h == $conf_execution_meridiem){ /*AM-PM*/
if($config_end_time >= $current_time){ /*Check if the time has ended*/
$conf_execution_proccess = false;
}
}
if($conf_execution_proccess == true){
echo 'YES THE GAME IS STILL ACTIVE';
}else{
echo "NO GAME HAS ENDED";
}
}
?>
Assuming your start_date column is type VARCHAR():
Assuming your end_time is always later in the day than your start_time:
You can use this sort of query to get a starting and ending DATETIME value for each row of your table. It glues together the date, the time, and the AM/PM fields and converts them to DATETIME. (http://sqlfiddle.com/#!9/2d84ea/1/0)
SELECT game_id,
STR_TO_DATE(CONCAT(start_date,'-',start_t,start_h), '%m/%d/%Y-%h:%i%p') start_ts,
STR_TO_DATE(CONCAT(start_date,'-',end_time,end_h), '%m/%d/%Y-%h:%i%p') end_ts,
status
FROM game_config
Then you can compare those start_ts and end_ts values to NOW() by appending this line to your query
Then you can fill out your query with the criteria you need, giving this query.
SELECT game_id,
STR_TO_DATE(CONCAT(start_date,'-',start_t,start_h), '%m/%d/%Y-%h:%i%p') start_ts,
STR_TO_DATE(CONCAT(start_date,'-',end_time,end_h), '%m/%d/%Y-%h:%i%p') end_ts,
status
FROM game_config
WHERE game_id = : gameid AND status = 0
HAVING start_ts <= NOW() and NOW() < end_ts
LIMIT 1
But, your start and end times sometimes span midnight, which means your end_ts needs to be on the next day. That makes your date-checking logic quite a bit trickier. You'll have to do some sort of conditional expression in which you do + INTERVAL 1 DAY to your end_ts if it is before your start_ts.
You will be much better off (#eggyal pointed this out in his comment) if you put the following columns in your table.
game_id int
start_ts timestamp of start
end_ts timestamp of end
status int
Then you don't have to worry about all the stuff about making a proper timestamp from three different columns, and you don't have to do anything special when the start and end times span midnight.
And, this will work intuitively no matter what timezone your users are in.

postgresql query in comparing dates with duration stored in other column

I have a table like
------------------------------
id | created_date | duration
------------------------------
duration is no.of days, now I want to select records that are created_date(timestamp)+duration(integer) > current time
I tried this in my where clause
"select * from table where (created_date + duration days) > now()"
now resultset is empty, I have records that should come out for my requirement, I suspect my query is not right, help me get it right.
Very close. I would do this as:
select *
from table
where created_date > now() - duration * interval '1 day'

writing a row for each day. mySQL

i've a web form, the user input two dates (YYYY/MM/DD) representing a interval and other informations about an activity on a day.
EXAMPLE: he select 2013/09/12 to 2013/10/02 activity: Swimming
in that case my table will look like:
date | activity
2013/09/12 | swimming
2013/09/13 | swimming
2013/09/14 | swimming
2013/09/15 | swimming
2013/09/16 | swimming
and so on....
i've to write a row with the activity for every day of the date inveral provided by the user
can i do this without iterate through days manually?
i'm using php+mysqli
EDIT: i can't use two columns for start and end date
I think it is better to make two columns (e.g start_day & end_day).
If it is not possible to change your DB construction you can try this:
PHP:
$last_date = $start_date;
$ready = false;
while ($ready === false) {
$last_date = date('Y-m-d', strtotime($last_date . ' + 1 day'));+
// add $last_date to your mysql table here
if ($last_date == $stop_date) {
$ready = true;
}
}
It's untested but I hope it works ;)
Use 2 different date columns in your table; one for the start date and one for the end date, you can then add the beginning and end dates of the activity to each of these respectively.
i can give You a clue if You can use PROCEDURES:
DELIMITER $$
DROP PROCEDURE IF EXISTS `dowhile_thing`$$
CREATE DEFINER=`root`#`localhost` PROCEDURE `dowhile_thing`(dt_start DATE,dt_stop DATE, thing CHAR(20))
BEGIN
DECLARE v1 INT DEFAULT 0;
CREATE TEMPORARY TABLE tmp (
`day_name` VARCHAR(256) DEFAULT NULL,
`date_change` DATE DEFAULT NULL,
thing CHAR(20)
) ENGINE=MYISAM DEFAULT CHARSET=utf8 ;
SELECT TIMESTAMPDIFF(DAY, dt_start, dt_stop)+1 INTO v1;
WHILE v1 > -1 DO
INSERT tmp (SELECT DAYNAME( ADDDATE(dt_start, INTERVAL v1 DAY) ),ADDDATE(dt_start, INTERVAL v1 DAY),thing );
SET v1 = v1 - 1;
END WHILE;
SELECT * FROM tmp;
DROP TEMPORARY TABLE IF EXISTS tmp;
END$$
DELIMITER ;
and then call:
CALL dowhile_thing('2013-06-10','2013-06-14','swim');
result:
day_name | date_change | thing
Saturday | 2013-06-15 | swim
Friday | 2013-06-14 | swim
...

unix_timestamp date and time difference

I have stored three essential timestamps and times into my database table, format as follows:
mysql> select receivedtime, requesttime, sla from table;
+---------------------+---------------------+----------+
| receivedtime | requesttime | sla |
+---------------------+---------------------+----------+
| 2013-05-26 22:37:04 | 2013-05-26 12:37:04 | 02:59:59 |
| 2013-05-26 14:36:44 | 2013-05-21 12:39:09 | 72:00:00 |
+---------------------+---------------------+----------+
2 rows in set (0.00 sec)
I need to put a few conditions like below:
difference = (receivedtime - requesttime);
if [difference <= sla]
{
show meet
} else {
show don't meet
}
legend:
receivedtime [timestamp] as because days
requesttime [timestamp] as because days
sla [time] as because hour
I have checked UNIX_TIMESTAMP() of mysql, strtotime() of php and few other tries.
I also have checked several threads # stackoverflow.com, but I can't find proper solution.
Any ideas...!
Check TIMESTAMPDIFF() and SEC_TO_TIME() functions:
SELECT
`receivedtime`,
`requesttime`,
`sla`,
(SEC_TO_TIME(TIMESTAMPDIFF(SECOND, `requesttime`, `receivedtime`)) < `sla`) as `meet`
FROM
`table`;
Try
SELECT receivedtime,
requesttime,
CASE WHEN TIMESTAMPDIFF(SECOND, requesttime, receivedtime) < TIME_TO_SEC(sla)
THEN 'meet' ELSE 'don\'t meet' END sla
FROM table1
Here is SQLFiddle demo
Try selecting from your database like this ...
select TIMESTAMPDIFF(SECOND, receivedtime, requesttime) as difference, TIME_TO_SEC(sla) as slasecs from table
and then you can do your.
if [difference <= sla]
{
show meet
}
else
{
show don't meet
}
That is a datetime field, not a timestamp field. You should start with reading up on what a timestamp is.
you can figure out the difference by converting to timestamp then subtracting.
$recieved = strtotime($res['receivedtime']);
$requested = strtotime($res['requesttime']);
$difference = $recieved - $requested;
if($difference <= $sla){
//do stuff
}
You can do that in SQL
SELECT (UNIX_TIMESTAMP(receivedtime) - UNIX_TIMESTAMP(requesttime)) AS difference FROM table
it gives you the difference in seconds.
Try this
select TIMESTAMPDIFF(SECOND,`requesttime`,`receivedtime`) as timeDiff FROM `table`
WHERE TIMESTAMPDIFF(SECOND,`requesttime`,`receivedtime`) > TIME_TO_SEC(`sla`)
SEE SQL FIDDLE
You can find relevant in mysql documentation

Categories