SQL - select students that had exams on every subject - sql

I have two tables:
Exams(StudentID, SubjectID),
Subjects(SubjectID)
And want to select students that had exam on all subjects. How to do it?
Is it possible without using GROUP and COUNT?

You can do it in many ways. One of those:
DECLARE #students TABLE ( id INT )
DECLARE #exams TABLE ( id INT )
DECLARE #studentexams TABLE
(
studentid INT ,
examid INT
)
INSERT INTO #exams
VALUES ( 1 ),
( 2 )
INSERT INTO #students
VALUES ( 1 ),
( 2 ),
( 3 )
INSERT INTO #studentexams
VALUES ( 1, 1 ),
( 1, 2 ),
( 2, 1 )
SELECT *
FROM #students s
WHERE NOT EXISTS ( SELECT *
FROM #exams e
WHERE e.id NOT IN ( SELECT se.examid
FROM #studentexams se
WHERE se.studentid = s.id ) )
Output:
id
1

Is this what you're after:
--list all students
select *
from #students st
--where there isn't
where not exists
(
--any subject
select top 1 1
from #subjects su
--for which that student did not take an exam
where su.id not in
(
select subjectid
from #exams e
where studentId = st.id
)
)
Here's the full code (i.e. including sample data tables):
declare #subjects table(id bigint not null identity(1,1), title nvarchar(32))
declare #students table(id bigint not null identity(1,1), name nvarchar(32))
declare #exams table(id bigint not null identity(1,1), studentId bigint, subjectId bigint, grade nchar(1), attempt int)
insert #subjects select 'Maths' union select 'English' union select 'Geography' union select 'Computer Science'
insert #students select 'Anna' union select 'Billy' union select 'Christie' union select 'Daniel'
insert #exams select st.Id, su.Id, grade, attempt
from
(
select 'Anna' student, 'Maths' subject, 'A' grade, 1 attempt
union select 'Anna' student, 'English' subject, 'A' grade, 1 attempt
union select 'Anna' student, 'Geography' subject, 'A' grade, 1 attempt
union select 'Anna' student, 'Computer Science' subject, 'A' grade, 1 attempt
union select 'Billy' student, 'Maths' subject, 'A' grade, 1 attempt
union select 'Billy' student, 'Computer Science' subject, 'A' grade, 1 attempt
union select 'Christie' student, 'Maths' subject, 'A' grade, 1 attempt
union select 'Christie' student, 'English' subject, 'F' grade, 1 attempt
union select 'Christie' student, 'English' subject, 'E' grade, 2 attempt
union select 'Christie' student, 'English' subject, 'A' grade, 3 attempt
union select 'Daniel' student, 'Maths' subject, 'A' grade, 1 attempt
union select 'Daniel' student, 'English' subject, 'A' grade, 1 attempt
union select 'Daniel' student, 'Geography' subject, 'A' grade, 1 attempt
union select 'Daniel' student, 'Computer Science' subject, 'F' grade, 1 attempt
union select 'Daniel' student, 'Computer Science' subject, 'A' grade, 2 attempt
) x
inner join #students st on st.name = x.student
inner join #subjects su on su.title = x.subject
--list all students
select *
from #students st
--where there isn't
where not exists
(
--any subject
select top 1 1
from #subjects su
--for which that student did not take an exam
where su.id not in
(
select subjectid
from #exams e
where studentId = st.id
)
)

Related

Selection of rows providing a specific condition in sql

