Make a grouping and transforming query Oracle - sql

I have a test table:
category   type  quantities
 a      1    100
 a      2    150
 b      2    45
 b      3    68
 b      1    72
 c      2    90
 c      3    39
It is assume that only 3 types appeared. My goal is select out there categories, and quantities of each type. Result should like:
category   type1   type2   type3
 a      100    150    0
 b      72     45    68
 c      0     90    39
I'm trying with union, but I think it is redundant and not briefness:
select category, sum(type1)type1, sum(type2)type2 ,sum(type3) type3 from
(
select category, sum(quantities) type1, 0 type2, 0 type3 from test where type=1 group by category
union all
select category, 0 type1, sum(quantities) type2, 0 type3 from test where type=2 group by category
union all
select category, 0 type1, 0 type2, sum(quantities) type3 from test where type=3 group by category
) group by category;
What can I do to shorten my query? Thank you for any suggestions.

That is simple conditional aggregation:
select
category,
nvl(sum(case when type = 1 then quantities end), 0) as type1,
nvl(sum(case when type = 2 then quantities end), 0) as type2,
nvl(sum(case when type = 3 then quantities end), 0) as type3
from mytable
group by category
order by category;

Also known as a pivot table -- see http://www.oracle.com/technetwork/articles/sql/11g-pivot-097235.html

Related

SQL Server Completion Percentage Category

im a bit new to sql server, so hopefully this isnt something too convoluted. if i have a table with a bunch of data that shows different records that have been complete or not...
TABLE 1
ID CATEGORY COMPLETE
1 reports yes
2 reports no
3 processes no
4 processes yes
5 reports no
6 events yes
...what would be the best way of creating a new field that would show the percentage complete for every category?
TABLE 2
ID CATEGORY PERCENTAGE
1 events 100%
2 processes 50%
3 reports 33%
any help would be greatly appreciated, thank you.
group by category column and use conditional sum to get only complete = 'yes' cases in the numerator.
select category,
100 * 1.0 * sum(case when complete = 'yes' then 1 else 0 end)/count(*) as pct
from tablename
group by category
You can use windowed functions and PARTITION BY Category:
SELECT DISTINCT Category,
[percentage] = ROUND(100 * SUM(CASE complete WHEN 'yes' THEN 1.0 ELSE 0.0 END)
OVER (PARTITION BY Category)/
COUNT(*) OVER (PARTITION BY Category),0)
FROM #tab;
LiveDemo
With insert to second table:
SELECT DISTINCT
[id] = IDENTITY(INT, 1,1)
,category
,[percentage] = ROUND(100 * SUM(CASE complete WHEN 'yes' THEN 1.0 ELSE 0.0 END)
OVER (PARTITION BY CATEGORY)/
COUNT(*) OVER (PARTITION BY Category),0)
INTO #table2
FROM #tab
ORDER BY [percentage] DESC;
SELECT *
FROM #table2;
LiveDemo2
I think the simplest approach is to use avg():
select category,
avg(case when complete = 'yes' then 100.0 else 0 end) as pct
from tablename
group by category;
If you want this as a number with a percentage, you need a bit more string manipulation:
select category,
str(avg(case when complete = 'yes' then 100.0 else 0 end)) + '%' as pct
from tablename
group by category;
However, I would recommend keeping the value as a number.

Looping in select query

