Grade For Student Based on Different Conditions - sql-server

Hi All,
I am having marks of student in Different Subjects.
Table #Maths Contain marks in maths of students named as a,b and c.
Similar for #Science and #English.
Now i want output as if student have more than 75 marks in two subject he will be given grade as 'Merit'.If he is having more than 75 in one subject and more than 60 in other then grade will be 'Pass' and if none of subject is having more than 75 and one subject is less than 50 then he will be given grade as 'Fail'?
Please Provide me the solution for the result?
Thanks

SELECT m.id, m.name, m.marks, s.marks, e.marks,
CASE WHEN (m.marks > 75 and s.marks > 75) OR (s.marks > 75 and e.marks > 75) OR (m.marks > 75 and e.marks > 75) THEN 'Pass'
WHEN (m.marks > 75 and (s.marks > 60 OR e.marks > 60)) OR
(s.marks > 75 and (m.marks > 60 OR e.marks > 60)) OR
(e.marks > 75 and (s.marks > 60 OR m.marks > 60)) THEN 'Pass'
WHEN (m.marks < 75 and s.marks < 75 and e.marks < 75 and (m.marks < 50 or e.marks < 50 or s.marks < 50)) THEN 'Fail'
ELSE NULL END as Merit
FROM #maths m
inner join #science s
on m.ID = s.ID
inner join #English e
on m.ID = e.ID
Not very classy but since there are only few columns that needs to be compared, i'd approach it this way

