Stupid simple question that I ended up spending 3.5 hours on. I'm running into a lot of syntax errors, so if anyone could help me answer this, I would learn a lot! Thank you so much!
I have 3 database tables:
Students Table
student_id, name
1, joe
2, jill
Courses Table
course_id, course_name
eng123, Engineering
stat111, Statistics
Marks Table
student_id, course_id, mark
1, stat111, 64
2, stat111, 90
1, eng123, 86
I need to write a single SQL query that will give me a summed up report card that looks like this:
student_id, student_name, eng123, stat 111
1, joe, 86, 64
2, jill, null, 90
---WHAT I EXPLORED:
I have looked into PIVOT, CASE and GROUP BY as my main leads, but I cannot put together the final pieces. My most promising query so far has been:
SELECT Students.student_id, Students.student_name,
CASE course_id WHEN 'eng123' THEN mark END as 'eng123',
CASE course_id WHEN 'stat111' THEN mark END as 'stat111'
FROM Students
INNER JOIN Marks
ON Students.student_id=Marks.student_id;
But that gives me the incorrect results of:
student_id, student_name, eng123, stat111
1, joe, null, 64
1, joe, 86, null
2, jill, null, 90
Your promising query is a stone's throw away from being what you want. You can simply modify it to GROUP BY the student_id and student_name (which should always pair the same together). Then take the sum of the marks. Note that I have added ELSE conditions to your CASE statements which assign a value of 0 (which therefore won't affect the sum aggregate).
SELECT Students.student_id, Students.student_name,
SUM(CASE course_id WHEN 'eng123' THEN mark ELSE 0 END) as 'eng123',
SUM(CASE course_id WHEN 'stat111' THEN mark ELSE 0 END) as 'stat111'
FROM Students
INNER JOIN Marks ON Students.student_id=Marks.student_id
GROUP BY Students.student_id, Students.student_name
Hi try using aggregate function MAX. try this:
WITH x AS (SELECT 1 AS student_id, 'joe' AS student_NAME FROM dual UNION ALL
SELECT 2 AS student_id, 'jill' AS student_NAME FROM dual),
y AS (SELECT 1 AS student_id , 'stat111' AS course_id, 64 AS mark FROM dual UNION ALL
SELECT 2 AS student_id , 'stat111' AS course_id, 90 AS mark FROM dual UNION ALL
SELECT 1 AS student_id , 'eng123' AS course_id, 86 AS mark FROM dual )
SELECT x.student_id, x.student_name,
MAX (CASE WHEN course_id = 'eng123' THEN mark END) eng123,
max(CASE WHEN course_id = 'stat111' THEN mark END) stat111
FROM x
INNER JOIN y
ON x.student_id=y.student_id
group by x.student_id, x.student_name;
select student_id,student_name,
max(case when course_id = 'eng123' then mark end) as eng123,
max(case when course_id = 'stat111' then mark end) as stat111
from (
SELECT Students.student_id as student_id,
Students.name as student_name,
Marks.course_id as course_id,
Marks.mark as mark
FROM Student Students
INNER JOIN Marks
ON Students.student_id=Marks.student_id)Z
group by student_id,student_name
Related
SET VARIABLE PREFERRED_CURRENCY='User Preferred Currency 1';SELECT
0 s_0,
"People - People Real Time"."People Details"."People Full Name" FullName,
case
when "People - People Real Time"."Job Application - Legislative Information"."Ethnicity" like '%,%' then 'Two Or More Races'
else "People - People Real Time"."Job Application - Legislative Information"."Ethnicity" end as Ethnicity,
"People - People Real Time"."Job Application - Legislative Information"."Gender" Gender,
"People - People Real Time"."Job Requisition - Basic Information"."Requisition Number" RequisitionNumber
FROM "People - People Real Time"
where "People - People Real Time"."Job Requisition - Basic Information"."Requisition Number" = :reqNumber
ORDER BY "People - People Real Time"."Job Application - Job Application Dates"."Creation Date" desc, 1, 2 ASC NULLS LAST, 3 ASC NULLS LAST, 4 ASC NULLS LAST, 5 ASC NULLS LAST
I am getting the output like -
FullName Ethnicity Gender RequisitionNumber
Smith Hispanic M 10
Smith Englush M 10
Smith Hindi M 10
Test English F 11
I want the above query to be tweaked in a way that i get the output as
FullName Ethnicity Gender RequisitionNumber
Smith Two Or More Races M 10
Test English F 11
I cannot use many functions in the query ( i can use Count) because it is OBIEE
Continuing on the output you got already...
WITH
dataset AS
(
Select 'Smith' "FULL_NAME", 'Hispanic' "ETHNICITY", 'M' "GENDER", 10 "REQUISITION_NUMBER" From Dual Union All
Select 'Smith' "FULL_NAME", 'English' "ETHNICITY", 'M' "GENDER", 10 "REQUISITION_NUMBER" From Dual Union All
Select 'Smith' "FULL_NAME", 'Hindi' "ETHNICITY", 'M' "GENDER", 10 "REQUISITION_NUMBER" From Dual Union All
Select 'Test' "FULL_NAME", 'English' "ETHNICITY", 'F' "GENDER", 11 "REQUISITION_NUMBER" From Dual
)
SELECT DISTINCT
FULL_NAME,
CASE WHEN Count(*) OVER(PARTITION BY FULL_NAME, GENDER, REQUISITION_NUMBER) > 1 THEN 'Two Or More Races' ELSE ETHNICITY END "ETHNICITY",
GENDER,
REQUISITION_NUMBER
FROM
dataset
/* R e s u l t :
FULL_NAME ETHNICITY GENDER REQUISITION_NUMBER
--------- ----------------- ------ ------------------
Test English F 11
Smith Two Or More Races M 10
*/
One of the options is to use CASE expresion with Count() analytic function to transform the ETHNICITY column to the text you want. There are multiple rows initialy so the DISTINCT keyword is used to get your expected result.
Regards...
I am relatively new to SQL and I may be over thinking it but given the following table.I want a list of all students that are enrolled in Chemistry but not Mathematics.So given the data I would have Student 1.
Student_ID Subject
1 Chemistry
2 Mathematics
2 Chemistry
3 History
Here's what I tried
SELECT Student_ID
FROM Student
WHERE (Subject = 'Chemistry') AND (Subject <> 'Mathematics')
Perhaps I need to group somewhere because rows and returned that exist for both criteria.
Here's one option using conditional aggregation:
select student_id
from student
group by student_id
having max(case when subject = 'Chemistry' then 1 end) = 1 and
max(case when subject = 'Mathematics' then 1 else 0 end) != 1
And here's another option using not exists:
select student_id
from student s
where subject = 'Chemistry' and not exists (
select 1
from student s2
where s.student_id = s2.student_id and st.subject = 'Mathematics'
)
You can use where and not in
select Student_ID
from Student
where Subject = 'Chemistry'
and Student_ID NOT IN ( select Student_ID from Student where subject ='Mathematics');
Please suggest a sql query to fetch the
"list of students and corresponding subject in which student has same score , if the student has same score in more than one subject"
eg.
Student Subject Score
John Science 80
John Maths 80
John English 80
John French 80
Peter Science 85
Peter Maths 70
Peter English 70
Peter French 70
Mathews French 70
Expected Result :
John Science
John Maths
John English
John French
Peter Maths
Peter English
Peter French
tried -
select person , subject where person in
( select person , score , count(score) group by person , score having count(score) > 1 )
but in this "Peter Science" appears , which is not required.
Please assist.
Thanks
Try:
select person , subject
from table t
join
( select person , score , count(score)
from table
group by person , score having count(score) > 1
) foo
on t.person = foo.person
where t.person = foo.person
and t.score = foo.score
The reason your query didn't work (aside from not including a table):
Your inner query was selecting the students and scores that match your criteria. But your outer query was selecting everything for all students who matched the criteria, which is why "peter science" was being included. So, you needed to limit the results in your outer query to only those that match all of your criteria.
You should tell us what RDBMS are you using. But this should work everywhere:
select
t.*
from <tableName> t
join (
select
student, score
from <tableName>
group by
student, score
having count(*)>1
) x
on t.student=x.student
and t.score=x.score
Btw you're missing FROM <tableName> in your query.
Try with this :
select student, subject
from Person
where student + cast(score as varchar(20)) in
(
select student + cast(score as varchar(20)) from Person group by student,score having count(score) > 1
)
You can achieve this goal self-joining table like in example:
SELECT
t.Student,
t.Subject,
t.Score
FROM Table1 AS t
JOIN
(
SELECT Student, Score FROM Table1 GROUP BY Student, Score HAVING COUNT(*) > 1
) AS t1 ON t.Student = t1.Student AND t.Score = t1.Score
Full example with test data and created table you can find here:
http://sqlfiddle.com/#!2/61f02/8
I have a table where each row has a description field as well as a boolean value. I'm trying to write a query where I can group by each respective description, and see the percentage of times that the boolean was true.
Example table:
PID Gender SeniorCitizen
1 M 1
2 M 1
3 F 0
4 F 1
5 M 0
And I want a query that will return this:
Gender SeniorPct
M .66
F .50
I've got to the point where I have a query that will calculate the individual percentages for a male or female - but I want to see both results at once
SELECT Gender, COUNT(*) * 1.0 /
(SELECT COUNT(*) FROM MyTable WHERE Gender='M')
FROM MyTable WHERE Gender='M' and SeniorCitizen=1;
I've been trying to include a "GROUP BY Gender" statement in my outer SELECT above, but I can't seem to figure out how to tweak the inner SELECT to get the correct results after tweaking the outer SELECT as such.
(I tested this under MySQL, please check if the same idea can be applied to the SQLite.)
To find the number of seniors (per gender), we can treat the bits as numbers and simply sum them up:
SELECT
Gender,
SUM(SeniorCitizen) Seniors
FROM MyTable
GROUP BY Gender
GENDER SENIORS
M 2
F 1
Based on that, we can easily calculate percentages:
SELECT
Gender,
SUM(SeniorCitizen) / COUNT(*) * 100 SeniorsPct
FROM MyTable
GROUP BY Gender
GENDER SENIORSPCT
M 66.6667
F 50
You can play with it in this SQL Fiddle.
UPDATE: Very similar idea works under SQLite as well. Please take a look at another SQL Fiddle.
Try the following:
CREATE TABLE #MyTable
(
PID INT,
Gender VARCHAR(1),
SeniorCitizen BIT
)
INSERT INTO #MyTable
(
PID,
Gender,
SeniorCitizen
)
SELECT 1, 'M', 1 UNION
SELECT 2, 'M', 1 UNION
SELECT 3, 'F', 0 UNION
SELECT 4, 'F', 1 UNION
SELECT 5, 'M', 0
SELECT
Gender,
COUNT(CASE WHEN SeniorCitizen = 1 THEN 1 END), -- Count of SeniorCitizens grouped by Gender
COUNT(1), -- Count of all users grouped by Gender
CONVERT(DECIMAL(2, 2), -- You can ignore this if you want
COUNT(CASE WHEN SeniorCitizen = 1 THEN 1 END) * 1.0 / COUNT(1) -- Your ratio
)
FROM
#MyTable
GROUP BY
Gender
In a program I'm maintaining we were given a massive (~500 lines) SQL statement by the customer. It is used for generating flat files with fixed length records for transmitting data to another big business. Since its a massive flat file its not relational and the standard normal forms of data are collapsed. So, if you have a record that can have multiple codes associated, in this case upto 19, they all have be written into single line, but seperate fields, in the flat file.
Note: this example is simplified.
The data might look like this, with three tables:
RECORDS
record_id firstname lastname
--------------------------------
123 Bob Schmidt
324 George Washington
325 Ronald Reagan
290 George Clooney
CODE_TABLE
code_id code_cd code_txt
--------------------------------
5 3 President
2 4 Actor
3 7 Plumber
CODES_FOR_RECORDS
record_id code_cd
-------------------
123 7
325 3
290 4
324 3
325 4
123 4
This needs to produce records like:
firstname lastname code1 code2 code3
Bob Schmidt Actor Plumber NULL
George Washington President NULL NULL
Ronald Reagon Actor President NULL
George Clooney Actor NULL NULL
The portion of the current query we were given looks like this, but with 19 code columns instead of the 5:
select
x.record_id,
max(case when x.rankk = 1 then code_txt end) as CodeColumn1,
max(case when x.rankk = 2 then code_txt end) as CodeColumn2,
max(case when x.rankk = 3 then code_txt end) as CodeColumn3,
max(case when x.rankk = 4 then code_txt end) as CodeColumn4,
max(case when x.rankk = 5 then code_txt end) as CodeColumn5,
from
(
select
r.record_id,
ct.code_txt as ctag ,
dense_rank() over (partition by r.record_id order by cfr.code_id) as rankk
from
records as r
codes_for_records as cfr,
code_table as ct
where
r.record_id = cfr.record_id
and ct.code_cd = cfr.code_cd
and cfr.code_cd is not null
and ct.code_txt not like '%V%'
) as x
where
x.record_id is not null
group by
x.record_id
I trimmed down things for simplicties sake, but the actual statment includes an inner query and a join and more where conditions, but that should get the idea across. My brain is telling me there has to be a better way, but I'm not an SQL expert. We are using DB2 v8 if that helps. And the codes have to be in seperate columns, so no coalescing things into a single string. Is there a cleaner solution than this?
Update:
I ended up just refacorting the original query, it sill uses the ugly MAX() business, but overall the query is much more readable due to reworking other parts.
It sounds like what you are looking for is pivoting.
WITH joined_table(firstname, lastname, code_txt, rankk) AS
(
SELECT
r.firstname,
r.lastname,
ct.code_txt,
dense_rank() over (partition by r.record_id order by cfr.code_id) as rankk
FROM
records r
INNER JOIN
codes_for_records cfr
ON r.record_id = cfr.record_id
INNER JOIN
codes_table ct
ON ct.code_cd = cfr.code_cd
),
decoded_table(firstname, lastname,
CodeColumn1, CodeColumn2, CodeColumn3, CodeColumn4, CodeColumn5) AS
(
SELECT
firstname,
lastname,
DECODE(rankk, 1, code_txt),
DECODE(rankk, 2, code_txt),
DECODE(rankk, 3, code_txt),
DECODE(rankk, 4, code_txt),
DECODE(rankk, 5, code_txt)
FROM
joined_table jt
)
SELECT
firstname,
lastname,
MAX(CodeColumn1),
MAX(CodeColumn2),
MAX(CodeColumn3),
MAX(CodeColumn4),
MAX(CodeColumn5)
FROM
decoded_table dt
GROUP BY
firstname,
lastname;
Note that I've never actually done this myself before. I'm relying on the linked document as a reference.
You might need to include the record_id to account for duplicate names.
Edit: Added the GROUP BY.
One of the possible solutions is using of recursive query:
with recursive_view (record_id, rankk, final) as
(
select
record_id,
rankk,
cast (ctag as varchar (100))
from inner_query t1
union all
select
t1.record_id,
t1.rankk,
/* all formatting here */
cast (t2.final || ',' || t1.ctag as varchar (100))
from
inner_query t1,
recursive_view t2
where
t2.rankk < t1.rankk
and t1.record_id = t2.record_id
and locate(t1.ctag, t2.final) = 0
)
select record_id, final from recursive_view;
Can't guarantee that it works, but hope it will be helpful. Another way is using of custom aggregate function.