I'm having an issue with a query using INNER JOIN.
I have two tables. I need the department name and all three approvers. If any of the approvers are NULL, I need that displayed also.
mysql> desc department;
+-------------------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+----------+------+-----+---------+----------------+
| id | int(8) | NO | PRI | NULL | auto_increment |
| departmentName | tinytext | YES | | NULL | |
| primaryApprover | int(8) | YES | | NULL | |
| secondaryApprover | int(8) | YES | | NULL | |
| tertiaryApprover | int(8) | YES | | NULL | |
+-------------------+----------+------+-----+---------+----------------+
mysql> desc approver;
+------------------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+------------+------+-----+---------+----------------+
| id | int(8) | NO | PRI | NULL | auto_increment |
| approverName | tinytext | YES | | NULL | |
| approverPosition | tinytext | YES | | NULL | |
| approverLogonId | tinytext | YES | | NULL | |
| approverEmail | tinytext | YES | | NULL | |
| isActive | tinyint(1) | YES | | NULL | |
+------------------+------------+------+-----+---------+----------------+
The following query works, but it does not give me data where the primary or secondary approver are NULL:
SELECT
a.departmentName as DEPARTMENT,
pa.approvername as PRIMARY,
sa.approvername as SECONDARY,
ta.approvername as TERTIARY
FROM
department as a
INNER JOIN
approver pa on a.primaryapprover=pa.id
INNER JOIN
approver sa on a.secondaryapprover = sa.id
INNER JOIN
approver ta on a.tertiaryapprover = ta.id
ORDER BY
a.departmentname;
Using this query, I get this result:
+--------------------------------+---------------------------+---------------------------+------------------------+
| DEPARTMENT | PRIMARY_APPROVER | SECONDARY_APPROVER | TERTIARY_APPROVER |
+--------------------------------+---------------------------+---------------------------+------------------------+
| Facilities | Washburn, Hoban | Cobb, Jayne | Reynolds, Malcomn |
| Personnel / HR | Frye, Kaylee | Serra, Inara | Book, Dariel |
+--------------------------------+---------------------------+---------------------------+------------------------+
2 rows in set (0.00 sec)
but should get this result:
+--------------------------------+---------------------------+---------------------------+------------------------+
| DEPARTMENT | PRIMARY_APPROVER | SECONDARY_APPROVER | TERTIARY_APPROVER |
+--------------------------------+---------------------------+---------------------------+------------------------+
| Business Office | NULL | Rample, Fanty | Niska, Adelei |
| Facilities | Washburn, Hoban | Cobb, Jayne | Reynolds, Malcomn |
| Personnel / HR | Frye, Kaylee | Serra, Inara | Book, Dariel |
| Technical Services | Tam, River | NULL | Tam, Simon |
+--------------------------------+---------------------------+---------------------------+------------------------+
4 rows in set (0.00 sec)
I'm not good at joins to begin with....what am I missing here?
Just use LEFT JOINS
SELECT
a.departmentName as DEPARTMENT,
pa.approvername as PRIMARY,
sa.approvername as SECONDARY,
ta.approvername as TERTIARY
FROM
department as a
LEFT JOIN
approver pa on a.primaryapprover=pa.id
LEFT JOIN
approver sa on a.secondaryapprover = sa.id
LEFT JOIN
approver ta on a.tertiaryapprover = ta.id
ORDER BY
a.departmentname;
INNER JOIN - keeps only records that match from both sides .
LEFT JOIN - keeps all the records from the left table, and only the record matching from the right table.
You can also use COALESCE to replace null values with a default value like '-1' or something.
Related
I'm having troubles in making a rather difficult MySQL query work. I've been trying, but creating complex queries has never been my strong side.
This query includes 4 tables, which I'll describe of course.
First, we have song table, which I need to select the needed info from.
+--------------+-----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-----------+------+-----+---------+----------------+
| ID | int(6) | NO | PRI | - | auto_increment |
| Anime | char(100) | NO | | - | |
| Title | char(100) | NO | | - | |
| Type | char(20) | NO | | - | |
| Singer | char(50) | NO | | - | |
| Youtube | char(30) | NO | | - | |
| Score | double | NO | | 0 | |
| Ratings | int(8) | NO | | 0 | |
| Favourites | int(7) | NO | | 0 | |
| comments | int(11) | NO | | 0 | |
| release_year | int(4) | NO | | 2019 | |
| season | char(10) | NO | | Spring | |
+--------------+-----------+------+-----+---------+----------------+
Then we have song_ratings, which basically represents the lists of each user, since once you rate a song, it appears on your list.
+------------+----------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+----------+------+-----+-------------------+----------------+
| ID | int(11) | NO | PRI | 0 | auto_increment |
| UserID | int(11) | NO | MUL | 0 | |
| SongID | int(11) | NO | MUL | 0 | |
| Rating | double | NO | | 0 | |
| RatedAt | datetime | NO | | CURRENT_TIMESTAMP | |
| Favourited | int(1) | NO | | 0 | |
+------------+----------+------+-----+-------------------+----------------+
Users have the option to create custom lists(playlists), and this is the table which they are stored in. This is table lists.
+------------+-----------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-----------+------+-----+-------------------+----------------+
| ID | int(11) | NO | PRI | 0 | auto_increment |
| userID | int(11) | NO | MUL | 0 | |
| name | char(50) | NO | | - | |
| likes | int(11) | NO | | 0 | |
| favourites | int(11) | NO | | 0 | |
| created_at | datetime | NO | | CURRENT_TIMESTAMP | |
| cover | char(100) | NO | | - | |
| locked | int(1) | NO | | 0 | |
| private | int(1) | NO | | 0 | |
+------------+-----------+------+-----+-------------------+----------------+
And finally, the table which contains all the songs that have been added to any playlists, called list_elements.
+--------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+----------------+
| ID | int(11) | NO | PRI | 0 | auto_increment |
| listID | int(11) | NO | MUL | 0 | |
| songID | int(11) | NO | MUL | 0 | |
+--------+---------+------+-----+---------+----------------+
What my query needs to do is list all the songs that are on the list of a user, basically these are the record in song_ratings where the userID = ?(obviously the ID of the user), but are not on a specific playlist(has no record in list_elements) where the ID/listID = ?(the ID of that playlist).
This is the query I've been using so far, but after a while I had realized this doesn't actually work the way I wanted to.
SELECT DISTINCT
COUNT(*)
FROM
song
INNER JOIN song_ratings ON song_ratings.songID = song.ID
LEFT JOIN list_elements ON song_ratings.songID = list_elements.songID
WHERE
song_ratings.userID = 34 AND list_elements.songID IS NULL
I have also tried something like this, and several variants of it
SELECT DISTINCT
COUNT(*)
FROM
song
INNER JOIN song_ratings ON song_ratings.songID = song.ID
INNER JOIN lists ON lists.userID = song_ratings.userID
LEFT JOIN list_elements ON song_ratings.songID = list_elements.songID
WHERE
song_ratings.userID = 34 AND lists.ID = 1
To make it easier, here's a SQL Fiddle, with all the necessary tables and records in them.
What you need to know. When you check for the playlist with the ID of 1, the query needs to return 23(basically all matches).
When you do the same with the ID 4, it need to return 21, if the query works correctly, because the playlist 1 is empty, thus all of the songs in the table song_ratings can be added to it(at least the ones that exist in song table, which is only half of the overall records now).
But playlist 4 already has 2 songs added to it, so only 21 are left available for adding.
Or in case the number are wrong, playlist 1 needs to return all matches. playlist 4 need to return all matches-2(because 2 songs are already added).
The userID needs to remain the same(34), and there are no records with different ID, so don't change it.
You could try subquery with NOT IN clause
SELECT DISTINCT
COUNT(*)
FROM
song
INNER JOIN song_ratings ON song_ratings.songID = song.ID
WHERE
song_ratings.userID = 34 AND song.ID not in (select songID from list_elements group by songID)
Your original query was almost correct. When you use a column from a joined table with a LEFT JOIN in the WHERE-clause, it causes the LEFT JOIN to turn into an INNER JOIN.
You can put the condition into the ON-clause:
SELECT COUNT(*)
FROM song
INNER JOIN song_ratings ON song_ratings.songID = song.ID
LEFT JOIN list_elements ON song_ratings.songID = list_elements.songID
AND list_elements.songID IS NULL
WHERE song_ratings.userID = 34
Using JOINs in MySQL is faster than using subqueries, this would probably be faster as well.
Btw, you do not need DISTINCT when you only have COUNT(*). The COUNT(*) returns only one row so there is no need to take distinct values from one value.
I'm trying to get a SQL query to give me the results of a count but I need the result to include rows where the count is 0. What I found for solutions to this was to use IFNULL(COUNT(*), 0) in place of COUNT(*) however that had no effect on the result. I also tried using a LEFT JOIN but SQL gave me a syntax error if I tried to put in those. Here's my table setup
User
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| UserID | mediumint(9) | NO | PRI | NULL | auto_increment |
| firstName | varchar(15) | NO | | NULL | |
| lastName | varchar(15) | NO | | NULL | |
| Protocol | varchar(10) | NO | | NULL | |
| Endpoint | varchar(50) | NO | | NULL | |
| UsergroupID | mediumint(9) | NO | MUL | NULL | |
+-------------+--------------+------+-----+---------+----------------+
Subscription
+----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+----------------+
| SubscriptionID | mediumint(9) | NO | PRI | NULL | auto_increment |
| TopicID | mediumint(9) | NO | MUL | NULL | |
| UserID | mediumint(9) | NO | MUL | NULL | |
+----------------+--------------+------+-----+---------+----------------+
Topic
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| TopicID | mediumint(9) | NO | PRI | NULL | auto_increment |
| Name | varchar(50) | NO | | NULL | |
| FBName | varchar(30) | YES | | NULL | |
| FBToken | varchar(255) | YES | | NULL | |
| TWName | varchar(10) | YES | | NULL | |
| TWToken | varchar(50) | YES | | NULL | |
| TWSecret | varchar(50) | YES | | NULL | |
+----------+--------------+------+-----+---------+----------------+
My SQL query to try and get the COUNT is :
SELECT Topic.TopicID as ID, Topic.Name AS TopicName, COUNT(*) AS numSubscriptions
FROM User, Topic, Subscription
WHERE Subscription.UserID = User.UserID
AND Subscription.TopicID = Topic.TopicID
GROUP BY Topic.TopicID;
I've tried replacing COUNT(*) with IFNULL(COUNT(*), 0) and I've tried to replace User,Topic,Subscription with User JOIN Subscription JOIN Topic and I also tried User LEFT JOIN Subscription LEFT JOIN Topic but that got a SQL error.
The output I'm getting is:
+----+-----------+------------------+
| ID | TopicName | numSubscriptions |
+----+-----------+------------------+
| 2 | test | 2 |
| 3 | test2 | 1 |
+----+-----------+------------------+
I need to be getting
+----+-----------+------------------+
| ID | TopicName | numSubscriptions |
+----+-----------+------------------+
| 2 | test | 2 |
| 3 | test2 | 1 |
| 4 | test3 | 0 |
+----+-----------+------------------+
By default, outer joins are left to right. So, the trick is to start with Topic:
SELECT Topic.TopicID as ID, Topic.Name AS TopicName,
COUNT(User.UserID) AS numSubscriptions
FROM Topic
LEFT JOIN Subscription
ON Subscription.TopicID = Topic.TopicID
JOIN User
ON User.UserID = Subscription.UserID
GROUP BY Topic.TopicID
This allows for multiple subscriptions per user and requires that the user record exists to be considered in the count.
COUNT(NULL) evaluates to 0, so any topic records without a corresponding subscription and user record will show as 0.
If you're not concerned whether the user record exists, you could simplify it to the following:
SELECT Topic.TopicID as ID, Topic.Name AS TopicName,
COUNT(Subscription.TopicID) AS numSubscriptions
FROM Topic
LEFT JOIN Subscription
ON Subscription.TopicID = Topic.TopicID
GROUP BY Topic.TopicID
The example below should do what you're after. The column in the COUNT() can be any column of the subscription table, but using its ID is a good practice.
Using the left join ensures that all entries of the user table will show up in the results, even if there are no matching subscriptions.
SELECT User.firstName,
User.lastName,
Topic.Name AS TopicName,
COUNT(Subscription.SubscriptionId) AS numSubscriptions
FROM USER
LEFT OUTER JOIN Subscription ON Subscription.UserID=USER.UserID
LEFT OUTER JOIN Topic ON Subscription.TopicID=Topic.TopicID
GROUP BY User.firstName, User.lastName, Topic.Name;
These are the three tables I have:
emails
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| emailID | int(11) | NO | PRI | NULL | auto_increment |
| subject | varchar(100) | YES | | NULL | |
| body | text | YES | | NULL | |
| userID | int(11) | NO | | NULL | |
| date | timestamp | YES | | NULL | |
+----------+--------------+------+-----+---------+----------------+
people
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| userID | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(50) | YES | | NULL | |
| email | varchar(50) | NO | | NULL | |
| relation | varchar(50) | YES | | NULL | |
+----------+-------------+------+-----+---------+----------------+
toLine
+---------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+---------+------+-----+---------+-------+
| emailID | int(11) | NO | PRI | NULL | |
| userID | int(11) | NO | PRI | NULL | |
+---------+---------+------+-----+---------+-------+
The field emails.userID is the ID of the sender, so who the email is from.
What I need is a query that will get me emails sent by me (relation='researcher') to my course director (relation='course director'). I don't know how to do the joins so I can use the relation field in two different ways.
I've tried the query:
SELECT count(emails.emailID)
FROM emails
LEFT JOIN people ON emails.userID = people.userID
WHERE people.relation='researcher';
This gets me all the emails sent by me, but I'm having trouble figuring out how to filter this down further based on who the email is sent to.
Please try this query:
select distict e.* from emails e
join `toLine` t on t.`emailID` = e.`emailID`
join people pto on pto.`userID` = t.`userID`
join people pfrom on e.`userID` = pfrom.`userID`
where pfrom.relation = 'researcher' and pto.relation = 'course director'
If you want all emails sent by a researcher to a course director, try this...
SELECT e.* FROM emails e
INNER JOIN people senders
ON e.userID = senders.userID
WHERE senders.relation = 'researcher'
AND EXISTS (
SELECT 1 FROM toLine t
INNER JOIN people recipients
ON t.userID = recipients.userID
WHERE e.emailID = t.emailID
AND recipients.relation = 'course director'
)
I've used the EXISTS clause so you don't end up with duplicate emails records.
+-----------------------------+
| tables |
+-----------------------------+
| Date |
| IP |
| Location |
| UserAgent |
+-----------------------------+
For simplicty assume that these four tables have only two columns: ID(int), name(VARCHAR)
Then I have this table called access_log, where I store only IDs of items i have in other four tables
+---------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| ip_id | int(11) | NO | | NULL | |
| ua_id | int(11) | NO | | NULL | |
| path_id | int(11) | NO | | NULL | |
| date_id | int(11) | NO | | NULL | |
+---------+---------+------+-----+---------+----------------+
Let's say I want to select everything from this access_log table and replace ids with column NAME I have in four other tables.
How can I achive that?
You need to join on all four tables:
SELECT access_log.id,
`date`.name AS date_name,
ip.name AS ip_name,
location.name AS location_name,
useragent.name AS useragent_name
FROM access_log
OUTER JOIN `date` ON `date`.id = access_log.date_id
OUTER JOIN ip ON ip.id = access_log.ip_id
OUTER JOIN location ON location.id = access_log.path_id
OUTER JOIN useragent ON useragent.id = access_log.ua_id
I have four tables like this:
mysql> describe courses;
+-----------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+-------------+------+-----+---------+----------------+
| course_id | int(11) | NO | PRI | NULL | auto_increment |
| course_name | varchar(75) | YES | | NULL | |
| course_price_id | int(11) | YES | MUL | NULL | |
+-----------------+-------------+------+-----+---------+----------------+
mysql> describe pricegroups;
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| price_id | int(11) | NO | PRI | NULL | auto_increment |
| price_name | varchar(255) | YES | | NULL | |
| price_value | int(11) | YES | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
mysql> describe courseplans;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| plan_id | int(11) | NO | PRI | NULL | auto_increment |
| plan_name | varchar(255) | YES | | NULL | |
| plan_time | int(11) | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
mysql> describe course_to_plan;
+-----------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+-------+
| course_id | int(11) | NO | PRI | NULL | |
| plan_id | int(11) | NO | PRI | NULL | |
+-----------+---------+------+-----+---------+-------+
Let me try to explain what I have and what I would like to do...
All my courses (course_id) has different steps (plan_id) wich has a value of 1 or more days (plan_time). A course has one or more steps (course_to_plan)A course is connected to a pricegroup (price_id).
I would like to query my MySQL database and get an output off:
The course_name, the plan_id's it has, and based on the value of price_id together with the value in the plan_time get a result who looks something like this:
+------------+--------------+------------+---------+
| course_name| pricegroup | plan_time | RESULT |
+------------+--------------+------------+---------+
| Math | Expensive | 7 | 3500 |
+------------+--------------+------------+---------+
I hope you understand me...
Is it even possible with the structure I have or should I "rebuild-and-redo-correct" something?
SELECT c.course_name, p.price_name, SUM(cp.plan_time), SUM(cp.plan_time * p.price_value)
FROM courses c
INNER JOIN pricegroups p ON p.price_id = c.course_price_id
INNER JOIN course_to_plan cpl ON cpl.course_id = c.course_id
INNER JOIN courseplans cp ON cp.plan_id = cpl.plan_id
GROUP BY c.course_name, p.price_name
Please note that it seems to me that your implementation might be erroneous. The way you want the data makes me think that you could be happier with a plan having a price, so you don't apply the same price for a plan which is "expensive" AND another plan which is "cheap", which is what you are doing at the moment. But I don't really know, this is intuitive :-)
Thanks for accepting the answer, regards.
Let me see if I understand what you need:
SELECT c.course_name, pg.price_name,
COUNT(cp.plan_time), SUM(pg.price_value * cp.plan_time) AS result
FROM courses c
INNER JOIN pricegroups pg ON c.course_price_id = pg.price_id
INNER JOIN course_to_plan ctp ON c.course_id = ctp.course_id
INNER JOIN courseplans cp ON ctp.plan_id = cp.plan_id
GROUP BY c.couse_name, pg.price_name