on my ratings table for my software i have 4 fields.
id autoincrement
rvid vendor id
ratedate date of rating
rating the actual numeric rating
I have done alot with it over the last few months but this time im stumped and i cant get a clear picture in my head of the best way to do this. What i am trying to do is find out if the vendor has had 3 low 'consecutive' ratings. If their last three ratings have been < 3 then i want to flag them.
I have been playing with this for a few hours now so i thought i would ask (not for the answer) but for some path direction just to push me forward, im stuck in thought going in circles here.
I have tried GROUP BY and several ORDER BY but those attempts did not go well and so i am wondering if this is not a mysql answer but a php answer. In other words maybe i just need to take what i have so far and just move to the php side of things via usort and the like and do it that way.
Here is what i have so far i did select id as well at first thinking that was the best way to get the last consective but then i had a small breakthrough that if they have had 3 in a row the id does not matter, so i took it out of the query.
$sql = "SELECT `rvid`, `rating` FROM `vendor_ratings_archive` WHERE `rating` <= '3' ORDER BY `rvid` DESC";
which give me this
Array
(
[0] => Array
(
[rvid] => 7
[rating] => 2
)
[1] => Array
(
[rvid] => 5
[rating] => 1
)
[2] => Array
(
[rvid] => 5
[rating] => 0
)
[3] => Array
(
[rvid] => 5
[rating] => 3
)
)
this is just just samples i tossed in the fields, and there are only 4 rows here where as in live it will be tons of rows. But basically this tells me that these are the vendors that have low ratings in the table. And that is where i get stumpted. I can only do one sort in the query so that is why i am thinking that i need to take this and move to the php side to finish it off.
I think i need to sort the elements by rvid with php first i think, and then see if three elements in a row are the same vender (rvid).
Hope that makes sense. My brain hurts lol...
update - here is all of the table data using *
Array
(
[0] => Array
(
[id] => 7
[rvid] => 7
[ratedate] => 2016-05-01
[rating] => 2
)
[1] => Array
(
[id] => 8
[rvid] => 5
[ratedate] => 2016-05-01
[rating] => 1
)
[2] => Array
(
[id] => 6
[rvid] => 5
[ratedate] => 2016-05-01
[rating] => 0
)
[3] => Array
(
[id] => 5
[rvid] => 5
[ratedate] => 2016-05-01
[rating] => 3
)
)
Here's one way you can begin approaching this - completely in SQL:
Get the last rating for the vendor. ORDER BY date DESC, limit 1.
Get the second to last rating for the vendor. ORDER BY date DESC, limit 1, OFFSET 1.
Then write a query that does a LEFT join of the first two tables. You will have a dataset that has three columns:
vendor id
last rating
second to last rating
Then you can write an expression that says "if column1 is <3 and column2 < 3, then this new column is true"
You should be able to extend this to three columns relatively easily.
Here is what a came up with to solve this riddle. I think explaining it on here helped as well as Alex also helped as he keyed my brain on using the date. I first started looking at using if statment inside of the query and actually that got my brain out of the box and then it hit me what to do.
It is not perfect and certainly could use some trimming to reduce the code, but i understand it and it seems to work, so that is par for me on this course.
the query...
$sql = "SELECT `rvid`, `ratedate`,`rating` FROM `vendor_ratings_archive` WHERE `rating` <= '3' ORDER BY `ratedate`, `rvid` DESC";
which gives me this
Array
(
[0] => Array
(
[rvid] => 7
[ratedate] => 2016-05-01
[rating] => 2
)
[1] => Array
(
[rvid] => 5
[ratedate] => 2016-05-01
[rating] => 1
)
[2] => Array
(
[rvid] => 5
[ratedate] => 2016-05-01
[rating] => 0
)
[3] => Array
(
[rvid] => 5
[ratedate] => 2016-05-01
[rating] => 3
)
)
notice how vendor (rvid) 5 is grouped together which is an added plus.
next a simple foreach to load a new array
foreach($results as $yield)
{
$rvidarray[] = $yield['rvid'];
}//close foreach
which gives me this
Array
(
[0] => 7
[1] => 5
[2] => 5
[3] => 5
)
then we count the array values to group dups
$rvidcounter = array_count_values($rvidarray);
which results in this
Array(
[7] => 1
[5] => 3
)
so now vender 7 as 1 low score and vendor 5 has 3 low scores and since they were already sorted by date i know that its consecutive. Well it sounds good anyway lol ")
then we create our final array with another foreach
foreach($rvidcounter as $key => $value)
{
//anything 3 or over is your watchlist
if($value > 2)
{
$watchlist[] = $key; //rvid number stored
}
}//close foreach
which gives me this
Array
(
[0] => 5
)
this was all done in a service function. So the final deal is everyone in this array has over 3 consecutive low ratings and then i just use a returned array back in my normal php process file and grab the name of each vender by id and pass that to the html and print out the list.
done...
please feel free to improve on this if you like. I may or may not use it because the above code makes sense to me. Something more complicated may not make sense to me 6 mos from now lol But it would be interesting to see what someone comes up with to shorten the process a bit.
Thanks so much and Happy Coding !!!!!
Dave :)
You could do it in SQL like that:
SET #rvid = -1;
SELECT DISTINCT rvid FROM
(
SELECT
rvid,
#neg := rating<3, /* 0 or 1 for addition in next line */
#count := IF(#rvid <> rvid , #neg, #count+#neg) AS `count`, /* set or add */
#rvid := rvid /* remember last row */
FROM
testdb.venrate
ORDER BY
rvid, datetime desc
) subq
WHERE count>=3
;
You set a variable to a non existing id. In each chronologically sorted row you check if rating is too low, that results in 1 (too low) or 0 (ok). If rvid is not equal to the last rvid, it means a new vender section is beginning. On begin of section set the value 0 or 1, else add this value. Finally store the current row's rvid for comparison in next row process.
The code above is looking for 3 consecutive low ratings (low means a value less than 3) over all the time.
A small modification checks if all the latest 3 ratings has been equal to or less than 3:
SET #rvid = -1;
SELECT DISTINCT
rvid
FROM
(
SELECT
rvid,
#high_found := rating>3 OR (#rvid = rvid AND #high_found) unflag,
#count := IF(#rvid <> rvid , 1, #count+1) AS `count`,
#rvid := rvid /* remember last row */
FROM
testdb.venrate
ORDER BY
rvid, datetime desc
) subq
WHERE count=3 AND NOT unflag
;
Related
My data in table t1 as below (only 2 record),
+-----------+-----------------+----------+
| shid | lvlmin | lvlmax |
+-----------+-----------------+----------+
| 1 | 1 | 10 |
| 2 | 5 | 10 |
+----------------------------------------+
My php code is:
$userinfo[0] = '9';
$ghunt = DB::fetch_all("SELECT shid FROM t1
WHERE lvlmin <= ".$userinfo[0]." AND lvlmax >= ".$userinfo[0].
"ORDER BY rand() LIMIT 5");
print_r($ghunt);
Result got 2 array:
Array ( [0] => Array ( [shid] => 2 ) [1] => Array ( [shid] => 1 ) )
How do I do when the array result is less than the LIMIT 5 in mysql query, auto use the array result in $ghunt to fill up the array?
What I mean is:
Array (
[0] => Array ( [shid] => 2 )
[1] => Array ( [shid] => 1 )
[2] => Array ( [shid] => 2 )
[3] => Array ( [shid] => 1 )
[4] => Array ( [shid] => 1 )
)
The shid can be random place in array.
Why don't you do something like this?
If (count($ghunt) < 5){
$realResultCount = count($ghunt);
for ($i = realResultCount; $i <= 5; $i++){
$ghunt[$i] = $ghunt[rand(0,realResultCount-1)];
}
}
Basically, what above code does is, if ghunt has less than 5 records in it, it tops it up to 5, by randomly selecting records out of initially returned records.
I don't code PHP, but I can describe one way you can achieve your goal simply. Most languages have a MOD operator, usually % - the php manual page for mod is here
It gives us the remainder of a division operation, so 10 mod 3 is 1, because 10 divided by 3 is 9 remainder 1
A useful property of MOD then, is that it always cycles between 0 and 1 less than what you're modding by. If you mod an incrementing number by 5, the result will always be 0,1,2,3,4,0,1,2,3,4 in a cycle. This means you can have a for loop with some incrementing number, mod by an array length and the result will be an integer that is certainly an array index. If the loop variable goes higher than the end of the array, the mod operator will make it wrap round to the start of the array again
MyArray[ 1746262848 mod MyArray.length ]
Will certainly not crash, even if the array only has 2 items
So for your case, just have a loop.. make he following pseudo code into PHP
// run the loop 5 times
For I as integer = 0 to 4 do
Print MyArray[ i mod MyArray.length ]
If you have 2 items in your array, A and B, it will simply print ABABA
If you have 3 items A B C it will print ABCAB
Hopefully this info will be helpful to you for implementing a solution in php for this, and many future problems. Mod can be really useful for implementing various things when working with arrays
I’ve seen the following question on StackOverflow, Intelligent MySQL GROUP BY for Activity Streams posted by Christian Owens 12/12/12.
So I decided to try out the same approach, make two tables similar to those of his. And then I pretty much copied his query which I do understand.
This is what I get out from my sandbox:
Array
(
[0] => Array
(
[id] => 0
[user_id] => 1
[action] => published_post
[object_id] => 776286559146635
[object_type] => post
[stream_date] => 2015-11-24 12:28:09
[rows_in_group] => 1
[in_collection] => 0
)
)
I am curious, since looking at the results in Owens question, I am not able to fully get something, and does he perform additional queries to grab the actual metadata? And if yes, does this mean that one can do it from that single query or does one need to run different optimized sub-queries and then loop through the arrays of data to render the stream itself.
Thanks a lot in advanced.
Array
(
[0] => Array
(
[id] => 0
[user_id] => 1
[fullname] => David Anderson
[action] => hearted
[object_id] => array (
[id] => 3438983
[title] => Grand Theft Auto
[Category] => Games
)
[object_type] => product
[stream_date] => 2015-11-24 12:28:09
[rows_in_group] => 1
[in_collection] => 1
)
)
In "pseudo" code you need something like this
$result = $pdo->query('
SELECT stream.*,
object.*,
COUNT(stream.id) AS rows_in_group,
GROUP_CONCAT(stream.id) AS in_collection
FROM stream
INNER JOIN follows ON stream.user_id = follows.following_user
LEFT JOIN object ON stream.object_id = object.id
WHERE follows.user_id = '0'
GROUP BY stream.user_id,
stream.verb,
stream.object_id,
stream.type,
date(stream.stream_date)
ORDER BY stream.stream_date DESC
');
then parse the result and convert it in php
$data = array(); // this will store the end result
while($row = $result->fetch(PDO::FETCH_ASSOC)) {
// here for each row you get the keys and put it in a sub-array
// first copy the selected `object` data into a sub array
$row['object_data']['id'] = $row['object.id'];
$row['object_data']['title'] = $row['object.title'];
// remove the flat selected keys
unset($row['object.id']);
unset($row['object.title']);
...
$data[] = $row; // move to the desired array
}
you should get
Array
(
[0] => Array
(
[id] => 0
[user_id] => 1
[fullname] => David Anderson
[verb] => hearted
[object_data] => array (
[id] => 3438983
[title] => Grand Theft Auto
[Category] => Games
)
[type] => product
[stream_date] => 2015-11-24 12:28:09
[rows_in_group] => 1
[in_collection] => 1
)
)
It seems that you want a query where you can return the data you're actually able to get plus the user fullname and the data related to the object_id.
I think that the best effort would be to include some subqueries in your query to extract these data:
Fullname: something like (SELECT fullname FROM users WHERE id = stream.user_id) AS fullname... or some modified version using the stream.user_id, as we can't identify in your schema where this fullname comes from;
Object Data: something like (SELECT CONCAT_WS(';', id, title, category_name) FROM objects WHERE id = stream.object_id) AS object_data. Just as the fullname, we can't identify in your schema where these object data comes from, but I'm assuming it's an objects table.
One object may have just one title and may have just one category. In this case, the Object Data subquery works great. I don't think an object can have more than one title, but it's possible to have more than one category. In this case, you should GROUP_CONCAT the category names and take one of the two paths:
Replace the category_name in the CONCAT_WS for the GROUP_CONCAT of all categories names;
Select a new column categories (just a name suggestion) with the subquery which GROUP_CONCAT all categories names;
If your tables were like te first two points of my answer, a query like this may select the data, just needing a proper parse (split) in PHP:
SELECT
MAX(stream.id) as id,
stream.user_id,
(select fullname from users where id = stream.user_id) as fullname,
stream.verb,
stream.object_id,
(select concat_ws(';', id, title, category_name) from objects where id = stream.object_id) as object_data,
stream.type,
date(stream.stream_date) as stream_date,
COUNT(stream.id) AS rows_in_group,
GROUP_CONCAT(stream.id) AS in_collection
FROM stream
INNER JOIN follows ON 1=1
AND stream.user_id = follows.following_user
WHERE 1=1
AND follows.user_id = '0'
GROUP BY
stream.user_id,
stream.verb,
stream.object_id,
stream.type,
date(stream.stream_date)
ORDER BY stream.stream_date DESC;
In ANSI SQL you can't reference columns not listed in your GROUP BY, unless they're in aggregate functions. So, I included the id as an aggregation.
I am writing a php script to extract some data from a MYSQL db, then I want to take that data and store it into a two dimensional array. Thinking of it normally, if I use code like this
$test = array();
$test[0]=array();
$test[0]['hello']='hello';
$test[1]=array();
$test[1]['hello']='hello';
$test[2]=array();
$test[2]['hello']='hello';
print_r($test);
the output will be:
Array ( [0] => Array ( [hello] => hello ) [1] => Array ( [hello] => hello ) [2] =>
Array ( [hello] => hello ) )
which is how I want my output to be
so this is what I do in my script
So basically in my database I have table with standings for a women's league and the columns are
team_name, played, won, drawn, lost, for, against, points
All the connections have been taken care of successfully, below is my query
$get_ladies_query = "SELECT
`team_name`, `played`, `won`, `drawn`, `lost`, `for`, `against`, `points`
FROM `standings_ladies` order by pos";
An important point to not before I show the next code is that, there are 2 other standings tables, men_senior and men_intermediate with the same structure but obviously only the data changes, below are the two queries just incase
$get_mens_inter_query = "SELECT
`team_name`, `played`, `won`, `drawn`, `lost`, `for`, `against`, `points`
FROM `standings_men_inter` order by pos";
$get_mens_senior_query = "SELECT
`team_name`, `played`, `won`, `drawn`, `lost`, `for`, `against`, `points`
FROM `standings_men_senior` order by pos";
Now I create 3 arrays which I want to hold standings seperately for the ladies, mens senior, mens intermediate
$standings_ladies = array();
$standings_men_inter = array();
$standings_men_senior = array();
The data I want displayed in the array is like so
array(0=>array(team_name,wins,drawn,lost,for,against,points)
1=>array(team_name,wins,drawn,lost,for,against,points)) and so on
Now since I wanted to create the multidimensional arrays of standings for all 3 categories, I could have run the queries in 3 separate while loops altough I thought, I could accomplish the same result in 1 and i felt it would help improve performance. If its better to use 3 while loops, please let me know, what I tried is below.
//I run the 3 queries and store them in the given variables
$result_mens_senior = mysqli_query($link,$get_mens_senior_query);
$result_mens_inter = mysqli_query($link,$get_mens_inter_query);
$result_ladies= mysqli_query($link, $get_ladies_query);
//I want to create 1 while loop so based of the results returned from the 3
//queries so based on the results returned from the 3 queries,
//I get the max number of times I want the query to run
$ladies_boundary = mysqli_num_rows($result_ladies);
$mens_senior_boundary = mysqli_num_rows($result_mens_senior);
$mens_inter_boundary = mysqli_num_rows($result_mens_inter);
$max_size = max(array($ladies_boundary,$mens_senior_boundary,$mens_inter_boundary));
//set an index to start from 0
$index = 0;
//I will only show the example for 1 of the arrays but you get an idea
that this issue will occur for all
while ($index < $max_size)
{
//first, everytime the loop is entered, we need the next row to be fetched
$standings_men_inter_table = mysqli_fetch_assoc($result_mens_inter);
$standings_ladies_table = mysqli_fetch_assoc($result_ladies);
//there is a high chance that the other two tables return a different row size
//so its best to check that we do not go beyond
if($index < $mens_senior_boundary)
{
//we fetch the rows every time we enter the block
$standings_men_senior_table = mysqli_fetch_assoc($result_mens_senior);
//then, this is how I attempt at creating the 2 dimensional array
array_push($standings_men_senior, array(
$standings_men_senior_table['team_name'],
$standings_men_senior_table['played'],
$standings_men_senior_table['won'],
$standings_men_senior_table['drawn'],
$standings_men_senior_table['lost'],
$standings_men_senior_table['for'],
$standings_men_senior_table['against'],
$standings_men_senior_table['points']));
}
//incrementing index each time the loop runs
$index++;
}
Then finally, I just want to print what I think is the array but get this, attached image, hope you can see it clearly
Just to investigate even further, every time, the 'if' block is entered, I just commented everything out and just put this to see what was being returned
if($index < $mens_senior_boundary)
{
print_r(mysqli_fetch_assoc($result_mens_senior));
}
The output I got was almost 90% what I need
Array
([team_name] => Morley Gaels [played] => 8 [won] => 6 [drawn] => 2
[lost] => 0 [for] => 110 [against] => 83 [points] => 14 )
Array ( [team_name] => Southern Districts [played] => 8 [won] => 3 [drawn] => 2
[lost] => 3 [for] => 104 [against] => 98 [points] => 8 )
Array ( [team_name] => St Finbarrs [played] => 8 [won] => 3 [drawn] => 2
[lost] => 3 [for] => 107 [against] => 99 [points] => 8 )
Array ( [team_name] => Western Shamrocks [played] => 8 [won] => 3 [drawn] => 0
[lost] => 5 [for] => 96 [against] => 88 [points] => 6 )
Array ( [team_name] => Greenwood [played] => 8 [won] => 1 [drawn] => 1
[lost] => 9 [for] => 82 [against] => 109 [points] => 3 )
What I need is for example:
Array(0=>Array
([team_name] => Morley Gaels [played] => 8 [won] => 6 [drawn] => 2
[lost] => 0 [for] => 110 [against] => 83 [points] => 14 )
1=>Array
([team_name] => Southern Districts [played] => 8 [won] => 3 [drawn] => 2
[lost] => 3 [for] => 104 [against] => 98 [points] => 8 )..... so on);
My questions are
What is wrong with my code and what is the correct way to dynamically create
multidimensional arrays in php ?
Is there something I have not understood about how mysql_fetch_assoc works and how it returns ?
Anything to improve, anything I am doing wrong ?
I appreciate your time, than you for reading it, I tried to be as detailed as I can about what I have tried.
Thank You.
Try this
After you do this:
$result_mens_senior = mysqli_query($link,$get_mens_senior_query);
$result_mens_inter = mysqli_query($link,$get_mens_inter_query);
$result_ladies= mysqli_query($link, $get_ladies_query);
just do this
while ($standings_men_senior[] = mysqli_fetch_assoc($result_mens_senior)){}
while ($standings_men_inter[] = mysqli_fetch_assoc($result_mens_inter)){}
while ($standings_ladies[] = mysqli_fetch_assoc($result_ladies)){}
Basically all of the posted code should be able to be replaced with:
<?php
$ladies_query = "SELECT `team_name`, `played`, `won`, `drawn`, `lost`, `for`, `against`, `points`
FROM `standings_ladies` order by pos";
$inter_query = "SELECT `team_name`, `played`, `won`, `drawn`, `lost`, `for`, `against`, `points`
FROM `standings_men_inter` order by pos";
$senior_query = "SELECT `team_name`, `played`, `won`, `drawn`, `lost`, `for`, `against`, `points`
FROM `standings_men_senior` order by pos";
$ladies_stmt = mysqli_query($link, $ladies_query) || die ("Couldn't get Ladies"); // reminds me of high school
$inter_stmt = mysqli_query($link, $inter_query) || die ("Couldn't get Inter");
$senior_stmt = mysqli_query($link, $serior_query) || die ("Couldn't get Seniors");
$standings_men_senior = array();
$standings_men_inter = array();
$standings_ladies = array();
while ($row = mysqli_fetch_assoc($senior_stmt)){
$standings_men_senior[] = $row;
}
while ($row = mysqli_fetch_assoc($inter_stmt)){
$standings_men_inter[] = $row;
}
while ($row = mysqli_fetch_assoc($ladies_stmt)){
$standings_ladies[] = $row;
}
Don't try to put everything in a single loop, it significantly reduces your code clarity and will give you little to no gain in performance, there is a term for this and it's called micro-optimization, and you should never do it unless it is really necessary (a bottleneck on a large site).
Now, what I suggest to you is to remake your loop and make it as clear as possible as to how data is manipulated.
The most important thing in your case is to debug, print_r the content of your arrays on every step of the process to check what it contains, from the database to the end of your code, you will find where the problem is.
here is a little background on what I'm trying to accomplish. I have an array from a MySQL query that is being displayed. I want to sort the array based on a factor. The factor is calculated inline based on the time the article was posted & the number of votes it's received. Something like this:
// ... MySQL query here
$votes = $row['0']
$seconds = strtotime($record->news_time)+time();
$sum_total = pow($votes,2) / $seconds;
So the array thats coming in looks something like this:
Array (
[0] => stdClass Object (
[id] => 13
[news_title] => Article
[news_url] => http://website.com/article/14
[news_root_domain] => website.com
[news_category] => Business
[news_submitter] => 2
[news_time] => 2013-02-18 12:50:02
[news_points] => 2
)
[1] => stdClass Object (
[id] => 14
[news_title] => Title
[news_url] => http://www.website.com/article/2
[news_root_domain] => www.website.com
[news_category] => Technology
[news_submitter] => 1
[news_time] => 2012-10-02 10:03:22
[news_points] => 8
)
)
I want to sort the aforementioned array using the factor I mentioned above. The idea is to show the highest rated articles first on the list (using the calculated factor), instead of the default sorting method that the array comes in. It seems like usort might be my best bet, but let me know what you all think?
Do it all in the query:
SELECT n.*, ( POW(?, 2) / (UNIX_TIMESTAMP(n.news_time) + UNIX_TIMESTAMP(NOW())) ) as rank
FROM news_table n
ORDER BY rank;
Now in order to get the votes you may have to do a subquery or a join, but i cant advise on that because you dont give enough info on where the votes are coming from. You could however supply the votes to the query as well instead of selecting it all in one shot something like:
$sql = sprintf('SELECT n.*, ( POW(%d, 2) / (UNIX_TIMESTAMP(n.news_time) + UNIX_TIMESTAMP(NOW())) ) as rank FROM news_table n ORDER BY rank', $votes);
Aside from that, yes you could use usort, but that would also require you to have the entire recordset in memory to provide accurate sorting, which could be problematic at some point.
I've got a MySQL query that runs very, very slowly and pegs the server usage at 100% as soon as it's executed....sometimes.
Here's the query:
SELECT DISTINCT r1.recordID,
(SELECT MAX(r2.date)
FROM reminders as r2
WHERE r2.owner = '$owner'
AND r2.recordID = r1.recordID
AND r2.status = 'Active'
AND r2.followUp != 'true'
ORDER BY r2.date DESC LIMIT 1) as maxDate
FROM reminders as r1
WHERE r1.owner = '$owner'
AND (SELECT MAX(r2.date)
FROM reminders as r2
WHERE r2.recordID = r1.recordID
AND r2.status = 'Active'
AND r2.followUp != 'true'
ORDER BY r2.date DESC LIMIT 1) <= '$date'
AND (SELECT do_not_call
FROM marketingDatabase
WHERE id = r1.recordID) != 'true'
AND r1.status = 'Active'
ORDER BY maxDate DESC
I'm not sure if it's a poorly written query (might be...I'm new at this) or something else. Sometimes it works fine and results are returned almost instantly but other times it takes a long time (15+ minutes) and 100% server resources to return the results.
Any idea why this could be happening? Anything I can do to the query to prevent this?
Thanks in advance!
[EDIT]
Here's the EXPLAIN.
Array
(
[0] => Array
(
[id] => 1
[select_type] => PRIMARY
[table] => r1
[type] => ALL
[possible_keys] =>
[key] =>
[key_len] =>
[ref] =>
[rows] => 2073
[Extra] => Using where; Using temporary; Using filesort
)
[1] => Array
(
[id] => 4
[select_type] => DEPENDENT SUBQUERY
[table] => marketingDatabase
[type] => eq_ref
[possible_keys] => PRIMARY
[key] => PRIMARY
[key_len] => 4
[ref] => teleforce.r1.recordID
[rows] => 1
[Extra] =>
)
[2] => Array
(
[id] => 3
[select_type] => DEPENDENT SUBQUERY
[table] => r2
[type] => ALL
[possible_keys] =>
[key] =>
[key_len] =>
[ref] =>
[rows] => 2073
[Extra] => Using where
)
[3] => Array
(
[id] => 2
[select_type] => DEPENDENT SUBQUERY
[table] => r2
[type] => ALL
[possible_keys] =>
[key] =>
[key_len] =>
[ref] =>
[rows] => 2073
[Extra] => Using where
)
)
Several things could make this query slow.
One: In general, the first thing to check is indexes. You have three embedded queries that have to be executed for each record in r1. If any of these are not able to effectively use an index and have to process a large number of records, this query will be very slow. Review your indexes, and use "explain" to see what's being used.
Two: Embedded queries tend to be slower than joins. See if you can't transform some of your embedded queries to joins.
Three: In this particular query, you're joining "remainders" back on itself, as far as I can figure out, just to find max(date). Why not just use a GROUP BY? My step 1 to improving this query would be:
select r1.recordID, max(r1.date) as maxdate
from reminders as r1
where r1.owner=$owner and r1.status='Active' and r1.followUp!='true'
and (SELECT do_not_call FROM marketingDatabase WHERE id = r1.recordID) != 'true'
group by r1.recordID
having max(r1.date)<=$date
order by maxdate desc
I don't have your database to test this, but I think it would give the same results.
Four: I'd turn the other embedded query into a join. Like:
select r1.recordID, max(r1.date) as maxdate
from reminders as r1
join marketingDatabase as m on m.id=r1.recordID
where r1.owner=$owner and r1.status='Active' and r1.followUp!='true'
and m.do_not_call != 'true'
group by r1.recordID, r1.owner
having max(r1.date)<=$date
order by maxdate desc
(I'm not sure what a recordID identifies. It appears from your query that you can have multiple records in reminders with the same recordid.)
Five: You'll probably get best performance if you have indexes on reminders(owner, date) and marketingDatabase(id).
Six: Just by the way, if "do_not_call" and followUp are true/false, they should be booleans and not varchars. You're just wasting disk space and execution time processing "true" instead of a boolean TRUE. And you create the problem of mis-spellings, like "ture" or "True". At the absolute worst, make them a char(1), T or F.
There are times when you need embedded queries, but they should be a last resort.
Try to create an index on reminders.owner with the command
CREATE INDEX someNameYouChoose ON reminders(owner);