mysql update table column from a select statement with count - mysql

I am working on some MySQL and am running into a bit of a problem.
I'm trying to update a column with the correct amount of credits taken by a student, but currently when I count the credits taken I get the total number of credits taken by all students. I
student table
ID varchar(5)
name varchar(20)
dept_name varchar(20)
tot_cred decimal(3,0)
takes table
ID varchar(5)
course_id varchar(8)
sec_id varchar(8)
semester varchar(6)
year decimal(4,0)
grade varchar(2)
course table
course_id varchar(8)
title varchar(50)
dept_name varchar(20)
credits decimal(2,0)
This is my statement that I'm currently using. I also have to add if they have an f then they don't get credit for the class that they took
update studentCopy set tot_cred = (
select sum(course.credits)
from student
left join takes on student.ID = takes.ID
left join course on takes.course_id = course.course_id
where student.ID = student.ID
group by studentCopy.ID);
Any help would be greatly appreciated. Thanks in advance!

Here are some simplifications to your query:
The left joins are unnecessary. You can just use join.
The group by is unnecessary. In fact, it is misleading, because it suggests that the subquery could return more than one row (which would generate an error).
The Student table is unnecessary; you can write the correlation clause directory to takes.
Table aliases make the query easier to write and to read.
So, I would write your query as:
update studentCopy sc
set tot_cred = (select sum(c.credits)
from takes t join
course c
on t.course_id = c.course_id
where sc.ID = t.ID
);
You can add the condition on grades in the where clause:
update studentCopy sc
set tot_cred = (select sum(c.credits)
from takes t join
course c
on t.course_id = c.course_id
where sc.ID = t.ID and grade <> 'f'
);

I actually got part of my answer just now. I changed
where student.ID = student.ID
to
where studentCopy.ID = student.ID
to give me the results of their own credits taken. Just working on getting the if grade = 'F' then don't count the grade.

Related

select student name with profession that apper more the once

sorry for the question, I'm usually using PostgreSQL but MySQL I can't find my legs In it,
I have this DB :
create table Students (
Student_id int,
Student_name varchar(100)
);
create table Grades
(
student_id numeric,
Profession varchar(10),
Grade numeric
);
insert into Students(Student_id,Student_name) values (1,"avi"),(2,"david"), (3,"mosh"), (4,"adir");
insert into Grades values (2,'math',95), (3,"sport",25), (4,"english",30);
insert into Grades(student_id,Profession) values (1,'math');
I want to get the student name the other profession paper more than once
in this example the output will be:
avi
david
because there both learning math
this is my attempt so far:
select
student_name,
profession
from Students
inner join Grades
on Students.Student_id = Grades.Student_id
group by profession
count(profession) > 1;
this is a link to SQL fiddle
Subquery returns profession wise student count and join with student and grades for retrieving final result where no_of_student per profession appears more than one.
-- Mysql
SELECT s.student_name
FROM Students s
inner join Grades g
on s.Student_id = g.Student_id
inner join (SELECT Profession
, COUNT(1) no_of_std
FROM Grades
GROUP BY Profession) t
on g.Profession = t.Profession
AND t.no_of_std > 1
order by s.student_id
Please check this url https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=a59293c694549e39bcf1678050f6b6f1
N.B.: This query is also applicable for all RDBMS (PostgreSQL, MSSQL, MySQL, ORACLE) because all basics syntax are used here.
Latest version of mysql(v5.8) where analytical function supports and this will also work postgresql, MSSQL etc
SELECT t.student_name
FROM (SELECT s.Student_id, s.student_name
, COUNT(s.Student_id) OVER (PARTITION BY Profession) no_of_std
FROM Students s
inner join Grades g
on s.Student_id = g.Student_id) t
where t.no_of_std > 1
Please check this url https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=f9bced508d1bfc77aacd8b5826aa6a38
For PostgreSQL, please check this url https://dbfiddle.uk/?rdbms=postgres_11&fiddle=1555612e72404eddc820c254c708304a

