How to find largest value,any of remaining max values and last remaining max value not used before - oracle

I need to use max_weight,max_height and max_volume using Oracle SQL
as follows:
Length = largest value of Max Height, Max Width or Max Depth
Height = any of the remaining 2 max values
Width = last remaining max value not used before
I have attached basic query to get the max values but I need above filtered one data out of this.
How to write query in order to calculate the same
]1

In the mean time, you might work with this... run it to see what the results are, and then wrap it with and use Case When rmax = 1...
select
MAXNUM
,RANK() over( order by maxnum desc)as rmax
,dim
from (
select
2 as maxnum, 'weight' as dim
union
select
4 as maxnum, 'length' as dim
union
select
8 as maxnum, 'height' as dim
) as nnn

Suppose you have the following data (the columns are labeled col1, col2, col3 in my example; labeling them as "height" or "max_height" is pretty meaningless if the task is to determine WHICH of them is the "height" in each case).
col1 col2 col3
------ ------ ------
10 8 15
11 11 30
13 9 9
15 7 15
And you want to rearrange the numbers in each ROW so that the output looks like this:
length height width
------ ------ ------
15 10 8
30 11 11
13 9 9
15 15 7
Here is an efficient way to do it - you don't have to unpivot the inputs (to put all the numbers in one long column). It works because GREATEST can give you the length, LEAST can give you the width, and to get the middle one, you can add the three numbers and subtract the two extremes. This simplistic approach would NOT work if you had to rank four or more numeric attributes, but it works fine for three.
with
test_data(col1, col2, col3) as(
select 10, 8, 15 from dual union all
select 11, 11, 30 from dual union all
select 13, 9, 9 from dual union all
select 15, 7, 15 from dual
)
-- End of simulated data (not part of the solution).
-- SQL query begins BELOW THIS LINE. Use actual table and column names.
select col1, col2, col3,
greatest(col1, col2, col3) as length,
col1 + col2 + col3 - greatest(col1, col2, col3)
- least(col1, col2, col3) as height,
least(col1, col2, col3) as width
from test_data
;
COL1 COL2 COL3 LENGTH HEIGHT WIDTH
---------- ---------- ---------- ---------- ---------- ----------
10 8 15 15 10 8
11 11 30 30 11 11
13 9 9 13 9 9
15 7 15 15 15 7

Related

Why when using grouping_id with rollup the total will be level 3 instead of 2?

