Oracle pivot function with two columns [duplicate] - sql

This question already has answers here:
Advice Using Pivot Table in Oracle
(4 answers)
Closed 8 years ago.
How to display the number of employees in each department with each salary in oracle
something like this
department_id salar_equal_1000 salar_equal_20000 city_boston city_detroit city_none
10 2 3 2 1 2
20 1 2 1 2 0
table contains data like this
department_id salary city
10 1000 boston
10 1000 boston
10 2000 detroit
10 2000
10 2000
20 1000 boston
20 2000 detroit
20 2000 detroit
I guess i have to use the pivot statement but i am not sure need some help on this

Try this:
select
department_id,
sum(case when salary = 1000 then 1 else 0 end) as salar_equal_1000,
sum(case when salary = 2000 then 1 else 0 end) as salar_equal_2000,
sum(case when city = 'boston' then 1 else 0 end) as city_boston,
sum(case when city = 'detroit' then 1 else 0 end) as city_detroit,
sum(case when city is null then 1 else 0 end) as city_none
from
employees
group by department_id
order by department_id
Edited again:
See it here on fiddle: http://sqlfiddle.com/#!4/3564c/6

Hi please go through the following example, Might be it will help you.
If you are working with oracle10g this is the format,
if 11g pivot function is there
WITH cte(dept_id,salary) AS (SELECT 10,1000 FROM dual
UNION ALL
SELECT 10,1000 FROM dual
UNION ALL
SELECT 10,1000 FROM dual
UNION ALL
SELECT 20,1000 FROM dual
UNION ALL
SELECT 10,2000 FROM dual
UNION ALL
SELECT 10,2000 FROM dual
UNION ALL
SELECT 20,1000 FROM dual
UNION ALL
SELECT 20,2000 FROM dual
UNION ALL
SELECT 20,2000 FROM dual
),
TEMP as(SELECT
dept_id,
count(salary) cnt,salary
FROM
cte
GROUP BY salary, dept_id)
SELECT dept_id,sum(CASE WHEN salary=1000 THEN cnt ELSE 0 END)salary_1000,
sum(CASE WHEN salary=2000 THEN cnt ELSE 0 END) salary_2000 FROM TEMP
GROUP BY dept_id

Related

