MYSQL Select As Case when number of rows statement - mysql

I'm trying to find the number of notifications for each user, I am having a little problem, I had the query working perfect for what I needed it for, then I changed my table around just a little bit.
Working query:
$numNotifications = mysql_num_rows(mysql_query("
SELECT
N.*,
P.*
FROM
notifications N,
posts P
WHERE
N.userID='$session'
AND
(
N.action='1' OR N.action='2'
)
AND
N.uniqueID=P.id AND P.state='0'"
));
However, uniqueID is now different for some rows. when N.aciton is "1" then N.uniqueID should be compared to P.id, however when N.action is "2" it should compare a row in that table with P.id.
Example query, (that SHOULD work, but doesn't)
$numNotifications = mysql_num_rows(mysql_query("
SELECT
N.*,
P.*,
C.*,
(CASE WHEN (
N.action = 2 AND N.state = 0
)
THEN
C.postID ELSE N.uniqueID END
) AS postId
FROM
notifications N,
posts P,
comments C
WHERE
N.userID='$session'
AND
(
N.action='1' OR N.action='2'
)
AND
postId=P.id AND P.state='0'"
));
diagram of my 3 table structures:
http://i41.tinypic.com/nyzolg.png

here you go :)
SELECT
COUNT(`notif_id`) AS `number_of_new_notifications`
FROM
(
SELECT
`notifications`.`id` AS `notif_id`
FROM
`notifications`
JOIN
`posts`
ON
`notifications`.`uniqueID`=`posts`.`id`
WHERE
`notifications`.`userID`='$session'
AND
`notifications`.`action`=1
AND
`notifications`.`state`=0
AND
`posts`.`state`=0
UNION ALL
SELECT
`notifications`.`id` AS `notif_id`
FROM
`notifications`
JOIN
`comments`
ON
`notifications`.`uniqueID`=`comments`.`id`
JOIN
`posts`
ON
`comments`.`postID`=`posts`.`id`
WHERE
`notifications`.`userID`='$session'
AND
`notifications`.`action`=2
AND
`notifications`.`state`=0
AND
`comments`.`state`=0
AND
`posts`.`state`=0
) AS notification_ids;

Related

MySQL aggregation query

I am currently trying to aggregate the people who visited a sports facility and how much it cost them.
I have run the following code successfully but need to aggregate the people.
How can I do that.
The tables used for the code is here
Tables
SELECT CONCAT( members.firstname, " ", members.surname ) AS fullname,
CASE WHEN slots >0
THEN (
bookings.slots * facilities.membercost + bookings.slots * facilities.guestcost
)
ELSE NULL
END AS "cost"
FROM `Bookings` AS bookings
LEFT JOIN `Members` AS members ON bookings.memid = members.memid
LEFT JOIN Facilities AS facilities ON bookings.facid = facilities.facid
WHERE bookings.starttime LIKE '2012-09-14%'
AND (
bookings.slots * facilities.membercost + bookings.slots * facilities.guestcost
) >30
ORDER BY 1 DESC
OutputScreenshot
I got throught with this....
Here is my solution:
SELECT CONCAT (members.firstname," ",members.surname) AS fullname,
SUM(bookings.slots*facilities.membercost + bookings.slots*facilities.guestcost) AS cost
FROM `Bookings` AS bookings
LEFT JOIN `Members` AS members
ON bookings.memid = members.memid
LEFT JOIN Facilities as facilities
ON bookings.facid =facilities.facid
WHERE bookings.starttime LIKE '2012-09-14%' AND (bookings.slots*facilities.membercost + bookings.slots*facilities.guestcost) >30
GROUP BY 1
ORDER BY 2 DESC

How to subtract the one row from another MySQL

Given that a table as enclosed
How do I find the difference between Mac and Windows sold each day.
Can someone pls explain the logic
An INNER JOIN would do the job.
SELECT
WindowsTable.Date,
ABS(WindowsTable.Sold - MacTable.Sold) absoluteDifference
FROM
(SELECT
*
FROM producttable
WHERE Products = 'Windows') WindowsTable
INNER JOIN
(
SELECT
*
FROM producttable
WHERE Products = 'Mac' ) MacTable
ON WindowsTable.Date = MacTable.Date;
DEMO HERE
Try reshaping your query using an INNER JOIN on date:
SELECT macs_sales.Date, (MacsSold - WindowsSold) AS sales_difference
FROM
(
SELECT Date, Sold as MacsSold
FROM computer_sales
WHERE Products="Mac"
) macs_sales
INNER JOIN
(
SELECT Date, Sold as WindowsSold
FROM computer_sales
WHERE Products="Windows"
) windows_sales
ON macs_sales.Date = windows_sales.Date

