Oracle SQL assign if less than configured parameter - sql

I have the below 2 tables:
assigned
VK
PC
A
B
RANK
VK1
PC1
A1
null
1
VK2
PC1
A1
A2
2
VK3
PC1
A2
null
3
VK4
PC1
A2
null
4
VK5
PC1
null
A1
5
VK6
PC1
null
A2
6
res
PC
A
MAXI
PC1
A1
2
PC1
A2
2
I would like to have the below desired output, based on this logic:
If B!=A, then assign B to C if the count of the value in B in the preceding rows order by rank is less than 'MAXI' in table res for that 'PC' and 'A'. If B=A or B is null, assign A to C.
After updating setting column C with the logic in point 1, if the count of any 'A's is less than 'MAXI' in table res, update the first null value to that 'A' until the count of 'A's is equal than 'MAXI' in res. Similarly, if the count of 'A's exceed 'MAXI' for any of the 'A's, set C to null the lowest assigned ranks until the condition is met.
Desired output:
VK
PC
A
B
RANK
C
VK1
PC1
A1
null
1
A1
VK2
PC1
A1
A2
2
A2
VK3
PC1
A2
null
3
A2
VK4
PC1
A2
null
4
A1
VK5
PC1
null
A1
5
null
VK6
PC1
null
A2
6
null
NOTE: row 4 was assigned to A1 instead of A2 because row 2 had to be assigned to A2 and thus row 4 exceeded the quota for A2. Quota for A1 was still 1 (less than 2), so could be assigned to A1. For row 5, there were already 2 A1s assigned (row 1 and 4), so the quota was exceeded and C had to be null.
EDIT: this is what I've tried so far.
with assigned (vk, pc, a, b, r) as(
select 'VK1', 'PC1', 'A1', null, 1 from dual union all
select 'VK2', 'PC1', 'A1', 'A2', 2 from dual union all
select 'VK3', 'PC1', 'A2', null, 3 from dual union all
select 'VK4', 'PC1', 'A2', null, 4 from dual union all
select 'VK5', 'PC1', null, 'A1', 5 from dual union all
select 'VK6', 'PC1', null, 'A2', 6 from dual),
res(pc, a, maxi) as(
select 'PC1', 'A1', 2 from dual union all
select 'PC1', 'A2', 2 from dual
)
, aux AS (
SELECT
a.*,
coalesce(a.b, a.a) d,
COUNT(coalesce(a.b, a.a)) OVER(
PARTITION BY coalesce(a.b, a.a)
ORDER BY
r
) i,
b.maxi
FROM
assigned a
LEFT JOIN res b ON ( b.pc = a.pc
AND b.a = a.a )
)
SELECT
a.*,
case
when i<=maxi then d
else a end c
FROM
aux a
order by r;

Your logic appears to be something like this:
WITH res_rows ( pc, a, maxi, total ) AS (
SELECT pc, a, maxi, SUM( maxi ) OVER( PARTITION BY pc )
FROM res
WHERE maxi > 0
UNION ALL
SELECT pc, a, maxi - 1, total FROM res_rows WHERE maxi > 1
),
p1 ( vk, pc, a, b, r, c, rn, pc_r ) AS (
SELECT a.*,
COALESCE(b, a),
ROW_NUMBER() OVER (PARTITION BY pc, COALESCE(b, a) ORDER BY r),
ROW_NUMBER() OVER (PARTITION BY pc ORDER BY r)
FROM assigned a
),
p2 ( vk, pc, a, b, r, c, rn ) AS (
SELECT p1.vk,
p1.pc,
p1.a,
p1.b,
p1.r,
r.a,
CASE
WHEN r.a IS NULL
THEN ROW_NUMBER() OVER (
PARTITION BY p1.pc
ORDER BY CASE WHEN r.a IS NULL THEN p1.r END
)
ELSE p1.rn
END
FROM p1
LEFT OUTER JOIN res_rows r
ON ( p1.pc = r.pc AND p1.c = r.a AND p1.rn = r.maxi AND p1.pc_r <= total )
),
missing ( pc, a, rn ) AS (
SELECT pc,
a,
ROW_NUMBER() OVER ( PARTITION BY pc ORDER BY ROWNUM )
FROM (
SELECT pc, a, maxi FROM res_rows
MINUS
SELECT pc, c, rn FROM p2 WHERE c IS NOT NULL
)
)
SELECT p2.vk,
p2.pc,
p2.a,
p2.b,
p2.r,
COALESCE( m.a, p2.c ) AS c
FROM p2
LEFT OUTER JOIN missing m
ON ( p2.pc = m.pc AND p2.c IS NULL AND p2.rn = m.rn )
ORDER BY r
Which outputs:
VK
PC
A
B
R
C
VK1
PC1
A1
1
A1
VK2
PC1
A1
A2
2
A2
VK3
PC1
A2
3
A2
VK4
PC1
A2
4
A1
VK5
PC1
A1
5
VK6
PC1
A2
6
db<>fiddle here

