SQL statement for average in specific row values group columns - sql

I've my table like the format below, I want to write sql statement for display column average for test1 and test2, column for main and column for position.
I've tried this sql:
select name, average as final,AVG(case when exam in('test1','test2') ) as testa
from table
where exam='main'
order by main final
group by name;
But It Does not working....help please
I want results to appear like this:

You could do something like the following.
Select t1.name, average12, main, rownum from (
select t1.name as name, t1.avg(average) as average12, t2.mainexam as main
from <table> t1 left join <table> t2
on t1.id = t2.id
where t2.exam = 'Main'
group by t1.name, t2.mainexam
) order by main desc;

Try this query for SQL Server:
SELECT
e.name AS 'Student Name'
, CAST( AVG( CASE WHEN e.Exam IN ( 'Test1', 'Test2' ) THEN e.average END ) AS FLOAT ) AS 'Test Average'
, e2.average AS 'Main Exam'
, ROW_NUMBER() OVER ( ORDER BY e2.average DESC ) AS 'Position'
FROM
exams AS e
INNER JOIN exams AS e2
ON e2.name = e.name
AND e2.exam = 'Main'
GROUP BY
e.name
, e2.average
ORDER BY
e2.average DESC
Output is:
+--------------+--------------+-----------+----------+
| Student Name | Test Average | Main Exam | Position |
+--------------+--------------+-----------+----------+
| Name1 | 60.5 | 89 | 1 |
| Name2 | 49.5 | 63 | 2 |
| Name3 | 73 | 38 | 3 |
+--------------+--------------+-----------+----------+

This simple query averages Test1 and Test2. Depending on your dbms, you might have trouble with reserved words like name.
select name, cast(avg(average) as integer)
from test
where exam = 'Test1'
or exam = 'Test2'
group by name;
This simple query gives you the values for "main".
select name, average as main
from test
where exam = 'Main'
Put them together in a common table expression, and join on "name".
with test_avg as (
select name, cast(avg(average) as integer)
from test
where exam = 'Test1'
or exam = 'Test2'
group by name
), main_avg as (
select name, average as main
from test
where exam = 'Main'
)
select t1.name, t1.avg, t2.main
from test_avg t1
inner join main_avg t2
on t1.name = t2.name;
name avg main
--
Name1 61 89
Name2 50 63
Name3 73 38
I don't know what you mean by position. If you're trying to rank the rows from highest average to lowest average, you might do it like this.
with test_avg as (
select name, cast(avg(average) as integer)
from test
where exam = 'Test1'
or exam = 'Test2'
group by name
), main_avg as (
select name, average as main
from test
where exam = 'Main'
)
select t1.name, t1.avg, t2.main, row_number() over (order by avg desc) as position
from test_avg t1
inner join main_avg t2
on t1.name = t2.name
order by position;
name avg main position
--
Name3 73 38 1
Name1 61 89 2
Name2 50 63 3

You can play with the max here.... Its very simple
select a.*,row_number() over (order by main desc) as position from
(
select name,
AVG(case when exam in ('Test1','Test2') then Average else null end ) as testa,
max(case when exam in ('Main') then Average else null end) as main
from abc
group by name
) a
order by name;
Output :
name testa main position1
Name1 60.50 89 1
Name2 49.50 63 2
Name3 73.00 38 3

Related

Select the non repeating/Distinct value in SQL