SQL Query To transform rows into columns with additional calculated column [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 9 days ago.
This post was edited and submitted for review 8 days ago and failed to reopen the post:
Original close reason(s) were not resolved
Improve this question
WIth sample data like here:
DATE Count Code
02-JAN-2023 25 A
03-JAN-2023 10 A
05-JAN-2023 15 A
01-JAN-2023 5 B
02-JAN-2023 20 B
04-JAN-2023 6 B
05-JAN-2023 9 B
I need to transform rows with code = 'A' or 'B' into A and B columns and to create another column to calculate the difference between A and B (DIFF = A - B). Empty values considered as 0. The result should be grouped and ordered by DATE.
The expected result should be:
Date A B DIFFERENCE(A-B)
01-JAN-2023 5 -5
02-JAN-2023 25 20 5
03-JAN-2023 10 10
04-JAN-2023 6 -6
05-JAN-2023 15 9 6
You need a conditional aggregation to get the desired result -
SELECT `date`, MAX(CASE WHEN CODE = 'A' THEN `count` ELSE NULL END) A,
MAX(CASE WHEN CODE = 'B' THEN `count` ELSE NULL END) B,
NVL(MAX(CASE WHEN CODE = 'A' THEN `count` ELSE NULL END), 0) -
NVL(MAX(CASE WHEN CODE = 'B' THEN `count` ELSE NULL END), 0) difference
FROM your_table
GROUP BY `date`;
Demo.
Conditional aggregation, yes - but most probably different aggregation (than Ankit suggested), I believe - sum instead of max. With sample data you posted it really doesn't matter, but there probably are some more rows, maybe even for the same date so max won't return correct result.
Instead of NVL function (Ankit used), consider using else 0 in case expression.
Also, you shouldn't (and you probably didn't) name columns using reserved words; date is reserved for datatype, so either column name isn't that, or you enclosed it into double quotes (which is mostly always bad idea).
Although you can do it in the same select statement, somewhat cleaner option is to use a subquery or a CTE (as my example shows); result will be just the same, but this is easier to read.
Sample data:
SQL> select * from test;
DATUM COUNT CODE
--------- ---------- -----
02-JAN-23 25 A
03-JAN-23 10 A
05-JAN-23 15 A
01-JAN-23 5 B
02-JAN-23 20 B
04-JAN-23 6 B
05-JAN-23 9 B
7 rows selected.
Query:
SQL> with temp as
2 (select datum,
3 sum(case when code = 'A' then count else 0 end) as count_a,
4 sum(case when code = 'B' then count else 0 end) as count_b
5 from test
6 group by datum
7 )
8 select datum, count_a, count_b,
9 count_a - count_b as diff
10 from temp
11 order by datum;
DATUM COUNT_A COUNT_B DIFF
--------- ---------- ---------- ----------
01-JAN-23 0 5 -5
02-JAN-23 25 20 5
03-JAN-23 10 0 10
04-JAN-23 0 6 -6
05-JAN-23 15 9 6
SQL>
You could use PIVOT to get your expected result:
Select A_DATE, COL_A, COL_B, Nvl(COL_A, 0) - Nvl(COL_B, 0) "DIFF_A_B"
From ( Select A_DATE, CNT, CODE From tbl )
PIVOT ( MAX(CNT) FOR CODE IN('A' "COL_A", 'B' "COL_B") )
Order By A_DATE
...
WITH -- Sample data
tbl AS
(
Select To_Date('02-JAN-2023', 'dd-MON-yyyy') "A_DATE", 25 "CNT", 'A' "CODE" From Dual Union All
Select To_Date('03-JAN-2023', 'dd-MON-yyyy') "A_DATE", 10 "CNT", 'A' "CODE" From Dual Union All
Select To_Date('05-JAN-2023', 'dd-MON-yyyy') "A_DATE", 15 "CNT", 'A' "CODE" From Dual Union All
Select To_Date('01-JAN-2023', 'dd-MON-yyyy') "A_DATE", 5 "CNT", 'B' "CODE" From Dual Union All
Select To_Date('02-JAN-2023', 'dd-MON-yyyy') "A_DATE", 20 "CNT", 'B' "CODE" From Dual Union All
Select To_Date('04-JAN-2023', 'dd-MON-yyyy') "A_DATE", 6 "CNT", 'B' "CODE" From Dual Union All
Select To_Date('05-JAN-2023', 'dd-MON-yyyy') "A_DATE", 9 "CNT", 'B' "CODE" From Dual
)
R e s u l t :
A_DATE COL_A COL_B DIFF_A_B
--------- ---------- ---------- ----------
01-JAN-23 5 -5
02-JAN-23 25 20 5
03-JAN-23 10 10
04-JAN-23 6 -6
05-JAN-23 15 9 6

How to write case statement to display only 1 row in flag column for an ID with 2 different values

I have a query with multiple joins that is being used to extract data from multiple tables.
As part of the select I have multiple case statements.
I a stuck with one of them. I need to add one condition where I get only one row each for Chris and John with flag 'Y', if they have PCT as 100 and the '0' or 'NULL' PCT rows should not be displayed.
DeptID
Employee
PCT
Utlility_PCT
101
Chris
100
Y
101
Chris
N
101
Sam
0
N
101
John
100
Y
101
John
N
Currently my case statement is case when PCT = 100 then 'Y' else 'N' end Utility_PCT
I want my result set to look like:
DeptID
Employee
PCT
Utlility_PCT
101
Chris
100
Y
101
Sam
0
N
101
John
100
Y
What you describe is filtering, and requires a WHERE clause. We can use ROW_NUMBER() to enumerate the rows according the priority rules that you describe, then use that information to filter out.
You are not showing your current query, so I'll assume it comes from a CTE:
with res as (... your query ...)
select *
from (
select r.*,
row_number() over(
partition by deptid, employee
order by case when pct = 100 and utility_pct = 'Y' then 0 else 1 end, pct, utility_pct
) rn
from res r
) r
where rn = 1
The query limits the result to one row per department / employee tuple; when there is a row with pct = 100 and utility_pct = 'Y', it is selected, else we get the row with the smallest pct and the smallest utility_pct (this can be adapted by modifying the end of the order by clause of the window function).
Since there is a tag 'greatest-n-per-group' you could filter the rows using GREATEST() function:
Select *
From tbl t
Where PCT = GREATEST( (Select PCT From
(Select DEPT_ID, EMPLOYEE, Max(Nvl(PCT, -1)) "PCT" From tbl Group By DEPT_ID, EMPLOYEE)
Where DEPT_ID = t.DEPT_ID And EMPLOYEE = t.EMPLOYEE) )
With your sample data:
WITH
tbl AS
(
Select 101 "DEPT_ID", 'Chris' "EMPLOYEE", 100 "PCT", 'Y' "UTILITY_PCT" From Dual Union All
Select 101 "DEPT_ID", 'Chris' "EMPLOYEE", Null "PCT", 'N' "UTILITY_PCT" From Dual Union All
Select 101 "DEPT_ID", 'Sam' "EMPLOYEE", 0 "PCT", 'N' "UTILITY_PCT" From Dual Union All
Select 101 "DEPT_ID", 'John' "EMPLOYEE", 100 "PCT", 'Y' "UTILITY_PCT" From Dual Union All
Select 101 "DEPT_ID", 'John' "EMPLOYEE", Null "PCT", 'N' "UTILITY_PCT" From Dual
)
... the result is:
DEPT_ID
EMPLOYEE
PCT
UTILITY_PCT
101
Chris
100
Y
101
Sam
0
N
101
John
100
Y
If there could be more rows per group than in the sample you should probably do some additional filterings and/or groupings.
Regards...

