Oracle : SQL Request with a Group By and a Percentage on two differents tables - sql

I'm currently blocked on an complex request... (with a join) :
I have this table "DATA":
order | product
----------------
1 | A
1 | B
2 | A
2 | D
3 | A
3 | C
4 | A
4 | B
5 | Y
5 | Z
6 | W
6 | A
And this table "DICO":
order | couple | first | second
-------------------------------
1 | A-B | A | B
2 | A-D | A | D
3 | A-C | A | C
4 | A-B | A | B
5 | Y-Z | Y | Z
6 | W-A | W | A
I would like to obtain, on one line :
order | count | total1stElem | %1stElem | total2ndElem | %1ndElem
------------------------------------------------------------------
A-B | 2 | 5 | 40% | 2 | 100%
A-D | 1 | 5 | 20% | 1 | 100%
A-C | 1 | 5 | 20% | 1 | 100%
Y-Z | 1 | 1 | 100% | 1 | 100%
W-A | 1 | 1 | 100% | 5 | 20%
I'm totally blocked on the jointure part of my request. Somebody can help me ?

Without any joins - just using UNPIVOT and PIVOT:
Oracle Setup:
CREATE TABLE DICO ( "order", couple, first, second ) AS
SELECT 1, 'A-B', 'A', 'B' FROM DUAL UNION ALL
SELECT 2, 'A-D', 'A', 'D' FROM DUAL UNION ALL
SELECT 3, 'A-C', 'A', 'C' FROM DUAL UNION ALL
SELECT 4, 'A-B', 'A', 'B' FROM DUAL UNION ALL
SELECT 5, 'Y-Z', 'Y', 'Z' FROM DUAL UNION ALL
SELECT 6, 'W-A', 'W', 'A' FROM DUAL;
Query:
SELECT "order",
"count",
"1stElem_TOTAL" AS Total1stElem,
100*"count"/"1stElem_TOTAL" AS "%1stElem",
"2ndElem_TOTAL" AS Total2ndElem,
100*"count"/"2ndElem_TOTAL" AS "%2ndElem"
FROM (
SELECT couple AS "order",
key,
COUNT(*) OVER ( PARTITION BY COUPLE )/2 AS "count",
COUNT(*) OVER ( PARTITION BY VALUE ) AS num_value
FROM DICO
UNPIVOT ( Value FOR Key IN ( first AS 1, second AS 2 ) )
)
PIVOT ( MAX( NUM_VALUE ) AS Total FOR key IN ( 1 AS "1stElem", 2 AS "2ndElem" ) );
Results:
order count TOTAL1STELEM %1stElem TOTAL2NDELEM %2ndElem
----- ----- ------------ -------- ------------ --------
A-D 1 5 20 1 100
A-B 2 5 40 2 100
A-C 1 5 20 1 100
Y-Z 1 1 100 1 100
W-A 1 1 100 5 20

Related

Oracle Aggregate(SUM function) after self-join

I have a table which contains ID, PARENT_ID AND COUNT.
EX)
+-----+-----------------------+--------------------------+
| ID | PARENT_ID | COUNT |...
+-----+-----------------------+--------------------------+
| 1 | NULL | 40 |...
| 2 | 1 | 10 |...
| 3 | 1 | 20 |...
| 4 | NULL | 35 |...
+-----+-----------------------+--------------------------+
And, i want result the sum of parent and sibling's count.
ID 1's count = ID 1's count + ID 2's count + ID 3's count
RESULT)
+-----+-----------------------+--------------------------+
| ID | PARENT_ID | COUNT |...
+-----+-----------------------+--------------------------+
| 1 | NULL | 70 |...
| 2 | 1 | 10 |...
| 3 | 1 | 20 |...
| 4 | NULL | 35 |...
+-----+-----------------------+--------------------------+
I used connect by to get the desired result, but I want to change the method as the above method uses too much oracle cpu.
Is there any way I can do this using sum function?
You can use the self join as follows:
SQL> with dataa (ID, PARENT_ID, CNT) as
2 (SELECT 1 , NULL, 40 FROM DUAL UNION ALL
3 SELECT 2 , 1 , 10 FROM DUAL UNION ALL
4 SELECT 3 , 1 , 20 FROM DUAL UNION ALL
5 SELECT 4 , NULL, 35 FROM DUAL)
6 -- your query starts from here
7 SELECT D1.ID, D1.PARENT_ID, D1.CNT + COALESCE(SUM(D2.CNT),0)
8 FROM DATAA D1 LEFT JOIN DATAA D2
9 ON D1.ID = D2.PARENT_ID
10 GROUP BY D1.ID, D1.PARENT_ID, D1.CNT
11 ORDER BY D1.ID;
ID PARENT_ID D1.CNT+COALESCE(SUM(D2.CNT),0)
---------- ---------- ------------------------------
1 70
2 1 10
3 1 20
4 35
SQL>

