Categories table:
+-----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+----------------+
| cat_id | int(8) | NO | PRI | NULL | auto_increment |
| cat_name | varchar(255) | NO | UNI | NULL | |
| cat_description | varchar(255) | NO | | NULL | |
| cat_order | int(8) | YES | | NULL | |
+-----------------+--------------+------+-----+---------+----------------+
Topics table:
+---------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+----------------+
| topic_id | int(8) | NO | PRI | NULL | auto_increment |
| topic_subject | varchar(255) | NO | | NULL | |
| topic_date | datetime | NO | | NULL | |
| topic_cat | int(8) | NO | MUL | NULL | |
| topic_by | int(8) | NO | MUL | NULL | |
+---------------+--------------+------+-----+---------+----------------+
topic_cat corresponds to cat_id in a foreign key relationship.
How can i write an sql statement which returns: The NEWEST topic from every category plus the category name???
Heres what i have so far:
SELECT * FROM categories
LEFT JOIN
(SELECT topic_id, topic_subject, max(topic_date) AS MaxDate, topic_cat, topic_by
FROM topics
GROUP BY topic_subject) AS mt
ON categories.cat_id = mt.topic_cat
ORDER BY cat_order;
It returns multiple topics from the same category, where i only want one topic per category.
When you try to get the greatest-n-per-group, you need to join back to the original table (topics) to pick the row that has the max date. Because just mentioning MAX(topic_date) in your subquery doesn't make the other columns come from the row where that max date is found. What if you also mentioned MIN(topic_date)?
This should be a solution (though I haven't tested it):
SELECT *
FROM topics AS t
JOIN (SELECT topic_cat, MAX(topic_date) AS topic_date
FROM topics
GROUP BY topic_cat) AS maxt USING (topic_cat, topic_date)
RIGHT JOIN categories AS c ON t.topic_cat = c.cat_id
The first, easy, but not so powerfull solution would be to GROUP BY topic_cat and ORDER BY topic_date in topics select:
SELECT * FROM categories
LEFT JOIN
(SELECT topic_id, topic_subject, topic_date, topic_cat, topic_by
FROM topics
ORDER BY topic_date DESC
GROUP BY topic_cat) AS mt
ON categories.cat_id = mt.topic_cat
ORDER BY cat_order;
I think MySQL should be able to handle this, even it's not really universal solution for other database engines, where the rules for grouping are more strict.
Related
I am running a mysql database. I have two tables that store the likes and comments on posts respectively.
Likes table:
+--------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| post_id | int(11) | NO | | NULL | |
| user_id | varchar(255) | NO | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
+--------------+------------------+------+-----+---------+----------------+
Comments Table:
+--------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| post_id | int(11) | NO | | NULL | |
| user_id | varchar(255) | NO | | NULL | |
| comment | varchar(255) | NO | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
+--------------+------------------+------+-----+---------+----------------+
I need to get unique user ids and their likes and comment counts.
I ran the following query:
select * from
(select user_id, count(*) as like_count from post_likes group by user_id) as a,
(select user_id, count(*) as comment_count from post_comments group by user_id) as b
limit 5
The query took 66 seconds on 1.8 million records (both tables combined). Also, it didn't give correct result.
If I do a LEFT JOIN, it returns only matching records from the left table, whereas I need from both, i.e. if a user id has only liked and not commented or only commented and not liked.
What I am trying to get is something like:
+--------------+------------------+------+-----+---------+
| user_id | like_count | comment_count |
+--------------+------------------+------+-----+---------+
| 1 | 1213 | 310 |
| 2 | 1098 | 0 |
| 3 | 0 | 115 |
+--------------+------------------+------+-----+---------+
An ideal way would be to LEFT JOIN on user table, e.g.:
SELECT u.user_id, COUNT(l.id), COUNT(c.id)
FROM users u LEFT JOIN Likes l ON u.user_id = l.user_id
LEFT JOIN Comments c ON u.user_id = c.user_id;
Do a UNION ALL instead, and then merge the rows:
select user_id, sum(like_count), sum(comment_count)
from
(
select user_id, count(*) as like_count, null as comment_count
from post_likes
group by user_id
union all
select user_id, null, count(*)
from post_comments
group by user_id
) as dt
group by user_id
One way is to use UNION ALL and conditional aggregation:
SELECT user_id,
SUM(type='likes') AS like_count,
SUM(type='comments') AS comment_count
FROM (
SELECT user_id, 'likes' AS type
FROM post_likes
UNION ALL
SELECT user_id, 'comments' AS type
FROM post_comments) AS t
GROUP BY user_id
This query will not return users having no likes and no comments.
here are the contents of the tables.
mysql> desc student;
+------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+----------------+
| name | varchar(20) | NO | | NULL | |
| sex | enum('F','M') | NO | | NULL | |
| student_id | int(10) unsigned | NO | PRI | NULL | auto_increment |
+------------+------------------+------+-----+---------+----------------+
mysql> desc grade_event;
+----------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+------------------+------+-----+---------+----------------+
| date | date | NO | | NULL | |
| category | enum('T','Q') | NO | | NULL | |
| event_id | int(10) unsigned | NO | PRI | NULL | auto_increment |
+----------+------------------+------+-----+---------+----------------+
mysql> desc score;
+------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+-------+
| student_id | int(10) unsigned | NO | PRI | NULL | |
| event_id | int(10) unsigned | NO | PRI | NULL | |
| score | int(11) | NO | | NULL | |
+------------+------------------+------+-----+---------+-------+
What I'm trying to accomplish is to display which students missed a quiz/test, found under 'category' in the grade_event table.
Here's what I've come up with, but am not generating any results;
select name, category, sc.event_id
from student s
join score sc on s.student_id=sc.student_id
join grade_event ge on sc.event_id=ge.event_id
where score is NULL
group by name, event_id;
I've also gone the route of attempting a subquery;
select name, category, sc.event_id
from student s
join score sc on s.student_id=sc.student_id
join grade_event ge on sc.event_id=ge.event_id
where score not in (select score from score)
group by name, event_id;
Any help would be appreciated.
I think you should just replace your join by left join, join is an inner join in MySQL : http://dev.mysql.com/doc/refman/5.7/en/join.html
And take care with group by event_id, it can be useful to precise group by sc.event_id. I don't know in MySQL but in sql server it wouldn't work.
Your second query is necessarily empty as you ask a column to have its values not in its values :)
You need to use an outer join instead of an inner join to get list of students that do not have a corresponding record in the events after creating a carthesian join of students and events:
select name, category, ge.event_id
from (student s
join grade_event ge) --no join condition creates a carthesian join
left join score sc on s.student_id=sc.student_id and sc.event_id=ge.event_id
where sc.score is NULL
I have three tables, and duplicate column names also :) I want to join albums to products and images to albums. Images are many. Trying such query, it gives me duplicate products. Is there a chance to grab everything in one query?
SELECT
*, p.name as nazwa, a.name as nazwa_al, i.name as obrazek
FROM products p
JOIN
albums a on p.album_id=a.id
JOIN
(SELECT *, images.name AS nazwa_im FROM images ORDER BY images.order ASC) i
ON i.album_id=a.id
ORDER BY p.order ASC
Products
+-------------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | text | NO | | NULL | |
| description | text | NO | | NULL | |
| album_id | int(11) | YES | | NULL | |
| order | int(11) | NO | | NULL | |
+-------------+---------+------+-----+---------+----------------+
Albums
+-------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | text | NO | | NULL | |
+-------+---------+------+-----+---------+----------------+
Images
+----------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | text | NO | | NULL | |
| alt | text | NO | | NULL | |
| album_id | int(11) | NO | | NULL | |
| order | int(11) | NO | | NULL | |
+----------+---------+------+-----+---------+----------------+
For the sake of simplicity, I don't want to modify structure of db. The easiest solution for me would be: one product=>one album=>many images
Use joins and use aliases to solve duplicate name error.
You can use distint or group by have results aligned as per same product id.
SELECT
*, p.name as nazwa, a.name as nazwa_al, i.name as obrazek
FROM
products p
JOIN
albums a on p.album_id = a.id
JOIN
images i ON i.album_id = a.id
GROUP BY p.id
ORDER BY p.order ASC
You need to use group_concat if multiple rows on right side.
SELECT
*, p.name as nazwa, a.name as nazwa_al, group_concat(i.name) as obrazek
FROM
products p
JOIN
albums a on p.album_id = a.id
JOIN
images i ON i.album_id = a.id
GROUP BY p.id
ORDER BY p.order ASC
This is very basic, I know, but I tend to happen on this issue often.
Tables
mysql> describe Posts;
+-----------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| Time | int(11) | YES | MUL | NULL | |
+-----------+---------+------+-----+---------+----------------+
mysql> describe PostCategories;
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| pid | int(11) | YES | | NULL | |
| Category | varchar(20) | YES | | NULL | |
+----------+-------------+------+-----+---------+----------------+
Query
SELECT P.id AS pid, P.Time, PC.Category FROM Posts P
LEFT OUTER JOIN PostCategories PC ON PC.pid = P.id
WHERE PC.Category IN('value1','value2') OR PC.Category IS NULL
ORDER BY P.Time DESC
Returns
+-----+------------+----------+
| pid | Time | Category |
+-----+------------+----------+
| 8 | 1396906256 | NULL |
| 7 | 1396524835 | value1 |
| 7 | 1396524835 | value2 |
+-----+------------+----------+
Desired outcome
I would like it to only give me one row for every pid. In other words, no matter how many categories the Post have, I want it to only result in one row in the result dataset.
+-----+------------+----------+
| pid | Time | Category |
+-----+------------+----------+
| 8 | 1396906256 | NULL |
| 7 | 1396524835 | value1 |
+-----+------------+----------+
Category result does not matter, I will not fetch it once it works as I want it to.
SELECT P.id AS pid, MIN(P.Time) as first_post_time, MAX(PC.Category) as Category FROM Posts P
LEFT OUTER JOIN PostCategories PC ON PC.pid = P.id
WHERE PC.Category IN('value1','value2') OR PC.Category IS NULL
GROUP BY P.id
ORDER BY P.Time DESC
Just add group by and calculate time and category (use min or max)
Group posts by using Group By and you can also get all categories if you want by using group_concat procedure in mysql.
SELECT P.id AS pid, P.Time, GROUP_CONCAT(PC.Category, ' , ') FROM Posts P
LEFT OUTER JOIN PostCategories PC ON PC.pid = P.id
WHERE PC.Category IN('value1','value2') OR PC.Category IS NULL
GROUP BY P.pid
ORDER BY P.Time DESC
I have three tables that look like this:
People:
+------------+-------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| fname | varchar(32) | NO | | NULL | |
| lname | varchar(32) | NO | | NULL | |
| dob | date | NO | | 0000-00-00 | |
| license_no | varchar(24) | NO | | NULL | |
| date_added | timestamp | NO | | CURRENT_TIMESTAMP | |
| status | varchar(8) | NO | | Allow | |
+------------+-------------+------+-----+-------------------+----------------+
Units:
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| number | varchar(3) | NO | | NULL | |
| resident | int(11) | NO | MUL | NULL | |
| type | varchar(16) | NO | | NULL | |
+----------+-------------+------+-----+---------+----------------+
Visits:
+----------+-----------+------+-----+---------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-----------+------+-----+---------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| vis_id | int(11) | NO | MUL | NULL | |
| unit | int(11) | NO | MUL | NULL | |
| time_in | timestamp | NO | | CURRENT_TIMESTAMP | |
| time_out | timestamp | NO | | 0000-00-00 00:00:00 | |
+----------+-----------+------+-----+---------------------+----------------+
There are multiple foreign keys linking these tables:
units.resident -> people.id
visits.unit -> units.id
visits.vis_id -> people.id
I am able to run this query to find all residents ie - everyone from people that are referenced by the units.resident foreign key:
SELECT concat(p.lname, ', ', p.fname) as 'Resident', p.dob as 'Birthday',
u.number as 'Unit #'
from people p, units u
where p.id = u.resident
order by u.number
It returns the results I want... However, it'd be useful to do the opposite of this to find all the people who are not residents ie- everyone from people who aren't referenced by the units.resident foreign key.
I've tried many different queries, most notably some inner and left joins, but I'm getting waaaaay too many duplicate entries (from what I've read here, this is normal). The only thing I've found that works is using a group by license_no, because as of now the "residents" don't have this information, like this:
SELECT p.id, concat(p.lname, ', ', p.fname) as 'Visitor',
p.license_no as 'License', u.number from people p
left join units u on u.number <> p.id
group by p.license_no order by p.id;
This works for all but one resident, who's u.number is displayed on ALL results. The residents will soon have license_no entries, and I can't have that one odd entry in the returned results all the time, so this query won't work as a long-term solution.
How can I structure a query without a group by that will return the results I want?
This should work
SELECT
p.id
, P.fname
, P.lname
FROM
people AS p
LEFT JOIN
units AS u
ON
p.id = u.resident
WHERE
u.resident IS NULL
Extra hint.
Table people should be called person.
By u.resident you mean a person. so it should be a person_id there in the unit table...
Better logic helps to write SQL better, if your name convention is clear to use.
Use a NOT EXISTS clause to exclude those people who are residents.
SELECT P.id
,P.fname
,P.lname
,etc...
FROM People P
WHERE NOT EXISTS (SELECT 1 FROM Units U WHERE U.resident = P.id)