Related

Assignation counting up to maximum

I have the below 2 tables:
aux1:
PC
A
MAXI
PC1
A1
1
PC1
A2
2
PC2
A1
1
PC2
A2
2
aux:
VK
D
PC
VK1
8
PC1
VK2
7
PC1
VK3
6
PC2
VK4
5
PC2
VK5
4
PC1
VK6
3
PC1
VK7
2
PC2
VK8
1
PC2
I would like to obtain the following output:
VK
D
PC
A
ORDER_A_PC
ORDER_PC
VK1
8
PC1
A1
1
1
VK2
7
PC1
A2
1
2
VK3
6
PC2
A1
1
1
VK4
5
PC2
A2
1
2
VK5
4
PC1
A2
2
3
VK6
3
PC1
NA
NA
NA
VK7
2
PC2
A2
2
3
VK8
1
PC2
NA
NA
NA
The logic for the columns is:
A: ordering aux by D desc and partitioning by PC, assign A (A1 or A2) until the count over partition reaches the maxi configured in table aux1 for each PC and A.
ORDER_A_PC: ordered count over partition by A and PC until the count exceeds the maxi for that PC, A in table aux1
ORDER_PC: same as ORDER_A_PC but partitioned by just PC.
with aux (vk, pc) as (
select 'VK1', 'PC1' from dual union all
select 'VK2', 'PC1' from dual union all
select 'VK3', 'PC2' from dual union all
select 'VK4', 'PC2' from dual union all
select 'VK5', 'PC1' from dual union all
select 'VK6', 'PC1' from dual union all
select 'VK7', 'PC2' from dual union all
select 'VK8', 'PC2' from dual),
aux1 (pc, a, maxi) as (
select 'PC1', 'A1', 1 from dual union all
select 'PC1', 'A2', 2 from dual union all
select 'PC2', 'A1', 1 from dual union all
select 'PC2', 'A2', 2 from dual)
select * from aux
Nice little problem. You can distribute the assets preprocessing the ranges and then joining by range.
For example, you can do:
with aux (vk, pc) as (
select 'VK1', 'PC1' from dual union all
select 'VK2', 'PC1' from dual union all
select 'VK3', 'PC2' from dual union all
select 'VK4', 'PC2' from dual union all
select 'VK5', 'PC1' from dual union all
select 'VK6', 'PC1' from dual union all
select 'VK7', 'PC2' from dual union all
select 'VK8', 'PC2' from dual),
aux1 (pc, a, maxi) as (
select 'PC1', 'A1', 1 from dual union all
select 'PC1', 'A2', 2 from dual union all
select 'PC2', 'A1', 1 from dual union all
select 'PC2', 'A2', 2 from dual),
s as (
select a.*,
coalesce(sum(maxi) over(partition by pc order by pc, a
rows between unbounded preceding and 1 preceding), 0) + 1 as first_maxi,
sum(maxi) over(partition by pc order by pc, a) as last_maxi
from aux1 a
),
x as (
select a.*,
row_number() over(order by vk) as rn
from aux a
),
y as (
select a.*,
(select count(*) from x b where b.rn <= a.rn and b.pc = a.pc) as cnt
from x a
)
select
y.vk, y.pc, s.a,
case when s.pc is not null then row_number()
over(partition by y.pc, s.a order by y.rn) end as order_a_pc,
case when s.pc is not null then row_number()
over(partition by y.pc order by y.rn) end as order_pc
from y
left join s on s.pc = y.pc and y.cnt between s.first_maxi and s.last_maxi
order by y.rn
Result:
VK PC A ORDER_A_PC ORDER_PC
--- --- ------ ---------- --------
VK1 PC1 A1 1 1
VK2 PC1 A2 1 2
VK3 PC2 A1 1 1
VK4 PC2 A2 1 2
VK5 PC1 A2 2 3
VK6 PC1 <null> <null> <null>
VK7 PC2 A2 2 3
VK8 PC2 <null> <null> <null>
See running example at db<>fiddle.