You can try a query like below:
This query is definitely faster than Joins as it reduces the operation set for the case condition
The business logic is neatly encapsulated as part of weights and top query conditions
No Joins and therefore faster. Speed will become apparent when you start increasing student records and subject tables.
Query:
select
id,
name,
case
when sum(weightRank) >=32 then "Merit"
when sum(weightRank) >=20 then "Pass"
when sum(weightRank) <16 and count(ALL weightRank) < count(weightRank)
then "Fail"
else "N/A"
end as grade
from
(
select id,name,'m' as subject,marks from #maths
union all
select id,name,'m' as subject,marks from #science
union all
select id,name,'m' as subject,marks from #english
) allmarks
-- allmarks get all records together, and is faster than joining all tables
-- this is also extensible as subjects and students may increase
-- and not all subjects may have marks for all students, so we will not lose data as in case of joins
join
(values (NULL,0, 50),(1,51, 60),(4,61, 75), (16,76,100)
as I(weightRank,lowNumber, highNumber)
-- here we create a temp dynamic table to weight the marks
on
allmarks.marks between I.lowNumber AND I.HighNumber
group by id,name

Related

How can I change the code to run the way I want?

I'm trying to solve the "Gradebook Challenge" from KhanAcademy but not in the platform but in SQL Management Studio. And I haven't obtain the same result as on the platform.
This is my code and the error that appears when I try to run it in SQL Management Studio. Yes, I've done the research and now I realise that is not posible use the GROUP BY clause with alias column, but I don't know what else do. So please, I really need a hand on this.
SELECT COUNT(*), Grade,
CASE
WHEN Grade > 90 THEN 'A'
WHEN Grade > 80 THEN 'B'
WHEN Grade > 70 THEN 'C'
ELSE 'F'
END AS LetterGrade
FROM StudentsGrades
GROUP BY Grade
Push the query containing the CASE statement into a subquery or Common Table Expression, eg
use tempdb
go
create table StudentsGrades(Id int identity primary key, StudentId int, Grade int);
insert into StudentsGrades(StudentId,Grade)
values (1,90),(2,99),(3,40),(5,88);
with q as
(
SELECT *,
CASE
WHEN Grade > 90 THEN 'A'
WHEN Grade > 80 THEN 'B'
WHEN Grade > 70 THEN 'C'
ELSE 'F'
END AS LetterGrade
FROM StudentsGrades
)
SELECT LetterGrade, Count(*) CountOfGrade
from q
group by LetterGrade
outputs
LetterGrade CountOfGrade
----------- ------------
A 1
B 2
F 1
(3 rows affected)
Just another option is CROSS APPLY. It allows you to stack calculations and reference the alias.
Example
Select LetterGrade
,CountofGrade = count(*)
From StudentsGrades A
Cross Apply ( values ( CASE WHEN Grade > 90 THEN 'A'
WHEN Grade > 80 THEN 'B'
WHEN Grade > 70 THEN 'C'
ELSE 'F'
END
) )B(LetterGrade)
Group By LetterGrade
Results
LetterGrade CountofGrade
A 1
B 2
F 1

T-SQL (Transact-SQL): Two separate tables. Need the find the date that is >= in Table A based on a date in Table B

Two separate tables. Need the find the date that is >= in Table A based on a date in Table B. Only TransactionCode 59 in Table A should be considered.
From the example tables below my return in table B First_Tran_Date should be "01/22/2022." Table A contains over 35 million records with thousands of AccountNumber's and grows each day.
Need T-SQL to take Table B ChangeDate "01/21/2022" and find the first time Table A shows a TransactionDate on or after that date and only TransactionCode 59 counts. All other TransactionCode dates should not be evaluated for the return.
Table A:
AccountNumber TransactionDate TransactionCode
xxxx310 2/3/2022 40
xxxx310 1/19/2022 40
xxxx310 1/22/2022 59
xxxx310 1/10/2022 59
xxxx310 3/15/2022 40
xxxx310 1/25/2022 59
xxxx310 1/30/2022 40
xxxx310 1/31/2022 59
xxxx310 1/31/2022 62
xxxx310 3/8/2022 59
Table B:
Account ChangeDate First_Tran_Date COUNT_OF_DAYS
xxxx310 01/21/2022 **RESULT NEEDED** (Calculated First_Tran_Date - ChangeDate = COUNT_OF_DAYS)
I have tried the following without getting a correct result:
T-SQL example...
Created a VIEW…
WITH added_row_number AS (
SELECT
*,
ROW_NUMBER() OVER(
PARTITION BY AccountNumber
ORDER BY
TransactionDate
) AS row_number
FROM dbo.LoanTransactions
)
SELECT
*
FROM added_row_number
WHERE row_number = 1
AND TransactionDate >= '2022-03-01'
AND TransactionCode IN ('59', '61', '70', '77', '82') Used a
SELECT
from that VIEW …
SELECT
DISTINCT Account,
Prod_CD,
OldValue,
NewValue,
Acct_Open_DT,
ChangeDate,
LOSVIEW_All_Transactions_From_CORE1.TransactionDate AS First_Tran_Date,
LastTransactionDate,
CASE
WHEN Prod_CD IN ('L50', 'L51', 'L54', 'L77') THEN DATEDIFF(
DAY,
ChangeDate,
LOSVIEW_All_Transactions_From_CORE1.TransactionDate
)
ELSE DATEDIFF(DAY, Acct_Open_DT, ChangeDate)
END AS COUNT_OF_DAYS
FROM dbo.R_InsuranceCodeChanges
LEFT JOIN dbo.LOSVIEW_All_Transactions_From_CORE AS LOSVIEW_All_Transactions_From_CORE1
ON dbo.R_InsuranceCodeChanges.Account = LOSVIEW_All_Transactions_From_CORE1.AccountNumber
WHERE
dbo.R_InsuranceCodeChanges.ChangeDate >= '2022-01-01'
AND dbo.R_InsuranceCodeChanges.NewValue <> '0'
If I understand correctly you want to look up two dates, and also calculate the number of days between them?
Those dates should be the first and last time a row with TransactionCode 59 appears, for a given account, and only including records on or after a given date?
So for the data in your example, the missing date should be 2022-01-22? Then the number of days would be 52 days?
For that I would use OUTER APPLY; which allows you to effectively run a query once for each input row...
SELECT
*,
DATEDIFF(DAY, table_a.MinTransactionDate, table_a.MaxTransactionDate)
FROM
table_b
OUTER APPLY
(
SELECT
MIN(TransactionDate) AS MinTransactionDate,
MAX(TransactionDate) AS MaxTransactionDate
FROM
table_a
WHERE
AccountNumber = table_b.Account
AND TransactionDate >= table_b.ChangeDate
AND TransactionCode = 59
)
AS table_a
Should be an index on tableA.TransactionDate. But I just translated your words to SQL and this is what I got:
select min(TransactionDate) as minDate
from tableA
where TransactionCode = 59
and TransactionDate >= (select max(TransactionDate) from tableB)

Finding class average and number of student per subject

How can I get column for class average per subject and column for No. of students offering each subject? I have created the following tables statement and queries. Below is my query that gives me the result of a student without class average and No. of students per subject.
Table Student has three columns containing three students
Studentid Firstname Lastname
--------------------------------
1 Oreofeoluwa Ogunkoya
2 Prevailer Adebayo
3 Arike Adeladan
4 Khalilat Yakubu
Table course contains four courses Irk and Crk are optional.
Courseid Course
------------------
1 Maths
2 English
3 Irk
4 Crk
I also have StudentCourse as a junction table for course and student. It contains score for all students.
SELECT Course
,Score
,Grade
,Comment
,Pos
,Minimum
,Maximum
FROM (
SELECT S.firstname
,S.lastname
,C.course
,Sc.score
,CASE
WHEN Score BETWEEN 80
AND 100
THEN 'A'
WHEN Score BETWEEN 70
AND 79
THEN 'B'
WHEN Score BETWEEN 60
AND 69
THEN 'C1'
WHEN Score BETWEEN 50
AND 59
THEN 'C2'
WHEN Score BETWEEN 40
AND 49
THEN 'D'
ELSE 'F'
END AS Grade
,CASE
WHEN Score BETWEEN 80
AND 100
THEN 'Excellent'
WHEN Score BETWEEN 70
AND 79
THEN 'Very Good'
WHEN Score BETWEEN 60
AND 69
THEN 'Good'
WHEN Score BETWEEN 50
AND 59
THEN 'Average'
WHEN Score BETWEEN 40
AND 49
THEN 'Pass'
ELSE 'Fail'
END AS Comment
,Rank() OVER (
PARTITION BY course ORDER BY Score DESC
) AS Pos
,Min(Score) OVER (
ORDER BY course
) AS Minimum
,Max(Score) OVER (
ORDER BY course
) AS Maximum
FROM Student S
JOIN Studentcourse Sc ON S.Studentid = Sc.Studentid
JOIN Courses C ON C.courseid = Sc.Courseid
) sub
WHERE firstname = 'Oreofeoluwa'
This query gives me the following table but i need the class average and no of students offering each subject
Course Score Grade Comment Pos Minimum Maximum
---------------------------------------------------
Crk 62.00 C1 Good 1 44.00 62.00
English 80.00 A Excellent 1 43.00 80.00
Maths 96.00 A Excellent 1 36.00 96.00
You have to group it at the course level to get the average scores at a course level. Please find below a sample query:
Select course,avg(score) as avg_score,count(distinct studentid) as students
from Student S
JOIN Studentcourse Sc ON S.Studentid = Sc.Studentid
JOIN Courses C ON C.courseid = Sc.Courseid
Group by course
If you want this added to the table you have created at a student level, then you will have to perform a left join. Hope this helps.

How do you select a number of random rows from different AgeGroup?

I am trying to create a for loop in python to connect it to Snowflake since Snowflake does not support loops.
I want to select a number of random rows from different AgeGroups. eg. 1500 rows from AgeGroup "30-40", 1200 rows from AgeGroup "40-50" , 875 rows from AgeGroup "50-60".
Any ideas how to do it or an alternative method for a loop in Snowflake?
Have you looked at Snowflake's Stored Procedures? They are Javascript and would allow you to loop natively in Snowflake:
https://docs.snowflake.net/manuals/sql-reference/stored-procedures-overview.html
What do you mean by "Snowflake doesn't have loops"? SQL has "loops" if you can find them...
The following query does what you asked for:
WITH POPULATION AS ( /* 10,000 persons with random age 0-100 */
SELECT 'Person ' || SEQ2() ID, ABS(RANDOM()) % 100 AGE
FROM TABLE(GENERATOR(ROWCOUNT => 10000))
)
SELECT
ID,
AGE,
CASE
WHEN AGE < 30 THEN '0-30'
WHEN AGE < 40 THEN '30-40'
WHEN AGE < 50 THEN '40-50'
WHEN AGE < 60 THEN '50-60'
ELSE '60-100'
END AGE_GROUP,
ROW_NUMBER() OVER (PARTITION BY AGE_GROUP ORDER BY RANDOM()) DRAW_ORDER
FROM POPULATION
QUALIFY DRAW_ORDER <= DECODE(AGE_GROUP, '30-40', 1500, '40-50', 1200, '50-60', 875, 0);
Addendum:
As pointed out by waldente, a simpler and more efficient way is to use SAMPLE:
WITH
POPULATION_30_40 AS (SELECT * FROM POPULATION WHERE AGE >= 30 AND AGE < 40),
POPULATION_40_50 AS (SELECT * FROM POPULATION WHERE AGE >= 40 AND AGE < 50),
POPULATION_50_60 AS (SELECT * FROM POPULATION WHERE AGE >= 50 AND AGE < 60)
SELECT * FROM POPULATION_30_40 SAMPLE(1500 ROWS) UNION ALL
SELECT * FROM POPULATION_40_50 SAMPLE(1200 ROWS) UNION ALL
SELECT * FROM POPULATION_50_60 SAMPLE(875 ROWS)
If you want to draw n random samples from each group you could create a subquery containing a row number that is randomly distributed within each group, and then select the top n rows from each group.
If you have a table like this:
USER DATE
1 2018-11-04
1 2018-11-04
1 2018-12-07
1 2018-10-09
1 2018-10-09
1 2018-11-07
1 2018-11-09
1 2018-11-09
2 2019-11-02
2 2019-10-02
2 2019-11-03
2 2019-11-06
3 2019-11-10
3 2019-11-13
3 2019-11-15
This query could be used to return two random rows for User 2 and 3, and 3 random rows for user 1:
SELECT User, Date
FROM (
SELECT *, ROW_NUMBER() OVER(PARTITION BY User ORDER BY RANDOM()) as random_row
FROM Users)
WHERE
(User = 3 AND random_row < 3) OR
(User = 2 AND random_row < 3) OR
(User = 1 AND random_row < 4);
So in your case partition on and filter age_group instead of User.
Snowflake has support for random and deterministic table sampling. For Example:
Return a sample of a table in which each row has a 10% probability of being included in the sample:
SELECT * FROM testtable SAMPLE (10);
https://docs.snowflake.net/manuals/sql-reference/constructs/sample.html

Find combination of values on same table with condition in SQL server

I have a table named PlayerScore that contains the player name and their average scores:
Id Name Average
1 Sakib 80
2 Tamim 70
3 Mushfiq 60
4 Sabbir 50
5 Ashraful 20
6 Aftab 40
7 Rubel 30
8 Kalu 10
I want to find their partnership combination based on a condition that,
palyer whose average score is greater than 40 can not be partner with players whose score is less than 40.
I tried the following query :
select a.Name,a.Average,b.Name,b.Average from ((select * from PlayerScore where Average<=40) as a inner join (select * from PlayerScore where Average<=40) as b on a.Id < b.Id)
union
select a.Name,a.Average,b.Name,b.Average from ((select * from PlayerScore where Average>=40) as a inner join (select * from PlayerScore where Average>=40) as b on a.Id < b.Id)
that results in :
Name Average Name Average
Aftab 40 Kalu 10
Aftab 40 Rubel 30
Ashraful 20 Aftab 40
Ashraful 20 Kalu 10
Ashraful 20 Rubel 30
Mushfiq 60 Aftab 40
Mushfiq 60 Sabbir 50
Rubel 30 Kalu 10
Sabbir 50 Aftab 40
Sakib 80 Aftab 40
Sakib 80 Mushfiq 60
Sakib 80 Sabbir 50
Sakib 80 Tamim 70
Tamim 70 Aftab 40
Tamim 70 Mushfiq 60
Tamim 70 Sabbir 50
Is their any solution without using UNION
select distinct a.Name,a.Average,b.Name,b.Average
from PlayerScore a
join PlayerScore b
on a.Id < b.Id
and ( a.Average<=40 and b.Average<=40
or a.Average>=40 and b.Average>=40
)
it will likely result in the same exceution plan.
Maybe you can do something like this:
SELECT
t.*,
t2.*
FROM
PlayerScore AS t
CROSS JOIN PlayerScore AS t2
WHERE t.Average>=40 AND t2.Average<40
ORDER BY t.Name
You can create 2 groups based on your condition and give them different values and then do a join based on the value. Something like this.
;WITH PlayerScore as
(
SELECT 1 AS Id,'Sakib' AS Name,80 AS Average
UNION ALL SELECT 2,'Tamim',70
UNION ALL SELECT 3,'Mushfiq',60
UNION ALL SELECT 4,'Sabbir',50
UNION ALL SELECT 5,'Ashraful',20
UNION ALL SELECT 6,'Aftab',40
UNION ALL SELECT 7,'Rubel',30
UNION ALL SELECT 8,'Kalu',10
),PlayerCriteria AS
(
SELECT *,CASE WHEN Average >= 40 THEN 1 ELSE 0 END joincondition
FROM PlayerScore
)
SELECT * FROM PlayerCriteria C1
INNER JOIN PlayerCriteria C2 ON C1.joincondition = C2.joincondition
AND C1.Id > C2.Id

Resources