Count percent of records among similar records - oracle

I have table t:
ID Type
---- ----
1 a
1 b
2 a
2 a
3 b
And table with names of IDs from first table - n:
ID Name
---- ----
1 name1
2 name2
3 name3
I need make query in PL/SQL for count percentage of type occurrence among all types for same id (group by ID).
The result must be:
Name a% b% row
--- ---- --- ---
name1 50 50 1
name2 100 0 2
name3 0 100 3
I tried:
select
n.name,
a.perc as "a%",
b.perc as "b%",
row_number() over (
order by name asc
) mf_rownumber
from n n
left join
(select
id,
round(100 * (count(*) / sum(count(*)) over ()), 2) perc
from t
where (type = 'a')
group by id) a
on a.id = n.id
left join
(select
id,
round(100 * (count(*) / sum(count(*)) over ()), 2) perc
from t
where (type = 'b')
group by id) b
on b.id = n.id;
What I get, is percentage of every type from all rows:
Name a% b% row
--- ---- --- ---
name1 20 20 1
name2 40 0 2
name3 0 20 3
But I need count everything in borders of the same ID, not all rows.

I think it can be simplified a lot :
http://sqlfiddle.com/#!4/6bb2a/20
select
n.name,
round(100 * (sum(case when type='a' then 1 else 0 end) / count(*)), 2) as "a%",
round(100 * (sum(case when type='b' then 1 else 0 end) / count(*)), 2) as "b%",
row_number() over (order by name asc ) mf_rownumber
from n
left join t on t.id = n.id
group by n.name

I would do something like this:
select
n.name,
n.id,
count(case when type='a' then 1 end)/count(*)*100 as "a%",
count(case when type='b' then 1 end)/count(*)*100 as "b%"
from n left join t on a.id=n.id
group by n.id;

Related

Requirement in Oracle PL/SQL to update labels

I have 2 tables - tab1 , tab2 with following data
tab1 data:
OID Label
1 MX1
1 MX2
1 MX3
2 MX4
2 MX5
tab2 data:
OID ID Label
1 5678
1 2347
1 9687
2 4567
2 3455
The join condition between these two tables is oid column.I need to create a process which will update Label column from tab1 to Label column of tab2.It doesn't matter which label gets assigned to which record of tab2 for a particular oid. The only check that needs to happen is that both the tables should have same number of records for an oid.The final outcome should be the following
tab2 data:
OID ID Label
1 5678 MX1
1 2347 MX2
1 9687 MX3
2 4567 MX4
2 3455 MX5
Again, it doesn't matter which label gets assigned to tab2 for a particular oid,but the same label cannot be repeated for a particular oid.What would be the best way to write a code for this requirement?
Here is a sql solution:
merge into tab2
using
(
select t2."id" as ide,t1."label" labela from
(select rownum n,"label","oid" from tab1 order by "oid")t1,
(select rownum n, a2.* from tab2 a2 order by "oid")t2
where t1.n=t2.n and
t1."oid"=t2."oid"
) tb4
on (tab2."id" = tb4.ide)
when matched then
update set tab2."label" = tb4.labela;
Result:
oid| id | label
-----------------
1 5678 mx1
1 2347 mx2
1 9687 mx3
2 4567 mx4
2 3455 mx5
Sample tables:
SQL> select * from tab1 order by oid, label;
OID LAB
---------- ---
1 mx1
1 mx2
1 mx3
2 mx4
2 mx5
SQL> select * from tab2 order by oid, id;
OID ID LAB
---------- ---------- ---
1 2347
1 5678
1 9687
2 3455
2 4567
SQL>
This is query that returns desired result:
SQL> with
2 t1 as (select oid, label, rowid rwid,
3 row_number() over (partition by oid order by label) rn
4 from tab1
5 ),
6 t2 as (select oid, id, rowid rwid,
7 row_number() over (partition by oid order by id) rn
8 from tab2
9 )
10 select b.oid, b.id, a.label
11 from t1 a join t2 b on a.oid = b.oid and a.rn = b.rn;
OID ID LAB
---------- ---------- ---
1 2347 mx1
1 5678 mx2
1 9687 mx3
2 3455 mx4
2 4567 mx5
SQL>
A few options I tried: correlated update won't work because of
ORA-01779: cannot modify a column which maps to a non key-preserved table
SQL> update (
2 with
3 t1 as (select oid, label, rowid rwid,
4 row_number() over (partition by oid order by label) rn
5 from tab1
6 ),
7 t2 as (select oid, id, rowid rwid, label,
8 row_number() over (partition by oid order by id) rn
9 from tab2
10 )
11 select b.oid, b.id, b.label b_label, a.label a_label
12 from t1 a join t2 b on a.oid = b.oid and a.rn = b.rn
13 )
14 set b_label = a_label;
set b_label = a_label
*
ERROR at line 14:
ORA-01779: cannot modify a column which maps to a non key-preserved table
SQL>
MERGE won't work because of
ORA-01732: data manipulation operation not legal on this view
SQL> merge into
2 (select oid, id, label, row_Number() over (partition by oid order by id ) rn from tab2) b
3 using (select oid, label, row_number() over (partition by oid order by label) rn from tab1) a
4 on (a.oid = b.oid and
5 a.rn = b.rn)
6 when matched then update set
7 b.label = a.label;
(select oid, id, label, row_Number() over (partition by oid order by id ) rn from tab2) b
*
ERROR at line 2:
ORA-01732: data manipulation operation not legal on this view
SQL>
Merge would accept a view (created with create view ...), but a view has to be updateable; this one can't be because it contains analytic function.
What's left is a PL/SQL procedure:
SQL> begin
2 for cur_r in (with
3 t1 as (select oid, label, rowid rwid,
4 row_number() over (partition by oid order by label) rn
5 from tab1
6 ),
7 t2 as (select oid, id, rowid rwid,
8 row_number() over (partition by oid order by id) rn
9 from tab2
10 )
11 select b.rwid, a.label
12 from t1 a join t2 b on a.oid = b.oid and a.rn = b.rn
13 )
14 loop
15 update tab2 b set
16 b.label = cur_r.label
17 where b.rowid = cur_r.rwid;
18 end loop;
19 end;
20 /
PL/SQL procedure successfully completed.
SQL> select * from tab2 order by oid, id;
OID ID LAB
---------- ---------- ---
1 2347 mx1
1 5678 mx2
1 9687 mx3
2 3455 mx4
2 4567 mx5
SQL>
Maybe someone has another idea; I'd like to see it & learn something new.