I want to create a query like this:
For student_name, if the number of grade=0 and grade=1 students is equal, let's not select this person, but if the number of grade=0 and grade=1 students is different, let's select this person. In the query I will use for my example, Jack will not be selected, everyone else will be selected.
CREATE TABLE student
(
student_name VARCHAR(50),
grade CHAR(1)
)
INSERT INTO student
SELECT 'Jack', '0' UNION ALL
SELECT 'Jack', '0' UNION ALL
SELECT 'Jack', '0' UNION ALL
SELECT 'Jack', '1' UNION ALL
SELECT 'Jack', '1' UNION ALL
SELECT 'Jack', '1' UNION ALL
SELECT 'Rose', '0' UNION ALL
SELECT 'Rose', '0' UNION ALL
SELECT 'John', '1' UNION ALL
SELECT 'John', '1' UNION ALL
SELECT 'John', '1' UNION ALL
SELECT 'John', '1' UNION ALL
SELECT 'Dave', '1' UNION ALL
SELECT 'Dave', '1' UNION ALL
SELECT 'Chris', '0'
select * from student
Use aggregation and set the condition in the HAVING clause:
SELECT student_name
FROM student
GROUP BY student_name
HAVING COUNT(CASE WHEN grade = '0' THEN 1 END) <> COUNT(CASE WHEN grade = '1' THEN 1 END);
See the demo.

BigQuery: Union on repreated fields with different order of fields

How to make a UNION ALL work for repeated fields if the order of the fields does not match?
In the example below I try to UNION data_1_nested and data_2_nested, while the repeated field nested has two fields: id and age but in different order.
I could UNNEST and renest but this would not be very helpful if I have more then 1 nested field that I need to UNION on.
Example:
with
data_1 as (
Select 'a123' as id, 1 as age, 'a' as grade
union all
Select 'a123' as id, 3 as age,'b' as grade
union all
Select 'a123' as id, 4.5 as age,'c' as grade
)
,
data_2 as (
Select 'b456' as id, 6 as age,'e' as grade
union all
Select 'b456' as id, 5 as age,'f' as grade
union all
Select 'b456' as id, 2.5 as age,'g' as grade
)
,
data_1_nested as (
SELECT id,
array_agg(STRUCT(
age,grade
)) as nested
from data_1
group by 1
)
,
data_2_nested as (
SELECT id,
array_agg(STRUCT(
grade, age
)) as nested
from data_2
group by 1
)
SELECT * from data_1_nested
union all
SELECT * from data_2_nested
Below should work for you
select * from data_1_nested
union all
select id, array(select as struct age, grade from t.nested) from data_2_nested t
if applied to sample data from your question - output is
I modified your data a little bit to make 2 nested fields that need to be union. I also added a JS function for parsing the JSON. It is an ugly solution, but it seems to be working. Not sure if it is scalable (how many functions have to be created to covert different nested fields).
CREATE TEMP FUNCTION JsonToItems(input STRING)
RETURNS ARRAY<STRUCT<age INT64, grade STRING>>
LANGUAGE js AS """
return JSON.parse(input);
""";
with
data_1 as (
Select 'a123' as id, 1 as age, 'a' as grade
union all
Select 'a123' as id, 3 as age,'b' as grade
union all
Select 'a123' as id, 4.5 as age,'c' as grade
)
,
data_2 as (
Select 'b456' as id, 6 as age,'e' as grade
union all
Select 'b456' as id, 5 as age,'f' as grade
union all
Select 'b456' as id, 2.5 as age,'g' as grade
)
,
data_1_nested as (
SELECT id,
array_agg(STRUCT(
age,grade
)) as nested,
array_agg(STRUCT(
age,grade
)) as nested2
from data_1
group by 1
)
,
data_2_nested as (
SELECT id,
array_agg(STRUCT(
grade, age
)) as nested,
array_agg(STRUCT(
grade, age
)) as nested2
from data_2
group by 1
)
select id, JsonToItems(json), JsonToItems(json2) from (
SELECT id, TO_JSON_STRING(nested) as json, TO_JSON_STRING(nested2) as json2 from data_1_nested
union all
SELECT id, TO_JSON_STRING(nested) as json, TO_JSON_STRING(nested2) as json2 from data_2_nested
);

How to delete/update nested data in bigquery

