Suppose I have a table t1 with two columns, Student_ID and Grade. The data shown like this;
Student_ID, Grade (1,'A'),(1,'C'),(1,'F'),(1,'A'),(2,'B'),(2,'A'),(2,'C'),(2,'A').
Now I have to group the Student_ID with a new column named 'Result'. Student_ID 1 have one grade 'F' so consider failed and Student_ID 2 have no 'F' grade so consider passed. Require result should be like this.
Student_ID Result
1 Failed
2 Passed
Because the table have 500 records so can not manually enter 'Passed" or 'Failed'.
SELECT `Student_ID`, (IF(`Grade`='F',"Failed","Passed")) AS `Result`
FROM t1 group by `Student_ID`;
Above code will not work as the column Grade has 4 values not 1.
So code will be some thing like this.
SELECT `Student_ID`, (IF(`Grade` CONTAINS 'F',"Failed","Passed")) AS `Result` FROM t1 group by `Student_ID`;
There is no such thing CONTAINS in sql but may be HAVING or IN but how to use them here?
You can use CASE statement with EXISTS:
select DISTINCT
t.Student_ID,
case
when exists (
select 1 from t1 where Student_ID = t.Student_ID and Grade = 'F'
) then 'Failed'
else 'Passed'
end Result
from t1 t
See the demo.
Results:
| Student_ID | Result |
| ---------- | ------ |
| 1 | Failed |
| 2 | Passed |
You can also do it by evaluating each Grade to 1 or 0 for 'F' or not 'F' and then grouping by Student_ID:
select
t.Student_ID,
case sum(t.Result)
when 0 then 'Passed'
else 'Failed'
end Result
from (select Student_ID, Grade = 'F' Result from t1) t
group by t.Student_ID
See the demo.
Related
This is my database
studentId
course
grade
1
CSE115
F
2
CSE115
C
3
CSE115
A
3
EEE111
B
2
EEE111
F
1
EEE111
B
I want to execute a query that will only return the 'studentId' who has not failed in any course(No 'F' with the studentId in the table). In my example table I have showed only two courses(CSE115,EEE111) but in my case it can be 30 courses and only those student will be selected who has passed in all the courses given to the table. If a student passed in 29 courses except 1 course he will not be selected.
For example in the given table the output will be,
Output:
studentId
course
grade
3
CSE115
A
3
EEE111
B
Can anyone help me building the query?
You can get all the studentIds that failed at least once with this query:
SELECT studentId FROM tablename WHERE grade = 'F'
Use it with NOT IN to get the rows that you want:
SELECT *
FROM tablename
WHERE studentId NOT IN (SELECT studentId FROM tablename WHERE grade = 'F')
Or with NOT EXISTS:
SELECT t.*
FROM tablename t
WHERE NOT EXISTS (SELECT 1 FROM tablename tt WHERE tt.studentId = t.studentId AND grade = 'F')
I am trying to write a query that will select all of the numbers in my table, but those numbers with duplicates i want to append something on the end that shows it as a duplicate. However I am not sure how to do this.
Here is an example of the table
TableA
ID Number
1 1
2 2
3 2
4 3
5 4
SELECT statement output would be like this.
Number
1
2
2-dup
3
4
Any insight on this would be appreciated.
if you mysql version didn't support window function. you can try to write a subquery to make row_number then use CASE WHEN to judgement rn > 1 then mark dup.
create table T (ID int, Number int);
INSERT INTO T VALUES (1,1);
INSERT INTO T VALUES (2,2);
INSERT INTO T VALUES (3,2);
INSERT INTO T VALUES (4,3);
INSERT INTO T VALUES (5,4);
Query 1:
select t1.id,
(CASE WHEN rn > 1 then CONCAT(Number,'-dup') ELSE Number END) Number
from (
SELECT *,(SELECT COUNT(*)
FROM T tt
where tt.Number = t1.Number and tt.id <= t1.id
) rn
FROM T t1
)t1
Results:
| id | Number |
|----|--------|
| 1 | 1 |
| 2 | 2 |
| 3 | 2-dup |
| 4 | 3 |
| 5 | 4 |
If you can use window function you can use row_number with window function to make rownumber by Number.
select t1.id,
(CASE WHEN rn > 1 then CONCAT(Number,'-dup') ELSE Number END) Number
from (
SELECT *,row_number() over(partition by Number order by id) rn
FROM T t1
)t1
sqlfiddle
I made a list of all the IDs that weren't dups (left join select) and then compared them to the entire list(case when):
select
case when a.id <> b.min_id then cast(a.Number as varchar(6)) + '-dup' else cast(a.Number as varchar(6)) end as Number
from table_a
left join (select MIN(b.id) min_id, Number from table_a b group by b.number)b on b.number = a.number
I did this in MS SQL 2016, hope it works for you.
This creates the table used:
insert into table_a (ID, Number)
select 1,1
union all
select 2,2
union all
select 3,2
union all
select 4,3
union all
select 5,4
I'm trying to write a query that will pull students that have passed tests 1 thru 3 AND failed test 4.
Students can retake tests so there may be failed records, followed by passed records for some tests, such is the case with student_id = 2 below.
Table setup like this -
test_id | student_id | status | completed_on
--------+------------+---------+------------
1 | 1 | passed | 2018-03-24
2 | 1 | passed | 2018-03-25
3 | 1 | passed | 2018-03-26
4 | 1 | failed | 2018-03-27
1 | 2 | failed | 2018-03-24
1 | 2 | passed | 2018-03-25
2 | 2 | passed | 2018-03-26
3 | 2 | passed | 2018-03-27
4 | 2 | failed | 2018-03-27
In this case the query should pull both student_id 1 and 2
I tried this but it obviously didn't work -
select *
from table
where (test_id = 1 and status = 'passed')
and (test_id = 2 and status = 'passed')
and (test_id = 3 and status = 'passed')
and (test_id = 4 and status = 'failed')
Demo
SELECT count(Z.Test_ID), Z.student_ID
FROM (SELECT distinct student_ID, test_ID, Status
FROM table) Z
WHERE (Z.Status = 'Passed' and Z.test_ID in (1,2,3,4))
OR (Z.status = 'Failed' and Z.test_ID = 4)
GROUP BY Z.Student_ID
HAVING count(Z.Test_ID) = 4;
This works by first ensuring we only have distinct records for each student, status, and test_ID. (derived table Z)
We then evaluate how many passes in tests 1,2,3,4 exist and existence of fails we have for test 4. if the count is anything other than 4 then we know either they didn't pass the tests 1-3 and fail 4 or they've passed test 4 as well.
I'm not claiming this is fast or most efficient, but it will do the job. Make sure you have the right indices on your table,
SELECT s1.student_id
FROM mytable s1
JOIN mytable s2 on s1.student_id=s2.student_id and s2.test_id=2 and s2.status='passed'
JOIN mytable s3 on s1.student_id=s3.student_id and s3.test_id=3 and s3.status='passed'
WHERE s1.test_id=1
AND s1.status='passed'
AND NOT EXISTS (
SELECT 1
FROM mytable s4
WHERE s4.student_id=s1.student_id
AND s4.test_id=4
AND s4.status='passed'
)
Another approach:
select distinct t1.student_id
from mytable t1
inner join
-- students passed all the 3 tests
(select student_id from mytable where test_id in (1, 2, 3)
and status = 'passed' group by student_id having count(distinct test_id) = 3 ) t2
on t1.test_id = 4 and t1.status = 'failed' and t1.student_id = t2.student_id
where not exists
(select 1 from mytable where student_id = t1.student_id and
status = 'passed' and test_id = 4)
PS. If a student passed a test (e.g. test 1) but later took it again and failed, the student would be considered as passed. Not sure if that is acceptable.
One approach you can use to meet your requirement is to basically make a pivot table. Any way you slice it you will probable need to use a subquery, here using the SUM function and CASE statements you can determine how many times each student passed a test.
Then, in your outer WHERE clause you can select only the rows where you got 1 or more 'passed' results for tests 1 through 3, and 0 'passed' results for test 4.
SQL Fiddle
SELECT student_id
FROM (SELECT student_id,
SUM(
CASE WHEN(test_id = 1 AND result = 'passed')
THEN 1
ELSE 0
END) AS "Test1",
SUM(
CASE WHEN(test_id = 2 AND result = 'passed')
THEN 1
ELSE 0
END) AS "Test2",
SUM(
CASE WHEN(test_id = 3 AND result = 'passed')
THEN 1
ELSE 0
END) AS "Test3",
SUM(
CASE WHEN(test_id = 4 AND result = 'passed')
THEN 1
ELSE 0
END) AS "Test4"
FROM TestResults
GROUP BY student_id) tr
WHERE Test1 > 0 AND Test2 > 0 AND Test3 > 0 AND Test4 = 0
Using this technique you can also determine how many times a student passed or failed a test. So for example, you can change the CASE statements to 'failed' and return the test columns in the outer query to see the number of times a student failed the test. For example:
SELECT student_id, Test1Fails
FROM (SELECT student_id,
SUM(
CASE WHEN(test_id = 1 AND result = 'failed')
THEN 1
ELSE 0
END) AS "Test1Fails"
FROM TestResults
GROUP BY student_id) tr
I have a table like this:
client msg_type msg_body id
------ -------- -------- ---
123 typeA success abc
123 typeB success abc
456 typeA success abc
456 typeB failure abc
123 typeA success abc
123 typeA success abc
789 typeA success def
789 typeB success def
etc.
I would like output like this:
client diff id
------ ---- ---
123 2 abc
456 1 abc
789 0 def
where diff is the count of typeA:success messages - typeB:success messages. I can get the count of the typeA success using something like:
select client, count(*) from mytable
where msg_type="typeA" and msg_body="success"
However, I can't figure out how to put another count in there (for typeB) and also subtract.
I tried something like:
select client, count(*) from mytable
where msg_type="typeA" and msg_body="success" - count(*)
from mytable where msg_type="typeB" and msg_body="success"
But of course it didn't work, or I wouldn't be asking here. :) Any advice?
Edit: added another column. I tried the two suggestions given, but it only seems to return the results for one of the ids, not both.
Edit #2: I tried wrapping the SELECT query with:
select id, count(*) from (select ...) as anothertable where count_a_minus_count_b = 0;
I was hoping the output would be like:
id count
--- -----
abc 2
def 1
where count is the number of clients where the difference between typeA:success and typeB:success is 0.
COUNT counts non-null values, so you can construct an expression that's non-null when msg_type = 'typeA', and an expression that's non-null when msg_type = 'typeB'. For example:
SELECT client,
COUNT(CASE WHEN msg_type = 'typeA' THEN 1 END) AS count_a,
COUNT(CASE WHEN msg_type = 'typeB' THEN 1 END) AS count_b,
COUNT(CASE WHEN msg_type = 'typeA' THEN 1 END)
- COUNT(CASE WHEN msg_type = 'typeB' THEN 1 END) AS count_a_minus_count_b
FROM mytable
WHERE msg_body = 'success'
GROUP
BY client
;
(Disclaimer: not tested.)
Another way:
SELECT
d.client, COALESCE(a.cnt, 0) - COALESCE(b.cnt, 0) AS diff, d.id
FROM
( SELECT DISTINCT client, id
FROM mytable
) AS d
LEFT JOIN
( SELECT client, COUNT(*) AS cnt, id
FROM mytable
WHERE msg_type = 'typeA'
AND msg_body = 'success'
GROUP BY client, id
) AS a
ON d.client = a.client
AND d.id = a.id
LEFT JOIN
( SELECT client, COUNT(*) AS cnt, id
FROM mytable
WHERE msg_type = 'typeB'
AND msg_body = 'success'
GROUP BY client, id
) AS b
ON d.client = b.client
AND d.id = b.id ;
Tested at SQL-Fiddle
Here you go:
select client,
(sum(case when msg_type='typeA' and msg_body='success' then 1 else 0 end) -
sum(case when msg_type='typeB' and msg_body='success' then 1 else 0 end)) as diff
from your_table
group by client
Here's one way to get the result:
SELECT t.client
, SUM(t.msg_type<=>'typeA' AND t.msg_body<=>'success')
- SUM(t.msg_type<=>'typeB' AND t.msg_body<=>'success') AS diff
FROM mytable t
GROUP BY t.client
(The expressions in this query are MySQL specific; for a more portable query, use a less concise CASE expression to obtain an equivalent result.)
As more terse and obfuscated alternative to return the same result:
SELECT t.client
, SUM((t.msg_body<=>'success')*((t.msg_type<=>'typeA')+(t.msg_type<=>'typeB')*-1)) AS diff
FROM mytable t
GROUP BY t.client
Could you please make some comments on the following strange and complicated design. I want to continue with this design. It is an existing design and not created by me.
Employee
EmpID
-----
1
2
Attribute
AttributeID Name Visible
------------------------
1 Name 1
2 Age 1
3 Salary 1
EmployeeAttribute
EmpID AttributeID Value
-----------------------
1 1 Rauf
1 2 23
1 3 100000
2 1 Amal
2 3 50000
I want to select the above table as follows
EmpID Name Age Salary
-------------------------
1 Rauf 23 100000
2 Amal 50000
How can I do it in SQL Server 2008 ?
This is an EAV model. To get the desired results you would need to PIVOT on AttributeID either using PIVOT or as below.
SELECT EmpID,
MAX(CASE WHEN AttributeID =1 THEN Value END) AS Name,
MAX(CASE WHEN AttributeID =2 THEN Value END) AS Age,
MAX(CASE WHEN AttributeID =3 THEN Value END) AS Salary
FROM EmployeeAttribute
GROUP BY EmpID
EAV is flexible but has a lot of disadvantages (google "EAV anti pattern" to find out more on this.)
You can self join too
SELECT
T1.EmpID,
T1.Value As [name], T2.Value As [age], T3.Value As [salary]
FROM
EmployeeAttribute T1
JOIN
EmployeeAttribute T2 ON T1.EmpID= T2.EmpID
JOIN
EmployeeAttribute T3 ON T1.EmpID= T3.EmpID
WHERE
T1.AttributeID =1
AND
T2.AttributeID =2
AND
T3.AttributeID =3