Get data based on condition in oracle sql

My table
loads(Unique) Value
T123 11
T234 9.5
T456 15
T678 35
T345 3.7
Want I want
count(values<=10) count(values>10 &<=20) count(values>20)
2 2 1
I tried to use CASE but don't know the usage
CASE yes; not with COUNT but with SUM:
SQL> with test (loads, value) as
2 (select 't123', 11 from dual union all
3 select 't234', 9.5 from dual union all
4 select 't456', 15 from dual union all
5 select 't678', 35 from dual union all
6 select 't345', 3.7 from dual
7 )
8 select
9 sum(case when value <= 10 then 1 end) cnt_1,
10 sum(case when value > 10 and value <= 20 then 1 end) cnt_2,
11 sum(case when value > 20 then 1 end) cnt_3
12 from test;
CNT_1 CNT_2 CNT_3
---------- ---------- ----------
2 2 1
SQL>
Use conditional aggregation as
select coalesce(sum(case when value<=10 then 1 end),0) as "values<=10",
coalesce(sum(case when value>10 and value<=20 then 1 end),0) as "values>10value<20",
coalesce(sum(case when value>20 then 1 end),0) as "values>20"
from your_table;

Oracle SQL grouping elements

I know this question is kind of trivial but I have difficulties writing the SQL for the example shown below.
As shown in the first table, is the result that i generated and they are ok for analytics,
REGION SUBREGION SUM
------ --------- ------
CORP CORP1 5
CORP CORP2 10
CORP CORP3 5
SB SB1 10
SB SB2 10
MID null 10
LARGE null 20
but for summary report i need to display result as shown in the second table. Any clues?
REGION SUM
------ ----
CORP 20
CORP1 5
CORP2 10
CORP3 5
SB 20
SB1 10
SB2 10
MID 10
LARGE 20
Simply change your existing GROUP BY to GROUPING SET:
SELECT
Coalesce(subregion, region) AS region,
Sum(column)
FROM mytable
GROUP BY GROUPING SETS(region, subregion)
HAVING Coalesce(subregion, region) IS NOT NULL
sounds like you need to aggregate the same table by different fields, but get both back as if it were one field. The solution that comes to mind is a UNION
select sum() as sum, REGION as sf from table group by REGION
union ALL
select sum() as sum, SUB_REGION as sf from table group by SUB_REGION;
Hope that helps
based on Dan's question below, I add, if you don't want to agg the vals, just take out the sum and group by and do the straight union
select REGION as sf from table
union ALL
select SUB_REGION as sf from table;
EDIT:
One more thought, perhaps when you do the query in the first place, you may want to look into the concept of ROLLUPs as an additional clause on your group bu and agg function, might help solve this in one shot.
Do the same group by query you're doing, but try using group by ROLLUP:
Something like (untested):
select region, subregion, sum(some_column) as sum
from some_table
group by rollup(region, subregion)
order by region, subregion;
Try using OLAP functions rollup and grouping like this:
select
nvl(subregion, region) region, sum("sum")
from t
group by region, rollup(subregion)
having case when count(*) = 1 then 0 else 1 end = grouping(subregion);
In the above,
having case when count(*) = 1 then 0 else 1 end = grouping(subregion);
The above excludes the rolluped row if there is only one row for that region so that there are no duplicates.
Also, avoid using reserved keywords such as sum or count in your identifiers.
Demo:
SQL> with t(REGION ,SUBREGION ,s) as (
2 select 'CORP' , 'CORP1' , 5 from dual union all
3 select 'CORP' , 'CORP2' , 10 from dual union all
4 select 'CORP' , 'CORP3' , 5 from dual union all
5 select 'SB' ,'SB1' ,10 from dual union all
6 select 'SB' ,'SB2' ,10 from dual union all
7 select 'MID' , null , 10 from dual union all
8 select 'LARGE' , null , 20 from dual
9 )
10 select
11 nvl(subregion, region) region, sum(s)
12 from t
13 group by region, rollup(subregion)
14 having case when count(*) = 1 then 0 else 1 end = grouping(subregion);
REGIO SUM(S)
----- ----------
SB1 10
SB2 10
SB 20
MID 10
CORP1 5
CORP2 10
CORP3 5
CORP 20
LARGE 20
9 rows selected.
SQL>