Is there any logic behind the reason to why the total grouping when using rollup will be "lvl" 3?..
example:
col1
col2
id1
1
id1
2
id2
1
id2
2
id3
1
id3
2
In cube method this is understandable as
level 0 is the "basic values" (my own term)such as "col1-ID1","col2-id1", "col3-id1" etc..,
level 1 will be the subtotal row of each basic value which means subtotal of ID1(1)+ID1(2) --> id1(3) for example.
level 2 will be the total of each combination which means subtotal of 1's and subtotal of 2's , in this example: 1- subtotal will be 3 and 2-subtotal will be 6
and level 3 will be the grand total of them all, in this example: 9
my explanation might not make any sense:) .. sorry
Is there any reason behind that skipping from lvl 0/1 straight to 3? or its just the way it is?
Summary: The grand total will be in the grouping set with the id the maximum value of the grouping set which is equal to 2(number of columns in the grouping set) - 1. Therefore, with 2 columns being cubed, the grand total is in 22-1 = 3 and, with 3 columns being cubed, the grand total is in 23-1 = 7.
Given the query:
SELECT LISTAGG(col1, ',') WITHIN GROUP (ORDER BY col1) AS col1,
LISTAGG(col2, ',') WITHIN GROUP (ORDER BY col2) AS col2,
GROUPING_ID(col1, col2) AS grp
FROM table_name
GROUP BY CUBE(col1, col2)
ORDER BY grp, col1, col2
Which, for the sample data:
CREATE TABLE table_name (col1, col2) AS
SELECT 'id1', 1 FROM DUAL UNION ALL
SELECT 'id1', 2 FROM DUAL UNION ALL
SELECT 'id2', 1 FROM DUAL UNION ALL
SELECT 'id2', 2 FROM DUAL UNION ALL
SELECT 'id3', 1 FROM DUAL UNION ALL
SELECT 'id3', 2 FROM DUAL;
Outputs:
COL1
COL2
GRP
id1
1
0
id1
2
0
id2
1
0
id2
2
0
id3
1
0
id3
2
0
id1,id1
1,2
1
id2,id2
1,2
1
id3,id3
1,2
1
id1,id2,id3
1,1,1
2
id1,id2,id3
2,2,2
2
id1,id1,id2,id2,id3,id3
1,1,1,2,2,2
3
You can see that when GROUPING_ID(col1, col2) is:
The un-grouped value.
The value grouped by the first column in the grouping set (and there is one value for the first column and N values for the second column).
The value grouped by the second column in the grouping set (and there are M values for the first column and one value for the second column).
The value grouped by the both columns in the grouping set (and there are M values for the first column and N values for the second column giving N*M total values); which will give you the grand total.
If you had the sample data with 3 columns:
CREATE TABLE table2 (col1, col2, col3) AS
SELECT t1.COLUMN_VALUE,
t2.COLUMN_VALUE,
t3.COLUMN_VALUE
FROM TABLE(SYS.ODCIVARCHAR2LIST('id1', 'id2', 'id3')) t1
CROSS JOIN TABLE(SYS.ODCINUMBERLIST(1, 2)) t2
CROSS JOIN TABLE(SYS.ODCINUMBERLIST(3, 4)) t3;
Then using CUBE across 3 columns:
SELECT LISTAGG(col1, ',') WITHIN GROUP (ORDER BY col1) AS col1,
LISTAGG(col2, ',') WITHIN GROUP (ORDER BY col2) AS col2,
LISTAGG(col3, ',') WITHIN GROUP (ORDER BY col3) AS col3,
GROUPING_ID(col1, col2, col3) AS grp
FROM table2
GROUP BY CUBE(col1, col2, col3)
ORDER BY grp, col1, col2, col3
Outputs:
COL1
COL2
COL3
GRP
id1
1
3
0
id1
1
4
0
id1
2
3
0
id1
2
4
0
id2
1
3
0
id2
1
4
0
id2
2
3
0
id2
2
4
0
id3
1
3
0
id3
1
4
0
id3
2
3
0
id3
2
4
0
id1,id1
1,1
3,4
1
id1,id1
2,2
3,4
1
id2,id2
1,1
3,4
1
id2,id2
2,2
3,4
1
id3,id3
1,1
3,4
1
id3,id3
2,2
3,4
1
id1,id1
1,2
3,3
2
id1,id1
1,2
4,4
2
id2,id2
1,2
3,3
2
id2,id2
1,2
4,4
2
id3,id3
1,2
3,3
2
id3,id3
1,2
4,4
2
id1,id1,id1,id1
1,1,2,2
3,3,4,4
3
id2,id2,id2,id2
1,1,2,2
3,3,4,4
3
id3,id3,id3,id3
1,1,2,2
3,3,4,4
3
id1,id2,id3
1,1,1
3,3,3
4
id1,id2,id3
1,1,1
4,4,4
4
id1,id2,id3
2,2,2
3,3,3
4
id1,id2,id3
2,2,2
4,4,4
4
id1,id1,id2,id2,id3,id3
1,1,1,1,1,1
3,3,3,4,4,4
5
id1,id1,id2,id2,id3,id3
2,2,2,2,2,2
3,3,3,4,4,4
5
id1,id1,id2,id2,id3,id3
1,1,1,2,2,2
3,3,3,3,3,3
6
id1,id1,id2,id2,id3,id3
1,1,1,2,2,2
4,4,4,4,4,4
6
id1,id1,id1,id1,id2,id2,id2,id2,id3,id3,id3,id3
1,1,1,1,1,1,2,2,2,2,2,2
3,3,3,3,3,3,4,4,4,4,4,4
7
And will generate 23 = 8 levels (from 0 to 7) since there are all the possible combinations of grouping 3 columns and the grand-total will be in level 7; compared to 22 levels (0 to 3) when you are cubing 2 columns and the grand total is in level 3.
fiddle
Update
What I don't understand is why roll up is skipping level 2 straight to 3?
From the SELECT documentation:
ROLLUP
The ROLLUP operation in the simple_grouping_clause groups the selected rows based on the values of the first n, n-1, n-2, ... 0 expressions in the GROUP BY specification, and returns a single row of summary for each group.
[...]
CUBE
The CUBE operation in the simple_grouping_clause groups the selected rows based on the values of all possible combinations of expressions in the specification. It returns a single row of summary information for each group.
CUBE generates all possible grouping sets; ROLLUP generates groups of the first 1 column then with the first 2 columns, 3 columns, ..., up to n columns which is the same as the CUBE when the grouping sets are restricted to the 20-1, 21-1, 22-1, ..., 2n-1 (or more simply 0, 1, 3, 7, ... 2n-1).
This means that ROLLUP will skip the grouping set with id 2 as that is grouping only by the 2nd column and that is not "one of the first n, n-1, n-2, ... 0 expressions" in the GROUP BY specification.
fiddle