MySQL Query is slow (Joins/counts)

I have this query:
SELECT `assemblies`.`id`,
`assemblies`.`type`,
`assemblies`.`champion`,
`assemblies`.`name`,
`assemblies`.`author`,
`assemblies`.`githublastmod`,
( assemblies.forum IS NOT NULL ) AS forumExists,
Count(votes.id) AS votesCount,
Count(install_clicks.id) AS installCount,
Count(github_clicks.id) AS githubCount,
Count(forum_clicks.id) AS forumCount
FROM `assemblies`
INNER JOIN `votes`
ON `votes`.`assembly` = `assemblies`.`id`
INNER JOIN `install_clicks`
ON `install_clicks`.`assembly` = `assemblies`.`id`
INNER JOIN `github_clicks`
ON `github_clicks`.`assembly` = `assemblies`.`id`
INNER JOIN `forum_clicks`
ON `forum_clicks`.`assembly` = `assemblies`.`id`
WHERE `assemblies`.`type` = 'utility'
AND Unix_timestamp(Date(assemblies.githublastmod)) > '1419536536'
GROUP BY `assemblies`.`id`
ORDER BY `votescount` DESC,
`githublastmod` DESC
For some reason this query is very slow, I'm using the database engine MyISAM. I hope someone can help me out here :)
Explain command:
I believe this is a case where making the subqueries for the counts will make it run a lot faster (and the values will be correct).
The problem with the original query is the explosion of the number of intermediate rows: For each 'assembly', there were n1 votes, n2 installs, etc. That led to n1*n2*... rows per assembly.
SELECT `assemblies`.`id`, `assemblies`.`type`, `assemblies`.`champion`,
`assemblies`.`name`, `assemblies`.`author`, `assemblies`.`githublastmod`,
( assemblies.forum IS NOT NULL ) AS forumExists,
( SELECT Count(*)
FROM votes
WHERE `assembly` = `assemblies`.`id`
) AS votesCount,
( SELECT Count(*)
FROM install_clicks
WHERE `assembly` = `assemblies`.`id`
) AS installCount,
( SELECT Count(*)
FROM github_clicks
WHERE `assembly` = `assemblies`.`id`
) AS githubCount,
( SELECT Count(*)
FROM forum_clicks.id
WHERE `assembly` = `assemblies`.`id`
) AS forumCount
FROM `assemblies`
WHERE `assemblies`.`type` = 'utility'
AND Unix_timestamp(Date(assemblies.githublastmod)) > '1419536536'
ORDER BY `votescount` DESC, `githublastmod` DESC
Each secondary table needs an INDEX starting with assembly.
Your problem should be fixed using the right indices:
CREATE INDEX index_name_1 ON `votes`(`assembly`);
CREATE INDEX index_name_2 ON `install_clicks`(`assembly`);
CREATE INDEX index_name_3 ON `github_clicks`(`assembly`);
CREATE INDEX index_name_4 ON `forum_clicks`(`assembly`);
Try your query again after creating these indices and it should be quite faster.

NOT IN Clause for Multiple columns in SQL

