How to combine values in table - sql

The database has the schema students(name TEXT, score INTEGER), and there is a table called grades:
Grade MIN_score MAX_score
A 4 5
B 3 4
C 2 3
I want to select the names of all students and their grades according to the table, and turn A and B to 'pass' in the resulting table.
Below is my partial solution without turning A and B to 'pass' in the resulting table, and I wonder how to achieve that additional function.
SELECT name, grade
FROM students
LEFT JOIN grades
ON grade BETWEEN MIN_score and MAX_score;

Don't use between. You'll get duplicates.
select s.name, s.score,
(case when g.grade in ('A', 'B') then 'Pass' end) as status
from students s join
grades g
on s.score > g.MIN_score and s.score <= MAX_score;
You need to be very careful about the join condition so a score of "4" is not treated as both an "A" and a "B" (as between would do).

You need to use case when expression, e.g.:
select case when grade in ('A', 'B') then 'Pass' else '' end
I believe you query should be something like this:
select name, score, case when grade in ('A', 'B') then 'Pass' else '-' end
from students
join grades on score between MIN_score and MAX_score

Related

SQL, what aggregation logic makes different results?

SQL1 returns lines with aggreated names while SQL2 returns the non-aggreated.
Question is what's the difference of aggregation logic when executing the two SQLs. Thanks.
SQL1
SELECT
name,
CASE WHEN COUNT(CASE WHEN course = 'SQL' THEN 1 END) > 0 THEN 'o' END AS SQL,
CASE WHEN COUNT(CASE WHEN course = 'UNIX' THEN 1 END) > 0 THEN 'o' END AS UNIX,
CASE WHEN COUNT(CASE WHEN course = 'Java' THEN 1 END) > 0 THEN 'o' END AS Java
FROM Courses
GROUP BY name;
SQL2
SELECT name,
CASE WHEN course = 'SQL' THEN '○' ELSE NULL END s,
CASE WHEN course = 'UNIX' THEN '○' ELSE NULL END u,
CASE WHEN course = 'Java' THEN '○' ELSE NULL END j
FROM Courses
GROUP BY name,course;
Create Table
CREATE TABLE Courses
(name VARCHAR(32),
course VARCHAR(32),
PRIMARY KEY(name, course));
INSERT INTO Courses VALUES('Tom', 'SQL');
INSERT INTO Courses VALUES('Tom', 'UNIX');
INSERT INTO Courses VALUES('Jack', 'SQL');
INSERT INTO Courses VALUES('Mike', 'SQL');
INSERT INTO Courses VALUES('Mike', 'Java');
INSERT INTO Courses VALUES('Jane', 'UNIX');
INSERT INTO Courses VALUES('Mary', 'SQL');
I would say that difference in logic is obvious, in first query you group by just name.
GROUP BY name
Basically, you are saying group all rows with same name as one row.
In second query you group by name and course.
GROUP BY name,course
Which means, all rows with same name and same course should be one row.

Having trouble with the subquery in this code

I'm trying to run this code for an assignment for a class I've got. The "x" at the end of my subquery keeps on giving me errors and I can't wrap my head around why this is.
The goal of this assignment is to count (by age group) the number of reports that Carditis was a symptom after receiving a COVID shot.
Thanks in advance
Select agegroup, sum(case when died= 'Y' then 1 else 0 end) as Deaths
From (Select *,
Case
when age<=2 then 'infant'
when age<18 then 'juvenile'
when age<35 then 'adult'
when age<65 then 'old adult'
when age>=65 then 'senior'
else 'unknown' end as agegroup
from dbo.symptoms as s
join dbo.vaersvax as v on s.vaers_id=v.vaers_id
join dbo.patient as p on s.vaers_id=p.vaers_id
where v.vax_type='COVID19' and OneVax='Y' and symptom='Carditis'
) as x
Group By agegroup
Order By avg(age)
As #Schmocken already said, you can't perform a SELECT FROM a subquery that returns more than one column with the same name. As I suppose from your external query, this would do the job for you:
Select agegroup, sum(case when died= 'Y' then 1 else 0 end) as Deaths
From (Select died, age,
Case
when age<=2 then 'infant'
when age<18 then 'juvenile'
when age<35 then 'adult'
when age<65 then 'old adult'
when age>=65 then 'senior'
else 'unknown' end as agegroup
from dbo.symptoms as s
join dbo.vaersvax as v on s.vaers_id=v.vaers_id
join dbo.patient as p on s.vaers_id=p.vaers_id
where v.vax_type='COVID19' and OneVax='Y' and symptom='Carditis'
) as x
Group By agegroup
Order By avg(age)
By using Select * you have specified the same column name to be returned more than once.
As an example, you are returning both s.vaers_id and v.vaers_id, which are the same. This is not allowed; a subquery must return a unique set of column names.
You could return s.* successfully, but not all columns from all tables.

