Assignation counting up to maximum - sql

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.

Related

Filtering rows and group by a column

C1
C2
C3
1
A
1000
1
B
2000
1
C
3000
1
D
4000
1
E
5000
2
A
1000
2
D
4000
2
E
5000
3
A
1000
3
B
2000
3
D
4000
3
E
5000
I want to filter C2 to value of B, but if the B doesn't exist so I need C and if the C doesn't exist so I need D
So the result will be as the following:
C1
C2
C3
1
B
2000
2
D
4000
3
B
2000
Use the RANK analytic function (if you want to include duplicate rows if there are multiple B values for a c1 group, otherwise use the ROW_NUMBER analytic function for a single row) and then filter to only include the first rank in each partition:
SELECT c1, c2, c3
FROM (
SELECT t.*,
RANK() OVER (PARTITION BY c1 ORDER BY c2) AS rnk
FROM table_name t
WHERE c2 IN ('B', 'C', 'D')
)
WHERE rnk = 1;
Which, for the sample data:
CREATE TABLE table_name (C1, C2, C3) AS
SELECT 1, 'A', 1000 FROM DUAL UNION ALL
SELECT 1, 'B', 2000 FROM DUAL UNION ALL
SELECT 1, 'C', 3000 FROM DUAL UNION ALL
SELECT 1, 'D', 4000 FROM DUAL UNION ALL
SELECT 2, 'A', 1000 FROM DUAL UNION ALL
SELECT 2, 'D', 4000 FROM DUAL UNION ALL
SELECT 2, 'E', 5000 FROM DUAL UNION ALL
SELECT 3, 'A', 1000 FROM DUAL UNION ALL
SELECT 3, 'B', 2000 FROM DUAL UNION ALL
SELECT 3, 'D', 4000 FROM DUAL UNION ALL
SELECT 3, 'E', 5000 FROM DUAL;
Outputs:
C1
C2
C3
1
B
2000
2
D
4000
3
B
2000
fiddle

SQL query return no rows

acid
tran_id
tran_date
tran_particular
tran_amt
part_tran_type
ab500
m1
01-01-2022
123:qwe
10
C
ab500
m5
10-01-2022
124:qse
20
C
ab500
m16
11-01-2022
123:pyh
10
D
I have the above table named htd. I am trying to fetch the result where tran_particular is unique before the ":"
Final output looking for:
acid
tran_id
tran_date
tran_particular
tran_amt
part_tran_type
ab500
m5
10-01-2022
124:qse
20
C
But below query returns no rows:
select tran_id||'|'||tran_date||'|'|| TRAN_PARTICULAR||'|'|| tran_amt
||'|'|| part_tran_type from htd a where a.acid ='ab500' and
Substr(TRAN_PARTICULAR,1,instr(TRAN_PARTICULAR,':')-1) not in (select Substr(TRAN_PARTICULAR,1,instr(TRAN_PARTICULAR,':')-1) from htd b where b.acid ='ab500'
and b.tran_id not in( a.tran_id)) order by tran_date;
koen >CREATE TABLE htd (tran_id, tran_particular)AS
2 (
3 SELECT 'm1', '123:qwe' FROM DUAL UNION ALL
4 SELECT 'm5', '124:qse' FROM DUAL UNION ALL
5 SELECT 'm16', '123:pyh' FROM DUAL
6* );
Table HTD created.
koen >WITH tp_unique_vals (tran_particular, cnt) AS
2 (
3 SELECT SUBSTR(tran_particular,1,INSTR(tran_particular,':')-1), COUNT(*)
4 FROM htd
5 GROUP BY SUBSTR(tran_particular,1,INSTR(tran_particular,':')-1)
6 HAVING COUNT(*) = 1
7 )
8 SELECT h.*
9 FROM htd h
10* JOIN tp_unique_vals u ON SUBSTR(h.tran_particular,1,instr(h.tran_particular,':')-1) = u.tran_particular;
TRAN_ID TRAN_PARTICULAR
__________ __________________
m5 124:qse
koen >
Try to use a regular expression and an aggregation:
with a as (select '123:qwe' b from dual union all
select '124:qse' from dual union all
select '123:pyh' from dual)
select b
from a
where regexp_substr(b, '[^:]+') in ( select c
from (select b, regexp_substr(b, '[^:]+') c
from a)
group by c
having count(*) = 1);
Returns 124:qse.