Group by with having and merge back to original table

I have a table like this
A_Count
B_Count
A
B
C
1
0
A
NULL
C1
0
1
NULL
B
C1
1
1
A
B
C2
1
1
A
B
C2
and I want to have a result table (only need to show column A and B) like:
A_Count
B_Count
A
B
C
1
1
A
B
C1
1
1
A
B
C2
1
1
A
B
C2
So my goal is to merge two row having the following condiction:
both rows belong to same group C and only merge when one row has A being null and one row has B being null.
so its like:
group by C
having sum(A_COUNT) =1 AND sum(B_COUNT) =1
but the problem is, I want to keep those rows that are not merged (ROW 3 & 4) , can someone tell me how to do that? many thanks!
You can use conditional analytical function and group by as follows:
Select max(a) as a, max(b) as b, c from
(Select a, b, c,
case when nulla = 1 and nullb = 1 and (a is null or b is null)
then 0
else row_number() over (partition by c order by 1)
end as rn
from (Select a, b, c,
count(case when a is null then 1 end) over(partition by c) as nulla,
count(case when b is null then 1 end) over (partition by c) as nullb
From your_table t
)
)
Group by c, rn
DB<>Fiddle Thanks to MT0. Used the sample data from MT0's fiddle.
If you were using Oracle 12 then you could use MATCH_RECOGNIZE:
SELECT a_count, b_count, a, b, c
FROM (
SELECT t.*,
NVL2(
A,
ROW_NUMBER() OVER ( PARTITION BY C ORDER BY NVL2( B, 1, 0 ) DESC, ROWNUM ),
ROW_NUMBER() OVER ( PARTITION BY C ORDER BY NVL2( A, 1, 0 ) DESC, ROWNUM )
) AS rn
FROM table_name t
)
MATCH_RECOGNIZE (
PARTITION BY C
ORDER BY rn, A NULLS LAST
MEASURES
FIRST( a_count ) AS a_count,
LAST( b_count ) AS b_count,
FIRST( a ) AS a,
LAST( b ) AS b
PATTERN ( a b? )
DEFINE
a AS a.a IS NOT NULL,
b AS a.b IS NULL AND b.a IS NULL AND b.b IS NOT NULL
)
Before that Oracle version, you can get a similar effect using analytic functions to determine which rows to aggregate:
SELECT SUM( a_count ) AS a_count,
SUM( b_count ) AS b_count,
MAX( a ) AS a,
MAX( b ) AS b,
c
FROM (
SELECT t.*,
NVL2(
A,
ROW_NUMBER() OVER ( PARTITION BY C ORDER BY NVL2( B, 1, 0 ) DESC, ROWNUM ),
ROW_NUMBER() OVER ( PARTITION BY C ORDER BY NVL2( A, 1, 0 ) DESC, ROWNUM )
) AS rn
FROM table_name t
)
GROUP BY c, rn
Which, for the sample data (in an unordered state, with additional rows to demonstrate grouping additional pairs of rows):
CREATE TABLE table_name ( A_Count, B_Count, A, B, C ) AS
SELECT 1, 0, 'A', NULL, 'C1' FROM DUAL UNION ALL
SELECT 0, 1, NULL, 'B', 'C1' FROM DUAL UNION ALL
SELECT 1, 1, 'A', 'B', 'C2' FROM DUAL UNION ALL
SELECT 0, 1, NULL, 'B', 'C2' FROM DUAL UNION ALL -- Added row
SELECT 1, 0, 'A', NULL, 'C2' FROM DUAL UNION ALL -- Added row
SELECT 1, 0, 'A', NULL, 'C2' FROM DUAL UNION ALL -- Added row
SELECT 1, 1, 'A', 'B', 'C2' FROM DUAL UNION ALL
SELECT 0, 1, NULL, 'B', 'C2' FROM DUAL -- Added row
Both output:
A_COUNT | B_COUNT | A | B | C
------: | ------: | :- | :- | :-
1 | 1 | A | B | C1
1 | 1 | A | B | C2
1 | 1 | A | B | C2
1 | 1 | A | B | C2
1 | 1 | A | B | C2
db<>fiddle here
You can do this with join:
select (t1.a_count + coalesce(t2.a_count, 0)) as a_count,
(t1.b_count + coalesce(t2.b_count, 0)) as b_count,
coalesce(t1.a, t2.a) as a,
coalesce(t1.b, t2.b) as b,
t1.c
from t t1 left join
t t2
on t1.c = t2.c and
t1.a is not null and t2.a is null and
t1.b is null and t2.b is not null
where t1.a is not null;
As you've described the problem, aggregation doesn't seem necessary.
Here is a db<>fiddle with your original data.