Output records that match the condition, as well as their parent records up to the root record. Oracle SQL

There is such an Oracle table:
+----+-----+--------+
| ID | PID | NAME |
+----+-----+--------+
| 1 | | testX |
| 2 | | test2 |
| 3 | 2 | test3 |
| 4 | 3 | testX |
| 5 | 3 | test5 |
| 6 | 3 | test6 |
| 7 | 4 | test7 |
| 8 | 5 | test8 |
| 9 | 3 | test9 |
| 10 | 4 | test10 |
| 11 | 5 | testX |
| 12 | 5 | test12 |
+----+-----+--------+
, where pid is the id of the parent record.
Need to output all records that match the condition, as well as their parent records up to the root record.
Such parent records should not be duplicated with those parent records that are found during the search phase.
For example, under this condition where name = 'testX', should get this result:
+----+-----+-------+
| ID | PID | NAME |
+----+-----+-------+
| 1 | | testX |
| 2 | | test2 |
| 3 | 2 | test3 |
| 4 | 3 | testX |
| 5 | 3 | test5 |
| 11 | 5 | testX |
+----+-----+-------+
How to do it?
P.S. Oracle 11.2.0.4.0.
I'm sure there is a more elegant way to do this, but this is what I came up with.
This is the with clause to generate the sample data:
with testdata as
(select 1 ID, null PID, 'testX' NAME from dual union all
select 2 , null, 'test2' from dual union all
select 3 , 2, 'test3' from dual union all
select 4 , 3, 'testX' from dual union all
select 5 , 3, 'test5' from dual union all
select 6 , 3, 'test6' from dual union all
select 7 , 4, 'test7' from dual union all
select 8 , 5, 'test8' from dual union all
select 9 , 3, 'test9' from dual union all
select 10, 4, 'test10' from dual union all
select 11, 5, 'testX' from dual union all
select 12, 5, 'test12' from dual)
Here is the query:
select distinct id, pid, name
from(
select sys_connect_by_path(name,'/') path,
id, pid, name
from testdata
connect by prior PID = ID)
where instr(path,'/testX') > 0
order by id
I used SYS_CONNECT_BY_PATH in order to get the name field from all parents. Then I just checked that testX was one of the elements in the string using instr.
My results are:
ID PID NAME
1 testX
2 test2
3 2 test3
4 3 testX
5 3 test5
11 5 testX
This simple query should help -
SELECT distinct id, pid, name FROM tab1
connect by prior pid = id
start with name = 'testX'
order by id
;
http://sqlfiddle.com/#!4/8da121/6/0
Output -
ID PID NAME
1 (null) testX
2 (null) test2
3 2 test3
4 3 testX
5 3 test5
11 5 testX

SQL: "Reverse" transpose a table

I saw a lot of questions on transposing from the below table...
scanid | region | volume
-------------------------
1 A 34.4
1 B 32.1
1 C 29.1
2 A 32.4
2 B 33.2
2 C 35.6
to this table.
scanid | A_volume | B_volume | C_volume
----------------------------------------
1 34.4 32.1 29.1
2 32.4 33.2 35.6
However, I need to do the inverse, and have trouble trying to wrap my head around this problem. Can anyone help?
Thank you.
it is not clear how you restore "A", "B", "C" values, so I just add them
prepare:
t=# create table s188 (scanid int,a float, b float,c float);
CREATE TABLE
t=# insert into s188 select 1,2,3,4;
INSERT 0 1
t=# insert into s188 select 2,12,13,14;
INSERT 0 1
t=# select * from s188;
scanid | a | b | c
--------+----+----+----
1 | 2 | 3 | 4
2 | 12 | 13 | 14
(2 rows)
select:
t=# with a as (
select scanid,unnest(array[a,b,c]) from s188
)
select scanid,chr((row_number() over (partition by scanid))::int + 64),unnest
from a;
scanid | chr | unnest
--------+-----+--------
1 | A | 2
1 | B | 3
1 | C | 4
2 | A | 12
2 | B | 13
2 | C | 14
(6 rows)
and more neat solution from a_horse_with_no_name
t=# with a as (
select scanid, x.*
from s188, unnest(array[a,b,c]) with ordinality as x(volume,idx)
)
select scanid,
chr(idx::int + 64) as region,
volume
from a;
scanid | region | volume
--------+--------+--------
1 | A | 2
1 | B | 3
1 | C | 4
2 | A | 12
2 | B | 13
2 | C | 14
(6 rows)
You could do this very simply with a UNION clause:
Select Scan_ID, 'A' as Region, A_Volume as volume
union all
Select Scan_ID, 'B' as Region, B_Volume as volume
union all
Select Scan_ID, 'C' as Region, C_Volume as volume