Inactivate duplicate record and re-point child records to active one

There are two table as below
Table1
ID Name Age Active PID
-----------------------------
1 A 2 Y 100
2 A 2 Y 100
3 A 2 Y 100
4 B 3 Y 200
5 B 3 Y 200
Table2
T2ID CID
---------
10 1
20 1
30 1
40 2
50 2
60 3
70 3
80 3
90 4
100 5
110 5
I am trying to inactivate the duplicate record of table 1 and reassign the table2 record to activated rows of table 1,The result for table1 and table2 should be as below
ID Name Age Active PID
-----------------------------
1 A 2 Y 100
2 A 2 N 100
3 A 2 N 100
4 B 3 N 200
5 B 3 Y 200
T2ID CID
---------
10 1
20 1
30 1
40 1
50 1
60 1
70 1
80 1
90 5
100 5
110 5
please help for oracle query to update
You can do this by using two merge statements, like so:
Update table2:
MERGE INTO table2 tgt
USING (WITH t1 AS (SELECT ID,
NAME,
age,
active,
pid,
MIN(ID) OVER (PARTITION BY pid) min_id,
CASE WHEN COUNT(CASE WHEN active = 'Y' THEN 1 END) OVER (PARTITION BY pid) > 1 THEN 'Y' ELSE 'N' END multi_active_rows
FROM table1)
SELECT t2.t2id,
t2.cid old_cid,
t1.min_id new_cid
FROM t1
INNER JOIN table2 t2 ON t1.id = t2.cid
WHERE t1.multi_active_rows = 'Y') src
ON (tgt.t2id = src.t2id)
WHEN MATCHED THEN
UPDATE SET tgt.cid = src.new_cid;
Update table1:
MERGE INTO table1 tgt
USING (WITH t1 AS (SELECT ID,
NAME,
age,
active,
pid,
MIN(ID) OVER (PARTITION BY pid) min_id,
CASE WHEN COUNT(CASE WHEN active = 'Y' THEN 1 END) OVER (PARTITION BY pid) > 1 THEN 'Y' ELSE 'N' END multi_active_rows
FROM table1)
SELECT ID
FROM t1
WHERE multi_active_rows = 'Y'
AND ID != min_id) src
ON (tgt.id = src.id)
WHEN MATCHED THEN
UPDATE SET active = 'N';
Since we want to derive the results to update both table1 and table2 from the original dataset in table1, it's easier to update table2 first before updating table1.
This works by finding the lowest id across each set of pids in table1, plus checking to see if there is more than one active row for each pid (there's no need to do any updates if we have at most one active row available).
Once we have that information, we can use that to decide which rows to update in each table, and we can use the min_id to update table2 with, and we can update any rows in table1 where the id doesn't match the min_id to be not active.
N.B. If you could have a mix of Ys and Ns in your data, you may need to skip the and id != min_id check in the second merge statement and amend the update part to update the row to Y if the id is the min_id, otherwise set it to N.

how to get following output in oracle using query?

I've following data like
ano asal
------------------
1 100
1 150
1 190
2 200
2 240
3 300
3 350
4 400
4 400
4 400
i want ans like max sal from 1 ,from 2,3 and 4
o/p like
ano asal
---------------
1 190
2 240
3 390
4 400
4 400
4 400
You want to return the max value of asal for each ano group, but you want to retain the duplicates in the original table if they exist. This means you can't just do a simple GROUP BY. But you can use a GROUP BY query to identify the max values and then retain those records via an INNER JOIN. Try this query:
SELECT t1.ano, t1.asal
FROM yourTable t1
INNER JOIN
(
SELECT ano, MAX(asal) AS asal
FROM yourTable
GROUP BY ano
) t2
ON t1.ano = t2.ano AND t1.asal = t2.asal
You can use an union the firt select with group by
select ano, max(asal)
from my_table
where ano != 4
group by ano
union all
select ano, asal
from my_table
where ano = 4
order by ano
SELECT
ano, asal
FROM (
SELECT
data.*,
MAX(asal) OVER (PARTITION BY ano) max
FROM
data)
WHERE
asal = max

Query to exclude row based on another row's filter

I'm using Oracle 10g.
Question: How can I write query to return just ID only if ALL the codes for that ID end in 6? I don't want ID=1 because not all its codes end in 6.
TABLE_A
ID Code
===============
1 100
1 106
2 206
3 316
3 326
4 444
Desired Result:
ID
==
2
3
You simply want each ID where the count of rows for that id is the same as the count of rows where the third digit is six.
SELECT ID
FROM TABLE_A
GROUP BY ID
HAVING COUNT(*) = COUNT(CASE WHEN SUBSTR(code,3,1) = '6' THEN 1 END)
Try this:
SELECT DISTINCT b.id
FROM (
SELECT id,
COUNT(1) cnt
FROM table_a
GROUP BY id
) a,
(
SELECT id,
COUNT(1) cnt
FROM table_a
WHERE CODE LIKE '%6'
GROUP BY id
)b
WHERE a.id = b.id
AND a.cnt = b.cnt
Alternative using ANALYTIC functions:
SELECT DISTINCT id
FROM
(
SELECT id,
COUNT(1) OVER(PARTITION BY id) cnt,
SUM(CASE WHEN code LIKE '%6' THEN 1 ELSE 0 END) OVER(PARTITION BY id) sm
FROM table_a
)
WHERE cnt = sm

Oracle Procedure to join two tables with latest status

Please help me make an oracle stored procedure ; I have two tables
tblLead:
lead_id Name
1 x
2 y
3 z
tblTransaction:
Tran_id lead_id date status
1 1 04/20/2010 call Later
2 1 05/05/2010 confirmed
I want a result like
lead_id Name status
1 x confirmed
2 y not available !
3 z not available !
Use an outer join to the relevant rows of tblTransaction:
SQL> SELECT l.lead_id, l.NAME,
2 CASE
3 WHEN t.status IS NULL THEN
4 'N/A'
5 ELSE
6 t.status
7 END status
8 FROM tbllead l
9 LEFT JOIN (SELECT lead_id,
10 MAX(status) KEEP(DENSE_RANK FIRST
11 ORDER BY adate DESC) status
12 FROM tbltransaction
13 GROUP BY lead_id) t ON l.lead_id = t.lead_id;
LEAD_ID NAME STATUS
---------- ---- ----------
1 x confirmed
2 y N/A
3 z N/A
Alternatively you can use analytics:
SQL> SELECT lead_id, NAME, status
2 FROM (SELECT l.lead_id, l.NAME,
3 CASE
4 WHEN t.status IS NULL THEN
5 'N/A'
6 ELSE
7 t.status
8 END status,
9 row_number()
10 over(PARTITION BY l.lead_id ORDER BY t.adate DESC) rn
11 FROM tbllead l
12 LEFT JOIN tbltransaction t ON l.lead_id = t.lead_id)
13 WHERE rn = 1;
LEAD_ID NAME STATUS
---------- ---- ----------
1 x confirmed
2 y N/A
3 z N/A
It can be written in plain SQL as follows,
SELECT lead_id, name, NVL(status,'not available !')
FROM (
SELECT tblLead.lead_id, tblLead.name, tblTransaction.status,
rank ( ) OVER (PARTITION BY tblTransaction.lead_id ORDER BY tblTransaction.datee DESC, tblTransaction.tran_id DESC) rank
FROM tblLead
LEFT JOIN tblTransaction ON tblLead.lead_id = tblTransaction.lead_id
)
WHERE rank = 1
ORDER BY lead_id;
Or you may think of writing a view as follows,
CREATE VIEW trx_view AS
------
------;
Personally I think stored procedure is not necessary for scenarios like this.

Resources