I'm updating a website for our school, and we have a list of courses available.
The list of courses is in tbl_course_details:
CREATE TABLE `tbl_course_details` (
`uid` int(11) NOT NULL auto_increment,
`course_name` varchar(100) NOT NULL,
`course_type` varchar(100) NOT NULL,
`course_details` longtext NOT NULL,
`course_added_by` int(4) NOT NULL,
`course_last_updated` int(30) NOT NULL,
`course_menu_id` int(4) NOT NULL,
PRIMARY KEY (`uid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=15;
I also have a table called tbl_course_related:
CREATE TABLE IF NOT EXISTS `tbl_course_related` (
`uid` int(11) NOT NULL auto_increment,
`course_id` int(4) NOT NULL,
`related_course_id` int(4) NOT NULL,
PRIMARY KEY (`uid`),
KEY `course_id` (`course_id`),
KEY `related_course_id` (`related_course_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=25;
When a staff member edits a course, they also get a list of all courses (Except the course they are currently editing). It also has check boxes next to them, so that if a course is related to the course they are editing, they tick the box.
My course details table has 3 courses in it at the moment:
Unique ID Course Name
---------------------------
1 Geography
2 History
3 Art
As an example, i've made geography related to history, and history related to art, so my relationship table looks like:
Unique ID Course ID Related Course Id
--------------------------------------------
1 1 2
2 2 1
3 2 3
4 3 2
I hope this makes sense so far.
Now, If a staff member went to edit the Geography Course (course ID: 1), then I would like a list of courses to contain History and Art, and only the history checkbox is ticked.
My current SQL query looks like this (although I have changed it around a bit and nothings worked so far):
SELECT d.uid,
d.course_name,
r.related_course_id
FROM tbl_course_details d
LEFT JOIN tbl_course_related r
ON r.course_id = d.uid
AND d.uid!=:courseId
However this makes the list look like this (if I edit geography)
Related? Course Name
No History
Yes History
No Art
Obviously I dont want history in the list twice, especially with conflicting information.
Any ideas?
Here is a SQLFiddle demo. If in result dataset r.related_course_id is not NULL then you should tick a checkbox
SELECT d.uid,
d.course_name,
r.related_course_id
FROM tbl_course_details d
LEFT JOIN tbl_course_related r
ON (r.course_id = d.uid) and (r.Related_Course_Id=:courseId)
where d.uid!=:courseId
TRy this ::
SELECT d.uid,
d.course_name,
r.related_course_id
FROM tbl_course_details d
LEFT JOIN tbl_course_related r
ON r.course_id = d.uid
AND d.uid!=:courseId
GROUP BY r.course_id
OR You can use DISTINCT::
SELECT
DISTINCT(d.course_name),
d.uid,
r.related_course_id
FROM tbl_course_details d
LEFT JOIN tbl_course_related r
ON r.course_id = d.uid
AND d.uid!=:courseId
GROUP BY r.course_id
Related
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
I'm a student of Java and do SQL too. In a lesson we were presented with an example database sketch, and a query that a replicate in this question.
I have made an example with MySql and it has three tables,
CREATE TABLE `employed` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
CREATE TABLE `department` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
CREATE TABLE `employees_departments` (
`employed_id` int(11) NOT NULL,
`department_id` int(11) NOT NULL,
PRIMARY KEY (`employed_id`,`department_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
employed was filled with
(1 'Karl'), (2 'Bengt'), (3 'Adam'), (4 'Stefan')
department was filled with
(4, 'HR'), (5, 'Sälj'), (6, 'New departm')
employees_departments was filled with
1 4
2 5
3 4
So "Stefan" has no department, and "New departm" has no employed.
I wanted a query that would give the employees with all their departments, and employees without departments and departments with no employees. I found on solution like this:
select A.name, C.name from employed A
left join employees_departments B on (A.id=B.employed_id)
left join department C on (B.department_id = C.id)
union
select C.name, A.name from department A
left join employees_departments B on (A.id=B.department_id)
left join employed C on (B.employed_id = C.id)
Would be nice if there was a short query to make it...
Also, I made this without foreign key constraints, since I want to do it as simple as possible for this example.
Greetings
MySQL doesn't support a FULL OUTER join operation.
We can emulate that by combining two sets... the result of an OUTER JOIN and the result from an anti-JOIN.
(
SELECT ee.name AS employed_name
, dd.name AS department_name
FROM employed ee
LEFT
JOIN employees_departments ed
ON ed.employed_id = ee.id
LEFT
JOIN department dd
ON dd.id = ed.department_id
)
UNION ALL
(
SELECT nn.name AS employed_name
, nd.name AS department_name
FROM department nd
LEFT
JOIN employees_departments ne
ON ne.deparment_id = nd.id
LEFT
JOIN employeed nn
ON nn.id = nd.employee_id
WHERE nn.id IS NULL
)
The first SELECT returns all employed name, along with matching department name, including employed that have no department.
The second SELECT returns just department name that have no matching rows in employed.
The results from the two SELECT are combined/concatenated using a UNION ALL set operator. (The UNION ALL operation avoids a potentially expensive "Using filesort" operation that would be forced with the UNION set operator.
This is the shortest query pattern to return these rows.
We could make the SQL a little shorter. For example, if we have a foreign key relationships between employeed_department and employed (no indication in the original post that such a relationship is enforced, so we don't assume that there is one)... but if that is enforced, then we could omit the employed table from the second SELECT
UNION ALL
(
SELECT NULL AS employed_name
, nd.name AS department_name
FROM department nd
LEFT
JOIN employees_departments ne
ON ne.deparment_id = nd.id
WHERE ne.department_id IS NULL
)
With suitable indexes available, this is going to give us the most efficient access plan.
Is there shorter SQL that will return an equivalent result? If there is, it's likely not going to perform as efficiently as the above.
I have the following tables.
CREATE TABLE `Customer` (
`CID` varchar(10) CHARACTER SET latin1 NOT NULL DEFAULT '',
`Name` varchar(40) CHARACTER SET latin1 NOT NULL DEFAULT '',
`City` varchar(40) CHARACTER SET latin1 NOT NULL DEFAULT '',
`State` varchar(40) CHARACTER SET latin1 NOT NULL DEFAULT '',
PRIMARY KEY (`CID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin;
CREATE TABLE `LineItem` (
`LID` varchar(10) NOT NULL DEFAULT '',
`OID` varchar(10) NOT NULL DEFAULT '',
`PID` varchar(110) NOT NULL DEFAULT '',
`Number` int(11) DEFAULT NULL,
`TotalPrice` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`LID`),
KEY `Order ID` (`OID`),
CONSTRAINT `Order ID` FOREIGN KEY (`OID`) REFERENCES `OrderItem` (`OID`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `OrderItem` (
`OID` varchar(10) NOT NULL DEFAULT '',
`CID` varchar(10) NOT NULL DEFAULT '',
PRIMARY KEY (`OID`),
KEY `CID` (`CID`),
CONSTRAINT `CID` FOREIGN KEY (`CID`) REFERENCES `Customer` (`CID`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `Product` (
`PID` varchar(10) NOT NULL DEFAULT '',
`ProductName` varchar(40) DEFAULT '',
`Price` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`PID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
What I've been trying to do in my query is run it so I can successfully get it to do the following:
List the products bought by all the customers of Newark
List the products ordered only by the customers of Newark
For #5, I tried this query:
Select product.productname
From Customer as c INNER JOIN OrderItem as o
ON c.CID = o.CID
INNER JOIN LineItem line
ON o.OID = line.OID
Inner Join Product product
ON line.PID = product.PID
Where C.City = 'Newark'
Having Count(product.productname) > 1;
But it only returns one value and it should return 2 (unless I am not using it properly).
For #6 I understand the concept but I don't know how to "subtract tables" in SQL.
The goal of the first question is to list the common items purchased by everyone from Newark. So if Person A bought Items X, Y and Z and Person B bought W, V, and Y, the query will return "Item Y".
I guess my comment is an answer.
Having Count(product.productname) > 1;
Having requires a group by to function correctly as it's a filter on an aggregate and aggregates require a group by. 90% of database engines would have returned an error explicitly stating it requires a group by...but MySQL prefers to do the wrong thing instead of return an error to you (it's why you got one row...MySQL did a group by of whatever it felt like). Add a group by (I assume on product name with what you have here) and it should work.
you need to add GROUP BY . Try this:
Select product.productname
From Customer as c
INNER JOIN OrderItem as o ON c.CID = o.CID
INNER JOIN LineItem line ON o.OID = line.OID
Inner Join Product product ON line.PID = product.PID
Where C.City = 'Newark'
Group by product.productname
Having Count(*) > 1;
Two other answers have pointed out that if you want the HAVING then you need a GROUP BY. But your question doesn't actually ask a question or explain what your query is supposed to return. (You are not explaining clearly in your question or comments.) You wrote a comment, "I have two people from Newark and was trying to show the item(s) [product(s)?] that both of them purchased." But if your query is only "corrected" with grouping then it calculates the wrong counts.
A problem is that you should return PID (and maybe ProductName). You need to list products. PIDs as key are presumably 1:1 with products but ProductName is not a key so product names are not 1:1 with products. ProductName can even be NULL. So selecting ProductName does not get you all the relevant products. (Also in LineItem PID should be a FOREIGN KEY.)
Another problem is that you should use PID to GROUP BY product. ProductName is not a key of Product. So two products can have the same name. So you will get the count for each product name, not for each product. Plus ProductName can be NULL. (Even if you were only returning the names not PIDs of products with those counts, you would need to group by PID.)
Another problem is the counts are wrong. Grouping by PID groups rows that can be made by combining a Newark Customer row, an OrderItem row and a LineItem row for a product. Those combination rows are counted by COUNT(PID). But you want the number of disinct Newark customers in those rows. You could do this by a sub-select but there happens to be a shorter way.
SELECT p.PID, p.ProductName
FROM Customer c
JOIN OrderItem AS o ON c.CID = o.CID
JOIN LineItem l ON o.OID = l.OID
JOIN Product p ON l.PID = p.PID
WHERE c.City = 'Newark'
GROUP BY (p.PID)
HAVING COUNT(DISTINCT c.CID) > 1;
You get a query for goal 1 after a change in part of its condition.
HAVING COUNT(DISTINCT c.CID)
= (SELECT COUNT(*) FROM Customer WHERE City='Newark')
Relational algebra MINUS/DIFFERENCE corresponds to EXCEPT in the SQL standard. But MySQL does not have it. You can do it using LEFT JOIN, NOT IN or NOT EXISTS instead.
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!
I'm working on a small project in regards of the upcoming World Cup. I'm building a roster/leaderboard/scoredboard based on groups with national teams. The idea is to have information on all upcoming matches within the group or in the knockout phase (scores, time of the match, match stats etc.). Currently I'm stuck with the DB in that I can't come up with a query that would return paired teams in a row. I have these 3 tables:
CREATE TABLE IF NOT EXISTS `wc_team` (
`id` INT NOT NULL AUTO_INCREMENT ,
`name` VARCHAR(45) NULL ,
`description` VARCHAR(250) NULL ,
`flag` VARCHAR(45) NULL ,
`image` VARCHAR(45) NULL ,
`added` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ,
PRIMARY KEY (`id`) ,
CREATE TABLE IF NOT EXISTS `wc_match` (
`id` INT NOT NULL AUTO_INCREMENT ,
`score` VARCHAR(6) NULL ,
`date` DATE NULL ,
`time` VARCHAR(45) NULL ,
`added` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ,
PRIMARY KEY (`id`) ,
CREATE TABLE IF NOT EXISTS `wc_team_has_match` (
`wc_team_id` INT NOT NULL ,
`wc_match_id` INT NOT NULL ,
PRIMARY KEY (`wc_team_id`, `wc_match_id`) ,
I've simplified the tables so we don't go in the wrong direction. Now I've tried al kinds of joins and groupings I could think of, but I never seem to get.
Example query:
SELECT t.wc_team_id, t.wc_match_id, c.id.c.name, d.id, d.name
FROM wc_team_has_match AS t
LEFT JOIN wc_match AS s ON t.wc_match_id = s.id
LEFT JOIN wc_team AS c ON t.wc_team_id = c.id
LEFT JOIN wc_team AS d ON t.wc_team_id = d.id
Which returns:
wc_team_id wc_match_id id name id name
16 5 16 Brazil 16 Brazil
18 5 18 Argentina 18 Argentina
But what I really want is:
wc_team_id wc_match_id id name id name
16 5 16 Brazil 18 Argentina
Keep in mind that a group has more matches I want to see all those matches not only one.
Any pointer or suggestion would be extremely appreciated since I'm stuck like a duck on this one :).
Since a soccer match is made up of always two teams, never more and never less, and the order is also of significance since there is a team 1 and a team 2, I would simply add team_1 and team_2 fields in wc_match and remove the wc_team_has_match table.
This would simplify your query considerably:
SELECT m.wc_match_id,
t1.id AS Team_1_ID,
t1.name AS Team_1,
t2.id AS Team_2_ID,
t2.name AS Team_2
FROM wc_match AS m
JOIN wc_team t1 ON (t1.id = m.team_1)
JOIN wc_team t2 ON (t2.id = m.team_2);
EDIT: Just noticed that you also intend to keep information on matches in the knockout stages, where the teams for each match might not have been decided yet. You can still keep with the same model, making team_1 and team_2 nullable. In that case you would want to use LEFT JOINs instead of INNER JOINs in order to receive a resultset that includes NULL teams. The reason for NULL in SQL is to define unknown information, and therefore fits this case perfectly.
i suggest to change your team_has_match table to teams_for_match which holds a reference for each team:
CREATE TABLE `teams_for_match` (
`match_id` INT,
`team1_id` INT,
`team2_id` INT,
PRIMARY KEY (`match_id`, `team1_id`, `team2_id`)
);
you can then select both teams for a match with:
SELECT *
FROM `teams_for_match` tm
LEFT JOIN `wc_match` m
ON tm.`match_id` = m.id
LEFT JOIN `wc_team` t1
ON tm.`team1_id` = t1.id
LEFT JOIN `wc_team` t2
ON tm.`team2_id` = t2.id;
i hope this works as you expect it to.
ps. apart from that, you could simply add team1_id and team2_id columns to your wc_team table, no need to create an additional table
CREATE TABLE IF NOT EXISTS `wc_match` (
`id` INT NOT NULL AUTO_INCREMENT ,
`score` VARCHAR(6) NULL ,
`date` DATE NULL ,
`time` VARCHAR(45) NULL ,
`added` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ,
`team1` INT FOREIGN KEY REFERENCES `wc_team`(`id`),
`team2` INT FOREIGN KEY REFERENCES `wc_team`(`id`),
PRIMARY KEY (`id`)
)