oracle sql split string to rows (alphabet in sequential order) - Oracle SQL

I am looking for a oracle sql solution for the below problem:
Here's one option (as union of 3 different cases).
Sample data:
SQL> with test (col) as
2 (select 'A-D' from dual union all
3 select 'J-K' from dual union all
4 select 'X-20' from dual union all
5 select 'XX-20542' from dual union all
6 select 'A/B' from dual union all
7 select 'J/K' from dual
8 )
Query begins here:
9 -- A-D option:
10 select col,
11 chr(ascii(substr(col, 1, 1)) + column_value - 1) res
12 from test cross join
13 table(cast(multiset(select level from dual
14 connect by level <= ascii(substr(col, -1)) - ascii(substr(col, 1, 1)) + 1
15 ) as sys.odcinumberlist))
16 where regexp_like(col, '[[:alpha:]]-[[:alpha:]]')
17 -- XX-20542 option:
18 union all
19 select col, 'No action / ignore'
20 from test
21 where regexp_like(col, '[[:alpha:]]+-\d+')
22 -- A/B option:
23 union all
24 select col,
25 regexp_substr(col, '[[:alpha:]]', 1, column_value)
26 from test cross join
27 table(cast(multiset(select level from dual
28 connect by level <= ascii(substr(col, -1)) - ascii(substr(col, 1, 1)) + 1
29 ) as sys.odcinumberlist))
30 where regexp_like(col, '[[:alpha:]]/[[:alpha:]]');
Result:
COL RES
-------- ------------------
A-D A
A-D B
A-D C
A-D D
J-K J
J-K K
X-20 No action / ignore
XX-20542 No action / ignore
A/B A
A/B B
J/K J
J/K K
12 rows selected.
SQL>
You can use a single recursive query:
WITH ranges (value, min_value, skip, max_value) AS (
SELECT value,
REGEXP_SUBSTR(value, '^([A-Z])([[:punct:]])([A-Z])$', 1, 1, 'i', 1),
CASE REGEXP_SUBSTR(value, '^([A-Z])([[:punct:]])([A-Z])$', 1, 1, 'i', 2)
WHEN '-'
THEN 0
ELSE 1
END,
REGEXP_SUBSTR(value, '^([A-Z])([[:punct:]])([A-Z])$', 1, 1, 'i', 3)
FROM table_name
UNION ALL
SELECT value,
CASE skip
WHEN 0
THEN CHR(ASCII(min_value) + 1)
ELSE max_value
END,
skip,
max_value
FROM ranges
WHERE min_value < max_value
)
SEARCH DEPTH FIRST BY value SET rn
SELECT value,
COALESCE(min_value, 'ignore') AS result
FROM ranges
Which, for the sample data:
CREATE TABLE table_name (value) AS
SELECT 'A-D' FROM DUAL UNION ALL
SELECT 'J-K' FROM DUAL UNION ALL
SELECT 'XX-Y' FROM DUAL UNION ALL
SELECT 'Y-ZZ' FROM DUAL UNION ALL
SELECT 'B/C' FROM DUAL UNION ALL
SELECT 'E/E' FROM DUAL UNION ALL
SELECT 'F/H' FROM DUAL;
Outputs:
VALUE
RESULT
A-D
A
A-D
B
A-D
C
A-D
D
B/C
B
B/C
C
E/E
E
F/H
F
F/H
H
J-K
J
J-K
K
XX-Y
ignore
Y-ZZ
ignore
db<>fiddle here