Show missing rows with 0 values to maintain the order

I have a table with a Name column that its values are either 'A', 'B' or 'C'. They come in order ( A, B, C, A, B, C, ...) however, sometimes a Name might be missing (A, B,[missing C] A, B, C, ...). I want a query that gives me all of Names in order without any missing name. The Value for missing names must be 0.
PS: The table is in a Netezza database and it gets truncated and reloaded with fresh data each time by an SSIS package. What we know is that there is also an ID column with a value between 1 and 27. But the number of rows after each truncation and loading could be different. The table I want does not need the ID column, but if it had, it would be from 1 to 27, meaning that the 'table I want' must always have 27 rows.
I would recommend fixing this in the source SSIS package, but I think the following will work in Netazza (for versions that support the WITH command). Note that recursion is not used which I believe isn't support by Netazza.
If the WITH command isn't supported then some other source of a numeric seqeunce could be used (e.g. by row_number() )
setup:
CREATE TABLE TableHave
(Name varchar(1), ID int, Value decimal(5,2))
;
INSERT INTO TableHave
(Name, ID)
VALUES
('A', 1),
('A', 4),
('A', 7),
('C', 21),
('B', 23),
('A', 25)
;
update TableHave set Value = id*1.12;
Query:
;WITH
Digits AS (
SELECT 0 AS digit UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
),
Tally AS (
SELECT
ones.digit
+ tens.digit * 10
+ hundreds.digit * 100
-- + thousands.digit * 1000
as num
FROM Digits ones
CROSS JOIN Digits tens
CROSS JOIN Digits hundreds
-- CROSS JOIN Digits thousands (keep adding more if needed)
)
select
d.id
, d.name
, t.value
from (
select
num + 1 as id
, case when num % 3 = 1 then 'B'
when num % 3 = 2 then 'C'
else 'A'
end Name
, coalesce(t.value,0) value
from Tally
where num <= (select ((max(id)/3)*3)+2 from TableHave)
) d
left join TableHave t on d.id = t.id
order by d.id
result:
+----+------+-------+
| id | name | value |
+----+------+-------+
| 1 | A | 1.12 |
| 2 | B | 0 |
| 3 | C | 0 |
| 4 | A | 4.48 |
| 5 | B | 0 |
| 6 | C | 0 |
| 7 | A | 7.84 |
| 8 | B | 0 |
| 9 | C | 0 |
| 10 | A | 0 |
| 11 | B | 0 |
| 12 | C | 0 |
| 13 | A | 0 |
| 14 | B | 0 |
| 15 | C | 0 |
| 16 | A | 0 |
| 17 | B | 0 |
| 18 | C | 0 |
| 19 | A | 0 |
| 20 | B | 0 |
| 21 | C | 23.52 |
| 22 | A | 0 |
| 23 | B | 25.76 |
| 24 | C | 0 |
| 25 | A | 28.00 |
| 26 | B | 0 |
| 27 | C | 0 |
+----+------+-------+
A running example (on SQL Server) is available here http://rextester.com/VXB89713

select the most recent in all groups of with the same value in one column

The question isn't very clear, but I'll illustrate what I mean, suppose my table is like such:
item_name | date added | val1 | val2
------------------------------------
1 | date+1 | 10 | 20
1 | date | 12 | 21
2 | date+1 | 5 | 6
3 | date+3 | 3 | 1
3 | date+2 | 5 | 2
3 | date | 3 | 1
And I want to select row 1, 3, 4 as they are the most recent entries for each item
Try this:
select *
from tableX t1
where t1.date_added = (select max(t2.date_added)
from tableX t2
where t2.item_name = t1.item_name )