MySQL data - Count number of phone calls at the same time - php

I have a MySQL table with phone calls. Every row means one phone call.
Columns are:
start_time
start_date
duration
I need to get a maximum phone calls called at the same time. It's because of telephone exchange dimensioning.
My solution is to create two timestamp columns timestamp_start and timestamp_end. Then I run a loop second by second, day by day and ask MySQL something like:
SELECT Count(*) FROM tbl WHERE start_date IN (thisday, secondday) AND "this_second_checking" BETWEEN timestamp_start AND timestamp_end;
It's quite slow.
Is there a better solution? Thank you!
EDIT - I use this solution and it gives me proper results. There is used SQL layer dibi - http://dibiphp.com/cs/quick-start .
$starts = dibi::query("SELECT ts_start, ts_end FROM " . $tblname . " GROUP BY ts_start");
if(count($starts) > 0):
foreach ($starts as $row) {
if(isset($result)) unset($result);
$result = dibi::query('SELECT Count(*) FROM ' . $tblname . ' WHERE "'.$row->ts_start.'" BETWEEN ts_start AND ts_end');
$num = $result->fetchSingle();
if($total_max < $num):
$total_max = $num;
endif;
}
endif;
echo "Total MAX: " . $total_max;

Instead of running it second by second, you should for each row (phonecall) see what other phone calls were active at that time. After that you group all of the results by the row's ID, and check which has the maximum count. So basically something like this:
SELECT MAX(calls.count)
FROM (
SELECT a.id, COUNT(*) AS count
FROM tbl AS a
INNER JOIN tbl AS b ON (
(b.timestamp_start BETWEEN a.timestamp_start AND a.timestamp_end)
OR
(b.timestamp_end BETWEEN a.timestamp_start AND a.timestamp_end)
)
GROUP BY a.id
) AS calls
Creating an index on the timestamp columns will help as well.

I'm going to add something to #reko_t answer. I think there is a use case to consider.
Calls that start before and ended after - Calls completely overlapped
So, how about:
SELECT MAX(calls.count)
FROM (
SELECT a.id, COUNT(*) AS count
FROM tbl AS a
INNER JOIN tbl AS b ON (
(b.timestamp_start BETWEEN a.timestamp_start AND a.timestamp_end)
OR
(b.timestamp_end BETWEEN a.timestamp_start AND a.timestamp_end)
OR
(b.timestamp_start <= a.timestamp_start AND b.timestamp_end >= a.timestamp_end)
)
GROUP BY a.id
) AS calls

How about:
SELECT MAX(callCount) FROM (SELECT COUNT(duration) AS callCount, CONCAT(start_date,start_time) AS callTime FROM tbl GROUP BY callTime)
That would give you the max number of calls in a single "time". Assuming start_date and start_time are strings. If they're integer times, you could probably optimise it somewhat.

Related

Querying efficiently