How do I write an SQL query that gives me the amount of times a value is repeated in a table?

So I have two tables which I have created:
CREATE TABLE IF NOT EXISTS `Advertising_Campaign` (
`CampaignID` VARCHAR(10) NOT NULL,
`AdvertName` varchar(45) NOT NULL,
`ProjectLead` VARCHAR(10) NULL,
`CostEstimate` decimal NULL,
`CampaignCost` decimal NULL,
`EndDateEst` date NULL,
`StartDate` date NULL,
`EndDate` date NULL,
`Theme` VARCHAR(45) NOT NULL,
`AdvertType` VARCHAR(45) NOT NULL,
PRIMARY KEY (`CampaignID`))
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `staff_works_campaign` (
`CampaignID` VARCHAR(10) NOT NULL,
`StaffID` VARCHAR(10) NOT NULL,
`SalaryGrade` Integer NOT NULL,
`isSup` VARCHAR(3) NOT NULL,
PRIMARY KEY (`StaffID`, `CampaignID`),
CONSTRAINT `FK_StaffID3` FOREIGN KEY (`StaffID`) REFERENCES `Staff` (`StaffID`),
CONSTRAINT `FK_CampaignID2` FOREIGN KEY (`CampaignID`) REFERENCES `Advertising_Campaign` (`CampaignID`))
ENGINE = InnoDB;
which gives the tables:
Basically, I want to write a query that will return me a list of the advertising_campaign.AdvertName with more than 2 staff members working on them and a count of the number of staff members whose staff_works_campaign.SalaryGrade is greater than 2.
I have tried:
select a.advertname, count(*) as 'Greater Than 2'
from advertising_campaign a inner join staff_works_campaign
where staff_works_campaign.SalaryGrade > 2;
Which isn't exactly what I want, it returns:
I am a bit unsure at what this is returning exactly because I would thought it would have returned a count of 2(because of the fact that there are 2 entries with a SalaryGrade of 4 in the table), might be because of the way inner join works?
I am also a bit confused as to how to filter for 'more than 2 staff members', My idea is to see the amount of times the staff_works_campaign.CampaignID has appeared in the staff_works_campaign table to see how many staff members are apart of the same campaign.
I'm not sure how to structure it to count the amount of times campaignID is repeated and to return the names of the adverts that have a campaignID that has 2 or more staff members working on it.
So in this case I would want it to return a table with AdvertName of only those campaigns with two or more people working on them and a count of those staff members who have a salary grade greater than 2.
SELECT
a.CampaignID
,a.AdvertName
,COUNT(DISTINCT s.StaffID) AS [Count of staff]
,SUM(
--Use this to get a total of the staff who are
--in a SalaryGrade greater than 2
CASE WHEN s.SalaryGrade > 2
THEN 1
ELSE 0 --anyone who is under this level will be a 0 and not count
END
) as [Count of staff above salary grade]
FROM
advertising_campaign AS a
INNER JOIN staff_works_campaign AS s
--dont forget the join condition
ON a.CampaignID = s.CampaignID
--Dont want a where here, we want to include ALL staff.
--WHERE
-- staff_works_campaign.SalaryGrade > 2
GROUP BY
a.CampaignID
,a.AdvertName
HAVING
--more than two members of staff working on the same campaign.
COUNT(DISTINCT s.StaffID) > 2
Firstly you need a condition to join the two tables on. Secondly you can use a Group By and Having clause to put the filter on aggregation. Finally you need to count the number of staff with a salary grade > 2, which you can SUM a conditional for. Something like this:
select a.advertname, Sum(CASE WHEN c.SalaryGrade > 2 THEN 1 ELSE 0 END) as 'Greater Than 2'
from advertising_campaign a inner join staff_works_campaign c
on a.CampaignId = c.CampaignId
Group By a.advertname Having count(*) >= 2;
You can do something like following if you want both conditions together,
2 people working on camp, whose salary_grade >2
SELECT AdvertName
FROM Advertising_Campaign
WHERE CampaignID IN
(
SELECT
CampaignID
FROM
staff_works_campaign
WHERE
SalaryGrade > 2
GROUP BY
CampaignID
HAVING
COUNT(DISTINCT StaffID) >= 2
)
What you have received as a result from your query is the count of all staff members across all campaigns that have a salary grade greater than 2. It returns "Star Wars 3" as the advert name simply because it's the first name it came across in all the results that the COUNT operates over. (Some other SQL technologies such as Microsoft SQL Server actually won't allow you to do this kind of query to avoid this confusion.)
In order to get the results to be split by the campaign, you have to use the GROUP BY clause as suggested in the other answers. This will tell SQL to calculate any aggregate functions (i.e. COUNT) over groups of records that all match for one or more fields. In your case, you want to group by the campaignID, since you want the COUNT to be calculated for each campaign individually. You could do this on the advert name as well, but better to do it on the ID in case you have two with the same name. Modifying your query to do that, we get:
select a.campaignID, count(*) as 'Greater Than 2'
from advertising_campaign a inner join staff_works_campaign
where staff_works_campaign.SalaryGrade > 2
group by a.campaignID;
This still isn't quite going to work though, because the salary grade condition is applied before the COUNT. We need to move that part out into a new query that wraps around this one. We also need to limit the campaigns down to those with two staff - thankfully, we don't need yet another outer query for that. The HAVING keyword allows a condition to be applied after a GROUP BY, so we can do:
select a.campaignID, count(*) as 'staff_amount'
from advertising_campaign a inner join staff_works_campaign
group by a.campaignID
having staff_amount > 2;
Now, adding the staff salary condition and another select from advertising_campaign to get the advert name in an outer query, we finally get:
select advertising_campaign.advertname
from advertising_campaign
inner join staff_works_campaign on advertising_campaign.campaignid = staff_works_campaign.campaignid
inner join
(
select a.campaignID, count(*) as 'staff_amount'
from advertising_campaign a inner join staff_works_campaign
group by a.campaignID
having staff_amount > 2
) large_campaigns on advertising_campaign.campaignid = large_campaigns.campaignid
where staff_works_campaign.salarygrade > 2