I want to do something like this:
select id,
count(*) as total,
FOR temp IN SELECT DISTINCT somerow FROM mytable ORDER BY somerow LOOP
sum(case when somerow = temp then 1 else 0 end) temp,
END LOOP;
from mytable
group by id
order by id
I created working select:
select id,
count(*) as total,
sum(case when somerow = 'a' then 1 else 0 end) somerow_a,
sum(case when somerow = 'b' then 1 else 0 end) somerow_b,
sum(case when somerow = 'c' then 1 else 0 end) somerow_c,
sum(case when somerow = 'd' then 1 else 0 end) somerow_d,
sum(case when somerow = 'e' then 1 else 0 end) somerow_e,
sum(case when somerow = 'f' then 1 else 0 end) somerow_f,
sum(case when somerow = 'g' then 1 else 0 end) somerow_g,
sum(case when somerow = 'h' then 1 else 0 end) somerow_h,
sum(case when somerow = 'i' then 1 else 0 end) somerow_i,
sum(case when somerow = 'j' then 1 else 0 end) somerow_j,
sum(case when somerow = 'k' then 1 else 0 end) somerow_k
from mytable
group by id
order by id
this works, but it is 'static' - if some new value will be added to 'somerow' I will have to change sql manually to get all the values from somerow column, and that is why I'm wondering if it is possible to do something with for loop.
So what I want to get is this:
id somerow_a somerow_b ....
0 3 2 ....
1 2 10 ....
2 19 3 ....
. ... ...
. ... ...
. ... ...
So what I'd like to do is to count all the rows which has some specific letter in it and group it by id (this id isn't primary key, but it is repeating - for id there are about 80 different values possible).
http://sqlfiddle.com/#!15/18feb/2
Are arrays good for you? (SQL Fiddle)
select
id,
sum(totalcol) as total,
array_agg(somecol) as somecol,
array_agg(totalcol) as totalcol
from (
select id, somecol, count(*) as totalcol
from mytable
group by id, somecol
) s
group by id
;
id | total | somecol | totalcol
----+-------+---------+----------
1 | 6 | {b,a,c} | {2,1,3}
2 | 5 | {d,f} | {2,3}
In 9.2 it is possible to have a set of JSON objects (Fiddle)
select row_to_json(s)
from (
select
id,
sum(totalcol) as total,
array_agg(somecol) as somecol,
array_agg(totalcol) as totalcol
from (
select id, somecol, count(*) as totalcol
from mytable
group by id, somecol
) s
group by id
) s
;
row_to_json
---------------------------------------------------------------
{"id":1,"total":6,"somecol":["b","a","c"],"totalcol":[2,1,3]}
{"id":2,"total":5,"somecol":["d","f"],"totalcol":[2,3]}
In 9.3, with the addition of lateral, a single object (Fiddle)
select to_json(format('{%s}', (string_agg(j, ','))))
from (
select format('%s:%s', to_json(id), to_json(c)) as j
from
(
select
id,
sum(totalcol) as total_sum,
array_agg(somecol) as somecol_array,
array_agg(totalcol) as totalcol_array
from (
select id, somecol, count(*) as totalcol
from mytable
group by id, somecol
) s
group by id
) s
cross join lateral
(
select
total_sum as total,
somecol_array as somecol,
totalcol_array as totalcol
) c
) s
;
to_json
---------------------------------------------------------------------------------------------------------------------------------------
"{1:{\"total\":6,\"somecol\":[\"b\",\"a\",\"c\"],\"totalcol\":[2,1,3]},2:{\"total\":5,\"somecol\":[\"d\",\"f\"],\"totalcol\":[2,3]}}"
In 9.2 it is also possible to have a single object in a more convoluted way using subqueries in instead of lateral
SQL is very rigid about the return type. It demands to know what to return beforehand.
For a completely dynamic number of resulting values, you can only use arrays like #Clodoaldo posted. Effectively a static return type, you do not get individual columns for each value.
If you know the number of columns at call time ("semi-dynamic"), you can create a function taking (and returning) polymorphic parameters. Closely related answer with lots of details:
Dynamic alternative to pivot with CASE and GROUP BY
(You also find a related answer with arrays from #Clodoaldo there.)
Your remaining option is to use two round-trips to the server. The first to determine the the actual query with the actual return type. The second to execute the query based on the first call.
Else, you have to go with a static query. While doing that, I see two nicer options for what you have right now:
1. Simpler expression
select id
, count(*) AS total
, count(somecol = 'a' OR NULL) AS somerow_a
, count(somecol = 'b' OR NULL) AS somerow_b
, ...
from mytable
group by id
order by id;
How does it work?
Compute percents from SUM() in the same SELECT sql query
SQL Fiddle.
2. crosstab()
crosstab() is more complex at first, but written in C, optimized for the task and shorter for long lists. You need the additional module tablefunc installed. Read the basics here if you are not familiar:
PostgreSQL Crosstab Query
SELECT * FROM crosstab(
$$
SELECT id
, count(*) OVER (PARTITION BY id)::int AS total
, somecol
, count(*)::int AS ct -- casting to int, don't think you need bigint?
FROM mytable
GROUP BY 1,3
ORDER BY 1,3
$$
,
$$SELECT unnest('{a,b,c,d}'::text[])$$
) AS f (id int, total int, a int, b int, c int, d int);

Join select statements to get columns in SQL

SELECT COUNT(Type) from House where Type = 1
SELECT COUNT(Type) from House where Type = 2
SELECT COUNT(Type) from House where Type = 3
My question is: I want to join the above 3 statements to get: 3 columns i.e. eg:
ColumnType1: '50', ColumnType2: '60', columnType3: '45'
thanks
You can create the columns using an aggregate function with a CASE expression:
SELECT
count(case when Type = 1 then Type end) as type_1,
count(case when Type = 2 then Type end) as type_2,
count(case when Type = 3 then Type end) as type_3
from House
You can use a case and add up if the Type matches
SELECT sum(case when Type = 1 then 1 else 0 end) as type_1,
sum(case when Type = 2 then 1 else 0 end) as type_2,
sum(case when Type = 3 then 1 else 0 end) as type_3
from House
There is a cleaner type of SQL which can give you this answer, but you will have each type on a different row:
SELECT Type, COUNT(Type) FROM House GROUP BY Type
It has the disadvantage of not giving you columns as you asked for; but the advantage is that it works for any number of different types without needing to change the query.
SELECT
COUNT(Type) as val1,
(SELECT COUNT(Type) from House where Type = 2) as val2,
(SELECT COUNT(Type) from House where Type = 3) as val3
from House where Type = 1

Counting all other types but the current one

I'm trying to write this query, that would calculate the average value of all the columns except the one that contains the type value, which I'm grouping the whole query by.
So for 4 types for example, each column in the resulting table will contain the average of all the other three type's values, i need to exclude the current type's rows.
As an example, if I was to calculate each type's average value for itself, the query would look like:
SELECT
SUM(some value) / COUNT(TYPE)
FROM TEMPTABLE
GROUP BY TYPE
Now I'm trying to calculate the other three's total average. Thanks.
You can do one query to get the distinct types, and LEFT JOIN the same table, checking for type-inequality:
SELECT t1.type,
SUM(t2.some_value) / COUNT(t2.type)
FROM ( SELECT DISTINCT type FROM temptable ) t1
LEFT JOIN temptable t2 ON ( t1.type <> t2.type )
GROUP BY t1.type
Since you only want the average, you could replace the line
FROM ( SELECT DISTINCT type FROM temptable ) t1
by
FROM temptable t1
but the first solution might perform better, since the number of rows is reduced earlier.
The starting point here is to make a cartesian join between your types and your temptable (guessing your tables structure is : type(id, type), valueTable(id, type_id, some_value))
The following query
SELECT t.type, SUM(vt.someValue) /
COUNT (*) AS sum FROM type t,
valueTable vt WHERE vt.type_id != t.id
GROUP BY t.type
should do the trick.
Will this do what you need?
(Possibly with another CASE statement to avoid divide by zero errors if there is a possibility none of a type might be returned, I've also not explicitly accounted for the case that type is NULL)
SELECT
SUM(CASE WHEN TYPE <> 'Type1' THEN someValue ELSE 0 END) /
SUM(CASE WHEN TYPE = 'Type1' THEN 1 ELSE 0 END) AS T1,
SUM(CASE WHEN TYPE <> 'Type2' THEN someValue ELSE 0 END) /
SUM(CASE WHEN TYPE = 'Type2' THEN 1 ELSE 0 END) AS T2,
SUM(CASE WHEN TYPE <> 'Type3' THEN someValue ELSE 0 END) /
SUM(CASE WHEN TYPE = 'Type3' THEN 1 ELSE 0 END) AS T3,
SUM(CASE WHEN TYPE <> 'Type4' THEN someValue ELSE 0 END) /
SUM(CASE WHEN TYPE = 'Type4' THEN 1 ELSE 0 END) AS T4
FROM TEMPTABLE
I think that you can just use this:
SELECT type, avg(col_01)
FROM myTable
GROUP BY type
Should work on Sybase too:
SELECT
SUM(some value) / SUM(CASE WHEN TYPE = 1 THEN 1 ELSE 0 END)
FROM TEMPTABLE
GROUP BY TYPE

SQL Pivot on subset

I have the following result set:
Type | Method | Amount
Type1 Cash Amount
Type1 Check Amount
Type2 Cash Amount
Type2 Check Amount
Type3 Cash Amount
Type3 Check Amount
And I want to make it look like this:
Type | Cash | Check
Type1 Amount Amount
Type2 Amount Amount
Type3 Amount Amount
How can I achieve this in T-SQL (2005 syntax ok)? I need to pivot by type (1, 2, 3...)
Here's an attempt at PIVOT:
select *
from YourTable
PIVOT (sum(amount) FOR Method in (Cash,Check)) as Y
Given that it's just two columns, could try with a join:
select
type
, cash = a.amount
, check = b.amount
from yourtable a
full join yourtable b on a.type = b.type
where a.method = 'cash' or b.method = 'Check'
Or better yet:
select
Type
, Cash = sum(case when Method = 'Cash' then Amount end)
, Check = sum(case when Method = 'Check' then Amount end)
from yourtable
group by
Type
Add an ELSE 0 to the CASE statements if appropriate.
This form is more flexible than the PIVOT operator and doesn't require a FULL JOIN. Just straight aggregation.