I have table definition like below:
Place (id, name)
Review (id, userid, placeid)
Favorite (id, userid, placeid)
Photo (id, url, placeid)
where placeid is foreign key to the id of Place table.
On that table, I want to derive this kind of information:
- placeid, place name, totalReview, totalFavorite, totalPhoto.
I got stucked. My progress currently I can derive information just from 1 table, like I can know totalReview of place, by using this mysql statement:
SELECT p.*, count(r.id) as totalReview from Place p left join Review r on p.id = r.placeid group by p.id.
But, I don't know how I can derive the totalFavorite and totalPhoto.
You need to aggregate each table separately. Here is one solution:
SELECT p.*,
totalreview,
totalfavorite,
totalphoto
FROM place p
LEFT OUTER JOIN (SELECT placeid,
Count(*) AS totalReview
FROM review
GROUP BY placeid) r
ON p.placeid = r.placeid
LEFT OUTER JOIN (SELECT placeid,
Count(*) AS totalFavorite
FROM favorite
GROUP BY placeid) f
ON p.placeid = f.placeid
LEFT OUTER JOIN (SELECT placeid,
Count(*) AS totalPhoto
FROM photo
GROUP BY placeid) ph
ON p.placeid = ph.placeid
This is a simple way to do this:
SELECT
p.id, p.name,
(SELECT COUNT(*) FROM Review r WHERE r.placeId=p.id) AS totalReview
(SELECT COUNT(*) FROM Favorite f WHERE f.placeId=p.id) AS totalFavorite
(SELECT COUNT(*) FROM Photo ph WHERE ph.placeId=p.id) AS totalPhoto
FROM Place p
Related
I have two tables: users and courses. Inside users table i have filed course where i have course id. Inside courses table i have just ID and NAME.
I need to get popular course. I do request:
SELECT u.course, COUNT(*) as freq FROM users u INNER JOIN courses c ON u.course = c.id GROUP BY u.course
As a result: id => freq. But i need to replace ID to NAME of course. How?
Thanks.
You don't say what database you use, but I would assume you can use CTEs since most modern databases do. Your query can be written as:
with x as (
select course, count(*) as freq from users group by course
),
y as (
select max(freq) as max_freq from x
)
select c.name, x.freq
from x
join y on x.freq = y.max_freq
join courses c on c.id = x.course
This query has the [desirable?] side effect that it shows more than one course, if there are more than one tied in first place.
Add c.name to both the SELECT clause and the GROUP BY clause.
SELECT u.course, c.name, COUNT(*) as freq
FROM users u
INNER JOIN courses c
ON u.course = c.id
GROUP BY u.course, c.name;
Demo: https://dbfiddle.uk/?rdbms=mariadb_10.3&fiddle=02a41e0f1e6407e516e91c49b4bdc1d2
SELECT u.course, COUNT(*) as freq, c.name FROM users u INNER JOIN courses c ON u.course = c.id GROUP BY u.course
If your DBMS supports row_number this will be suitable:
select t.id, c.name, t.cnt
from course c
join (
select c.id, count(1) cnt, ROW_NUMBER() over(order by count(1) desc) rn
from users u
join course c on c.id = u.course
group by id
)t on t.id = c.id and t.rn = 1
Some background:
I'm developing kind of social network.
There is DB with next tables:
users(user_id, user_name, password, etc.)
posts(post_id, user_id, post_text, post_data, etc.)
followings(relationship_id, follower_id, following_id, added_date_time)
likes(like_id, user_id, post_id, added_date_time)
comments(comment_id, user_id, post_id, comment_text, added_date_time)
The question is:
How can I implement query which would fetch information about what my followings(people I follow) have did recently (e.g. someone liked/commented something) and order this array of info by date_time?
Can I make it using one query? Or I'll have to make multiple queries and handle all this stuff in PHP by myself?
What is the best approach?
You can normalize the output in a select union like this one
SELECT t.type, t.user_id, u.user_name, t.type_id, t.post_id, t.text, t.added_date_time
FROM (
SELECT 'posts' as type, p.user_id, p.post_id as type_id, p.post_id, post_text as text, p.added_date_time
FROM posts p
JOIN followings f ON (f.following_id = p.user_id)
WHERE f.follower_id = #user
UNION
SELECT 'comments' as type, c.user_id, c.comment_id as type_id, c.post_id, comment_text as text, c.added_date_time
FROM comments c
JOIN followings f ON (f.following.id = c.user_id)
WHERE f.follower_id = #user
UNION
SELECT 'likes' as type, l.user_id, l.like_id as type_id, l.post_id, post_text as text, l.added_date_time
FROM likes l
JOIN followings f ON (f.following.id = c.user_id)
JOIN posts p ON (l.post_id = p.post_id)
WHERE f.follower_id = #user
) as t
JOIN users u ON (t.user_id = u.user_id)
ORDER BY added_date_time DESC;
So you can get all the data in only one query.
I hope it works fine for you.
You have to seperate at least the queries for likes and comments, since the result set is different.
Otherwise your query would look e.g. like:
SELECT comment_id,comment_text,added_date_time FROM comments WHERE user_id IN (SELECT following_id FROM followings WHERE follower_id={{USER_ID_FROM_USER}}) ORDER BY added_date_time
This will get you the comments from people which are followed by {{USER_ID_FROM_USER}} ordered by date.
The following statement gets row counts for user_ids from various tables/conditions where the users are within specific computers of a specific account. It works as expected. An example output would be something like this :
Array
(
[0] => Array
(
[computer_name] => COMPUTER_1
[username] => Steve
[t1count] => 13
[t2count] =>
[t3count] => 23
[t4count] => 64
)
... and so on for each
the statement :
$stmt = $db->prepare("
SELECT c.computer_name, users.username, t1count, t2count, t3count, t4count
FROM
( SELECT account_id, computer_id, computer_name
FROM computers
WHERE account_id = ".$_SESSION['user']['account_id']."
ORDER BY computer_id ASC LIMIT 0, ".$_SESSION['user']['licenses']."
) as c
LEFT JOIN users
on users.computer_id = c.computer_id
LEFT JOIN
(SELECT user_id, COUNT(user_id) as t1count
FROM t1
WHERE t1.title LIKE 'started'
GROUP BY user_id) as t_t1
on t_t1.user_id = users.user_id
LEFT JOIN
(SELECT user_id, COUNT(user_id) as t2count
FROM t2
GROUP BY user_id) as t_t2
on t_t2.user_id = users.user_id
LEFT JOIN
(SELECT user_id, COUNT(user_id) as t3count
FROM t1
WHERE t1.title LIKE 'blocked'
GROUP BY user_id) as t_t3
on t_t3.user_id = users.user_id
LEFT JOIN
(SELECT user_id, COUNT(user_id) as t4count
FROM t1
WHERE t1.title LIKE 'closed'
GROUP BY user_id) as t_t4
on t_t4.user_id = users.user_id
... and so on for each
WHERE c.account_id = ?
");
I want to also return a totalsum = t1count + t2count + t3count + t4count in this statement as well, but can't seem to get anything working. In this situation I cannot do outside processing (adding the values that are returned)... it needs to be in the statement. I am also open to any suggestions in what I already have if there are better options.
What are you trying to do with these values? SUM(t1count,t2count,t3count,t4count) AS totalsum should work to get a count of counts.
Your query is a bit hard to read. You have implicit and explicit JOINs. You are grouping numerous times and pulling columns when you can generally do this all in one shot. If you share an SQLFiddle, I will be able to clean this up, but in short:
SELECT
c.computer_name,
users.username,
count(t_t1.user_id) AS t1count,
count(t_t2.user_id) AS t2count,
count(t_t3.user_id) AS t3count,
count(t_t4.user_id) AS t4count,
(count(t_t1.user_id)+count(t_t2.user_id)+count(t_t3.user_id)+count(t_t4.user_id)) AS totalsum FROM users
LEFT JOIN computers AS c ON users.computer_id=c.computer_id AND c.account_id=?
LEFT JOIN t1 AS t_t1 ON t_t1.user_id = users.user_id AND t_t1.title LIKE "started"
LEFT JOIN t2 AS t_t2 ON t_t2.user_id = users.user_id
LEFT JOIN t1 AS t_t3 ON t_t3.user_id = users.user_id AND t_t3.title LIKE 'blocked'
LEFT JOIN t1 AS t_t4 ON t_t4.user_id = users.user_id AND t_t4.title LIKE 'closed'
WHERE c.account_id = ?
AND t_t1.title LIKE started
GROUP BY users.user_id;
This may need tweaking, as I stated, but it is a lot cleaner and easier to read and should accomplish something very similar.
Alternately, if you can't get the query to run the way you want it to when you change it to entirely explicit joins, rather than using SUM, try adding the values together like I did in the example above. It should prevent them from aggregating in the same way.
EDIT
After viewing your SQLFiddle, I have doctored up a solution which does away with nested queries. The positive is that it is cleaner. The negative is that it requires you to specify the users using an IN clause.
SELECT computers.account_id,computers.computer_id,computers.computer_name,users.user_id,users.username,count(distinct t_count1.log_id) AS count1,count(distinct t_count2.log_id) AS count2,count(distinct t_count3.log_id) AS count3, count(distinct t_count4.event_id) AS count4,
(count(distinct t_count1.log_id) + count(distinct t_count2.log_id) + count(distinct t_count3.log_id) + count(distinct t_count4.event_id)) AS totalcount
FROM users
INNER JOIN computers ON computers.computer_id=users.computer_id
LEFT JOIN logs AS t_count1 ON t_count1.type LIKE 'type1' AND t_count1.user_id=users.user_id
LEFT JOIN logs AS t_count2 ON t_count2.type LIKE 'type2' AND t_count2.user_id=users.user_id
LEFT JOIN logs AS t_count3 ON t_count3.type LIKE 'type3' AND t_count3.user_id=users.user_id
LEFT JOIN events AS t_count4 ON t_count4.user_id = users.user_id
WHERE computers.account_id=1 AND computers.computer_id in (1,2)
GROUP BY users.user_id
ORDER BY users.user_id ASC,computers.computer_id ASC;
If you choose to keep your current query structure for any reason, adapting it like so should make it work for you:
select *,ifnull(count1,0)+ifnull(count2,0)+ifnull(count3,0)+ifnull(count4,0) AS totalcount from
( select account_id, computer_id, computer_name
from computers
order by computer_id asc limit 0, 2
) as c
left join users
on users.computer_id = c.computer_id
left join
(select user_id, count(user_id) as count1
from logs
where logs.type like 'type1'
group by user_id) as t_count1
on t_count1.user_id = users.user_id
left join
(select user_id, ifnull(count(user_id),0) as count2
from logs
where logs.type like 'type2'
group by user_id) as t_count2
on t_count2.user_id = users.user_id
left join
(select user_id, count(user_id) as count3
from logs
where logs.type like 'type3'
group by user_id) as t_count3
on t_count3.user_id = users.user_id
left join
(select user_id, count(user_id) as count4
from events
group by user_id) as t_count4
on t_count4.user_id = users.user_id
where c.account_id = 1;
My advice would be to follow along each query to understand what you are asking SQL to do. To actually add all values together, you should be counting the number of records that are returned. Counting values of your primary keys helps you to count records. Also, using your other example, ifnull in the second example make sure that null values are not going to interfere with adding. "If a value is null make it 0 instead."
I have tried the following syntax, which seems to work fine on SQL when I use Oracle SQL Developer. However, when I use the code in MySQL, I get ther error "Something went wrong!".
$result = mysqli_query($con, "SELECT * FROM Table1 NATURAL JOIN (SELECT ID, SUM(row2) FROM table2 GROUP BY ID) NATURAL JOIN (SELECT ID, COUNT(col1) FROM Table2 WHERE ID IS NOT NULL GROUP BY ID)")
or die("Something went wrong!");
Is there a difference in syntax in this case, or could it be anything else I'm doing wrong?
Thanks!
This is your query:
SELECT *
FROM Table1 NATURAL JOIN
(SELECT ID, SUM(row2) FROM table2 GROUP BY ID) NATURAL JOIN
(SELECT ID, COUNT(col1) FROM Table2 WHERE ID IS NOT NULL GROUP BY ID)
MySQL supports NATURAL JOIN (I don't recommend using it, but that is another matter). Unlike Oracle, you need to have table aliases on the tables, essentially names for them. You may also need column aliases. I always use them, so I don't know if they are needed.
Try this:
SELECT *
FROM Table1 t1 NATURAL JOIN
(SELECT ID, SUM(row2) as cnt_row2
FROM table2
GROUP BY ID
) t2row NATURAL JOIN
(SELECT ID, COUNT(col1) as cnt_col1
FROM Table2
WHERE ID IS NOT NULL
GROUP BY ID
) t2col1
You can actually simplify this query:
SELECT t1.*, t2.cnt_col1, t2.cnt_row2
FROM Table1 t1 INNER JOIN
(SELECT ID, COUNT(col1) as cnt_col1, SUM(row2) as cnt_row2
FROM Table2
GROUP BY ID
) t2
ON t1.id = t2.id
The filtering on ID IS NOT NULL has no effect, because a NULL value will not be included in the join.
In MySQL all the Sub queries that are used in the FROM part need an alias name. In your case its missing. And I don't use NATURAL JOIN because using other specific INNER JOIN or LEFT JOIN clarifies the condition and scenario. So may be you could rewrite your query like this to make it work:
SELECT * FROM Table1
INNER JOIN (SELECT ID, SUM(row2) FROM table2 GROUP BY ID) a
ON a.ID = Table1.ID
INNER JOIN (SELECT ID, COUNT(col1) FROM Table2 WHERE ID IS NOT NULL GROUP BY ID) b
ON b.ID = Table2.ID;
I have the following query which works perfectly:
SELECT *
FROM contacts
WHERE id in (
SELECT DISTINCT contacts.id
FROM contacts
INNER JOIN contacts2tags
ON contacts.id = contacts2tags.contactid
WHERE tagid in(7,4)
)
Here contacts table contains id, first_name, last_name, ..and tags table contains id, name. contacts2tags table contains contactid and tagid which are same as contacts.id and tags.id respectively
Now, what I want is, to display only the contacts which have both a tagid 7 and a tagid 4.
I tried something like this:
SELECT *
FROM contacts
WHERE id IN
(
SELECT CT1.contactid
FROM
tags T1, contacts2tags CT1, tags T2, contacts2tags CT2
WHERE CT1.contactid = CT2.contactid
AND CT1.tagid = T1.id
AND CT2.tagid = T2.id
AND (T1.id = 7 AND T2.id = 4)
and it works too.
My problem is, I want to convert the above second query to one using inner joins.
I have an array of ids stored in $tmp in php
I want to use those ids and write the above query for them.
How do I do that? I am not comfortable with sql. Might be its a very simple thing to ask.
Thanks in advance
EDIT:
The answer below solved the problem. But the sql runs very slow for 10k records. Any suggestions to optimise it? Pasting the updated query as given in the answer.
SELECT c.id
FROM contacts c
inner join contacts2tags t on c.id = t.contactid
where t.tagid in (7,4)
group by c.id
having count(distinct t.tagid) = 2
This should work
SELECT c.id
FROM contacts c
inner join contacts2tags t on c.id = t.contactid
where t.tagid in (7,4)
group by c.id
having count(distinct t.tagid) = 2