I have following scenario:
ID Campus Credit_Hr
===== ====== ====
1 MIC 3
1 Warrens 4
1 Online 3
1 Online 3
2 MIC 5
2 Warrens 3
2 Online 6
3 Online 3
3 Online 3
3 West 2
4 Warrens 3
4 MIC 3
4 West 7
5 Online 3
5 West 3
5 East 3
Warrens and MIC are major campus. So, when Warrens and MIC has equal credit hr, like in ID 4, chose either Warrens / MIC
For ID 1: Warrens > MIC , chose Warrens though sum(Online) = 6 and is greater
For ID 2: MIC> Warrens, chose MIC
For ID 3: no Major Campus (Warrens/MIC) so chose max credit hr. er sum(online) is maximum so chose Online
For ID 5: West / East /Online all are minor campus, so chose any of them.
There are more than 50 campuses in real.
Assign information about MAJOR campuses, then use this column for ordering, in addition to the sum of hours:
dbfiddle demo
select *
from (
select a.*, row_number() over (partition by id order by major, sm desc) rn
from (
select id, campus,
case when campus in ('MIC', 'Warrens') then 1 else 2 end major,
sum(credit_hr) over (partition by id, campus) sm
from t) a)
where rn = 1
If all you need is to select max credit hours for each ID, but in such a way that if credit hours exist for 'MIC' or 'Warrens' for a given ID, then all other campuses for the same ID should be ignored, then the most efficient way is to use the FIRST aggregate function, like so:
with
sample_data(id, campus, credit_hr) as (
select 1, 'MIC' , 3 from dual union all
select 1, 'Warrens', 4 from dual union all
select 1, 'Online' , 3 from dual union all
select 1, 'Online' , 3 from dual union all
select 2, 'MIC' , 5 from dual union all
select 2, 'Warrens', 3 from dual union all
select 2, 'Online' , 6 from dual union all
select 3, 'Online' , 3 from dual union all
select 3, 'Online' , 3 from dual union all
select 3, 'West' , 2 from dual union all
select 4, 'Warrens', 3 from dual union all
select 4, 'MIC' , 3 from dual union all
select 4, 'West' , 7 from dual union all
select 5, 'Online' , 3 from dual union all
select 5, 'West' , 3 from dual union all
select 5, 'East' , 3 from dual
)
select id,
max(credit_hr) keep (dense_rank first
order by case when campus in ('MIC', 'Warrens') then 0 end)
as max_hr
from sample_data
group by id
order by id
;
ID MAX_HR
----- ------------------
1 4
2 5
3 3
4 3
5 3
You can also modify the query (add more columns) to show whether the max was from a main campus (that is, if that ID had ANY credit hours from one of the major campuses), and/or to show which campus had the max hours for that ID (or one of the campuses, if there was a tie for most hours).
Related
I am currently trying to reset a sequence if an ID of a Customer ends
right now, it is something like this:
CustomerID Product PosNr
1 Banana 1
1 Papaya 2
1 Apple 3
2 Laptop 1
2 Keyboard 2
I hope it is clear what I mean.
The PosNr should reset for another Customer.
Can I Set up something like this while inserting the values into the table, or in any other way?
It is row_number analytic function with appropriate partitioning.
SQL> with test (customerid, product) as
2 (select 1, 'banana' from dual union all
3 select 1, 'papaya' from dual union all
4 select 1, 'apple' from dual union all
5 select 2, 'laptop' from dual union all
6 select 2, 'keyboard' from dual
7 )
8 select customerid, product,
9 row_number() over (partition by customerid order by product) posnr
10 from test
11 /
CUSTOMERID PRODUCT POSNR
---------- -------- ----------
1 apple 1
1 banana 2
1 papaya 3
2 keyboard 1
2 laptop 2
SQL>
Consider the following table:
ID Feature
1 1
1 2
1 3
2 3
2 4
2 6
3 5
3 10
3 12
4 12
4 18
5 10
5 30
I would like to group the individuals based on overlapping features. If two of these groups again have overlapping features, I would consider both as one group. This process should be repeated until there are no overlapping features between groups. The result of this procedure on the table above would be:
ID Feature Flag
1 1 A
1 2 A
1 3 A
2 3 A
2 4 A
2 6 A
3 5 B
3 10 B
3 12 B
4 12 B
4 18 B
5 10 B
5 30 B
So actually the problem I am trying to solve is finding connected components in a graph. Here [1,2,3] is the graph with ID 1 (see https://en.wikipedia.org/wiki/Connectivity_(graph_theory)). The problem is equivalent to this problem, however I would like to solve it with Oracle SQL.
Here is one way to do this, using a hierarchical ("connect by") query. The first step is to extract the initial relationships from the base data; the hierarchical query is built on the result from this first step. I added one more row to the inputs to illustrate a node that is a connected component by itself.
You marked the connected components as A and B - of course, that won't work if you have, say, 30,000 connected components. In my solution, I use the minimum node name as the marker for each connected component.
with
sample_data (id, feature) as (
select 1, 1 from dual union all
select 1, 2 from dual union all
select 1, 3 from dual union all
select 2, 3 from dual union all
select 2, 4 from dual union all
select 2, 6 from dual union all
select 3, 5 from dual union all
select 3, 10 from dual union all
select 3, 12 from dual union all
select 4, 12 from dual union all
select 4, 18 from dual union all
select 5, 10 from dual union all
select 5, 30 from dual union all
select 6, 40 from dual
)
-- select * from sample_data; /*
, initial_rel(id_base, id_linked) as (
select distinct s1.id, s2.id
from sample_data s1 join sample_data s2
on s1.feature = s2.feature and s1.id <= s2.id
)
-- select * from initial_rel; /*
select id_linked as id, min(connect_by_root(id_base)) as id_group
from initial_rel
start with id_base <= id_linked
connect by nocycle prior id_linked = id_base and id_base < id_linked
group by id_linked
order by id_group, id
;
Output:
ID ID_GROUP
------- ----------
1 1
2 1
3 3
4 3
5 3
6 6
Then, if you need to add the ID_GROUP as a FLAG to the base data, you can do so with a trivial join.
The following SQL:
WITH vehicle_build_structure (part_no, parent_part, qty_per_assembly) AS (
SELECT 'LORRY', '*', null FROM dual UNION -- if it makes things easier, you may substitute "null" for "1"
SELECT 'AXLEA', 'LORRY', 3 FROM dual UNION
SELECT 'BRAKEPAD', 'AXLEA', 2 FROM dual UNION
SELECT 'WHEEL', 'AXLEA', 4 FROM dual UNION
SELECT 'CAR', '*', null FROM dual UNION
SELECT 'AXLEB', 'CAR', 2 FROM dual UNION
SELECT 'BRAKEPAD', 'AXLEB', 2 FROM dual UNION
SELECT 'WHEEL', 'AXLEB', 2 FROM dual
), exploded_structure AS (
SELECT level lev, CONNECT_BY_ROOT s.part_no top_level_part, s.part_no, s.qty_per_assembly,
(
SELECT sum (sp.qty_per_assembly)
FROM vehicle_build_structure sp
START WITH sp.part_no = s.part_no
CONNECT BY prior sp.parent_part = sp.part_no
) sum_of_structure,
(
SELECT exp(sum(ln(sp.qty_per_assembly)))
FROM vehicle_build_structure sp
START WITH sp.part_no = s.part_no
CONNECT BY prior sp.parent_part = sp.part_no
) product_of_structure
FROM vehicle_build_structure s
START WITH s.parent_part = '*'
CONNECT BY prior s.part_no = s.parent_part
)
SELECT e.lev, e.top_level_part, e.part_no, e.qty_per_assembly,
e.sum_of_structure, e.product_of_structure
FROM exploded_structure e
Produces the following grid:
LEV TOP_LEVE PART_NO QTY_PER_ASSEMBLY SUM_OF_STRUCTURE PRODUCT_OF_STRUCTURE
---------- -------- -------- ---------------- ---------------- --------------------
1 CAR CAR
2 CAR AXLEB 2 2 2
3 CAR BRAKEPAD 2 9 24
3 CAR WHEEL 2 11 48
1 LORRY LORRY
2 LORRY AXLEA 3 3 3
3 LORRY BRAKEPAD 2 9 24
3 LORRY WHEEL 4 11 48
But I need it to produce this grid:
LEV TOP_LEVE PART_NO QTY_PER_ASSEMBLY SUM_OF_STRUCTURE PRODUCT_OF_STRUCTURE
---------- -------- -------- ---------------- ---------------- --------------------
1 CAR CAR
2 CAR AXLEB 2 2 2
3 CAR BRAKEPAD 2 4 4
3 CAR WHEEL 2 4 4
1 LORRY LORRY
2 LORRY AXLEA 3 3 3
3 LORRY BRAKEPAD 2 5 6
3 LORRY WHEEL 4 7 12
In other words, I need the SQL to tell me that when I build a Car, I need 4 wheels, and that when I build a Lorry I need 12 wheels.
However, because the part WHEEL is part of both the CAR and LORRY structures, the internal connect-by-prior statement is working its way back up both branches, and multiplying the quantities of both structures together (12 * 4 = 48). Obviously when the structure gets very complex this ends up giving me a wildly incorrect answer.
I can't work out how to limit that internal connect-by-prior statement to only work its way back up the branch from where it came.
Can anyone help?
I think you should try do something about the duplicate values in column part_no. Expanding the tree beyond 'brakepad' and 'wheel' will be difficult with your table.
That being said, the query you are looking for should look like it (not tested on Oracle, please correct any typo):
WITH vehicle_build_structure (part_no, parent_part, qty_per_assembly) AS (
SELECT 'LORRY', '*', null FROM dual UNION
SELECT 'AXLEA', 'LORRY', 3 FROM dual UNION
SELECT 'BRAKEPAD', 'AXLEA', 2 FROM dual UNION
SELECT 'WHEEL', 'AXLEA', 4 FROM dual UNION
SELECT 'CAR', '*', null FROM dual UNION
SELECT 'AXLEB', 'CAR', 2 FROM dual UNION
SELECT 'BRAKEPAD', 'AXLEB', 2 FROM dual UNION
SELECT 'WHEEL', 'AXLEB', 2 FROM dual
), rec_build_structure (lv, top_part, part_no, qty_per_assembly, qty_sum, qty_product) AS (
SELECT 1, part_no, part_no, 1, 0, 1
FROM vehicle_build_structure
WHERE parent_part = '*'
UNION ALL
SELECT 1 + r.lv, r.top_part, v.part_no, v.qty_per_assembly,
v.qty_per_assembly + r.qty_sum,
v.qty_per_assembly * r.qty_product
FROM vehicle_build_structure v
INNER JOIN rec_build_structure r
ON v.parent_part = r.part_no
)
SELECT rbs.*
FROM rec_build_structure rbs
ORDER BY rbs.top_part, rbs.lv, rbs.part_no
I feel that this is jumping through hoops a little bit, but it does work. Basically, you write out your calculation as you traverse down the tree, and then do an eval() on the calculation that you've defined.
It works, but it still feels to me that there should be a construct in the language that does this a little bit more eloquently.
WITH vehicle_build_structure (part_no, parent_part, qty_per_assembly) AS (
SELECT 'LORRY', '*', null FROM dual UNION
SELECT 'AXLEA', 'LORRY', 3 FROM dual UNION
SELECT 'BRAKEPAD', 'AXLEA', 2 FROM dual UNION
SELECT 'WHEEL', 'AXLEA', 4 FROM dual UNION
SELECT 'CAR', '*', null FROM dual UNION
SELECT 'AXLEB', 'CAR', 2 FROM dual UNION
SELECT 'BRAKEPAD', 'AXLEB', 2 FROM dual UNION
SELECT 'WHEEL', 'AXLEB', 2 FROM dual
), exploded_structure AS (
SELECT level lev, CONNECT_BY_ROOT s.part_no top_level_part, s.part_no, s.qty_per_assembly,
nvl (ltrim (sys_connect_by_path (s.qty_per_assembly, '*'), '*'), 0) my_calculation
FROM vehicle_build_structure s
START WITH s.parent_part = '*'
CONNECT BY prior s.part_no = s.parent_part
)
SELECT e.lev, e.top_level_part, e.part_no, e.qty_per_assembly, e.my_calculation,
xmlquery (e.my_calculation returning content).getnumberval() my_result
FROM exploded_structure e
Produces:
LEV TOP_LEVE PART_NO QTY_PER_ASSEMBLY MY_CALCULATION MY_RESULT
---------- -------- -------- ---------------- ------------------------------ ----------
1 CAR CAR 0 0
2 CAR AXLEB 2 2 2
3 CAR BRAKEPAD 2 2*2 4
3 CAR WHEEL 2 2*2 4
1 LORRY LORRY 0 0
2 LORRY AXLEA 3 3 3
3 LORRY BRAKEPAD 2 3*2 6
3 LORRY WHEEL 4 3*4 12
I have the following table:
SELECT *
FROM mytable
ORDER BY id;
id name code time
1 A 111 1
2 A 111 2
3 A 888 3
4 A 888 4
5 A 888 5
6 A 888 6
7 A 888 7
8 A 111 8
9 A 111 9
10 A 111 10
I need to get a result like this:
name code times_between
A 111 1,2
A 888 3,7
A 111 8,10
Is it possible to group by "chunks"?
I need to make a distinction based on time, so I can't just group by name,code and get the first and last element only.
One way is this:
with the_table(id, name , code , time) as(
select 1, 'A',111 , 1 union all
select 2, 'A',111 , 2 union all
select 3, 'A',888 , 3 union all
select 4, 'A',888 , 4 union all
select 5, 'A',888 , 5 union all
select 6, 'A',888 , 6 union all
select 7, 'A',888 , 7 union all
select 8, 'A',111 , 8 union all
select 9, 'A',111 , 9 union all
select 10, 'A',111 , 10
)
select name, code, min(time) ||','|| max(time) from (
select name, code, time, id,
row_number() over(order by id) -
row_number() over(partition by name , code order by id) as grp
from the_table
) t
group by name, code, grp
order by min(id)
(I forgot and just can't find/remember the name of technique, which creates groups grp)
I have a table like below
select 1 group_rank, 1 row_rank union all
select 1 , 2 union all
select 1 , 3 union all
select 1 , 4 union all
select 1 , 5 union all
select 2 , 1 union all
select 2 , 2 union all
select 2 , 3 union all
select 2 , 4 union all
select 2 , 5 union all
select 3 , 1 union all
select 3 , 2 union all
select 3 , 3 union all
select 3 , 4 union all
select 3 , 5 union all
select 4 , 1 union all
select 4 , 2 union all
select 4 , 3 union all
select 4 , 4 union all
select 4 , 5
I want to break row_rank further based upon size. If my size is 2, split the row_rank further like below. Output third column should be like below
select 1 group_rank, 1 row_rank, 1 batch_number union all
select 1 , 2, 1 union all
select 1 , 3, 2 union all
select 1 , 4, 2 union all
select 1 , 5, 3 union all
select 2 , 1, 4 union all
select 2 , 2, 4 union all
select 2 , 3, 5 union all
select 2 , 4, 5 union all
select 2 , 5, 6 union all
select 3 , 1, 7 union all
select 3 , 2, 7 union all
select 3 , 3, 8 union all
select 3 , 4, 8 union all
select 3 , 5, 9 union all
select 4 , 1, 10 union all
select 4 , 2, 10 union all
select 4 , 3, 11 union all
select 4 , 4, 11 union all
select 4 , 5, 12
As the split size is 2,
first two rows with in the 1st group_rank gets 1st batch number,
third fourth rows with in 1st group_rank gets 2nd batch number,
fifth row with in 1st group_rank gets 3rd batch number,
first two rows with in the 2nd group_rank gets 4th batch number,
third fourth rows with in 2nd group_rank gets 5th batch number,
fifth row gets with in 3rd group_rank gets 6th batch number
... and so on ..
As, I vary the split size,,, the batch number should grow or shrink accordingly.
Please provide me sql server TSQL query to do this.
Thanks,
Sounds like simple math to me, with a little 0-based/1-based voodoo at least:
((row_number() over (order by group_rank, row_rank) - 1) / #batch_size) + 1
SQL Fiddle demo
What this does is:
row_number() over (order by group_rank, row_rank): get the row number ordered over the entire set, ordered first by group_rank then by row_rank
- 1: make the row numbers 0-based
/ #batch_size: divide by the "batch size" you want (integer division at its best)
+ 1: make the result 1-based to match your output