SQL AVG with Case - sql

I've a DB which stores a value from C to AAA, while C is the worst and AAA the best.
Now I need the average of this value and I don't know how to first convert the values into an int, calculate the average, round the average to an int and convert it back.
Definitions:
C = 1
B = 2
A = 3
AA = 4
AAA = 5
Is that even possible with an SQL statement? I tried to combine AVG and CASE, but I don't bring it to work...
Thanks for your help!
Regards,

select avg(case score
when 'C' then 1
when 'B' then 2
when 'A' then 3
when 'AA' then 4
when 'AAA' then 5
end) as avg_score
from the_table;
(this assumes that the column is called score)
To convert this back into the "character value", wrap the output in another case:
select case cast(avg_score as int)
when 1 then 'C'
when 2 then 'B'
when 3 then 'A'
when 4 then 'AA'
when 5 then 'AAA'
end as avg_score_value
from (
select avg(case score
when 'C' then 1
when 'B' then 2
when 'A' then 3
when 'AA' then 4
when 'AAA' then 5
end) as avg_score
from the_table;
) t
The above cast(avg_score as int) assumes ANSI SQL. Your DBMS might have different ways to cast a value to an integer.

I've created this example for u.
u can cast ur ranking into temp table, then calculate and when ur done, drop it.
create table sof (id int identity,a nvarchar (10))
insert into sof values ('a')
insert into sof values ('b')
insert into sof values ('c')
select case a when 'AAA ' then 5
when 'AA' then 4
when 'A' then 3
when 'B' then 2
else 1
end as av
into #temp
from sof
----for rounded
select ROUND(AVG(CAST(av AS FLOAT)), 4)
from #temp
--not rounded
select AVG (av)
from #temp

Related

Convert wide format data to long format using SQL

I recently started working with SQL for data manipulation. So, forgive me if this is too basic.
I have data table1 in the following format.
id pg_a pg_b
12 1 0
35 1 1
46 0 1
And I would like to convert this into a long format like shown in the following table.
id pg value
12 a 1
12 b 0
35 a 1
35 b 1
46 a 0
46 a 1
I have an sql query using case when but, in no luck. The query only executes the first when in both case statements.
This is the query:
select id,
(case when pg_a is not null then pg_a
when pg_b is not null then pg_b
else null
end) AS pg,
(case when pg_a is not null then a
when pg_b is not null then b
else null
end) AS value
from table1
What do I need to do differently? Any pointers will be appreciated.
Thank you in advance.
You can use a lateral join:
select t.id, v.*
from t cross join lateral
(values ('a', pg_a), ('b', pg_b)
) v(pg, value);
If you are new to SQL, you might you might be more comfortable with union all:
select id, 'a' as pg, pg_a as value
from t
union all
select id, 'b', pg_b
from t;

SQL Server - Is there a better way for me to select based on values in varying numbers of rows?

Suppose I have a table like this:
id value
1 a
1 x
1 x
2 b
2 b
2 y
3 c
3 d
3 z
10 c
10 c
10 z
11 x
11 y
11 z
I want to select id's (duplicate id's are okay) where the value is a, b, or both c and d. So in this case, I would select 1, 2, and 3. Note that duplicate values are possible for each id-value combination.
I have this code that works:
SELECT id
FROM table
WHERE value = 'a' OR value = 'b'
UNION
SELECT t1.id
FROM table t1
INNER JOIN table t2
ON t1.id = t2.id
WHERE t1.value = 'c' AND t2.value = 'd'
But my SQL skills are rusty and I can't shake the feeling that there's a better way to do this. This is going to be a subquery in the middle of some complex legacy code, so I want to be sure that my code is concise and efficient.
Also this question is not a duplicate because it deals with selecting id's based on one row value or two row values at the same time, all the questions I found dealt with selecting based on two row values. I've already solved that issue with the second half of my query.
A HAVINGclause with some conditional aggregates will do the trick here:
SELECT id
FROM (VALUES(1 ,'a'),
(1 ,'x'),
(1 ,'x'),
(2 ,'b'),
(2 ,'b'),
(2 ,'y'),
(3 ,'c'),
(3 ,'d'),
(3 ,'z'),
(10,'c'),
(10,'c'),
(10,'z'),
(11,'x'),
(11,'y'),
(11,'z')) V(id, [value])
GROUP BY id
HAVING COUNT(CASE [value] WHEN 'a' THEN 1 WHEN 'b' THEN 1 END) > 0
OR (COUNT(CASE [value] WHEN 'c' THEN 1 END) > 0
AND COUNT(CASE [value] WHEN 'd' THEN 1 END) > 0);

How do I find all values where all rows associated with that value meet a condition?

