I'm trying to build what should be (in my mind anyway) a simply SQL query to understand what clients have been invoiced each financial year. The output might look like this:
{clientName (Client A)} - {2010/2011 Value} - {2011/2012 Value} - {2012/2013 Value}
What I've been able to achieve is an output that looks like this:
{clientName (Client A)} - {2010/2011 Value}
{clientName (Client A)} - {2011/2012 Value}
{clientName (Client A)} - {2012/2013 Value}
{clientName (Client B)} - {2010/2011 Value}
And so on…
Now, I know this is not correct but the query I'm working with looks like this:
$query = "SELECT i.invoiceValue, fy.year, c.clientName, c.clientID FROM cms_invoices i
LEFT JOIN cms_financialYear fy ON fy.yearID = i.yearID
LEFT JOIN cms_projects p ON p.projectID = i.projectID
LEFT JOIN cms_clients c ON c.clientID = p.clientID
ORDER BY fy.year, c.clientName";
$result = mysql_query($query) or die(mysql_error());
while($row = mysql_fetch_array($result)) {
echo $row['year'] . " - ";
echo $row['clientName'] . " - $";
echo number_format($row[invoiceValue], 2, '.', ',') . "";
echo "<br>";
I'd be much appreciated if I could get some sort of a steer on this. I've tried for hours but alas, no luck.
Thanks,
#rrfive
You could try something like this:
SELECT c.clientID
, c.clientName
, SUM(IF(fy.year=2010,i.invoiceValue,0)) AS fy_2010
, SUM(IF(fy.year=2011,i.invoiceValue,0)) AS fy_2011
, SUM(IF(fy.year=2012,i.invoiceValue,0)) AS fy_2012
FROM cms_invoices i
LEFT
JOIN cms_financialYear fy ON fy.yearID = i.yearID
LEFT
JOIN cms_projects p ON p.projectID = i.projectID
LEFT
JOIN cms_clients c ON c.clientID = p.clientID
GROUP BY c.clientID, c.clientName
ORDER BY c.clientID, c.clientName
The "trick" is to use an IF function (or a more ANSI portable CASE expression), to determine if a row applies to a given fiscal year. If it does, then return the invoice value, otherwise, return a 0.
Wrap those expressions in a SUM aggregate function, and do the GROUP BY on the client.
If you want to guard against returning a NULL value, then you can wrap those SUM expressions in an IFNULL( ... ,0)
Related
I'd like to ask if there is a good practise about performance of following.
My SQL SELECT:
SELECT avpc.category_id, a.attribute_id, ad.name, a.type, a.sort_order, avpc.attribute_value_id, avd.value, COUNT(pav.product_id) AS product_count
FROM attribute_value_per_category avpc
JOIN category_path cp ON cp.category_id = avpc.category_id
JOIN attribute_value av ON av.attribute_value_id = avpc.attribute_value_id
JOIN attribute_value_description avd ON avd.attribute_value_id = av.attribute_value_id AND avd.language_id = 2
JOIN attribute a ON a.attribute_id = av.attribute_id
JOIN attribute_description ad ON ad.attribute_id = a.attribute_id AND ad.language_id = 2
JOIN product_attribute_value pav ON pav.attribute_value_id = av.attribute_value_id
WHERE path_id = 2
GROUP BY attribute_value_id
I need to add condition: HAVING product_count > 10. I've read, that HAVING is very slow and there is no optimisation in.
So I've got these two suggestions:
Use HAVING
Not use HAVING and filter out the condition in PHP (For example by array_filter)
Would you please to give me any advice of performance of this problem?
Can anyone tell me how to make this query faster?
$session_id = '000000000015';
$start = 0;
$finish = 30;
try {
$stmt = $conn->prepare("SELECT TOPUSERS.ID, TOPUSERS.USERNAME, TOPUSERS.NAME, TOPUSERS.NAME2, TOPUSERS.PHOTO, TOPUSERS.FB_USERID, TOPUSERS.IMAGE_TYPE, TOPUSERS.TW_USERID, TOPUSERS.TW_PHOTO,
COALESCE((SELECT COUNT(USERS_BUCKETS.ID) FROM USERS_BUCKETS WHERE USERS_BUCKETS.USERID=TOPUSERS.ID),0) AS NUM_ALL,
COALESCE((SELECT SUM(CASE WHEN USERS_BUCKETS.STATUS='Completed' THEN 1 ELSE 0 END) FROM USERS_BUCKETS WHERE USERS_BUCKETS.USERID=TOPUSERS.ID),0) AS NUM_DONE,
COALESCE((SELECT COUNT(USERS_LIKES.ID) FROM USERS_LIKES WHERE USERS_LIKES.USERID=TOPUSERS.ID),0) AS NUM_LIKES,
(SELECT USERS_BUCKETS.BUCKETID FROM USERS_BUCKETS WHERE USERS_BUCKETS.USERID=TOPUSERS.ID ORDER BY USERS_BUCKETS.DATE_MODIFIED DESC LIMIT 1) AS RECENT_BUCKET,
(SELECT BUCKETS_NEW.BUCKET_NAME FROM BUCKETS_NEW WHERE BUCKETS_NEW.ID=RECENT_BUCKET) AS REC,
COALESCE((SELECT COUNT(ID) FROM FOLLOW WHERE FOLLOW.USER_ID=TOPUSERS.ID),0) AS FOLLOWING,
COALESCE((SELECT COUNT(ID) FROM FOLLOW WHERE FOLLOW.FOLLOW_ID=TOPUSERS.ID),0) AS FOLLOWERS,
(SELECT IF(TOPUSERS.NAME = '',0,1) + IF(TOPUSERS.BIO = '',0,1) + IF(TOPUSERS.LOCATION = '',0,1) + IF(TOPUSERS.BIRTHDAY = '0000-00-00',0,1) + IF(TOPUSERS.GENDER = '',0,1)) as COMPLETENESS,
CASE WHEN ? IN (SELECT USER_ID FROM FOLLOW WHERE FOLLOW_ID = TOPUSERS.ID) THEN 'Yes' ELSE 'No' END AS DO_I_FOLLOW_HIM
FROM TOPUSERS
LEFT JOIN FOLLOW ON TOPUSERS.ID = FOLLOW.FOLLOW_ID
LEFT JOIN USERS_BUCKETS ON USERS_BUCKETS.USERID=TOPUSERS.ID
LEFT JOIN BUCKETS_NEW ON BUCKETS_NEW.ID=USERS_BUCKETS.BUCKETID
WHERE NOT TOPUSERS.ID = ?
GROUP BY TOPUSERS.ID ORDER BY TOPUSERS.RANDOM, TOPUSERS.USERNAME LIMIT $start, $finish");
When I run this in a browser it takes about 7 seconds to load. Without a few lines (the COALESCE in the middle, the two SELECTS above and the line below them) the time is reduced to 3-4 seconds.
The result of the query is a list of people with names, profile picture and some data.
TL,DR: you need to rewrite the query.
You need to rewrite your query to make it more efficient. I had to rewrite a similar query at work last week and here is what I have done.
The structure of your query should look like this to be efficient:
select ...
...
from ...
join ...
where ...
what you have now is something like:
select ...
inner select
inner select
from ...
join ...
where ...
That's the inner selects that kill your query. You need to find a way to move the inner select into the from section. Especially that you already query the tables.
What you need to understand is that your inner selects run for every records you have. So if you have 10 records, it would be alright (speed wise). But with hundred or thousand of records, it would be very slow.
If you want more information on your query run it with the explain keyword in from of it.
I have two mysql tables with content one is called petitions
{"success":1,"petitions":[{"id":"6","name":"should he go","timestamp":"2013-10-26 03:02:44"},{"id":"3","name":"Olara Otunu should get married","timestamp":"2013-10-24 14:33:53"},{"id":"4","name":"Teachers deserve 30 not 20 salary rise","timestamp":"2013-10-24 14:33:53"},{"id":"5","name":"Prostitution should be banned","timestamp":"2013-10-24 14:33:53"},{"id":"1","name":"Has Jennifer Musisi done great work for Kampala","timestamp":"2013-10-24 14:32:58"},{"id":"2","name":"Do lecturers deserve 100% salary increase","timestamp":"2013-10-24 14:32:58"}]}
and the other table is called petition_response
{"success":1,"petition_response":[{"id":"2","petitionID":"2","yes":"0","no":"1","memberID":"14","timestamp":"2013-11-02 08:36:20"},{"id":"1","petitionID":"1","yes":"1","no":"0","memberID":"14","timestamp":"2013-11-01 21:26:02"}]}
I need to select * petitions which have no response in the petition_response. But if they have any response in the petition_response table then the memberID should not be equal to 14
I have tried this code below but its not working
$result = mysql_query("select * from petition_response where memberID='14' order by timestamp DESC", $db->connect());
while($row = mysql_fetch_assoc($result)){
$id = $row['id'];
$result2 = mysql_query("select * from petitions where id != '$id' order by timestamp DESC", $db->connect());
}
return $result2;
You're looking to effect an anti-join, for which there are three possibilities in MySQL:
Using NOT IN:
SELECT *
FROM petitions
WHERE id NOT IN (
SELECT petitionID
FROM petition_response
WHERE memberID = 14
)
Using NOT EXISTS:
SELECT *
FROM petitions p
WHERE NOT EXISTS (
SELECT *
FROM petition_response r
WHERE r.petitionID = p.id
AND r.memberID = 14
LIMIT 1
)
Using an OUTER JOIN:
SELECT p.*
FROM petitions p
LEFT OUTER JOIN petition_response r
ON r.petitionID = p.id AND r.memberID = 14
WHERE r.petitionID IS NULL
See them on sqlfiddle.
According to #Quassnoi's analysis:
Summary
MySQL can optimize all three methods to do a sort of NESTED LOOPS ANTI JOIN.
It will take each value from t_left and look it up in the index on t_right.value. In case of an index hit or an index miss, the corresponding predicate will immediately return FALSE or TRUE, respectively, and the decision to return the row from t_left or not will be made immediately without examining other rows in t_right.
However, these three methods generate three different plans which are executed by three different pieces of code. The code that executes EXISTS predicate is about 30% less efficient than those that execute index_subquery and LEFT JOIN optimized to use Not exists method.
That’s why the best way to search for missing values in MySQL is using a LEFT JOIN / IS NULL or NOT IN rather than NOT EXISTS.
$query = "
SELECT *
FROM petition_response pr
LEFT
JOIN petitions p
ON p.id = pr.id
WHERE pr.memberID = 14
AND p.id IS NULL
ORDER
BY pr.timestamp DESC;
";
I am making a stats page about golf for the people I play with. I am trying to pull out of the database the number of times out of all our scorecards that we received birdies (which is -1 under par). It does pull out the -1s per hole, however I noticed that you if you had 2 birdies on a scorecard, it still only counts as 1 birdie instead of 2. I want it to keep counting, so if someone gets 9 birdies, those 9 are added to the total.
$query_p321 = "SELECT t1.*,COUNT(t1.player_id),t2.* FROM scorecards t1 LEFT JOIN courses t2 ON t1.course_id=t2.course_id
WHERE t1.hole1<t2.hole1_par AND t1.hole1>t2.hole1_par-2
OR t1.hole2<t2.hole2_par AND t1.hole2>t2.hole2_par-2
OR t1.hole3<t2.hole3_par AND t1.hole3>t2.hole3_par-2
OR t1.hole4<t2.hole4_par AND t1.hole4>t2.hole4_par-2
OR t1.hole5<t2.hole5_par AND t1.hole5>t2.hole5_par-2
OR t1.hole6<t2.hole6_par AND t1.hole6>t2.hole6_par-2
OR t1.hole7<t2.hole7_par AND t1.hole7>t2.hole7_par-2
OR t1.hole8<t2.hole8_par AND t1.hole8>t2.hole8_par-2
OR t1.hole9<t2.hole9_par AND t1.hole9>t2.hole9_par-2
OR t1.hole10<t2.hole10_par AND t1.hole10>t2.hole10_par-2
OR t1.hole11<t2.hole11_par AND t1.hole11>t2.hole11_par-2
OR t1.hole12<t2.hole12_par AND t1.hole12>t2.hole12_par-2
OR t1.hole13<t2.hole13_par AND t1.hole13>t2.hole13_par-2
OR t1.hole14<t2.hole14_par AND t1.hole14>t2.hole14_par-2
OR t1.hole15<t2.hole15_par AND t1.hole15>t2.hole15_par-2
OR t1.hole16<t2.hole16_par AND t1.hole16>t2.hole16_par-2
OR t1.hole17<t2.hole17_par AND t1.hole17>t2.hole17_par-2
OR t1.hole18<t2.hole18_par AND t1.hole18>t2.hole18_par-2
GROUP BY t1.player_id ORDER BY count(t1.player_id) DESC";
$result_p321 = mysql_query($query_p321);
$number = 1;
while ($row_p321 = mysql_fetch_array($result_p321)) {
$player_id2 = $row_p321["player_id"];
}
and so on..
You'll notice the "-2" in there. That is taking the par minus 2, as I don't want to record if the person is 2 strokes under. Just one stroke under. Any help is appreciated. Thank you.
Oh, also, GROUP BY needs to be used as I don't want to list the player name more than once. Just want it to count all the birdies. I guess my big problem is its not counting more than 1 per row. Thanks.
The problem is the where clause. You need to do the comparisons in the select clause in order to count them:
SELECT t1.*,
sum((t1.hole1 = t2.hole1_par - 1) +
(t1.hole2 = t2.hole2_par - 1) +
. . .
(t1.hole18 = t2.hole18_par - 1)
) as birdies
FROM scorecards t1 LEFT JOIN
courses t2 ON t1.course_id=t2.course_id
GROUP BY t1.player_id
ORDER BY birdies DESC
This uses the MySQL convention that true is 1 and false 0 to add the numbers up. An alternative formulation using standard SQL is:
sum((case when t1.hole1 = t2.hole1_par - 1) then 1 else 0 end) +
Try something like that:
SELECT t1.*, SUM( IF(t1.hole1 = t2.hole1_par-1,1,0) +
IF(t1.hole2 = t2.hole2_par-1,1,0) +
IF(t1.hole3 = t2.hole3_par-1,1,0) +
IF(t1.hole4 = t2.hole4_par-1,1,0) +
-- etc.
IF(t1.hole18 = t2.hole18_par-1,1,0) ) AS birdies
FROM scorecards t1
LEFT JOIN courses t2 ON t1.course_id=t2.course_id
GROUP BY t1.player_id
ORDER BY birdies DESC
I'm querying four tables (which are: resources, tag_list, resource_tags and votes) and trying to retrieve a list of resources with each list item having grouped tags and the sum of votes for that resource.
This is my current model:
$this->db->select('*');
$this->db->select('GROUP_CONCAT(DISTINCT tag SEPARATOR " | ") AS tags, SUM(vote) AS sumvotes');
$this->db->from('resources');
$this->db->join('resource_tags', 'resources.r_id = resource_tags.resource_id', 'left');
$this->db->join('tag_list', 'tag_list.t_id = resource_tags.tag_id', 'left');
$this->db->join('votes', 'votes.resource_id = resources.r_id', 'left');
$this->db->where('resources.published', '1');
$this->db->group_by('resources.r_id');
$this->db->order_by('votes.vote', 'desc');
$query = $this->db->get();
Edit: Here is the raw generated SQL
SELECT *, GROUP_CONCAT(DISTINCT tag SEPARATOR " | ") AS tags, SUM(vote) AS sumvotes
FROM (`olu_resources`)
LEFT JOIN `olu_resource_tags` ON `olu_resources`.`r_id` = `olu_resource_tags`.`resource_id`
LEFT JOIN `olu_tag_list` ON `olu_tag_list`.`t_id` = `olu_resource_tags`.`tag_id`
LEFT JOIN `olu_votes` ON `olu_votes`.`resource_id` = `olu_resources`.`r_id`
WHERE `olu_resources`.`published` = '1'
GROUP BY `olu_resources`.`r_id`
ORDER BY `olu_votes`.`vote` desc
It seems to do everything except for calculating the correct number of votes, it returns the number of votes there are multiplied by the number of tags that item has.
Does anyone know why this is happening? Or how to go about fixing this query?
Why don't you use a sub-query to get the votes?
Active Record in CI is great to use for simple queries but difficult (and slow) when you have more complex queries like yours.
It is probably all possible with Active Record though I would check the profiler first and look at the difference in speed. Last time I did something like that it made a 7 second difference.
I would try something like this:
$SQL = "SELECT *, GROUP_CONCAT(DISTINCT tag SEPARATOR ' | '),
(SELECT SUM(vote) FROM olu_votes WHERE olu_votes.resource_id = olu_resources.r_id) AS sumvotes
FROM (olu_resources)
LEFT JOIN olu_resource_tags ON olu_resources.r_id = olu_resource_tags.resource_id
LEFT JOIN olu_tag_list ON olu_tag_list.t_id = olu_resource_tags.tag_id
LEFT JOIN olu_votes ON olu_votes.resource_id = olu_resources.r_id
WHERE olu_resources.published = '1'
GROUP BY olu_resources.r_id
ORDER BY olu_votes.vote desc"
$this->db->query($SQL);
This should hopefully also resolve your problem. Let me know if this works!
if needed I'll create a test query on my own system to get you the result you want.