Row data multiplication in Oracle

Consider following table where I am doing row data multiplication:
with v1 (member_id, the_number) as
(
select 1, 3 from dual union all
select 1, 5 from dual union all
select 2, 2 from dual union all
select 2, 3 from dual union all
select 2, 4 from dual union all
select 3, 9 from dual union all
select 3, 3 from dual union all
select 3, 2 from dual
)
select member_id, EXP(SUM(LN(the_number))) from v1
GROUP BY member_id;
It gives the correct result as:
MEMBER_ID EXP(SUM(LN(THE_NUMBER)))
1 15
2 24
3 54
The moment I put a negative value in the the_number column, I get the following Oracle error: ORA-01428: argument 'x' is out of range This is because the range for LN () argument is > 0.
How can I modify the query so that I can have negative values as well in the_number column? I am using Oracle 11g.
Get the product of the absolute values of the numbers and finally multiply by -1 or 1 depending on whether there is an odd or even number of negative numbers:
select
member_id,
CASE WHEN MOD(SUM(CASE WHEN the_number < 0 THEN 1 ELSE 0 END), 2) = 1 THEN -1 ELSE 1 END *
EXP(SUM(LN(ABS(the_number)))) from v1
GROUP BY member_id;
See the demo.

Sum values by date range in multiple columns

I need to sum value by date range in multiple columns. Every date range is one week of a month. It can be shorter than 7 days if it is the start of the month or the end of the month.
For example, I have dates for February:
my_user my_date my_value
A 01.02.2019 100
A 02.02.2019 150
B 01.02.2019 90
Z 28.02.2019 120
How can I have in date range format such as below?
my_user 01/02-03/02 04/02-10/02 11/02-17/02 18/02-24/02 25/02-28/02
A 250 0 0 0 0
B 90 0 0 0 0
Z 0 0 0 0 120
Any suggestions? Thanks!
You can do this:
select *
from (
select to_char(dt, 'iw') - to_char(trunc(dt, 'month'), 'iw') + 1 wk, usr, val from t)
pivot (sum(val) for wk in (1, 2, 3, 4, 5, 6))
demo
USR 1 2 3 4 5 6
--- ---------- ---------- ---------- ---------- ---------- ----------
A 250
B 90
Z 120
Header numbers are the weeks of month. Maximum may be 6 if month starts at the end of the week and is longer than 28 days.
Similiar way you can find first and last day of each week if needed, but you can't put them as headers, or at least not easily.
Edit:
it is possible to define certain date range with pivot, simple as two
dates? For example, I need to sum values from 5 December 2018 to 4
January 2019, 5 January 2019 to 4 February 2019, 5 March 2019 to 4
April 2019
Yes. Everything depends on how we count first and next weeks. Here:
to_char(dt, 'iw') - to_char(trunc(dt, 'month'), 'iw') + 1
i am subtracting week in year for given date and week in year of first day in month for this date. You can simply replace this second value with your starting date, either by hardcoding it in your query or by sending parameter to query or finding minimum date at first in a subquery:
(to_char(dt, 'iw') - to_char(date '2019-03-05', 'iw')) + 1
or
(to_char(dt, 'iw') - to_char((select min(dt) from data), 'iw')) + 1
Edit 2:
There is one problem however. When user defined period contains two or more years. to_date(..., 'iw') works fine for one year, but for two we get values 51, 52, 01, 02... We have to deal with this somehow, for instance like here:
with t(dt1, dt2) as (select date '2018-12-16', date '2019-01-15' from dual)
select min(dt) mnd, max(dt) mxd, iw, row_number() over (order by min(dt)) rn
from (select dt1 + level - 1 dt, to_char(dt1 + level - 1, 'iw') iw
from t connect by level -1 <= dt2 - dt1)
group by iw
which gives us:
MND MXD IW RN
----------- ----------- -- ----------
2018-12-16 2018-12-16 50 1
2018-12-17 2018-12-23 51 2
2018-12-24 2018-12-30 52 3
2018-12-31 2019-01-06 01 4
2019-01-07 2019-01-13 02 5
2019-01-14 2019-01-15 03 6
In first line we have user defined date ranges. Then I did hierarchical query looping through all dates in range assigning week, then grouped by this week, found start and end dates for each week and assigned row number rn which can be further used by pivot.
You can now simply join your input data with this query, let's name it weeks:
from data join weeks on dt between mnd and mxd
and make pivot. But for longer periods you have to find how many weeks there can be and specify them in pivot clause in (1, 2, 3, 4...). You can also add aliases if you need:
pivot ... for rn in (1 week01, 2 week02... 12 week12)
There is no simply way to avoid listing them manually. If you need it please look for oracle dynamic pivot in SO, there where hundreds similiar questions already. ;-)