Get Distinct values without null

I have a table like this;
--Table_Name--
A | B | C
-----------------
A1 NULL NULL
A1 NULL NULL
A2 NULL NULL
NULL B1 NULL
NULL B2 NULL
NULL B3 NULL
NULL NULL C1
I want to get like this ;
--Table_Name--
A | B | C
-----------------
A1 B1 C1
A2 B2 NULL
NULL B3 NULL
How should I do that ?
Here's one option:
sample data is from line #1 - 9
the following CTEs (lines #11 - 13) fetch ranked distinct not null values from each column
the final query (line #15 onward) returns desired result by outer joining previous CTEs on ranked value
SQL> with test (a, b, c) as
2 (select 'A1', null, null from dual union all
3 select 'A1', null, null from dual union all
4 select 'A2', null, null from dual union all
5 select null, 'B1', null from dual union all
6 select null, 'B2', null from dual union all
7 select null, 'B3', null from dual union all
8 select null, null, 'C1' from dual
9 ),
10 --
11 ta as (select distinct a, dense_rank() over (order by a) rn from test where a is not null),
12 tb as (select distinct b, dense_rank() over (order by b) rn from test where b is not null),
13 tc as (select distinct c, dense_rank() over (order by c) rn from test where c is not null)
14 --
15 select ta.a, tb.b, tc.c
16 from ta full outer join tb on ta.rn = tb.rn
17 full outer join tc on ta.rn = tc.rn
18 order by a, b, c
19 /
A B C
-- -- --
A1 B1 C1
A2 B2
B3
SQL>
If you have only one value per column, then I think a simpler solution is to enumerate the values and aggregate:
select max(a) as a, max(b) as b, max(c) as c
from (select t.*,
dense_rank() over (partition by (case when a is null then 1 else 2 end),
(case when b is null then 1 else 2 end),
(case when c is null then 1 else 2 end)
order by a, b, c
) as seqnum
from t
) t
group by seqnum;
This only "aggregates" once and only uses one window function, so I think it should have better performance than handling each column individually.
Another approach is to use lateral joins which are available in Oracle 12C -- but this assumes that the types are compatible:
select max(case when which = 'a' then val end) as a,
max(case when which = 'b' then val end) as b,
max(case when which = 'c' then val end) as c
from (select which, val,
dense_rank() over (partition by which order by val) as seqnum
from t cross join lateral
(select 'a' as which, a as val from dual union all
select 'b', b from dual union all
select 'c', c from dual
) x
where val is not null
) t
group by seqnum;
The performance may be comparable, because the subquery removes so many rows.

Exclude columns with no data in them

Using Oracle with TOAD.
I have a table that looks something like this (columns 2 and 4 are empty and columns 1, 3 and 5 have data in them):
column_1 column_2 column_3 column_4 column_5
a1 b1 c1
a2 b2 c2
a3 b3 c3
a4 b4 c4
I would like to do a simple select that excludes columns with no data in them (= columns 2 and 4) or ain other words, to select only the columns that have data in them.
Is there a select command such as SELECT * FROM test_table WHERE columns ARE NOT NULL (this is pseudo code just for clarification for my problem).
The result should look like this:
column_1 column_3 column_5
a1 b1 c1
a2 b2 c2
a3 b3 c3
a4 b4 c4
You have to do a three-step approach but it is largely tedious but do-able in sqlplus
1)First identify the columns which are empty
2)Define the headers without those columns
3)define the body without those columns
WITH data AS
(
SELECT '1' a,
'' b ,
2 c ,
'' d,
5 e
FROM dual
UNION ALL
SELECT '7' a,
'' b ,
2 c ,
'' d,
6
FROM dual
UNION ALL
SELECT '3' a,
'' b ,
3 c ,
'' d,
7
FROM dual
UNION ALL
SELECT '4' a,
'' b ,
3 c ,
'' d,
8
FROM dual
UNION ALL
SELECT '5' a,
'' b ,
2 c ,
'' d,
9
FROM dual),d1 AS
(
SELECT First_value(a) ignore nulls over (PARTITION BY a ORDER BY ROWNUM) ca,
first_value(b) ignore nulls over (PARTITION BY b ORDER BY ROWNUM) cb,
first_value(c) ignore nulls over (PARTITION BY c ORDER BY ROWNUM) cc,
first_value(d) ignore nulls over (PARTITION BY d ORDER BY ROWNUM) cd,
first_value(e) ignore nulls over (PARTITION BY e ORDER BY ROWNUM) ce
FROM data
WHERE ROWNUM=1 ),
d2 as (SELECT 0 rw,
CASE
WHEN ca IS NOT NULL THEN 'a'
ELSE ''
END
||chr(9)
||
CASE
WHEN cb IS NOT NULL THEN 'b'
ELSE ''
END
||chr(9)
||
CASE
WHEN cc IS NOT NULL THEN 'c'
ELSE ''
END
||chr(9)
||
CASE
WHEN cd IS NOT NULL THEN 'd'
ELSE ''
END
||chr(9)
||
CASE
WHEN ce IS NOT NULL THEN 'e'
ELSE ''
END as DATA1
FROM d1
UNION ALL
SELECT
rownum rw,
a
||chr(9)
||b
||chr(9)
||c
||chr(9)
||d
||chr(9)
||e
FROM data)
select /*ansiconsole*/ DATA1
from d2 order by rw asc;