I'm trying to select the record based on the distinct id. When i go for 'DISTINCT' it picks the duplicate record and truncates the repeating record and gives me the one left out.
How can i SQL to pick to just that record which isn't repeated ?
INPUT
id
name
age
location
1
a
22
usa
1
a
23
usa
2
b
44
uk
3
e
33
eu
3
f
55
eu
8
k
49
usa
OUTPUT
id
name
age
location
2
b
44
uk
8
k
49
usa
ok , here is how you can do it :
select * from (
select * , count(*) over (partition by id) cn
from tablename
) t
where cn = 1
Try this:
SELECT *
FROM [Input]
WHERE ID IN (
SELECT ID FROM [Input] GROUP BY ID HAVING COUNT(ID) = 1
)
This should achieve the output you're after:
SELECT *
FROM yourtable
WHERE id IN (
SELECT id
FROM yourtable
GROUP BY id
HAVING COUNT(*) = 1)
You can use SQL Common Transaction Expression (CTE) AS FOLLOWS
declare #mytable as table(id int ,name nvarchar(100),age int,location nvarchar(50))
insert into #mytable values
(1,'a',22,'usa'),(1,'a',23,'usa'),(2,'b',44,'uk'),(3,'e',33,'eu'),(3,'f',55,'Tunisia'),('8','k',49,'Palestine')
with
cte1 as(select * from #mytable),
cte2 as (select id, count(1) N from #mytable group by id),
cte3 as (select TA.id,TA.name,TA.age,TA.location from cte1 TA inner join cte2 TB on TA.id=TB.id where TB.N=1)
select * from cte3

How to write a query to allow null in minimum function

I need to write a query to get minimum values for a column from a table and if the value is null then I want to include that row. I wrote following query but it ignores the null values. How I can modify this query to include null values in the result?
select * from TABLE where COLUMN = (select min(COLUMN) from TABLE );
If the table is like below
|ID | VALUE | NAME
101 1 John
101 null John
102 1 Bill
103 1 Tina
103 null Tina
104 null James
Result Should be
|ID | VALUE | NAME
101 1 John
102 1 Bill
103 1 Tina
104 null James
You need distinct on:
with my_table(id, value, name) as (
values
(101, 1, 'John'),
(101, null, 'John'),
(102, 1, 'Bill'),
(103, 1, 'Tina'),
(103, null, 'Tina'),
(104, null, 'James')
)
select distinct on (id) *
from my_table
order by id, value
id | value | name
-----+-------+-------
101 | 1 | John
102 | 1 | Bill
103 | 1 | Tina
104 | | James
(4 rows)
Distinct on is a fantastic feature specific for Postgres. An alternative in other RDBMS may be:
select t.id, t.value, t.name
from my_table t
join (
select id, min(value) as value
from my_table
group by id
) u on u.id = t.id and u.value is not distinct from t.value;
Note, you should use is not distinct from because value may be null.
SQL SERVER
select DISTINCT j.ID,j.VALUE,j.NAME from Table1 j
join (
select id, MIN(VALUE) VALUE from Table1
group by id
) as t
on t.ID = j.ID and (t.VALUE = j.VALUE or t.VALUE is null)
You cannot do an equals (=) for a null value, you have to check is null or so. So one simple solution is to default the null value to a number that would not otherwise be used:
select * from TABLE where coalesce(COLUMN, -9999) = (select min(coalesce(COLUMN,-9999)) from TABLE );
The coalesce function returns the first non-null value passed to it.
with c as (
select column as c
from table
order by column nulls first
limit 1
)
select *
from table cross join c
where column = c or column is null
If you want to user order by:
select t.*
from t
order by t.column asc nulls first
limit 1;
Alternatively, use rank():
select t.*
from (select t.*,
rank() over (order by col asc nulls first) as seqnum
from t
) t
where seqnum = 1;
I hope this solve your problem.
SELECT id,
CASE WHEN MIN(
CASE WHEN value IS NULL THEN 0 ELSE 1 END) = 0 THEN null
ELSE MIN(value) END
FROM tableName
GROUP BY id
or using COALESCE.
SELECT id,
CASE WHEN MIN(COALESCE(value, 0)) = 0 THEN null
ELSE MIN(value) END
FROM tableName
GROUP BY id
I am on mobile phone now, so I cannot test.

Postgresql Skip Row if value is equal to last row

in Postgres 9.1 is it possible to skip row(s) if the value of NAME is equal to the one before f.e. following table
ID | NAME | AGE | SEX | CLASS
---------------------------------
1 Paul 17 M 2b
2 Paul 16 M 2b
3 Paul 18 F 2b
4 Lexi 18 M 2b
5 Sarah 16 F 2b
6 Sarah 17 F 2b
The result should be:
1 Paul 17 M 2b
4 Lexi 18 M 2b
5 Sarah 16 F 2b
Thanks for your help,
t book
select *
from (
select id,
name,
age,
sex,
class,
lag(name) over (order by id) as prev_name
from the_table
) as t
where name <> prev_name;
alternatively
select *
from (
select id,
name,
age,
sex,
class,
row_number() over (partition by name order by id) as rn
from the_table
) as t
where rn = 1;
Another option would be to use Postgres' distinct on operator:
select distinct on (name)
id,
name,
age,
sex,
class
from the_table
order by name,id
but that will return the result ordered by name (which is limitation of the distinct on operator). If you don't want that you'll need to wrap this again:
select *
from (
select distinct on (name)
id,
name,
age,
sex,
class
from the_table
order by name,id
) t
order by id;
SELECT ID , NAME , AGE , SEX , CLASS
FROM thetable t
WHERE NOT EXISTS (
SELECT * FROM thetable nx
WHERE nx.NAME = t.NAME
-- AND nx.ID < t.ID -- ANY one before it
AND nx.ID = t.ID-1 -- THE one before it
);

How can I get the first result for each account in this SQL query?

I'm trying to write a query that follows this logic:
Find the first following status code of an account that had a previous status code of X.
So if I have a table of:
id account_num status_code
64 1 X
82 1 Y
72 2 Y
87 1 Z
91 2 X
103 2 Z
The results would be:
id account_num status_code
82 1 Y
103 2 Z
I've come up with a couple of solutions but I'm not all that great with SQL and so they've been pretty inelegeant thus far. I was hoping that someone here might be able to point me in the right direction.
View:
SELECT account_number, id
FROM table
WHERE status_code = 'X'
Query:
SELECT account_number, min(id)
FROM table
INNER JOIN view
ON table.account_number = view.account_number
WHERE table.id > view.id
At this point I have the id that I need but I'd have to write ANOTHER query that uses the id tolook up the status_code.
Edit: To add some context, I'm trying to find calls that have a status_code of X. If a call has a status_code of X we want to dial it a different way the next time we make an attempt. The aim of this query is to provide a report that will show the results of the second dial if the first dial resulted an X status code.
Here's a SQL Server solution.
UPDATE
The idea is to avoid a number of NESTED LOOP joins as proposed by Olaf because they roughly have O(N * M) complexity and thus extremely bad for your performance. MERGED JOINS complexity is O(NLog(N) + MLog(M)) which is much better for real world scenarios.
The query below works as follows:
RankedCTE is a subquery that assigns a row number to each id partioned by account and sorted by id which represents the time. So for the data below the output of this
SELECT
id,
account_num,
status_code,
ROW_NUMBER() OVER (PARTITION BY account_num ORDER BY id DESC) AS item_rank
FROM dbo.Test
would be:
id account_num status_code item_rank
----------- ----------- ----------- ----------
87 1 Z 1
82 1 Y 2
64 1 X 3
103 2 Z 1
91 2 X 2
72 2 Y 3
Once we have them numbered we join the result on itself like this:
WITH RankedCTE AS
(
SELECT
id,
account_num,
status_code,
ROW_NUMBER() OVER (PARTITION BY account_num ORDER BY id DESC) AS item_rank
FROM dbo.Test
)
SELECT
*
FROM
RankedCTE A
INNER JOIN RankedCTE B ON
A.account_num = B.account_num
AND A.item_rank = B.item_rank - 1
which will give us an event and a preceding event in the same table
id account_num status_code item_rank id account_num status_code item_rank
----------- ----------- ----------- ----------- ----------- ----------- ----------- -----------
87 1 Z 1 82 1 Y 2
82 1 Y 2 64 1 X 3
103 2 Z 1 91 2 X 2
91 2 X 2 72 2 Y 3
Finally, we just have to take the preceding event with code "X" and the event with code not "X":
WITH RankedCTE AS
(
SELECT
id,
account_num,
status_code,
ROW_NUMBER() OVER (PARTITION BY account_num ORDER BY id DESC) AS item_rank
FROM dbo.Test
)
SELECT
A.id,
A.account_num,
A.status_code
FROM
RankedCTE A
INNER JOIN RankedCTE B ON
A.account_num = B.account_num
AND A.item_rank = B.item_rank - 1
AND A.status_code <> 'X'
AND B.status_code = 'X'
Query plans for this query and #Olaf Dietsche solution (one of the versions) are below.
Data setup script
CREATE TABLE dbo.Test
(
id int not null PRIMARY KEY,
account_num int not null,
status_code nchar(1)
)
GO
INSERT dbo.Test (id, account_num, status_code)
SELECT 64 , 1, 'X' UNION ALL
SELECT 82 , 1, 'Y' UNION ALL
SELECT 72 , 2, 'Y' UNION ALL
SELECT 87 , 1, 'Z' UNION ALL
SELECT 91 , 2, 'X' UNION ALL
SELECT 103, 2, 'Z'
SQL Fiddle with subselect
select id, account_num, status_code
from mytable
where id in (select min(t1.id)
from mytable t1
join mytable t2 on t1.account_num = t2.account_num
and t1.id > t2.id
and t2.status_code = 'X'
group by t1.account_num)
and SQL Fiddle with join, both for MS SQL Server 2012, both returning the same result.
select id, account_num, status_code
from mytable
join (select min(t1.id) as min_id
from mytable t1
join mytable t2 on t1.account_num = t2.account_num
and t1.id > t2.id
and t2.status_code = 'X'
group by t1.account_num) t on id = min_id
SELECT MIN(ID), ACCOUNT_NUM, STATUS_CODE FROM (
SELECT ID, ACCOUNT_NUM, STATUS_CODE
FROM ACCOUNT A1
WHERE EXISTS
(SELECT 1
FROM ACCOUNT A2
WHERE A1.ACCOUNT_NUM = A2.ACCOUNT_NUM
AND A2.STATUS_CODE = 'X'
AND A2.ID < A1.ID)
) SUB
GROUP BY ACCOUNT_NUM
Here's an SQLFIDDLE
Here's query, with your data, checked under PostgreSQL:
SELECT t0.*
FROM so13594339 t0 JOIN
(SELECT min(t1.id), t1.account_num
FROM so13594339 t1, so13594339 t2
WHERE t1.account_num = t2.account_num AND t1.id > t2.id AND t2.status_code = 'X'
GROUP BY t1.account_num
) z
ON t0.id = z.min AND t0.account_num = z.account_num;

How to perform SQL Query to get last entry

I am working on creating a SQL query where the result will return a student test score
from the last test that they took. I think that this should be fairly simple but I am just not seeing it.
Here is my test data
Name Date Score
John 2/3/2012 94
John 2/14/2012 82
John 2/28/2012 72
Mary 2/3/2012 80
Mary 2/28/2012 71
Ken 2/14/2012 68
Ken 2/14/2012 66
I want the returned result to be
John 2/28/2012 72
Mary 2/28/2012 80
Ken 2/14/2012 66
I appreciate any assistance.
select date, name, score
from temp t1
where date = (select max(date) from temp where t1.name = temp.name)
OR
SELECT a.*
FROM temp a
INNER JOIN
(
SELECT name,MAX(date) as max_date
FROM temp a
GROUP BY name
)b ON (b.name = a.name AND a.date=b.max_date)
Here is a sql fiddle with an example
or even this if you have more than one record for each person on a date like you show in your sample data.
SELECT c.name,c.date, MAX(c.score) as max_score
FROM
(
SELECT a.*
FROM temp a
INNER JOIN
(
SELECT name,MAX(date) as max_date
FROM temp a
GROUP BY name
)b ON (b.name = a.name AND a.date=b.max_date)
)c
group by c.name,c.date
Sql fiddle with this example
SELECT Name, Date, Score
FROM tablename t1
WHERE Date = (SELECT MAX(Date)
FROM tablename
WHERE Name = t1.Name
GROUP BY Name)
Which database are you using? Most support row_number() which is the right way to answer this:
select *
from
(
select t.*, row_number() over (partition by name order by date desc) as seqnum
from table t
)
where rownum = 1