Select Top (Max) Amount From Two Of Four Fields (Columns)

I have this query
SELECT code, username, week1money, week2money, week3money, week4money FROM(
--subquery goes here
)
How to select the top two weeks, i.e. weeks with the highest value? I want to sum the top two weeks to be precise.
If I understand correct you want to get 2 top values per every (code, username) row and (code, username) is a key of recordset.
Supposing you can have two top weeks with the same values and you don't have nulls this might be one of solutions:
SQL> with t (id, code, week1, week2, week3, week4)
2 as (
3 select 1, 'a', 10, 15, 11, 8 from dual union all
4 select 2, 'b', 7, 4, 2, 9 from dual union all
5 select 3, 'c', 3, 3, 1, 0 from dual
6 )
7 select id, code, max(week) first_top, min(week) next_top from (
8 select id, code, row_number() over(partition by id, code order by week desc) rnk, week
9 from (
10 select t.id, t.code,
11 decode(r.rn,1,week1,2,week2,3,week3,4,week4) week
12 from t,
13 (select rownum rn from dual connect by level <= 4) r
14 ))
15 where rnk in (1,2)
16 group by id, code
17 /
ID C FIRST_TOP NEXT_TOP
---------- - ---------- ----------
3 c 3 3
1 a 15 11
2 b 9 7
If you have non-null and different values in weeks you can use something like:
SQL> with t (id, code, week1, week2, week3, week4)
2 as (
3 select 1, 'a', 10, 15, 11, 8 from dual union all
4 select 2, 'b', 7, 4, 2, 9 from dual union all
5 select 3, 'c', 3, 2, 1, 0 from dual
6 )
7 select id, code
8 , greatest(week1, week2, week3, week4) first_top
9 , greatest(
10 case when week1 < greatest(week1, week2, week3, week4) then week1 else -1e28 end,
11 case when week2 < greatest(week1, week2, week3, week4) then week2 else -1e28 end,
12 case when week3 < greatest(week1, week2, week3, week4) then week3 else -1e28 end,
13 case when week4 < greatest(week1, week2, week3, week4) then week4 else -1e28 end
14 ) second_top
15 from t
16 /
ID C FIRST_TOP SECOND_TOP
---------- - ---------- ----------
1 a 15 11
2 b 9 7
3 c 3 2
But to get the right solution more details are required.
Answering my question...
select * from(
select * from(
select week1money col from dual
union
select week2money col from dual
union
select week3money col from dual
union
select week4money col from dual
) order by col desc
) where rownum < 3
Using GREATESTS() also may help.

Resources