I have two tables: Exam (ExamID, Date, Modality) and CT(ctdivol, ExamID(FK)) with the attributes in brackets.
Note: CT table has about 100 000 entries.
I want to calculate the average of ctdivol in a specific interval of dates.
I have this code that works but is too slow:
function get_CTDIvolAVG($min, $max) {
$values = 0;
$number = 0;
$query = "SELECT (unix_timestamp(date)*1000), examID
from exam use index(dates)
where modality = 'CT'
AND (unix_timestamp(date)*1000) between '" . $min . "' AND '" . $max . "';";
$result = mysql_query($query) or die('Query failed: ' . mysql_error());
while($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
$avg = "SELECT SUM(ctdivol_mGy), count(ctdivol_mGy)
from ct use index(ctd)
where examID ='" . $line["examID"] ."'
AND ctdivol_mGy>0;";
$result1 = mysql_query($avg) or die('Query failed: ' . mysql_error());
while ($ct = mysql_fetch_array($result1, MYSQL_ASSOC)) {
$values = $values + floatval($ct["SUM(ctdivol_mGy)"]);
$number = $number + floatval($ct["count(ctdivol_mGy)"]);
}
}
if ($number!=0) {
echo $values/$number;
}
}
How can I make it faster?
Use EXPLAIN to see the query execution plan.
For that first query, MySQL can't make effective use of a index range scan operation. That expression in the WHERE clause has to be evaluated for every row in the table. We get better performance when we do the comparison to a bare column. Do the manipulation on the literal side... get those values converted to the datatype of the column you're comparing to.
WHERE e.date BETWEEN expr1 AND expr2
For expr1, you need an expression that converts your $min value into a datetime. Just be careful of timezone conversions. I think this might do what you need for expr1:
FROM_UNIXTIME( $min /1000)
Something like:
WHERE e.date BETWEEN FROM_UNIXTIME( $min /1000) AND FROM_UNIXTIME( $max /1000)
Then we should see MySQL able to make effective use of an index with leading column of date. The EXPLAIN output should show range for the access type.
If the number of columns being returned is a small subset, consider a covering index. Then the EXPLAIN will show "Using index", which means the query can be satisfied entirely from the index, with no lookups to pages in the underlying table.
Secondly, avoid running queries multiple times in a loop. It is usually more efficient to run a single query that returns a single resultset, because of the overhead of sending the SQL to the database, that database parsing the SQL text, for valid syntax (keywords in the right places), valid semantics (identifiers reference valid objects), considering possible access paths and determining which is lowest cost, then executing the query plan, obtaining metadata locks, generating the resultset, returning that to the client, and then cleaning up. It's not noticeable for a single statement, but when you start running a lot of statements in a tight loop, it starts to add up. Couple that with an inefficient query, and it starts to get really noticeable.
IF examID column in exam is unique and not null (or its the PRIMARY KEY of exam, then it looks like you could use a single query, like this:
SELECT UNIX_TIMESTAMP(e.date)*1000 AS `date_ts`
, e.examID AS `examID`
, SUM(ct.ctdivol_mGy) AS `SUM(ctdivol_mGy)`
, COUNT(ct.ctdivol_mGy) AS `count(ctdivol_mGy)`
FROM exam e
LEFT
JOIN ct
ON ct.examid = e.examID
AND ct.ctdivol_mGy > 0
WHERE e.modality = 'CT'
AND e.date >= FROM_UNIXTIME( $min /1000)
AND e.date <= FROM_UNIXTIME( $max /1000)
GROUP
BY e.modality
, e.date
, e.examID
ORDER
BY e.modality
, e.date
, e.examID
For best performance of that, you'd want covering indexes:
... ON exam (modality, date, examID)
... ON ct (examID, ctdivol_mGy)
We'd want to see the EXPLAIN output; we'd expect that MySQL could make use of the index on exam to do the GROUP BY (and avoiding a "Using filesort" operation), and also make use of a ref operation on the index to ct.
To reiterate... that query requires that examID be the PRIMARY KEY of the exam table (or at least be guaranteed to be unique and non-null). Otherwise, the result from that can be different than the original code. Absent that gurantee, we could use either an inline view, or subqueries in the SELECT list. But in terms of performance, we don't want to go there without good reason to.
That's just some general ideas, not a hard and fast "this will be faster".
You can write a join on the first table to a subquery table by exam_id:
$query = "SELECT (unix_timestamp(date)*1000) as time_calculation, ed.examID, inner_ct.inner_sum, inner_ct.inner_count "
" FROM exam ed,"
. " ( SELECT SUM(ctdivol_mGy) as inner_sum, count(ctdivol_mGy) as inner_count, examID"
. " FROM ct"
. " WHERE ctdivol_mGy>0 ) inner_ct"
. " WHERE ed.modality = 'CT' AND time_calculation between"
. " '$min' and '$max'"
. " AND ed.examId = inner_ct.examID";
The ( SELECT . . .) inner_ct creates an in memory table you can join from. Useful if you're selecting composed data (sums in your case) across a join.
Conversely, you can use the following syntax:
$query = "SELECT (unix_timestamp(date)*1000) as time_calculation, ed.examID, inner_ct.inner_sum, inner_ct.inner_count "
" FROM exam ed,"
. " LEFT JOIN ( SELECT SUM(ctdivol_mGy) as inner_sum, count(ctdivol_mGy) as inner_count, examID"
. " FROM ct"
. " WHERE ctdivol_mGy>0 ) inner_ct"
. " ON ed.examID = inner_ct.examID"
. " WHERE ed.modality = 'CT' AND time_calculation between"
. " '$min' and '$max'";
You have not provided sample data in the question so we resort to assumptions in an attempt to answer. If there is only one exam row for many rows in ct - but an exam row can exist that has no ct rows at all - then this single query should provide the results required.
SELECT
exam.examID
, (unix_timestamp(exam.date) * 1000
, SUM(ct.ctdivol_mGy)
, COUNT(ct.ctdivol_mGy)
FROM exam
LEFT OUTER JOIN ct on exam.examID = ct.examID AND ct.ctdivol_mGy > 0
WHERE exam.modality = 'CT'
AND exam.date >= #min AND exam.date < #max
GROUP BY
exam.examID
, (unix_timestamp(exam.date) * 1000)
;
Note I am not attempting the PHP code, just concentrating on the SQL. I have used #min and #max to indicate the 2 dates required in the where clause. These should be of the same data type as the column exam.date so do those calculations in PHP before adding into the query string.
I want to calculate the average of ctdivol in a specific interval of
dates.
If you are trying to return a single figure, then this should help:
SELECT
AVG(ct.ctdivol_mGy)
FROM exam
INNER JOIN ct on exam.examID = ct.examID AND ct.ctdivol_mGy > 0
WHERE exam.modality = 'CT'
AND exam.date >= #min AND exam.date < #max
;
Note for this variant we probably don't need a left join (but again due to a lack of sample data and expected result that is an assumption).

SQL How do I sum up many records key:value pairs?

I need to obtain the sum of many values with the same key from a SQL Query.
For example...say I have 30 rows returned from a Query and they look like this.
1) (a:2), (b:4),(f:1), (h:3)
2) (c:2), (f:4),(t:1), (z:3)
3) (a:5), (b:2),(s:1), (z:3)
4) (d:2), (g:4),(s:1), (t:3)
and so on....
What I need to do is sum up all the "a" and all the "b" and all the "c" etc
And put all that into an array or object so I can access them for later processing.
$total = [(a:7),(b:6),(c:2),(d:2).....(z:6)];
Is there a way to do this all from SQL? Or do I have to sum all these records externally using PHP for example?
This is my SQL code to obtain the 30 records...
while($row = mysql_fetch_array($rs)) {
$queryActivity = "SELECT activity, count(activity) FROM users u, activities_users au, activities a WHERE ($row[zip] = u.zip AND u.id = au.user_id AND au.activity_id = a.id) GROUP BY a.activity";
$rs2 = mysql_query($queryActivity);
while($row2 = mysql_fetch_array($rs2)) {
echo "<tr><td>$row2[0]</td><td>";
echo "<tr><td>$row2[1]</td><td>";}
It returns all the activities and the sum of those activities for a certain zipcode. I just need to total all the sums up for each activity.
Is (a:2), (b:4)... is a row in the database?
You will need to use some sort of split, SUM, and GROUP BY's. It will be hard to help until we have more information.
So here is a quick query that will group by the first subset of the rows. You can do the same thing for every other subset. I don't know of a way to do what you want to do in pure mysql.
SELECT
SUBSTRING(activity, 1,2),
count(activity)
FROM users u, activities_users au, activities a
WHERE ($row[zip] = u.zip AND u.id = au.user_id AND au.activity_id = a.id)
GROUP BY SUBSTRING(activity, 1,2)
I am not sure I fully understand your requirement, but you can use the SUM() method in sql.
SELECT SUM(column_name) FROM table GROUP BY column_name
Add WITH ROLLUP to your query after GROUP BY clause:
SELECT activity, count(activity)
FROM users u, activities_users au, activities a
WHERE ($row[zip] = u.zip AND u.id = au.user_id AND au.activity_id = a.id)
GROUP BY a.activity WITH ROLLUP
It will add an extra row (null, totalsum) to the result set which you need to handle in PHP.
As alternative, you can calculate it with PHP inside your while loop:
$total[$row2[0]] = (empty($total[$row2[0]]) ? 0 : $total[$row2[0]]) + $row2[1]

Too heavy query execution

I have a Database with 187840 lines .
When i execute this query i have this message Query execution was interrupted
TOO HEAVY QUERY
SELECT days.day,count(U.sig_name) as number
FROM days
LEFT JOIN linked U ON
days.day = date(timestamp)
AND
U.sig_name REGEXP "^Tester"
GROUP BY days.day;
What is th solution ?
This is your query:
select days.day, count(U.sig_name) as number
from days left join
linked U
on days.day = date(timestamp) AND U.sig_name REGEXP "^Tester"
group by days.day;
You have a problem because of the function call around timestamp. You might find this version better:
select days.day,
(select count(*)
from linked u
where u.timestamp >= days.day an du.timestamp < date_add(days.day, interval 1 day) and
u.sig_name not like '%Tester%'
)
from days;
For performance, you want a composite index on linked(timestamp, sig_name). This eliminates the outer aggregation (the aggregation uses the index instead), and allows an index to be used for the matching.
You can handle massive data using LIMIT:
$limit_size = 10000;
$flag_done = false;
for ($i = 1; ! $flag_done; $i++) {
$queryString = "SELECT days.day,count(U.sig_name) as number from days left join linked U on days.day = date(timestamp) AND U.sig_name REGEXP "^Tester" group by days.day LIMIT $index*$limit_size, $limit_size";
if($result = mysql_query($queryString, $db)){
[WHAT YOU WANT TO DO WITH RESULT HERE]
} else $flag_done = true;
}

PHP Calculate rank from database

I got a little problem, I've got a database, in that database are different names, id, and coins. I want to show people their rank, so your rank has to be 1 if you have the most coins, and 78172 as example when your number 78172 with coins.
I know I can do something like this:
SELECT `naam` , `coins`
FROM `gebruikers`
ORDER BY `coins` DESC
But how can I get the rank you are, in PHP :S ?
You can use a loop and a counter. The first row from MySql is going the first rank,I.e first in the list.
I presume you want something like:
1st - John Doe
2nd - Jane Doe
..
..
right?
See: http://www.if-not-true-then-false.com/2010/php-1st-2nd-3rd-4th-5th-6th-php-add-ordinal-number-suffix
Helped me a while ago.
You could use a new varariable
$i = "1";
pe care o poti folosi in structura ta foreach,while,for,repeat si o incrementezi mereu.
and you use it in structures like foreach,while,for,repeat and increment it
$i++;
this is the simplest way
No code samples above... so here it is in PHP
// Your SQL query above, with limits, in this case it starts from the 11th ranking (0 is the starting index) up to the 20th
$start = 10; // 0-based index
$page_size = 10;
$stmt = $pdo->query("SELECT `naam` , `coins` FROM `gebruikers` ORDER BY `coins` DESC LIMIT {$start}, {$page_size}");
$data = $stmt->fetchAll();
// In your template or whatever you use to output
foreach ($data as $rank => $row) {
// array index is 0-based, so add 1 and where you wanted to started to get rank
echo ($rank + 1 + $start) . ": {$row['naam']}<br />";
}
Note: I'm too lazy to put in a prepared statement, but please look it up and use prepared statements.
If you have a session table, you would pull the records from that, then use those values to get the coin values, and sort descending.
If we assume your Session table is sessions(session_id int not null auto_increment, user_id int not null, session_time,...) and we assume that only users who are logged in would have a session value, then your SQL would look something like this: (Note:I am assuming that you also have a user_id column on your gebruikers table)
SELECT g.*
FROM gebruikers as g, sessions as s WHERE s.user_id = g.user_id
ORDER BY g.coins DESC
You would then use a row iterator to loop through the results and display "1", "2", "3", etc. The short version of which would look like
//Connect to database using whatever method you like, I will assume mysql_connect()
$sql = "SELECT g.* FROM gebruikers as g, sessions as s WHERE s.user_id = g.user_id ORDER BY g.coins DESC";
$result = mysql_query($sql,$con); //Where $con is your mysql_connect() variable;
$i = 0;
while($row = mysql_fetch_assoc($result,$con)){
$row['rank'] = $i;
$i++;
//Whatever else you need to do;
}
EDIT
In messing around with a SQLFiddle found at http://sqlfiddle.com/#!2/8faa9/6
I came accross something that works there; I don't know if it will work when given in php, but I figured I would show it to you either way
SET #rank = 0; SELECT *,(#rank := #rank+1) as rank FROM something order by coins DESC
EDIT 2
This works in a php query from a file.
SELECT #rank:=#rank as rank,
g.*
FROM
(SELECT #rank:=0) as z,
gebruikers as g
ORDER BY coins DESC
If you want to get the rank of one specific user, you can do that in mysql directly by counting the number of users that have more coins that the user you want to rank:
SELECT COUNT(*)
FROM `gebruikers`
WHERE `coins` > (SELECT `coins` FROM `gebruikers` WHERE `naam` = :some_name)
(assuming a search by name)
Now the rank will be the count returned + 1.
Or you do SELECT COUNT(*) + 1 in mysql...

Best way to sum and seperate by date in MYSQL with/witout php

Hi i have such table information:
what i want to do with php with while or just in mysql, is to SUM (time_used) of the rows with status 44 until its reached row with status 55. after that it should begin from start with new summing.
first query should return 37, second 76 (keep in mind it should be universal, for unlimited occurrences of 55 status row)
i thought of a way with time/date filtering and have this:
select sum(time_used) as sumed
from timelog
where start_time > (select end_time from timelog where (status='55')
ORDER BY id DESC LIMIT 1) ORDER BY id DESC
but this works only for last combination of 44 and 55
i know i will need two way filtering( < end_time and > end_time) so it will work for all cases, but cant think of a way to do it in php
can anyone help me?
EDIT:
sqlfiddle whoever want it:
http://sqlfiddle.com/#!2/33820/2/0
There are two ways to do it: Plain SQL or PHP. If you treat thousands of rows, it may be interresting to choose between the two by testing performance.
Plain SQL
select project_id, task_id, user_id, sum(time_used) as time_used,
min(start_time) as start_time, max(end_time) as end_time, max(comment) as comment from
(select t.id, t.project_id, t.task_id, t.user_id, t.time_used,
count(t2.id) as count55, t.start_time, t.end_time, t.comment
from timelog t
left join timelog t2 on t.id>t2.id and t2.status=55 and t.task_id=t2.task_id
group by t.id) as t
group by count55;
I assume here that a task can belong to one user only
SQL and PHP
$link = mysqli_connect( ... );
$query = "select id, project_id, task_id, user_id, time_used, start_time, end_time, status
from timelog order by id";
$result = mysqli_query($link, $query);
$table = array();
$time_used = 0;
$start_sum = true;
$i = 0;
while($row = mysqli_fetch_assoc ($result)){
if($start_sum){
$table[$i] = $row;
$start_sum = false;
} else {
$table[$i]['time_used'] += $row['time_used'];
$table[$i]['end_time'] += $row['end_time'];
}
if($row['state'] == 55){
$i++;
$start_sum = true;
}
}
If two tasks can run in simultaneously, solution 1 will work, but solution 2 will need to be adapted in order to take this in account.
here is my intepretation:
http://sqlfiddle.com/#!2/33820/45
set #n=0;
select project_id, task_id, user_id,sum(time_used) from (
SELECT time_used,project_id, task_id, user_id,
#n:=if(status=55,#n+1,#n),
if(status=55,-1,#n) as grouper FROM timelog
) as t
where grouper>-1
group by grouper;
I'm neither a php nor MySQL programmer, but I can explain the logic you want to follow. You can then code it.
First, query your db and return the results to php.
Next, set two sum variables to 0.
Start looping through your query results. Increment the first sum variable until you reach the first row that has status 55. Once you do, start incrementing the second variable.
The tricky part will be to sort your query by the row number of the table. Here is a link that will help you with that part.

Categories