I have a SQL query :
select * from product_structure_resources
where resource_id NOT IN (select resource_id from resources);
Now I need to add the columns like branch_id and scheme_id right after the where clause .
Like :
select * from product_structure_resources
where resource_id,scheme_id,branch_id NOT IN (select resource_id from resources);
How can I do it ?
P.S : The resources table does not have scheme_id and branch_id column in it. It has only resource_id column .
select *
from product_structure_resources pr
where not exists (select 1 from resources r where pr.resource_id = r.resource_id)
and not exists (select 1 from resources r where pr.scheme_id= r.resource_id)
and not exists (select 1 from resources r where pr.branch_id= r.resource_id)
This should return the exact same result as juergen d's query.
SELECT psr.*
FROM product_structure_resources AS psr
LEFT JOIN resources AS r ON r.resource_id = psr.resource_id)
LEFT JOIN resources AS s ON s.resource_id = psr.scheme_id)
LEFT JOIN resources AS b ON b.resource_id = psr.branch_id)
WHERE r.resource_id IS NULL
AND s.resource_id IS NULL
AND b.resource_id IS NULL
And just for fun, there also is this extreme (obscure and ill-advised) way:
SELECT *
FROM product_structure_resources AS psr
WHERE resource_id IN (
SELECT resources.resource_id FROM resources
WHERE resources.resource_id NOT IN (psr.resource_id, psr.scheme_id, psr.branch_id)
)
I think it may works and helps you
select *
from product_structure_resources
where concat(resource_id,scheme_id,branch_id) NOT IN
(select concat(resource_id,scheme_id,branch_id) from resources);

Using MySQL COUNT(1), COUNT(2) ...etc using JOIN