I have a table in SQL that is structured as follows:
Name Value1 Value2
A .2 .3
A .1 .05
A .5 .3
B .2 .4
B .1 .08
C .3 .4
C .2 .5
C .1 .3
How do I get all the Names where Value1 is less than Value2 for every row associated with a Name? In the example above, I would only want to pull out Name C.
I read this article: Select in MySQL where all rows meet a condition
but I don't think this applies to my problem, as this solution assumes you are comparing a row to fixed value.
A variant on some of the other answers, also using conditional aggregation:
SELECT Name
FROM yourTable
GROUP BY Name
HAVING SUM(CASE WHEN value1 >= value2 THEN 1 ELSE 0 END) = 0;
The above query asserts that each matching name group does not have any record for which value1 is greater than or equal to value2.
You can use group by and having:
select name
from t
group by name
having count(*) = sum(case when value1 < value2 then 1 else 0 end);
There are other ways to phrase this, such as:
select distinct name
from t
where not exists (select 1 from t t2 where t2.name = t.name and t2.value2 >= t2.value1);
Or:
select name
from t
except
select name
from t
where value2 >= value1;
You can get desired result with in clause
declare #table table (name varchar(10), value1 float, value2 float)
insert #table
select 'A', '0.2', '.3'
union all
select 'A' , '.1', '.05'
union all
select 'A', '.5', '.3'
union all
select 'B', '.2', '.4'
union all
select 'B', '.1' , '.08'
union all
select 'C', '.3' , '.4'
union all
select 'C', '.2' , '.5'
union all
select 'C' , '.1' , '.3'
Use In Clause where value1<value2
select * from #table where value1<value2
and name not in ( select name from #table where value1>value2)
You can use EXIST to check if the value exist in your desired condition wherein all rows by certain name have value2 greater than value1.
SELECT *
FROM TableName a
WHERE EXISTS (SELECT 1
FROM TableName b
WHERE a.name = b.Name
GROUP BY b.name
HAVING COUNT(case when b.value1 < b.value2 THEN 1 END) = COUNT(*))
Here's a Demo.
This will display all rows for the valid name. However, check other answer if you only want to display distinct name.

Replace column values

I've got this table,
Name Rating
A 2
B 1
C 5
D 3
E 1
F 4
and I've got a rating system
1-Excellent, 2-Very Good, 3-Good, 4-OK, 5-Poor
I was wondering if i could replace the nueric values in the table to get the following result table.
Name Rating
A Very Good
B Excellent
C Poor
D Good
E Excellent
F OK
Thanks
I don't think it's good idea to update your data inplace, it's better to store id of the rating and not text representation of the data. But you can query your table and replace int with text:
select
Name,
case Rating
when 1 then 'Excellent'
when 2 then 'Very Good,'
when 3 then 'Good'
when 4 then 'OK'
when 5 then 'Poor'
end as Rating
from <your table>
Or you can create a lookup table and join with it
create table Rating (id int, desc nvarchar(128))
insert into Rating (id, desc)
select 1, 'Excellent' union all
select 2, 'Very good' union all
select 3, 'Good' union all
select 4, 'OK' union all
select 5, 'Poor'
select
t.Name,
R.desc as Rating
from <your table> as t
left outer join Rating as R on R.id = t.Rating
Use a CASE statement. Of course, this will only work if your column is not set to a numeric value.
UPDATE tblRatings
SET Rating = CASE WHEN 1 THEN 'Excellent'
WHEN 2 THEN 'Very Good'
WHEN 3 THEN 'Good'
WHEN 4 THEN 'OK'
ELSE 'Poor'
END
If it is, you'll need to use a SELECT statement;
SELECT CASE WHEN 1 THEN 'Excellent'
WHEN 2 THEN 'Very Good'
WHEN 3 THEN 'Good'
WHEN 4 THEN 'OK'
ELSE 'Poor'
END
FROM tblRatings
Try using Choose
IF sql-server 2012
like this:
select Rating,name,choose(Rating,'Excellent','Very Good','Good','OK','Excellent','Poor') from table
Fiddle Demo
You can use update function :
update yourtable set rating = 'Excellent' where rating = '1'
and zou can do this update for all your ratings
Update YOURTABLE
set Rating = case when '2' then ' Very good'
when '3' then ' good' end

Can I get the minimum of 2 columns which is greater than a given value using only one scan of a table

This is my example data (there are no indexes and I do not want to create any):
CREATE TABLE tblTest ( a INT , b INT );
INSERT INTO tblTest ( a, b ) VALUES
( 1 , 2 ),
( 5 , 1 ),
( 1 , 4 ),
( 3 , 2 )
I want the minimum value in of both column a and column b which is greater then a given value. E.g. if the given value is 3 then I want 4 to be returned.
This is my current solution:
SELECT MIN (subMin) FROM
(
SELECT MIN (a) as subMin FROM tblTest
WHERE a > 3 -- Returns 5
UNION
SELECT MIN (b) as subMin FROM tblTest
WHERE b > 3 -- Returns 4
)
This searches the table twice - once to get min(a) once to get min(b).
I believe it should be faster to do this with just one pass. Is this possible?
You want to use conditional aggregatino for this:
select min(case when a > 3 then a end) as minA,
min(case when b > 3 then b end) as minB
from tblTest;
To get the minimum of both values, you can use a SQLite extension, which handles multiple values for min():
select min(min(case when a > 3 then a end),
min(case when b > 3 then b end)
)
from tblTest
The only issue is that the min will return NULL if either argument is NULL. You can fix this by doing:
select coalesce(min(min(case when a > 3 then a end),
min(case when b > 3 then b end)
),
min(case when a > 3 then a end),
min(case when b > 3 then b end)
)
from tblTest
This version will return the minimum value, subject to your conditions. If one of the conditions has no rows, it will still return the minimum of the other value.
From the top of my head, you could modify the table and add a min value column to store the minimum value of the two columns. then query that column.
Or you can do this:
select min(val)
from
(
select min(col1, col2) as val
from table1
)
where
val > 3
The outer SELECT, queries the memory, not the table itself.
Check SQL Fiddle