Is there a way to delete/update nested field in bigquery?
Let's say I have this data
wives.age wives.name name
21 angel adam
20 kale
21 victoria rossi
20 jessica
or in json:
{"name":"adam","wives":[{"name":"angel","age":21},{"name":"kale","age":20}]}
{"name":"rossi","wives":[{"name":"victoria","age":21},{"name":"jessica","age":20}]}
As you can see from the data above.
Adam has 2 wives, named angel and kale. How to:
Delete kale record.
Update jessica to dessica
I tried to google this, but can't find it. I also tried to unnest, etc but no luck.
The reason why we want to do this is because we insert the array to the wrong records and want to remove/update array data with some condition.
Below is for BigQuery Standard SQL
#standardSQL
WITH updates AS (
SELECT 'rossi' name, 'jessica' oldname, 'dessica' newname UNION ALL
SELECT 'rossi' name, 'victoria' oldname, 'polly' newname UNION ALL
SELECT 'adam' name, 'angel' oldname, 'jen' newname
), divorces AS (
SELECT 'adam' name, 'kale' wifename UNION ALL
SELECT 'adam' name, 'milly' wifename UNION ALL
SELECT 'rossi' name, 'linda' wifename
)
SELECT t.name,
ARRAY(
SELECT AS STRUCT
age,
CASE
WHEN NOT oldname IS NULL THEN newname
ELSE name
END name
FROM UNNEST(wives)
LEFT JOIN UNNEST(updates) ON t.name = u.name AND name = oldname
LEFT JOIN UNNEST(divorces) AS wifename ON t.name = d.name AND name = wifename
WHERE wifename IS NULL
) waves
FROM `project.dataset.table` t
LEFT JOIN (
SELECT name, ARRAY_AGG(STRUCT(oldname, newname)) updates
FROM updates GROUP BY name
) u ON t.name = u.name
LEFT JOIN (
SELECT name, ARRAY_AGG(wifename) divorces
FROM divorces GROUP BY name
) d ON t.name = d.name
You can test / play with above using dummy data as below
#standardSQL
WITH `project.dataset.table` AS (
SELECT 'adam' name, [STRUCT<age INT64, name STRING>(21, 'angel'), (20, 'kale'), (22, 'milly')] wives UNION ALL
SELECT 'rossi', [STRUCT<age INT64, name STRING>(21, 'victoria'), (20, 'jessica'), (23, 'linda')]
), updates AS (
SELECT 'rossi' name, 'jessica' oldname, 'dessica' newname UNION ALL
SELECT 'rossi' name, 'victoria' oldname, 'polly' newname UNION ALL
SELECT 'adam' name, 'angel' oldname, 'jen' newname
), divorces AS (
SELECT 'adam' name, 'kale' wifename UNION ALL
SELECT 'adam' name, 'milly' wifename UNION ALL
SELECT 'rossi' name, 'linda' wifename
)
SELECT t.name,
ARRAY(
SELECT AS STRUCT
age,
CASE
WHEN NOT oldname IS NULL THEN newname
ELSE name
END name
FROM UNNEST(wives)
LEFT JOIN UNNEST(updates) ON t.name = u.name AND name = oldname
LEFT JOIN UNNEST(divorces) AS wifename ON t.name = d.name AND name = wifename
WHERE wifename IS NULL
) waves
FROM `project.dataset.table` t
LEFT JOIN (
SELECT name, ARRAY_AGG(STRUCT(oldname, newname)) updates
FROM updates GROUP BY name
) u ON t.name = u.name
LEFT JOIN (
SELECT name, ARRAY_AGG(wifename) divorces
FROM divorces GROUP BY name
) d ON t.name = d.name
result is as expected
name waves.age waves.name
adam 21 jen
rossi 21 polly
20 dessica
I hope you will be able to apply above to your real case :o)

Query for Multiple row SQL Where clause