how to return multiple records from single record in Oracle

I have source Tbl like
CID No_Of_Seats_Booked Seat_Numbers
-------------------------------------
1 3 01A01B01C
Tgt table O/P
CID Seat_id
------------
1 01A
1 01B
1 01C
Here's one option:
SQL> with test (cid, no_of_seats_booked, seat_numbers) as
2 -- sample data
3 (select 1, 3, '01A01B01C' from dual union all
4 select 2, 2, '02A02B' from dual)
5 -- query you need begins here
6 select cid,
7 substr(seat_numbers, 1 + (column_value - 1) * 3, 3) seat_id
8 from test cross join
9 table(cast(multiset(select level from dual
10 connect by level <= no_of_seats_booked
11 ) as sys.odcinumberlist))
12 order by cid, seat_id;
CID SEA
---------- ---
1 01A
1 01B
1 01C
2 02A
2 02B
SQL>
Another approach, just for variety:
with demo(cid, no_of_seats_booked, seat_numbers) as
( select 1, 3, '01A01B01C' from dual union all
select 2, 2, '02A02B' from dual
)
select d.cid
, regexp_substr(d.seat_numbers, '...', 1, r.rnum) seat_id
from demo d
cross apply (select rownum as rnum from dual connect by rownum <= d.no_of_seats_booked) r
order by d.cid, seat_id;

Oracle SQL assign if less than configured parameter

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

group by field for specific values

How do we use group by only to consider a certain value of a column
eg
if the column has values like , and I only want to group the records with the merge_ind = 'Y' or null, if it is say N the record should be treated as separate value
Merge1 Merge2
A Y
A Y
A Y
B Y
B Y
B Y
C N
C N
C N
D N
D N
E null
E null
F null
F null
null null
the o/p should be
count Merge1 merge2
3 A Y
3 B Y
1 C N
1 C N
1 C N
1 D N
1 D N
2 E null
1 F null
1 null null
I implemented it using a union but am not very happy with the performance.
Thanks
Ali
you can do something like this:
SQL> with data as (select 'A' Merge1, 'Y' Merge2 from dual union all
2 select 'A', 'Y' from dual union all
3 select 'A', 'Y' from dual union all
4 select 'B', 'Y' from dual union all
5 select 'B', 'Y' from dual union all
6 select 'B', 'Y' from dual union all
7 select 'C', 'N' from dual union all
8 select 'C', 'N' from dual union all
9 select 'C', 'N' from dual union all
10 select 'D', 'N' from dual union all
11 select 'D', 'N' from dual union all
12 select 'E', null from dual union all
13 select 'E', null from dual union all
14 select 'F', null from dual union all
15 select 'F', null from dual union all
16 select null, null from dual)
17 select merge1, max(merge2), count(*)
18 from (select merge1, merge2,
19 case when merge2 = 'Y' or merge2 is null then merge2 else to_char(rownum) end grp
20 from data)
21 group by merge1, grp
22 order by merge1;
M M COUNT(*)
- - ----------
A Y 3
B Y 3
C N 1
C N 1
C N 1
D N 1
D N 1
E 2
F 2
1
test fiddle: http://sqlfiddle.com/#!4/b85cc/1
Try:
select Merge1, Merge2, count(*)
from table1
group by Merge1, Merge2, case when Merge2 = 'N' then to_char(rownum) else Merge2 end
order by Merge1
Here is a sqlfiddle demo
After some considerable mucking around, I have a query that does the job although I could swear this question was originally tagged mysql, and unfortunately this is a mysql only answer:
select count, merge1, merge2
from (
select count(*) count, merge1, merge2,
if(merge2 = 'Y' or merge2 is null, 0, n)
from (
select merge1, merge2,
(#n := if(#n is null, 1, #n + 1)) n
from t
) x
group by 2, 3, 4
) y
Values not Y are treated as separate values with their own group.
It works by assigning a unique number to each row, them selectively grouping by that too when the value is not Y, thus creating a separate group for each non-Y row.
Here's an sqlfiddle with this query running your dara.