How can I select data from multiple SQL tables?

I have 2 tables, as described below:
Grades
student id,
exem1,
exam2,
exam3
Names
student id,
names
I want to display the names of students, their average on the 3 exams, and a letter grade. The letter grade is computed as follows:
90+ is an “A”, 80 - <90 will be a “B”, and so on. How should i do it.?
> SELECT n.student_names, ((g.exam1+g.exam2+g.exam3)/3) AS 'AVERAGE_RESULT', (CASE
WHEN ((g.exam1+g.exam2+g.exam3)/3) =90
THEN 'A'
WHEN (((g.exam1+g.exam2+g.exam3)/3) BETWEEN 80 AND 89)
THEN 'B'
WHEN (((g.exam1+g.exam2+g.exam3)/3) BETWEEN 70 AND 79)
THEN 'C'
ELSE 'D') END AS 'Student_grades',
FROM names n, grades g
WHERE n.students_id = g.students_id;
Using BETWEEN to include the ranges
SELECT n.NAME AS "Name", CASE WHEN ((g.exam1+g.exam2+g.exam3)/3) = 90 then 'A'
WHEN ((g.exam1+g.exam2+g.exam3)/3) < 90 then 'B'
ELSE 'C'
END AS "Grade"
FROM NAMES AS n INNER JOIN GRADES AS g
ON g.student_id = n.student_id
Modify the ranges as you want.

How would I go about writing the SQL for the attached report?

We're moving from data system to another. I'm having to rewrite some reports the new system has a custom reporting module where you can write a query and paste it into a window and push it out to the end user. I have one report that all of users have been requesting but I can't figure out how to write it.
It breaks it down the school population by grade level, gender and race. see attached below.
I wrote this which breaks down the population by grade level but not sure where to go from here.
SELECT
gl.title as Grade,
COUNT (s.student_id)
FROM
students s,
student_enrollment se,
school_gradelevels gl
WHERE
s.student_id = se.student_id
AND se.school_id=gl.school_id
AND se.grade_id=gl."id"
AND se.syear =2012
AND se.end_date IS NULL
AND se.school_id =10
group by gl.title
order by Grade;
For reference gender is s.gender and race is s.race. I'm wondering if were going to have to purchase a reporting software like crystal reports.
There is no data example so would presum that the gender and race information is captured against the student.
What you want to do is get an initial table that would look like that:
GradeLevel | Category | NumOfStudents
------------------------------------------------
PK | B - Male | 10
PK | B - Female | 12
0 | B - Male | 5
0 | B - Female | 6
This is easy to achieve by doing something like that:
SELECT
gl.title as GradeLevel,
s.Race + '-' +s.Gender as Category
COUNT (s.student_id) as NumOfStudents
FROM
students s,
inner join student_enrollment se on s.student_id = se.student_id,
inner join school_gradelevels gl on se.school_id=gl.school_id AND se.grade_id=gl."id"
WHERE
se.syear =2012
AND se.end_date IS NULL
AND se.school_id =10
group by gl.title, s.Race + '-' +s.Gender
order by Grade;
next you would want to make a Pivot out of that data by using your reporting tool weather its SSRS or Crystal or anything else that supports matrix reports.
if there is no report tool available that knows how to make a matrix use the SQL Pivot clause: http://msdn.microsoft.com/en-us/library/ms177410(v=sql.105).aspx
SELECT GradeLevel, [B-Male], [B-Female] ....
FROM
(
SELECT
gl.title as GradeLevel,
s.Race + '-' +s.Gender as Category
COUNT (s.student_id) as NumOfStudents
FROM
students s,
inner join student_enrollment se on s.student_id = se.student_id,
inner join school_gradelevels gl on se.school_id=gl.school_id AND se.grade_id=gl."id"
WHERE
se.syear =2012
AND se.end_date IS NULL
AND se.school_id =10
group by gl.title, s.Race + '-' +s.Gender
) rawData
PIVOT
(
SUM(NumOfStudents)
FOR Category IN ( [B-Male], [B-Female], ... )
) AS pvt
Fundamentally, you must group by everything you're counting, which appears to be grade, race, and sex. Do that first: get everything in rows, summed up the way you need.
What may be confusing you is that the report has two levels of grouping: one by grade, race, and sex, and one by grade & race. In effect, it looks like this:
create view V as
SELECT gl.title as Grade
, s.sex
, s.race
, COUNT (s.student_id) as Q
FROM students as s
JOIN student_enrollment as se
ON s.student_id = se.student_id
JOIN school_gradelevels gl
ON se.school_id = gl.school_id
AND se.grade_id = gl.id
WHERE
se.syear = 2012
AND se.end_date IS NULL
AND se.school_id = 10
group by gl.title, s.sex, s.race
select Grade, race, sex, Q
from V
UNION
select Grade, race, 'Z', sum(Q) as Q
from V
group by Grade, race
How you turn that into a cross-tab report depends on the tools at your disposal. It's possible in SQL, using the pivot operator or a series of cases in your select. The trick is to notice you need three columns for every race: male, female, and both. It's a little tedious, here's a sample
select Grade, 1 as sort_order
, max(case race when 1 then
case sex when 'M' then Q end end) as r1mq
, NULL as r1mb
, max(case race when 1 then
case sex when 'F' then Q end end) as r1fq
, max(case race when 2 then
case sex when 'M' then Q end end) as r2mq
, NULL as r2mb
, max(case race when 2 then
case sex when 'F' then Q end end) as r2fq
...
from V group by Grade
UNION
select Grade, 2 as sort_order
, NULL as r1mq
, max(case race when 1 then
case sex when 'Z' then Q end end) as r1bq
, NULL as r1fq
, NULL as r2mq
, max(case race when 2 then
case sex when 'Z' then Q end end) as r2bq
, NULL as r2fq
...
from V group by Grade
order by Grade, sort_order
That produces two rows per grade, with the combined student totals in the second row for each one.
You get the idea. You'll also need queries to count students grouped by grade and sex, and by grade alone. Those could be joined to the output of your pivot query. And totals by race, for the bottom row, unioned on with grade of 99 or something to make it sort at the bottom. The result would match the row & column layout of your report.
with detail as (
select
gl.title as grade, s.race, s.gender, count(s.student_id) total
from
students s
inner join
student_enrollment se using (student_id)
inner join
school_gradelevels gl on se.school_id = gl.school_id and se.grade = gl.id
where
and se.syear = 2012
and se.end_date is null
and se.school_id = 10
group by 1, 2, 3
order by 1, 2, 3
) agg as (
select grade, array_agg(total) total
from detail
group by grade
)
select
grade,
(select sum(e) from unnest(total) s(e)) total_grade,
total[1,1] + total[2,1] + total[3,1] + total[4,1] + total[5,1] + total[6,1] total_F,
total[1,2] + total[2,2] + total[3,2] + total[4,2] + total[5,2] + total[6,2] total_M,
total[1,1] total_A_F,
total[1,2] total_A_M,
total[1,1] + total[1,2] total_A,
total[2,1] total_B_F,
total[2,2] total_B_M,
total[2,1] + total[2,2] total_B,
total[3,1] total_H_F,
total[3,2] total_H_M,
total[3,1] + total[3,2] total_H,
total[4,1] total_I_F,
total[4,2] total_I_M,
total[4,1] + total[4,2] total_I,
total[5,1] total_M_F,
total[5,2] total_M_M,
total[5,1] + total[5,2] total_MM,
total[6,1] total_W_F,
total[6,2] total_W_M,
total[6,1] + total[6,2] total_W,
from agg

