2 LEFT JOINs in a MySQL Query - mysql

I am trying to list all competitions in a table, whether a user has entered each competition, and the total number of entries for each competition.
Here are the tables:
CREATE TABLE `competition` (
`competitionID` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` char(255) NOT NULL DEFAULT '',
`description` varchar(750) NOT NULL DEFAULT '',
`startDate` date DEFAULT NULL,
`endDate` date DEFAULT NULL,
`isLive` tinyint(1) NOT NULL,
PRIMARY KEY (`competitionID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `competition` (`competitionID`, `name`, `description`,
`startDate`, `endDate`, `isLive`)
VALUES
(1,'Win a car','Win a really cool car!','2018-04-01 09:30:27','2019-04-01 09:30:27',1),
(2,'Another competition','Win something even better!','2018-04-01 09:30:27','2019-04-01 09:30:27',1);
CREATE TABLE `competition_entrant` (
`competitionEntrantID` int(11) NOT NULL AUTO_INCREMENT,
`userID` int(11) NOT NULL,
`competitionID` int(11) NOT NULL,
PRIMARY KEY (`competitionEntrantID`),
UNIQUE KEY `userID` (`userID`,`competitionID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `competition_entrant` (`competitionEntrantID`, `userID`,
`competitionID`)
VALUES
(1,1,1),
(2,1,2),
(3,2,1);
So in this example user with id 1 has entered both competitions and user with id 2 has entered competition with id 1.
Here is my query.
SELECT
`c`.`name`,
COUNT(`ce1`.`userID`) AS 'hasEnteredCompetition',
COUNT(`ce2`.`userID`) AS 'totalEntries'
FROM competition c
LEFT JOIN `competition_entrant` `ce1` ON `c`.`competitionID` =
`ce1`.`competitionID`
AND `ce1`.`userID` = 2
LEFT JOIN `competition_entrant` `ce2` ON `c`.`competitionID` =
`ce2`.`competitionID`
GROUP BY (c.competitionID);
The problem is that hasEnteredCompetition is showing the total number of entries rather than just 1 for the user entered i.e. the count for that user.
Can anyone tell me what I'm doing wrong here?

You are joining to the competition_entrant table twice, so the user "2" entry is being pulled twice. You can see it this way:
SELECT C.COMPETITIONID,C.NAME,CE1.USERID,CE1.COMPETITIONID
FROM COMPETITION C
LEFT JOIN COMPETITION_ENTRANT CE1 ON C.COMPETITIONID = CE1.COMPETITIONID AND CE1.USERID = 2
LEFT JOIN COMPETITION_ENTRANT CE2 ON C.COMPETITIONID = CE2.COMPETITIONID
1 Win a car 2 1
2 Another competition null null
1 Win a car 2 1
You could add a count distinct to your query like this:
select C.NAME,C.COMPETITIONID,
COUNT(DISTINCT CE1.USERID) as "hasEnteredCompetition",
COUNT(CE2.USERID) as "totalEntries"
from COMPETITION C
left join COMPETITION_ENTRANT CE1 on C.COMPETITIONID = CE1.COMPETITIONIDand CE1.USERID = 2
left join COMPETITION_ENTRANT CE2 on C.COMPETITIONID = CE2.COMPETITIONID
group by (C.NAME,C.COMPETITIONID)

If I understand you correctly (a "expected result" would be nice) you only need to list all competitions, the number of users that entered and if anyone entered at all, right? Then you do not need the second left join, you could go with something like this:
select
competition.competitionID,
competition.name,
case when count(competition.competitionID) > 0 THEN 'yes' ELSE 'no' END AS hasEnteredCompetition,
count(competition.competitionID) AS 'totalEntries'
from competition
left join competition_entrant ON competition.competitionID = competition_entrant.competitionID
group by competitionId, name

Related

LEFT JOIN count from another table

I am trying to count how many transactions have been complete for a course, I am trying to left join the training_transactions with a count of all the rows where the training_transaction_course = course_id and where training_transaction_status = 'completed' Here's the code I have so far:
SELECT training.*,
Count(DISTINCT training_transactions.training_transaction_course) AS completed_training_payments
left JOIN users
ON training.course_user = users.user_id
LEFT JOIN training_transactions
ON training.course_user = training_transactions.training_transaction_user
FROM training
WHERE course_id = ?
AND training_transactions.training_transaction_status = 'complete'
AND course_enabled = 'enabled'
My tables:
training transactions
CREATE TABLE IF NOT EXISTS `training_transactions` (
`training_transaction_id` int(11) NOT NULL,
`training_transaction_user` int(11) NOT NULL,
`training_transaction_course` int(11) NOT NULL,
`training_transaction_status` varchar(50) NOT NULL,
`training_transaction_enabled` varchar(50) NOT NULL DEFAULT 'enabled',
`training_transaction_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
training
CREATE TABLE IF NOT EXISTS `training` (
`course_id` int(11) NOT NULL,
`course_user` int(11) NOT NULL,
`course_type` varchar(255) NOT NULL,
`course_name` varchar(255) NOT NULL,
`course_location` varchar(255) NOT NULL,
`course_duration` varchar(255) NOT NULL,
`course_fitness_type` varchar(255) NOT NULL,
`course_instructor_name` varchar(255) NOT NULL,
`course_price` int(15) NOT NULL,
`course_start_date` date NOT NULL,
`course_max_attendees` int(8) NOT NULL,
`course_accommodation` varchar(255) NOT NULL,
`course_accommodation_price` varchar(255) NOT NULL,
`course_status` varchar(50) NOT NULL,
`course_enabled` varchar(10) NOT NULL DEFAULT 'enabled'
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=latin1;
As you can see I am trying to get the count of completed transactions as a count to deduct from course_max_attendees, and then I can check if there's any places left.
You want to select trainings. So select from training. You want to show the transaction count with it, which you can do in a subquery in your select clause:
select
t.*,
(
select count(*)
from training_transactions tt
where tt.training_transaction_user = t.course_user
and tt.training_transaction_status = 'complete'
) as completed_training_payments
from training t
where t.course_id = ?
and t.course_enabled = 'enabled';
And here is the same with a join:
select
t.*, coalesce(tt.cnt, 0) as completed_training_payments
from training t
left join
(
select training_transaction_status, count(*) as cnt
from training_transactions
where training_transaction_status = 'complete'
group by training_transaction_status
) tt on tt.training_transaction_user = t.course_user
where t.course_id = ?
and t.course_enabled = 'enabled';
First, if you want to know how many completed transactions have taken place for each course, you can't get the User table involved. You will be aggregating away any user information.
Then, you must start with the course table, which looks like you have named training. Now you want to count every completed transaction for each course. A left join works just about perfectly for this:
select t.Name, count( * ) as completed_training_payments
from training t
left join training_transactions tt
on tt.user = t.course_user
and tt.status = 'complete'
where t.course_status = 'enabled'
group by t.Name;
The problem with this is that it will give a count value of "1" for every course with one completed transaction but also those with no completed transactions at all! So every row with a count of "1" would be suspect. The solution is to count keys, not rows. This is done with the sum rather than the count function.
select t.Name, sum( case when tt.course_user is null then 0 else 1 end ) as completed_training_payments
from training t
left join training_transactions tt
on tt.user = t.course_user
and tt.status = 'complete'
where t.course_status = 'enabled'
group by t.Name;
Since tt.course_user will only be null when there are no completed transactions at all, that course will show a "count" of "0".

MySQL left join doesnt give me what i expect

I'd like some help with an left join statement thats not doing what i, probably incorrectly, think it should do.
there are two tables:
cd:
CREATE TABLE `cd` (
`itemID` int(11) NOT NULL AUTO_INCREMENT,
`title` text NOT NULL,
`artist` text NOT NULL,
`genre` text NOT NULL,
`tracks` int(11) NOT NULL,
PRIMARY KEY (`itemID`)
)
loans
CREATE TABLE `loans` (
`itemID` int(11) NOT NULL,
`itemType` varchar(20) NOT NULL,
`userID` int(11) NOT NULL,
`dueDate` date NOT NULL,
PRIMARY KEY (`itemID`,`itemType`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
and i want to select all cd's thats not in loans using a left join and then an where dueDate is null
select
t.itemID,
t.artist as first,
t. title as second,
(select AVG(rating) from ac9039.ratings where itemType = 'cd' and itemId = t.itemID) as `rating avarage`,
(select COUNT(rating) from ac9039.ratings where itemType = 'cd' and itemId = t.itemID) as `number of ratings`
from
cd t left join loans l
on t.itemID = l.itemID
where l.itemType = 'cd' and l.dueDate is null;
this one however returns an empty table even though there are plenty rows in cd with itemIDs thats not in loans
now i was under the understanding that the left join should preserv the righthandside and fill the columns from the lefthandside with null values
but this does not seem to be the case, can anbyone enlighten me?
Your WHERE condition causes the error. The L.ItemType = 'cd' will always return false if the L.DueDate IS NULL is true. (All of your fields are NOT NULL, so the DueDate can only be NULL if there is no matching records, but in this case the ItemType field will be NULL too).
Another point is that your query is semantically incorrect. You are trying to get the record from the cd table where the loans table do not contains any rows with dueDates.
The second table acts as a condition, so it should go to the WHERE conditions.
Consider to use the EXISTS statement to achieve your goal:
SELECT
t.itemID,
t.artist as first,
t. title as second,
(select AVG(rating) from ac9039.ratings where itemType = 'cd' and itemId = t.itemID) as `rating avarage`,
(select COUNT(rating) from ac9039.ratings where itemType = 'cd' and itemId = t.itemID) as `number of ratings`
FROM
cd t
WHERE
NOT EXISTS (SELECT 1 FROM loans l WHERE t.itemID = l.itemID AND L.itemType = 'cd')
Based on your data model you have to add another condition to the subquery to filter out those records which are out-of-date now (dueDate is earlier than the current time)
This is the case, when you do not delete outdated loan records.
NOT EXISTS (SELECT 1 FROM loans l WHERE t.itemID = l.itemID AND AND L.itemType = 'cd' l.dueDate > NOW())

complicated sql query returns a result with empty tables

I have three empty tables
--
-- Tabellenstruktur für Tabelle `projects`
--
CREATE TABLE IF NOT EXISTS `projects` (
`id_project` int(11) NOT NULL AUTO_INCREMENT,
`id_plan` int(11) DEFAULT NULL,
`name` varchar(255) NOT NULL,
`description` longtext NOT NULL,
PRIMARY KEY (`id_project`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `project_plans`
--
CREATE TABLE IF NOT EXISTS `project_plans` (
`id_plan` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`description` longtext NOT NULL,
`max_projects` int(11) DEFAULT NULL,
`max_member` int(11) DEFAULT NULL,
`max_filestorage` bigint(20) NOT NULL DEFAULT '3221225472' COMMENT '3GB Speicherplatz',
PRIMARY KEY (`id_plan`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `project_users`
--
CREATE TABLE IF NOT EXISTS `project_users` (
`id_user` int(11) NOT NULL,
`id_project` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
All these tables are empty but i get a result with my query?
my query:
SELECT
A.id_plan,
A.name AS plan_name,
A.description AS plan_description,
A.max_projects,
A.max_member,
A.max_filestorage,
B.id_plan,
B.name AS project_name,
B.description AS project_description,
C.id_user,
C.id_project,
COUNT(*) AS max_project_member
FROM
".$this->config_vars["projects_plans_table"]." AS A
LEFT JOIN
".$this->config_vars["projects_table"]." AS B
ON
B.id_plan = A.id_plan
LEFT JOIN
".$this->config_vars["projects_user_table"]." AS C
ON
C.id_project = B.id_project
WHERE
C.id_project = '".$id."'
&& B.deleted = '0'
i think the problem is the COUNT (*) AS ...
how i can solve the problem?
For one, you are getting a record explicitly due to the COUNT(). Even though you have no records, you are asking the engine how many records which at worst case will return zero. Count(), like other aggregates are anticipated to have a group by, so even though you don't have one, you are still asking.
So the engine is basically stating hey... there are no records, but I have to send you a record so you can get the count() column to look at and do with what you will. So, it is doing what you asked.
Now, for the comment to the other question where you asked...
Yes but i want to count the project member from a project, how i can count the users from project_users where all users have the id_project 1.
Since you only care about a count, and not the specific WHO involved, you can get this result directly from the project_users table (which should have an index on both the ID_User and another on the ID_Project. Then
select count(*)
from project_users
where id_project = 1
To expand from basis of your original question to get the extra details, I would do...
select
p.id_project,
p.id_plan,
p.name as projectName,
p.description as projectDescription,
pp.name as planName,
pp.description as planDescription,
pp.max_projects,
pp.max_member,
pp.max_filestorage,
PJCnt.ProjectMemberCount
from
( select id_project,
count(*) as ProjectMemberCount
from
project_users
where
id_project = 1 ) PJCnt
JOIN Projects p
on PJCnt.id_project = p.id_project
JOIN Project_Plans PP
on p.id_plan = pp.id_plan
Now, based on this layout of tables, a plan can have a max member count, but there is nothing indicating max members for the plan based on all projects, or max per SINGLE project. So, if a plan allows for 20 people, can there be 20 people for 10 different projects under the same plan? That's something only you would know the impact of... just something to consider what you are asking for.
Your cleaned-up query should look like :
See sqlfidle demo as well : http://sqlfiddle.com/#!2/e693f5/9
SELECT
A.id_plan,
A.name AS plan_name,
A.description AS plan_description,
A.max_projects,
A.max_member,
A.max_filestorage,
B.id_plan,
B.name AS project_name,
B.description AS project_description,
C.id_user,
C.id_project,
COUNT(*) AS max_project_member
FROM
project_plans AS A
LEFT JOIN
projects AS B
ON
B.id_plan = A.id_plan
LEFT JOIN
project_users AS C
ON
C.id_project = B.id_project
WHERE
C.id_project = '".$id."';
This will return you null values for all the cols from the select because you have one legit return form the result set and that is the count(*) output 0.
To fix this just add a group by at the end (see group by example http://sqlfiddle.com/#!2/14d46/2) or
Remove the count(*) and the null values will be gone as well as the count(*) values 0
See simple sql example here : http://sqlfiddle.com/#!2/ab7dd/5
Just comment the count() and you fixed you null problem!

Trying to get 2 sums within one table with addition of a join

I've got a submissions table and in it are submissions that have the type either tip or request.
I'm trying to grab all the submissions of a particular user (to display as an aggregation of all their activity on their dashboard).
E.g.
You have submitted: 5 requests and 1 tip.
My submissions create table looks like this:
Table: submissions
Create Table: CREATE TABLE `submissions` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`slug` varchar(255) NOT NULL,
`description` mediumtext NOT NULL,
`user_id` int(11) NOT NULL,
`created` datetime NOT NULL,
`type` enum('tip','request') NOT NULL,
`thumbnail` varchar(64) CHARACTER SET latin1 DEFAULT NULL,
`removed` tinyint(1) unsigned NOT NULL DEFAULT '0',
`keywords` varchar(255) NOT NULL,
`ip` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
FULLTEXT KEY `search` (`title`,`description`,`keywords`)
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
I came up with one query that works and gives me the amount of the users' submissions, but because each submission (row that comes back) saves the type as either tip or request. So I'm trying to figure out how to aggregate that info now.
My query which returns the user with all tips. I'm trying to do one for requests as well.
SELECT users.*, count(submissions.id)
AS "tipsCount"
FROM users
LEFT JOIN submissions on users.id = submissions.user_id
WHERE username = 'blahbster'
AND submissions.type = 'tip'
ORDER BY submissions.created DESC
LIMIT 1;
Perhaps I could use a sum here? My attempt:
SELECT users.*,
SUM(case when type = 'tip' then 1 else 0 end) as "tipsCount"
SUM(case when type = 'request' then 1 else 0 end) as "requestsCount"
FROM users
LEFT JOIN submissions on users.id = submissions.user_id
WHERE username = 'blahbster'
ORDER BY submissions.created DESC
LIMIT 1;
SELECT a.username, b.type,
SUM(case when b.type = 'tip' then 1 else 0 end) as "tipsCount",
SUM(case when b.type = 'request' then 1 else 0 end) as "requestsCount"
FROM users as a
LEFT JOIN submissions as b
ON a.id = b.user_id
GROUP BY a.username, b.type;
The second query you had was close... but it wasn't aggregating a particular user's totals tips and total requests. The sum didn't compute across anything, ie, there was no GROUP BY. The query above should help. You can obviously add the WHERE filter back in if you need it.

Non implicit join, return when right join is empty

I have two tables:
contactGroups cg and contactGroupLink cgl.
CREATE TABLE `contactgroups` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`account` int(11) DEFAULT NULL,
`name` varchar(50) DEFAULT NULL,
`description` text,
`dateCreated` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=latin1
CREATE TABLE `contactgrouplink` (
`groupId` int(11) NOT NULL,
`ContactId` int(11) NOT NULL,
PRIMARY KEY (`groupId`,`ContactId`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
If a contact is in a group then an entry gets put into the cgl table with the contactId and the groupId.
This all works fine.
I am wanting to do a query that gets all information for all groups from the cg table where account = 1.
As part of this query I also want to add an extra row called number which has the number if entries in the cgl table for that group.
I am using
SELECT
cg.id AS id,
NAME,
description,
dateCreated,
COUNT(*) AS number
FROM
contactgroups cg,
contactgrouplink cgl
WHERE
cg.account = 1
AND
cg.id = cgl.groupId
GROUP BY
cg.id
Which works perfect as long as at least one contact exists for a group.
otherwise the group is not returned.
What must I do to this query to make it return all groups?
Use a LEFT JOIN to return a row for every member of the left table, even if there is no corresponding row in the right table.
In other words, use this query:
SELECT
cg.id AS id,
NAME,
description,
dateCreated,
COUNT(cgl.groupId) AS number
FROM contactgroups AS cg
LEFT JOIN contactgrouplink AS cgl
ON cg.id = cgl.groupId
WHERE cg.account = 1
Of course this will only return all groups where cg.account = 1, but I assume that is what you meant by "all groups". (If you really want all groups, then omit the WHERE clause).
Use a LEFT JOIN, and the IFNULL() will prevent null values on conditions no such matching records in cgl aliased table
SELECT
cg.id AS id,
NAME,
description,
dateCreated,
IFNULL( COUNT(*), 0 ) AS number
FROM
contactgroups cg
LEFT JOIN contactgrouplink cgl
ON cg.id = cgl.groupID
WHERE
cg.account = 1