How can I write a query that shows the Institutions that have more than one one contact at them?

CREATE TABLE Institutions
(
Institution_ID INTEGER PRIMARY KEY,
Institution_Name VARCHAR(200))
CREATE TABLE Contact_Persons
(
Contact_No INTEGER PRIMARY KEY,
First_Name VARCHAR(60) NOT NULL,
Last_Name VARCHAR(60) NOT NULL,
Institution_ID INTEGER,
FOREIGN KEY (Institution_ID) REFERENCES Institutions(Institution_ID))
How can I write a mysql query that shows the Institutions that have more than one one Contact Person at them? The Query should show Institution_ID and Institution_Name from Table Institutions. The following Query works but It only shows Institution_ID. I want to see Institution_Name too. I think there must be a join between these two tables. Please help.
SELECT Institution_ID
FROM Contact_Persons
GROUP BY Institution_ID
HAVING COUNT(*)>1;
You were right, you just need to join your two tables, and make sure you also group by everything you want to select (unless it is an aggregate), so in this case add Institution_Name to the group by clause:
SELECT i.Institution_ID, i.Institution_Name
FROM Contact_Persons AS cp
INNER JOIN Institutions AS i
ON i.Institution_ID = cp.Institution_ID
GROUP BY i.Institution_ID, i.Institution_Name
HAVING COUNT(*) > 1;
Try this
SELECT I.Institution_ID,I.Institution_name
FROM Contact_Persons as C inner join Institutions as I on
C.Institution_ID=I.Institution_ID
GROUP BY I.Institution_ID,I.Institution_name
HAVING COUNT(*)>1;

SQL statement that shows at least 16 courses not taken