SQL using if statement in stored procedure to update a table

I have a table Students with two fields, StudentName and Grade.
I am trying to write a stored procedure to update the Grade. If the student has an A, I want to change it to B. If they have a B, I want to change it to A. If they have anything else I want to leave it alone. Here is my best attempt
create procedure sp_changegrades
if Grade = 'A' update Students set Grade = 'B'
else if Grade = 'B' update Students set Grade = 'A'
just use CASE
UPDATE Students
SET Grade =
(
CASE WHEN Grade = 'A' THEN 'B'
WHEN Grade = 'B' THEN 'A'
ELSE Grade -- "If they have anything else I want to leave it alone."
END
)
or
UPDATE Students
SET Grade =
(
CASE WHEN Grade = 'A'
THEN 'B'
ELSE 'A'
END
)
WHERE Grade IN ('A','B')
You can utilize a Case statement, and add a where clause so you only update the relavant rows.
UPDATE Students
SET Grade =
(
CASE WHEN Grade = 'A' THEN 'B'
WHEN Grade = 'B' THEN 'A'
ELSE Grade -- "Included for Completeness, should never be utilized."
END
)
WHERE Grade in ('A','B')
you can write smth like this. In this solution you define rules of updating in join part and then updating.
create procedure sp_changegrades
as
begin
update Students set
Grade = G.Grade_New
from Students as S
inner join (values
('A', 'B'),
('B', 'A')
) as G(Grade_Old, Grade_New) on G.Grade_Old = S.Grade
end
or you can use case
create procedure sp_changegrades
as
begin
update Students set
Grade =
case Grade
when 'A' then 'B'
when 'B' then 'A'
else Grade
end
end