Multiple rows show in a single row using query in sql?

I want to display the department no. and the number of employees in each department from EMP table in a single row. I had one query which display the result in separate rows.
select deptno, count(*) from emp
group by deptno;
Dptno Count(*)
10 5
20 3
30 4
I want to display the result as a single-row. For example:
Dpt10 Count(*) Dpt20 Count(*) Dpt30 Count(*)
10 5 20 3 30 4
The output in this forum is not proper but try to understand that the no. 5,3 & 4 should be below count(*) column and 10,20 & 30 should be below deptno.
Since pivot doesn't support several aggregates in SQL Server:
with t as (
select 10 id, 15 su union all
select 10 id, 10 su union all
select 10 id, 5 su union all
select 20 id, 135 su union all
select 20 id, 100 su union all
select 20 id, 15 su union all
select 30 id, 150 su union all
select 30 id, 1000 su union all
select 30 id, 500 su
)
select max(case when id = 10 then id end) dept10
, count(case when id = 10 then id end) dept10_cnt
, max(case when id = 20 then id end) dept20
, count(case when id = 20 then id end) dept20_cnt
, max(case when id = 30 then id end) dept30
, count(case when id = 30 then id end) dept30_cnt
from t
SQLFiddle
In SQL Server, there are several ways that you can convert multiple rows of data into columns.
If you needed to just convert the count of each deptno into columns, then you could do this easily with either the PIVOT function or a CASE/aggregate combination
select
sum(case when deptno = 10 then 1 else 0 end) Count_Dpt10,
sum(case when deptno = 20 then 1 else 0 end) Count_Dpt20,
sum(case when deptno = 30 then 1 else 0 end) DCount_pt30
from emp
See Demo
But part of the problem is that you want to pivot both the DeptNo and the Total_Count into column - this would need to use 2 different aggregate functions. In your situation the PIVOT function won't work, so you'd have to use a different aggregate function along with a CASE expression similar to:
select
max(case when deptno = 10 then deptno end) Dpt10,
sum(case when deptno = 10 then 1 else 0 end) Count_Dpt10,
max(case when deptno = 20 then deptno end) Dpt20,
sum(case when deptno = 20 then 1 else 0 end) Count_Dpt20,
max(case when deptno = 30 then deptno end) Dpt30,
sum(case when deptno = 30 then 1 else 0 end) DCount_pt30
from emp;
See SQL Fiddle with Demo. Now since you have unknown departments, so you'll need use dynamic sql. This will create a sql string that will then be executed. To create the sql string you'll need to use STUFF and FOR XML PATH. The dynamic code would be:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols
= STUFF((SELECT
', max(case when deptno = '+cast(deptno as varchar(10))+' then deptno end) as '+ QUOTENAME('Dpt'+cast(deptno as varchar(10)))
+ ', sum(case when deptno = '+cast(deptno as varchar(10))+' then 1 else 0 end) as '+ QUOTENAME('Count_Dpt'+cast(deptno as varchar(10)))
from emp
group by deptno
order by deptno
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'SELECT ' + #cols + '
from emp'
exec sp_executesql #query;
See SQL Fiddle with Demo. this gives you a result:
| DPT10 | COUNT_DPT10 | DPT20 | COUNT_DPT20 | DPT30 | COUNT_DPT30 |
|-------|-------------|-------|-------------|-------|-------------|
| 10 | 5 | 20 | 3 | 30 | 4 |
You can try this -
Schema
DECLARE #emp TABLE ([deptno] int NULL, [deptcount] int NULL);
INSERT #emp ([deptno], [deptcount]) VALUES (10, 5), (20, 3), (30, 4);
Query
SELECT STUFF((
SELECT ' ' + CAST([deptno] AS VARCHAR) + ' ' + CAST([deptcount] AS VARCHAR)
FROM #emp
FOR XML PATH('')
), 1, 1, '')
OutPut
10 5 20 3 30 4