Group by multiple columns and select the records with the maximum values of another column which is less than a specific value

I have a SQL question. I have the following table:
ID A B C
1 A1 B1 C1
2 A1 B1 C2
3 A2 B2 C3
4 A1 B1 C3
5 A2 B2 C2
6 A3 B1 C1
7 A1 B1 C4
8 A2 B1 C1
I want to select one row from each group where the 'A' and 'B' are the same. For instance rows 1,2,4,and 7 form a group where A = 'A1' & B = 'B1'. Within these groups I want the record with the greatest value in column 'C' that isn't greater than 'C3' so record #4 in the above group.
Here is the result set I'm looking for:
ID A B C
4 A1 B1 C3
3 A2 B2 C3
6 A3 B1 C1
8 A2 B1 C1
You could use a CTE and a ranking function:
WITH CTE AS
(
SELECT ID, A, B, C,
RN = ROW_NUMBER() OVER (PARTITION BY A, B ORDER BY C DESC)
FROM dbo.TableName
WHERE C <= 'C3'
)
SELECT ID, A, B, C
FROM CTE
WHERE RN = 1
This might give you what you want:
Select A, B, Max(C) as maxC
(
Select *
From SomeTable
Where C <= 'C3'
) as nested
Group By A, B
Try this,
SELECT *
FROM (SELECT *,
Row_Number()
OVER (
PARTITION BY A, B
ORDER BY C DESC) AS RN
FROM #TEMP)A
WHERE RN = 1
Try this,
SELECT DISTINCT ID,A,
B,C
FROM (SELECT Row_number()
OVER(
partition BY A, B
ORDER BY id, c DESC)rn,
*
FROM tabl1)p
WHERE rn IN( 1, 3 )
AND id > 1