Need help for one query which is fetching result from multiple rows based on some condition. For e.g. we have table with [Roll no] with [subjects]. Table can have multiple records for the same [Roll No]. My requirement is if the Student opt for only 'English' then result should return 'E', if Maths then 'M' and if both then 'B'.
// I think this is what you want.
INSERT INTO dbo.rolls
( name, subject )
VALUES ( 'Jones', 'English'),
( 'Smith', 'Math'),
('Adams','English'),
('Adams', 'Math')
GO
;WITH CTE AS (
SELECT subquery1.name, 'B' AS code FROM (
SELECT name,COUNT(name) AS cnt
FROM rolls
WHERE subject = 'English' OR subject = 'Math'
GROUP BY name
HAVING COUNT(name) > 1 ) AS subquery1
UNION
SELECT subquery2.name, SUBSTRING(rolls.subject,1,1) AS code FROM (
SELECT name,COUNT(name) AS cnt
FROM rolls
WHERE subject = 'English' OR subject = 'Math'
GROUP BY name
HAVING COUNT(name) = 1 ) AS subquery2
INNER JOIN dbo.rolls
ON rolls.name = subquery2.name
)
SELECT * FROM CTE

List cities where more than half of the students accepted

I have two tables:
tbl1: schoolID schoolname
tbl2 StudentID, schoolID ,Stu_Name, address, city, state, status
Status field contains value A for acceptance or R for rejected
I need to list cities in NY state where more than half of student applications were accepted.
SAMPLE TABLES
SELECT * INTO #tbl1 FROM
(
SELECT 1 schoolID,'SchoolA' schoolname
UNION ALL
SELECT 1,'SchoolB'
)TAB
SELECT * INTO #tbl2 FROM
(
SELECT 1 StudentID,1 schoolID ,'A' Stu_Name,'XXX' address,'CITYA' [CITY],'NY' [STATE],'A' [STATUS]
UNION ALL
SELECT 2,1,'A','XXX','CITYA','NY','A'
UNION ALL
SELECT 3,1,'A','XXX','CITYA','NY','A'
UNION ALL
SELECT 4,1,'A','XXX','CITYA','NY','A'
UNION ALL
SELECT 14,1,'A','XXX','CITYA','NY','R'
UNION ALL
SELECT 5,1,'A','XXX','CITYA','NY','R'
UNION ALL
SELECT 6,1,'A','XXX','CITYA','NY','R'
UNION ALL
SELECT 7,1,'A','XXX','CITYB','NY','A'
UNION ALL
SELECT 8,1,'A','XXX','CITYB','NY','A'
UNION ALL
SELECT 9,1,'A','XXX','CITYC','NY','A'
UNION ALL
SELECT 10,1,'A','XXX','CITYC','NY','R'
)TAB
QUERY
If you need to find out the cities where more than half of student applications were accepted irrespective of school, you can follow the below query.
DECLARE #CITY VARCHAR(30)='NY'
SELECT [CITY]
FROM
(
SELECT DISTINCT [CITY]
,CASE WHEN
(
-- Gets half of total count
COUNT([STATUS]) OVER(PARTITION BY [CITY])/2)
>=
-- Checks if half of total count is greater than count of accepted for each cities
COUNT(CASE WHEN [STATUS]='A' THEN 1 END) OVER(PARTITION BY [CITY]
)
THEN 'N'
ELSE 'Y'
END ACCEPTED
FROM #tbl2
WHERE [CITY] = #CITY
)TAB
WHERE ACCEPTED='Y'
If you wan tot filter this condition by school, you can follow the below query
DECLARE #SCHOOLID INT = 1
DECLARE #CITY VARCHAR(30)='NY'
SELECT [CITY]
FROM
(
SELECT DISTINCT [CITY]
,CASE WHEN
(
COUNT([STATUS]) OVER(PARTITION BY [CITY])/2)
>=
COUNT(CASE WHEN [STATUS]='A' THEN 1 END) OVER(PARTITION BY [CITY]
)
THEN 'N'
ELSE 'Y'
END ACCEPTED
FROM #tbl1 T1
JOIN #tbl2 T2 ON T1.schoolID=T2.schoolID AND T1.schoolID=#SCHOOLID
WHERE [CITY] = #CITY
)TAB
WHERE ACCEPTED='Y'