I have four schemas:
takes(ID,course_id,sec_id,semester,year)
student(ID,name,dept_name,tot_credit)
course(course_id,title,dept_name,credits)
department(dept_name,building,budget)
I want to create a query that finds the name and id of each Astronomy student whose name begins with the letter ’T’ and who has not taken at least 16 Astronomy courses.
What's the easiest way I could do this?
I already wrote this beginning bit
SELECT name, id
FROM student
WHERE dept_name='Astronomy' AND name LIKE '%T%'
I'm not quite sure how to finish this off.
Any help would be greatly appreciated :)
Here's the result
NAME ID CLASS_TAKEN
-------------------- ----- -----------
Tolle 38279 12
Teo 62268 13
Tolle 93223 13
Tsukamoto 17707 5
Titi 11576 9
Teo 91772 12
Toraichi 50387 11
Tewari 80754 14
Tiroz 64091 14
9 rows selected
I need Teo with the id 91772 and Tewari 80754 to be gone
Given my reading of the requirements and the comments it's pretty clear that the question is not very clear. :-) What you're looking for are students where (total # of courses given by the Astronomy department) - (# of Astronomy courses taken by student) >= 16. So, how do we find these values? First, let's start with the total number of courses given by the Astronomy department. This is pretty simple:
SELECT COUNT(*) AS ASTRONOMY_COURSE_COUNT
FROM COURSE
WHERE DEPT_NAME = 'ASTRONOMY'
Now, the second part is to determine how many courses given by the Astronomy department each student has taken. To do this we need to start with the student, join to the courses the student has taken (the TAKES table), then join to the COURSES table to find out which department each course is part of. Something like the following should do it:
SELECT s.ID, s.NAME, COUNT(*) AS STUDENT_ASTRO_COUNT
FROM STUDENT s
INNER JOIN TAKES t
ON t.ID = s.ID
INNER JOIN COURSE c
ON c.COURSE_ID = t.COURSE_ID
WHERE c.DEPT_NAME = 'ASTRONOMY' AND
s.NAME LIKE 'T%'
GROUP BY s.ID, s.NAME;
OK, now we need to put this together. You've tagged this question for both Oracle and MySQL so I'm going to guess you'll accept valid syntax for either database; thus I'll use Oracle Common Table Expression syntax to pull everything together:
WITH ASTRONOMY_COURSES AS (SELECT COUNT(*) AS ASTRONOMY_COURSE_COUNT
FROM COURSE
WHERE DEPT_NAME = 'ASTRONOMY'),
STUDENT_ASTRO_COURSES AS (SELECT s.ID,
s.NAME,
COUNT(*) AS STUDENT_ASTRO_COUNT
FROM STUDENT s
INNER JOIN TAKES t
ON t.ID = s.ID
INNER JOIN COURSE c
ON c.COURSE_ID = t.COURSE_ID
WHERE c.DEPT_NAME = 'ASTRONOMY' AND
s.NAME LIKE 'T%'
GROUP BY ID)
SELECT s.ID,
s.NAME,
s.STUDENT_ASTRO_COUNT,
a.ASTRONOMY_COURSE_COUNT - s.STUDENT_ASTRO_COUNT AS UNTAKEN_COUNT
FROM STUDENT_ASTRO_COURSES s
CROSS JOIN ASTRONOMY_COURSES a
WHERE a.ASTRONOMY_COURSE_COUNT - s.STUDENT_ASTRO_COUNT >= 16;
Note here that a CROSS JOIN is used to put together the subqueries. This means that all the rows of each subquery are joined to all the rows of the other subquery - but since in this case the ASTRONOMY_COURSES subquery will only return a single row what we're doing is appending the ASTRONOMY_COURSE_COUNT value onto each row returned by the STUDENT_ASTRO_COURSES subquery.
That should at least get you pretty close. Amend as needed.
Not tested on animals - you'll be first! :-)
Share and enjoy.
Do you need to use all tables?
Table department has no links with the student,
Table takes has no links with the student,
Table coursehas no links with the student.
If student lists total credits that are all Astronomy I think this can be used:
select name, id, MAX(tot_credit) as credits
from student
where dept_name='Astronomy' and name like 'T%'
group by name, id
having MAX(tot_credit)<=16
PS - you schema is not good; PK-FK references are missing
Your query will need to reference more tables than just the student table.
Your tables seem be missing some important information, which student has taken which course. There's a table named takes, but there doesn't appear to be any relationship between takes and student.
So first, figure out how to list the students along with the Astronomy courses they have taken. Each row will identify the student and a course.
SELECT s.id AS student_id
, s.name AS student_name
, t.???
FROM student s
JOIN ??? t
ON t.student_id = s.id
WHERE ...
You may also need to include another "join" to an additional table, in order to identify which course a student has taken is an Astronomy course.
To also include students that have not take any Astronomy courses, you can use an outer join, rather than an inner join. (That would mean including the LEFT keyword before JOIN, and relocating predicates from the WHERE clause to the ON clause. (A predicate in the WHERE clause that can only be satisfied by non-NULL values will negate the outer-ness of the join.)
Once you have a query that returns that set (students along with any astronomy courses they've taken), you can then add a GROUP BY clause to "collapse" a set of rows into a single row. (Looks like you want the rows "grouped" by student.)
And then an aggregate function like COUNT() or SUM() can be used to get a count of rows for each group. (If you don't want to count any re-takes of a course (a "duplicate" course for a student) you may be able to make use of the COUNT(DISTINCT t.foo) form.
And then a HAVING clause can be added to the query, to compare the value returned from the aggregate expression to a constant value, to return only rows that satisfy a specific condition.
FOLLOWUP
Assuming:
CREATE TABLE course
( id INT UNSIGNED NOT NULL COMMENT 'PK'
, title VARCHAR(30) NOT NULL COMMENT 'course title'
, dept_name VARCHAR(30) NOT NULL COMMENT 'FK ref dept.name'
, credits DECIMAL(5,2) COMMENT 'credit hours'
, PRIMARY KEY (id)
);
CREATE TABLE student
( id INT UNSIGNED NOT NULL COMMENT 'PK'
, name VARCHAR(30) NOT NULL COMMENT 'student name'
, dept_name VARCHAR(30) NOT NULL COMMENT 'FK ref dept.name'
, tot_credit INT COMMENT '?'
, PRIMARY KEY (id)
);
CREATE TABLE takes
( student_id INT UNSIGNED NOT NULL COMMENT 'FK ref student.id'
, course_id INT UNSIGNED NOT NULL COMMENT 'FK ref course.id'
, sec_id INT UNSIGNED NOT NULL COMMENT '?'
, semester INT UNSIGNED NOT NULL COMMENT '?'
, year INT UNSIGNED NOT NULL COMMENT '?'
, PRIMARY KEY (student_id, course_id, sec_id, semester, year)
, CONSTRAINT FK_takes_course FOREIGN KEY (course_id) REFERENCES course (id)
, CONSTRAINT FK_takes_student FOREIGN KEY (student_id) REFERENCES student (id)
);
Query to get a list of students...
SELECT s.id
, s.name
FROM student s
WHERE s.name LIKE 'T%'
AND s.dept_name = 'ASTRONOMY'
Get list of students along with the courses they've taken, returning the id of the ASTRONOMY courses they've taken...
SELECT s.id AS student_id
, s.name AS student_name
, c.id AS course_id
FROM student s
LEFT
JOIN takes t
ON t.student_id = t.id
LEFT
JOIN course c
ON c.id = t.course_id
AND c.dept_name = 'ASTRONOMY'
WHERE s.name LIKE 'T%'
AND s.dept_name = 'ASTRONOMY'
Collapse the rows to one per student using a GROUP BY, and use aggregate functions to get counts or totals.
SELECT s.id AS student_id
, s.name AS student_name
, SUM(c.credits) AS total_astronomy_credits_taken
, COUNT(c.id) AS count_astronomy_courses_taken
, COUNT(DISTINCT c.id) AS count_distinct_astronomy_courses_taken
FROM student s
LEFT
JOIN takes t
ON t.student_id = t.id
LEFT
JOIN course c
ON c.id = t.course_id
AND c.dept_name = 'ASTRONOMY'
WHERE s.name LIKE 'T%'
AND s.dept_name = 'ASTRONOMY'
GROUP
BY s.id
, s.name
To omit rows from this resultset, add a HAVING clause. For example, to exclude rows where total_astronomy_credits_taken is greater than or equal to 12...
HAVING total_astronomy_credits_taken >= 12
If you want the rows returned in a certain sequence, specify that in an ORDER BY clause
ORDER BY s.id
If you want to replace NULL values from the aggregates with zeroes, you can warp the aggregate expression in an IFNULL(foo,0) function, e.g.
, IFNULL(COUNT(c.id),0) AS count_astronomy_courses_taken
Try this :
select a.name, a.id, count(b.ID) as class_taken
from student a inner join takes b
on a.ID = b.ID
inner join course c
on b.course_id = c.course_id
where a.dept_name='Astronomy' and substring(a.name,1,1) = 'T'
group by a.name, a.id
having count(b.ID) < 17

Using Count and Max in SQL Query

I have two tables that I am trying to query from, Enrollment and Course. In the course Table, there is just one entry for each course, but in the Enrollment table, there is an entry for each student that is enrolled in any course, so there may be 30 entries for one course. My task is to find The course that has the most enrollments, and print out that course name, as well as the number of enrollments for that course. Here is my query so far
select c.CourseCode ,(SELECT count( * ) FROM Enrollment WHERE CourseCode = c.CourseCode) as test from Course c ;
this gives me the results:
CS227 - 29
CS228 - 34
CS309 - 31
CS311 - 25
, ect, which is good, but NOW, how do I print out only the class that has the most enrollments(in this case, CS228). I have tried using the max(), but I can't get anything to work.
Here is the table structure
create table Course(
CourseCode char(50),
CourseName char(50),
PreReq char (6));
create table Enrollment (
CourseCode char(6) NOT NULL,
SectionNo int NOT NULL,
StudentID char(9) NOT NULL references Student,
Grade char(4) NOT NULL,
primary key (CourseCode,StudentID),
foreign key (CourseCode, SectionNo) references Offering);
Just take the top 1 after ordering by the count.
That is:
Select Top 1 A.CourseCode, Count(*) From Course A inner join Enrollment B on (A.CourseCode=B.CourseCode)
Group By A.CourseCode
Order By Count(*) DESC
Also - use an inner join as I've shown here rather than a subquery. I do tend to like SubQueries and this one will work but it is just not appropriate in this kind of query!
Based on your comment, I think the blow query is what you want, although it is untested and I am not entirely sure on the HAVING clause being valid. From the documentation on MySQL's page, it seems it should work.
SELECT A.CourseCode, COUNT(*) AS count FROM Course A
JOIN Enrollment B ON A.CourseCode = B.CourseCode
GROUP BY A.CourseCode
HAVING count = MAX(count)
As for performance, I cannot tell if it's a good idea to run a MAX on an aggregate function (probably not).
Otherwise, just use the other query to return the top X and simply run through comparing to the previous number.
SELECT
c.CourseCode,
c.CourseName,
COUNT(*) AS cnt
FROM
Course AS c
INNER JOIN
Enrollment AS e
ON c.CourseCode = e.CourseCode
GROUP BY
c.CourseCode
HAVING
COUNT(*) =
( SELECT
COUNT(*) AS cnt
FROM
Enrollment AS e
GROUP BY
e.CourseCode
ORDER BY
cnt DESC
LIMIT 1
)