Transpose multiple Columns at same - sql

I have this:
Year Apple Orange
1 100 150
2 200 250
3 300 350
2 200 250
1 100 150
I need this:
Fruit 1 2 3
Apple 200 400 300
Orange 300 500 350
I have option A and option B, but it only transposes 1 fruit unless i do an "Union all".
Option A:
select
'Apple' as Fruit
,MAX(DECODE(year, '1', sum(Apple)) "1"
,MAX(DECODE(year, '2', sum(Apple)) "2"
from MyTable
Option B:
select
*
from (
select
Apple
,Year
from MyTable
)
PIVOT(sum(Apple) for year in ('1', '2', '3'))
Question:
Can U transpose all columns without an "Union"?

Oracle Setup:
CREATE TABLE table_name ( year, apple, orange ) AS
SELECT 1, 100, 150 FROM DUAL UNION ALL
SELECT 2, 200, 250 FROM DUAL UNION ALL
SELECT 3, 300, 350 FROM DUAL UNION ALL
SELECT 2, 200, 250 FROM DUAL UNION ALL
SELECT 1, 100, 150 FROM DUAL;
Query - Unpivot then pivot:
SELECT *
FROM (
SELECT *
FROM table_name
UNPIVOT( value FOR fruit IN ( Apple, Orange ) )
)
PIVOT ( SUM( value ) FOR year IN ( 1, 2, 3 ) );
Output:
FRUIT 1 2 3
------ --- --- ---
ORANGE 300 500 350
APPLE 200 400 300

This is how you can do it dynamically.
Create statements
CREATE TABLE MyTable
(Year int, Apple int, Orange int) ;
INSERT ALL
INTO MyTable (Year, Apple, Orange) VALUES (1, 100, 150)
INTO MyTable (Year, Apple, Orange) VALUES (2, 200, 250)
INTO MyTable (Year, Apple, Orange) VALUES (3, 300, 350)
INTO MyTable (Year, Apple, Orange) VALUES (2, 200, 250)
INTO MyTable (Year, Apple, Orange) VALUES (1, 100, 150)
SELECT * FROM dual;
Run this in SQL Developer or SQLPlus (I tried in SQL Developer).
Or you can encapsulate it in a procedure and can return the result.
SET ServerOutput ON size 100000;
variable rc refcursor;
DECLARE
v_column_list varchar2 (2000);
v_years varchar2(2000);
BEGIN
SELECT listagg('"' || column_name || '"', ',') within
GROUP (ORDER BY column_id)
INTO v_column_list
FROM all_tab_columns
WHERE table_name = 'MYTABLE'
AND column_name <> 'YEAR';
SELECT listagg(year, ',') within
GROUP (ORDER BY year)
INTO v_years
FROM (
SELECT DISTINCT year
FROM MyTable);
-- dbms_output.put_line(' v_column_list =' || v_column_list);
-- dbms_output.put_line(' v_years =' || v_years);
OPEN :rc FOR
'SELECT * FROM
( SELECT *
FROM MyTable
UNPIVOT ( val for fruit in ( ' || v_column_list || ' )
)
)
PIVOT ( sum ( val ) for year in ( ' || v_years || ' ) )';
END;
/
PRINT :rc
Output:
------------------------------------------------------------------------
FRUIT 1 2 3
------ ---------------------- ---------------------- ----------------------
ORANGE 300 500 350
APPLE 200 400 300

Related

Column Transpositions into Oracle (pl/sql, sql)

I'm doing transpositions and looking if for some column exist exactly the same value into other table. Example:
tableA: id = 10, col = abc , value = 10
tableB: id = 10, abc = 10
I have below code:
declare
TYPE t_my_list is record(id VARCHAR2(4000),col VARCHAR2(4000),val VARCHAR2(4000));
Type list_3STR is table of t_my_list;
v_stmt VARCHAR2(32000) := 'SELECT id, col, val FROM userA.tableA';
v_lstmt VARCHAR2(32000);
v_ret list_3STR := list_3STR();
cDel number;
begin
EXECUTE IMMEDIATE v_stmt BULK COLLECT INTO v_ret;
for i in v_ret.first..v_ret.last loop
--DBMS_OUTPUT.PUT_LINE('ID: '||v_ret (i).id||', COL: '||v_ret (i).col||', VAL: '||v_ret (i).val);
v_lstmt := 'SELECT count(*) FROM userB.tableB WHERE NVL(cast('||v_ret (i).col||' as Varchar2(100)), ''<null>'') in ('''||v_ret (i).val||''', ''<null>'') and ID = '''||v_ret (i).id||''' ';
DBMS_OUTPUT.PUT_LINE(v_lstmt);
EXECUTE IMMEDIATE v_lstmt INTO cDel;
If cDel > 0 Then
DBMS_OUTPUT.PUT_LINE('delete row from userA.tableA')
End if;
end loop;
DBMS_OUTPUT.PUT_LINE('v_ret = '||v_ret.count);
end;
I have to consider 5 CASE:
userA.tableA to userB.tableB
NULL to NULL = 1 --DELETE value
NULL to DATA = 0 --NOT DELETE
the same DATA to DATA = 1 --DELETE
diffrent DATA to diffrent DATA = 0 --NOT DELETE
DATA to NULL = 0 = --NOT DELETE
My code is working for case 1 to 4. How to resolve 5th problem?
Update: CASE example:
1.
id = 10, col = test, val = null
id = 10, test = null
2.
id = 10, col = test, val = null
id = 10, test = 99
3.
id = 10, col = test, val = 99
id = 10, test = 99
4.
id = 10, col = test, val = 5
id = 10, test = 99
5.
id = 10, col = test, val = 4
id = 10, test = null
The way I would do this (if I absolutely had to do it this way) is to find out what columns need to be compared to in table b, then you can work out what the values in table a can be used to compare with those columns.
Once you have that (by querying user_tables (or all_tables/dba_tables as necessary) to retrieve the relevant columns), you can then generate a join clause.
The join clause needs to check that either both columns are null or both columns have the same non-null value.
Once you have that, you can then use that in a merge statement to delete the rows that match the join condition. We do that by first updating the matched rows (which we need to do in order for those rows to be seen by the delete in the next step) and then delete them.
Here's a working test case:
Setup:
create table a (id integer, col varchar2(30), val number, constraint a_pk primary key (id, col));
create table b (id integer, abc number, test number, xyz number, constraint b_pk primary key (id));
insert into a (id, col, val)
select 10, 'test', null from dual union all
select 11, 'test', null from dual union all
select 12, 'test', 99 from dual union all
select 13, 'test', 5 from dual union all
select 14, 'test', 4 from dual union all
select 10, 'abc', 1 from dual union all
select 10, 'xyz', 7 from dual union all
select 11, 'abc', 4 from dual union all
select 11, 'xyz', 6 from dual union all
select 12, 'abc', 12 from dual union all
select 12, 'efg', 30 from dual union all
select 13, 'abc', 3 from dual union all
select 13, 'xyz', 5 from dual union all
select 14, 'abc', 8 from dual union all
select 14, 'xyz', 9 from dual;
insert into b (id, abc, test, xyz)
select 10, 1, null, 7 from dual union all
select 11, 4, 99, 8 from dual union all
select 12, 11, 99, 30 from dual union all
select 13, 1, 5, 5 from dual union all
select 14, 1, null, 7 from dual;
commit;
Rows we're expecting to remain in table a
select a.*
from a tgt
full outer join b src on (tgt.id = src.id
and (1 = 0
or (upper(tgt.col) = 'ABC' and (tgt.val = src.ABC or (tgt.val is null and src.ABC is null)))
or (upper(tgt.col) = 'TEST' and (tgt.val = src.TEST or (tgt.val is null and src.TEST is null)))
or (upper(tgt.col) = 'XYZ' and (tgt.val = src.XYZ or (tgt.val is null and src.XYZ is null)))))
where tgt.id is not null and src.id is NULL
ORDER BY a.id, a.col;
ID COL VAL
-- ---- ---
11 test
11 xyz 6
12 abc 12
12 efg 30
13 abc 3
14 abc 8
14 test 4
14 xyz 9
Run the code
set serveroutput on
declare
v_sql clob;
begin
v_sql := 'merge into a tgt' || chr(10) ||
' using b src' || chr(10) ||
' on (tgt.id = src.id' || chr(10) ||
' and (1 = 0';
-- Generate the join conditions
for rec in (select ' or (upper(tgt.col) = '''||column_name||''' and (tgt.val = src.'||column_name||' or (tgt.val is null and src.'||column_name||' is null)))' join_condition
from user_tab_columns
where table_name = 'B'
and column_name != 'ID')
loop
v_sql := v_sql || chr(10) || rec.join_condition;
end loop;
v_sql := v_sql || '))' || chr(10) ||
'when matched then' || chr(10) || -- we only care about rows that match on the join clause
' update set tgt.col = tgt.col' || chr(10) || -- we need to physically update those rows, or the delete clause won't see them.
' delete where 1=1'; -- we need to have the where clause here, but we're deleting all rows that were updated in the previous step, hence 1=1 which is always true.
dbms_output.put_line (v_sql||';');
execute immediate v_sql;
end;
/
dbms_output of the statement
merge into a tgt
using b src
on (tgt.id = src.id
and (1 = 0
or (upper(tgt.col) = 'ABC' and (tgt.val = src.ABC or (tgt.val is null and src.ABC is null)))
or (upper(tgt.col) = 'TEST' and (tgt.val = src.TEST or (tgt.val is null and src.TEST is null)))
or (upper(tgt.col) = 'XYZ' and (tgt.val = src.XYZ or (tgt.val is null and src.XYZ is null)))))
when matched then
update set tgt.col = tgt.col
delete where 1=1;
Check we have the columns we expected would remain
select *
from a
order by id, col;
ID COL VAL
-- ---- ---
11 test
11 xyz 6
12 abc 12
12 efg 30
13 abc 3
14 abc 8
14 test 4
14 xyz 9
However, if you have any choice over the matter, I would seriously, seriously ask you to rethink this design.
Having key-values in table a is a very strange way to go about updating table b with, and as you have found out, it makes doing the simplest things very tricky.

SQL oracle group list number

Please help me: group list number
A new group starts when the values descend. You can find the groups where they start using lag(). Then do a cumulative sum:
select t.*,
1 + sum(case when prev_col2 < col2 then 0 else 1 end) over (order by col1) as grp
from (select t.*,
lag(col2) over (order by col1) as prev_col2
from t
) t;
In Oracle 12.1 and above, this is a simple application of the match_recognize clause:
with
inputs ( column1, column2 ) as (
select 1, 1000 from dual union all
select 2, 2000 from dual union all
select 3, 3000 from dual union all
select 4, 6000 from dual union all
select 5, 7500 from dual union all
select 6, 0 from dual union all
select 7, 500 from dual union all
select 8, 600 from dual union all
select 9, 900 from dual union all
select 10, 2300 from dual union all
select 11, 4700 from dual union all
select 12, 40 from dual union all
select 13, 1000 from dual union all
select 14, 2000 from dual union all
select 15, 4000 from dual
)
-- End of simulated inputs (not part of the solution).
-- SQL query begins BELOW THIS LINE. Use actual table and column names.
select column1, column2, column3
from inputs
match_recognize(
order by column1
measures match_number() as column3
all rows per match
pattern ( a b* )
define b as column2 >= prev(column2)
)
order by column1 -- If needed.
;
OUTPUT:
COLUMN1 COLUMN2 COLUMN3
---------- ---------- ----------
1 1000 1
2 2000 1
3 3000 1
4 6000 1
5 7500 1
6 0 2
7 500 2
8 600 2
9 900 2
10 2300 2
11 4700 2
12 40 3
13 1000 3
14 2000 3
15 4000 3
You can use window function to mark the point where column_2 restarts and use cumulative sum to get the desired result
Select column_1,
Column_2,
Sum(flag) over (order by column_1) as column_3
From (
Select t.*,
Case when column_2 < lag(column_2,1,0) over (order by column_1) then 1 else 0 end as flag
From your_table t
) t;

working with permutations of a column values

I am trying from the below table
key val
A 10
B 20
C 30
D 40
to achieve the following result. Column label displays all combinations of key in alphabetical order. Column total displays the addition of the vals for the combination of keys.
label total
A 10
AB 30
ABC 60
ABCD 100
AC 40
AD 50
B 20
BC 50
BCD 90
BD 60
C 30
CD 70
D 40
while managed to get a query going, still not so convinced with it. Looking for better ways to get the same result set. thanks in advance.
with f (rn, key, val) as
(
select rownum, a.* from
(
select 'A' key, 10 val from dual
union all select 'B', 20 from dual
union all select 'C', 30 from dual
union all select 'D', 40 from dual
-- union all select 'E', 50 from dual
-- union all select 'F', 60 from dual
order by 1
) a
)
,
-- irn, ikey, ival: anchor rownum, key and val to remember the starting row
-- rn, key, val: for the current row in the recursion
-- r1: current label in the recursion
-- r2: combination of anchor key and the current row key in the recursion
-- total: addition of all values for keys in r1
rs(irn, ikey, ival, rn, key, val, r1, r2, total) as
(
select rn, key, val, rn, key, val, key, null, val from f
union all
select rs.irn, rs.ikey, rs.ival, f.rn, f.key, f.val, rs.r1 || f.key, rs.ikey || f.key, rs.total+f.val
from rs join f on (f.rn = rs.rn+1)
)
,
-- to add the additional rows required for the r2 col
-- when either r2 is not empty and not the same as r1 in rs
frs(irn, ikey, ival, rn, key, val, r1, r2, total) as
(
select * from rs
union all
select irn, ikey, ival, rn, key, val, r2, r2, ival+val
from frs
where r2 is not null and r1 != r2
)
select r1, total from frs
order by 1
;
Here is one way - using recursive subquery factoring, available since Oracle 11.2:
with
-- Begin test data
test_data ( key, val ) as (
select 'A', 10 from dual union all
select 'B', 20 from dual union all
select 'C', 30 from dual union all
select 'D', 40 from dual
),
-- End of test data (not part of the solution).
-- SQL query begins with the keyword "with" from above and continues below this line.
rec_cte ( label, total, last_symbol ) as (
select key, val, key -- anchor member
from test_data
union all
select r.label || t.key, r.total + t.val, t.key -- recursive member
from rec_cte r join test_data t on r.last_symbol < t.key
)
select label, total
from rec_cte
order by label -- if needed
;
Output:
LABEL TOTAL
----- -----
A 10
AB 30
ABC 60
ABCD 100
ABD 70
AC 40
ACD 80
AD 50
B 20
BC 50
BCD 90
BD 60
C 30
CD 70
D 40
15 rows selected.
You can use a PL/SQL block/Proceduere to achieve the same.
Assuming t_perm is your table, this proc will give you what you want. But currently it is displaying it on dbms_output. You can insert it in a table or display by a cursor etc. But this is the logic
create or replace procedure perm as
v_hold varchar2(20);
v_sum integer:=0;
cursor crs is select * from t_perm;
cursor crs1 is select * from t_perm;
begin
for rec in crs
loop
for rec1 in crs1
loop
if rec.key <= rec1.key then
v_hold:=v_hold||rec1.key;
v_sum:=v_sum+rec1.val;
dbms_output.put_line(v_hold||' '||v_sum);
end if;
end loop;
v_hold:='';
v_sum:=0;
end loop;
end;
Output
A 10
AB 30
ABC 60
ABCD 100
B 20
BC 50
BCD 90
C 30
CD 70
D 40

SELECT statement to get sum of grouped columns with column header

Table data:
NAME RS
A 10
A 20
A 30
B 15
B 5
B 10
C 70
C 30
Expected output:
A -- column name
10
20
30
60 -- total of A
B
15
5
10
30 -- total of B
C
70
30
100 -- total of C
So far I have tried:
UNION ALL
Select all datas and manipulated with front end.
Is there any other easiest solution without UNION ALL?
To do it in a single query with plain SQL:
SELECT unnest(arr)
FROM (
SELECT ARRAY[name] || array_agg(rs::text) || sum(rs)::text AS arr
FROM tbl
GROUP BY name
) sub
ORDER BY arr[1];
Similar to what #WingedPanther posted, but clean and safe for any name.
Returns:
A
10
20
30
60
B
15
15
C
70
30
100
Or:
WITH cte AS (
SELECT name, sum(RS)::text AS sum_rs
FROM tbl
GROUP BY 1
)
SELECT unicol
FROM (
SELECT name AS unicol, name AS order1, 1 AS order2 FROM cte
UNION ALL
SELECT sum_rs, name, 2 FROM cte
) sub
ORDER BY order1, order2;
Returns:
A
60
B
15
C
100
SQL Fiddle.
Or just send the result from the CTE and do the rest your client.
try this
select unnest(string_to_array( name ||','||rs||','||sum, ',')) AS elem
from (
select name
,string_agg(rs::text,',') rs
,sum(rs)
from tbl
group by name
)t
If you want print Total Of in Result use
select unnest(string_to_array( name ||','||rs||','||sum, ',')) AS elem
from (
select name
,string_agg(rs::text,',') rs
,'Total Of '||name||' '|| sum(rs) sum
from tbl
group by name
)t
> SQLFIDDLE
Not 100% certain what you're after but sounds like you just want this:
Select name, Sum(RS)
from sometable
group by name
Try this:
DECLARE #t TABLE(Name NVARCHAR(MAX), RS INT)
INSERT INTO #t VALUES
('A', 10),
('A', 20),
('A', 30),
('B', 15),
('C', 70),
('C', 30)
With UNION
SELECT Name, RS, 0 AS OrderBit FROM #t
UNION ALL
SELECT Name, SUM(RS) AS Total, 1 AS OrderBit FROM #t
GROUP BY Name
ORDER BY Name, OrderBit
Without Union:
SELECT Name, SUM(RS) AS RS FROM #t
GROUP BY GROUPING SETS((Name), (Name, RS))

Count occurrence of id across multiple columns

Ok, what am I doing wrong here. This should be simple...
I have a table that isn't normalized. I want to get a count of the IDs that appear in three columns of the table.
1 100 200 300
2 200 700 800
3 200 300 400
4 100 200 300
result:
2 100
4 200
3 300
1 400
1 700
1 800
Here is my attempt. The the union works. It is my attempt to sum and group them that fails:
select sum(cnt), ICDCodeID from
(
select count(*) cnt, ICDCodeID1 ICDCodeID from encounter
where (ICDCodeID1 is not null) group by ICDCodeID1
UNION ALL
select count(*) cnt, ICDCodeID2 ICDCodeID from encounter
where (ICDCodeID2 is not null) group by ICDCodeID2
UNION ALL
select count(*) cnt, ICDCodeID3 ICDCodeID from encounter
where (ICDCodeID3 is not null) group by ICDCodeID3
) group by cnt, ICDCodeID
Or better way?
Here is the error I am getting: "Incorrect syntax near the keyword 'GROUP'."
Maybe this helps:
SELECT D.ICDCode,
COUNT(*) as Cnt
FROM(SELECT ICDCodeID1 AS ICDCode
FROM encounter
UNION
ALL
SELECT ICDCodeID2 AS ICDCode
FROM encounter
UNION
ALL
SELECT ICDCodeID3 AS ICDCode
FROM encounter
)D
GROUP
BY D.ICDCode;
Try this:
-- build sample data
create table temp(
id int,
col1 int,
col2 int,
col3 int
)
insert into temp
select 1, 100, 200, 300 union all
select 2, 200, 700, 800 union all
select 3, 200, 300, 400 union all
select 4, 100, 200, 300
-- start
;with cte(id, col_value) as(
select id, col1 from temp union all
select id, col2 from temp union all
select id, col3 from temp
)
select
col_value,
count(*)
from cte
group by col_value
-- end
-- clean sample data
drop table temp