I have 4 tables:
Table talks
table talks_fan
table talks_follow
table talks_comments
What I'm trying to achieve is counting all comments, fans, followers for every single talk.
I came up with this so far.
All tables have talk_id and only in talks table is a primary key
SELECT
g. *,
COUNT( m.talk_id ) AS num_of_comments,
COUNT( f.talk_id ) AS num_of_followers
FROM
talks AS g
LEFT JOIN talks_comments AS m
USING ( talk_id )
LEFT JOIN talks_follow AS f
USING ( talk_id )
WHERE g.privacy = 'public'
GROUP BY g.talk_id
ORDER BY g.created_date DESC
LIMIT 30;
I also tried using this method
SELECT
t.*,
COUNT(b.talk_id) AS comments,
COUNT(bt.talk_id) AS followers
FROM
talks t
LEFT JOIN talks_follow bt
ON bt.talk_id = t.talk_id
LEFT JOIN talks_comments b
ON b.talk_id = t.talk_id
GROUP BY t.talk_id;
Both give me the same results ....?!
Update: Create Statements
CREATE TABLE IF NOT EXISTS `talks` (
`talk_id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` mediumint(9) NOT NULL,
`title` varchar(255) NOT NULL,
`content` text NOT NULL,
`created_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`privacy` enum('public','private') NOT NULL DEFAULT 'private',
PRIMARY KEY (`talk_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=7 ;
CREATE TABLE IF NOT EXISTS `talks_comments` (
`comment_id` bigint(20) NOT NULL AUTO_INCREMENT,
`talk_id` bigint(20) NOT NULL,
`user_id` mediumint(9) NOT NULL,
`comment` text NOT NULL,
`date_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`status` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`comment_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=8 ;
CREATE TABLE IF NOT EXISTS `talks_fan` (
`fan_id` bigint(20) NOT NULL AUTO_INCREMENT,
`talk_id` bigint(20) NOT NULL,
`user_id` bigint(20) NOT NULL,
`created_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`status` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`fan_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
CREATE TABLE IF NOT EXISTS `talks_follow` (
`follow_id` bigint(20) NOT NULL AUTO_INCREMENT,
`talk_id` bigint(20) NOT NULL,
`user_id` mediumint(9) NOT NULL,
`date_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`follow_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=5 ;
The final query that works
SELECT t.* , COUNT( DISTINCT b.comment_id ) AS comments,
COUNT( DISTINCT bt.follow_id ) AS followers,
COUNT( DISTINCT c.fan_id ) AS fans
FROM talks t
LEFT JOIN talks_follow bt ON bt.talk_id = t.talk_id
LEFT JOIN talks_comments b ON b.talk_id = t.talk_id
LEFT JOIN talks_fan c ON c.talk_id = t.talk_id
WHERE t.privacy = 'public'
GROUP BY t.talk_id
ORDER BY t.created_date DESC
LIMIT 30
EDIT: Final answer to the whole issue...
I have modified the Query and created some code in PHP (Codeigniter) to solve my issue apone the reccomendation of #Bill Karwin
$sql="
SELECT t.*,
COUNT( DISTINCT b.comment_id ) AS comments,
COUNT( DISTINCT bt.follow_id ) AS followers,
COUNT( DISTINCT c.fan_id ) AS fans,
GROUP_CONCAT( DISTINCT c.user_id ) AS list_of_fans
FROM talks t
LEFT JOIN talks_follow bt ON bt.talk_id = t.talk_id
LEFT JOIN talks_comments b ON b.talk_id = t.talk_id
LEFT JOIN talks_fan c ON c.talk_id = t.talk_id
WHERE t.privacy = 'public'
GROUP BY t.talk_id
ORDER BY t.created_date DESC
LIMIT 30
";
$query = $this->db->query($sql);
if($query->num_rows() > 0)
{
$results = array();
foreach($query->result_array() AS $talk){
$fan_user_id = explode(",", $talk['list_of_fans']);
foreach($fan_user_id AS $user){
if($user == 1 /* this supposed to be user id or session*/){
$talk['list_of_fans'] = 'yes';
}
}
$follower_user_id = explode(",", $talk['list_of_follower']);
foreach($follower_user_id AS $user){
if($user == 1 /* this supposed to be user id or session*/){
$talk['list_of_follower'] = 'yes';
}
}
$results[] = array(
'talk_id' => $talk['talk_id'],
'user_id' => $talk['user_id'],
'title' => $talk['title'],
'created_date' => $talk['created_date'],
'comments' => $talk['comments'],
'followers' => $talk['followers'],
'fans' => $talk['fans'],
'list_of_fans' => $talk['list_of_fans'],
'list_of_follower' => $talk['list_of_follower']
);
}
}
I STILL BELIEVE IT COULD BE OPTIMIZED IN THE DB AND JUST USE THE RESULT...
Im thinking if there are 1000 follower and 2000 fans of every single TALK then the result will take much longer to load.. HOW IF YOUT MULTIPLY THE NO WITH 10. Or im mistaking hear...
EDIT: adding benchmark for the query test...
I have used codeigniter profiler to know how long it take for the query to finish excuting.
that been said i also start adding data in the tables gratually
the result as follows.
Testing the DB after answerting data into it
Query Results time
table Talks
---------------
table data 50 rows.
Time: 0.0173 seconds
Table Rows: 644 rows
Time: 0.0535 seconds
Table Rows: 1250 rows
Time: 0.0856 seconds
Adding data to other tables
--------------------------
Talks = 1250 rows
talks_follow = 4115
talks_fan = 10 rows
Time: 2.656 seconds
Adding data to other tables
--------------------------
Talks = 1250 rows
talks_follow = 4115
talks_fan = 10 rows
talks_comments = 3650 rows
Time: 10.156 seconds
After replacing LEFT JOIN with STRAIGHT_JOIN
Time: 6.675 seconds
It seems that its extremely heavy on the DB.....
NOW Im Going to another dilemma on how to enhance its performance
Edited: using #leonardo_assumpcao suggestion
After rebuilding the DB using #leonardo_assumpcao suggestion
for indexing few fields..........
Adding data to other tables
--------------------------
Talks = 6000 Rows
talks_follow = 10000 Rows
talks_fan = 10000 Rows
talks_comments = 10000 Rows
Time: 17.940 second
Is this normal for heavy data DB......?
I can say this is (at least) one of the coolest select statements I improved today.
SELECT STRAIGHT_JOIN
t.* ,
COUNT( DISTINCT b.comment_id ) AS comments,
COUNT( DISTINCT bt.follow_id ) AS followers,
COUNT( DISTINCT c.fan_id ) AS fans
FROM
(
SELECT * FROM talks
WHERE privacy = 'public'
ORDER BY created_date DESC
LIMIT 0, 30
) AS t
LEFT JOIN talks_follow bt ON (bt.talk_id = t.talk_id)
LEFT JOIN talks_comments b ON (b.talk_id = t.talk_id)
LEFT JOIN talks_fan c ON (c.talk_id = t.talk_id)
GROUP BY t.talk_id ;
But it seems to me that your problem resides on your tables; A first step to obtain efficient queries is to index every field involved on your desired joins.
I've made some modifications on the tables you shown above; You can see its code here (updated).
Quite interesting, isn't it? Since we're here, take also your ERR model:
First try it using MySQL test database. Hopefully it will solve your performance troubles.
(Forgive my english, it's my second language)
You can force this into one query like so:
SELECT COUNT(*) num, 'talks' item FROM talks
UNION
SELECT COUNT(*) num, 'talks_fan' item FROM talks_fan
UNION
SELECT COUNT(*) num, 'talks_follow' item FROM talks_follow
UNION
SELECT COUNT(*) num, 'talks_comment' item FROM talks_comment
This will give you a five row resultset with one row per table. Each row is the count in a particular table.
If you must get it all into a single row you can do a pivot like so.
SELECT
SUM( CASE item WHEN 'talks' THEN num ELSE 0 END ) AS 'talks',
SUM( CASE item WHEN 'talks_fan' THEN num ELSE 0 END ) AS 'talks_fan',
SUM( CASE item WHEN 'talks_follow' THEN num ELSE 0 END ) AS 'talks_follow',
SUM( CASE item WHEN 'talks_comment' THEN num ELSE 0 END ) AS 'talks_comment'
FROM
( SELECT COUNT(*) num, 'talks' item FROM talks
UNION
SELECT COUNT(*) num, 'talks_fan' item FROM talks_fan
UNION
SELECT COUNT(*) num, 'talks_follow' item FROM talks_follow
UNION
SELECT COUNT(*) num, 'talks_comment' item FROM talks_comment
) counts
(This doesn't take into account your WHERE g.privacy = clause because I don't understand that. But you could add a WHERE clause to one one of the four queries in the UNION item to handle that.)
Notice that this truly is four queries on four separate tables coerced into a single query.
And, by the way, there is no difference in value between COUNT(*) and COUNT(id) when id is the primary key of the table. COUNT(id) doesn't count the rows for which the id is NULL, but if id is the primary key, then it is NOT NULL. But COUNT(*) is faster, so use it.
Edit if you need the number of fan, follow, and comment rows for each distinct talk, do this. It's the same idea of doing a union and a pivot, but with an extra parameter.
SELECT
talk_id,
SUM( CASE item WHEN 'talks_fan' THEN num ELSE 0 END ) AS 'talks_fan',
SUM( CASE item WHEN 'talks_follow' THEN num ELSE 0 END ) AS 'talks_follow',
SUM( CASE item WHEN 'talks_comment' THEN num ELSE 0 END ) AS 'talks_comment'
FROM
(
SELECT talk_id, COUNT(*) num, 'talks_fan' item
FROM talks_fan
GROUP BY talk_id
UNION
SELECT talk_id, COUNT(*) num, 'talks_follow' item
FROM talks_follow
GROUP BY talk_id
UNION
SELECT talk_id, COUNT(*) num, 'talks_comment' item
FROM talks_comment
GROUP BY talk_id
) counts
GROUP BY talk_id
After doing this for (too) many years, I've discovered that the best way to describe a query you need is to say to yourself "I need a result set with one row for each xxx, with columns for yyy, zzz, and qqq."
The reason the counts are the same is that it's counting rows after the joins have combined the tables. By joining to multiple tables, you're creating a Cartesian product.
Basically, you're counting not only how many comments per talk, but how many comments * followers per talk. Then you count the followers as how many followers * comments per talk. Thus the counts are the same, and they're all way too high.
Here's a simpler way to write a query to count each distinct comment, follower, etc. only once:
SELECT t.*,
COUNT(DISTINCT b.comment_id) AS comments,
COUNT(DISTINCT bt.follow_id) AS followers
FROM talks t
LEFT JOIN talks_follow bt ON bt.talk_id = t.talk_id
LEFT JOIN talks_comments b ON b.talk_id = t.talk_id
GROUP BY t.talk_id;
Re your comment: I wouldn't fetch all the followers in the same query. You could do it this way:
SELECT t.*,
COUNT(DISTINCT b.comment_id) AS comments,
COUNT(DISTINCT bt.follow_id) AS followers,
GROUP_CONCAT(DISTINCT bt.follower_name) AS list_of_followers
FROM talks t
LEFT JOIN talks_follow bt ON bt.talk_id = t.talk_id
LEFT JOIN talks_comments b ON b.talk_id = t.talk_id
GROUP BY t.talk_id;
But what you'd get back is a single string with the follower names separated by commas. Now you have to write application code to split the string on commas, you have to worry if some follower names actually contain commas already, and so on.
I'd do a second query, fetching the followers for a given talk. It's likely you want to display the followers only for a specific talk anyway.
SELECT follower_name
FROM talks_follow
WHERE talk_id = ?