Oracle generate Data - sql

I tried to generate data using the "by level" expression.
I want to generate GROUPNR, starting with 1,
meanwhile each group contains 4 items with nr 1 to 4
This is, how the result should look like
groupnr itemnr
1 1
1 2
1 3
1 4
2 1
2 2
2 3
2 4
3 1
I used this statement, but I have no idea how to handle the nvl2 or other functions to get the right values
select level, floor(level+3/4) GROUPNR, nvl2(0, mod(level,4),4) ITEMNR
from dual
connect by level <= 25;
The result of the select is:
groupnr itemnr
1 1
1 2
1 3
1 0
2 1
2 2
2 3
2 0
4 1
Please give me a hint how to modify the level to get the right values.
Bye Jochen

This could be a way:
select floor((level -1) / 4) +1 as groupNR,
row_number() over (partition by floor((level -1) / 4) +1 order by level) as itemNR
from dual
connect by level <= 25;
or even, without analytic functions:
level - 4*floor((level -1) / 4) as itemNR
TEST:
SQL> select floor((level -1) / 4) +1 as groupNR,
2 row_number() over (partition by floor((level -1) / 4) +1 order by level) as itemNR,
3 level - 4*floor((level -1) / 4) as itemNR_2
4 from dual
5 connect by level <= 25
6 order by level;
GROUPNR ITEMNR ITEMNR_2
---------- ---------- ----------
1 1 1
1 2 2
1 3 3
1 4 4
2 1 1
2 2 2
2 3 3
2 4 4
3 1 1
3 2 2
3 3 3
3 4 4
4 1 1
4 2 2
4 3 3
4 4 4
5 1 1
5 2 2
5 3 3
5 4 4
6 1 1
6 2 2
6 3 3
6 4 4
7 1 1
25 rows selected.

as varaition to Aleksejs solution (Thanks):
select floor((level +3) / 4) as groupNR,
row_number() over (partition by floor((level +3) / 4) +1 order by 1) as itemNR
from dual
connect by level <= 25;

Your query was very close. I suggest you change it to:
select level,
TRUNC((LEVEL-1) / 4) + 1 AS GROUPNR,
mod(LEVEL-1, 4) + 1 AS ITEMNR
from dual
connect by level <= 25
This produces:
LEVEL GROUPNR ITEMNR
1 1 1
2 1 2
3 1 3
4 1 4
5 2 1
6 2 2
7 2 3
8 2 4
9 3 1
10 3 2
11 3 3
12 3 4
13 4 1
14 4 2
15 4 3
16 4 4
17 5 1
18 5 2
19 5 3
20 5 4
21 6 1
22 6 2
23 6 3
24 6 4
25 7 1
LEVEL won't be NULL so there's no need to fiddle around with NVL or anything like that.
Best of luck.

Related

Fill in sequence numbers in sql oracle

I have a table that includes two columns, these columns have ranges i.e
Batch from _serial_no to_serial_no
a 1 5
b 2 7
I want to create another column to fill in the gaps for a abd b separately
Something like this
Batch from _serial_no to_serial_no seq_number
a 1 5 1
a 1 5 2
a 1 5 3
a 1 5 4
a 1 5 5
b 2 7 2
b 2 7 3
b 2 7 4
b 2 7 5
b 2 7 6
b 2 7 7
Is there an sql I could use?
I tried something like this but it didn't work
select *
from (
select a.*,rownum n
from my_table a connect by level <= TO_SERIAL_NO
)
where n >= FROM_SERIAL_NO;
SQL> with
2 data as (select 'a' batch, 1 from_serial_number, 5 to_serial_number from dual
3 union all
4 select 'b' batch, 2 from_serial_number, 7 to_serial_number from dual),
5 seq as (select rownum n# from dual connect by level <= (select max(to_serial_number) from data))
6 select
7 data.*,
8 seq.n#
9 from
10 data,
11 seq
12 where
13 seq.n# between data.from_serial_number and data.to_serial_number
14 order by
15 1, 2, 4;
BATCH FROM_SERIAL_NUMBER TO_SERIAL_NUMBER N#
----- ------------------ ---------------- ----------
a 1 5 1
a 1 5 2
a 1 5 3
a 1 5 4
a 1 5 5
b 2 7 2
b 2 7 3
b 2 7 4
b 2 7 5
b 2 7 6
b 2 7 7
11 rows selected
Yet another option:
SQL> with test (batch, from_serial_no, to_serial_no) as
2 (select 'a', 1, 5 from dual union
3 select 'b', 2, 7 from dual
4 )
5 select
6 batch,
7 from_serial_no,
8 to_serial_no,
9 froM_serial_no + column_value - 1 seq_number
10 from test,
11 table(cast(multiset(select level from dual
12 connect by level <= to_serial_no - from_serial_no + 1
13 ) as sys.odcinumberlist))
14 order by batch, seq_number;
B FROM_SERIAL_NO TO_SERIAL_NO SEQ_NUMBER
- -------------- ------------ ----------
a 1 5 1
a 1 5 2
a 1 5 3
a 1 5 4
a 1 5 5
b 2 7 2
b 2 7 3
b 2 7 4
b 2 7 5
b 2 7 6
b 2 7 7
11 rows selected.
SQL>
Using a join
select d.*, t.seq_number
from data d
join
(
SELECT from_serial_no + level - 1 seq_number
FROM (select min(from_serial_no) from_serial_no,
max(to_serial_no) to_serial_no
from data) t
CONNECT BY from_serial_no + level - 1 <= to_serial_no
) t on d.from_serial_no <= t.seq_number and
d.to_serial_no >= t.seq_number
order by d.batch, t.seq_number;
dbfiddle demo

Repeat values with in the GROUP in SQL

I am trying to repeat a row value in the subsequent rows with in GROUP. A Group can have one or more TAG. The requirement is to populate NEW_TAG in the row where the TAG is populated and in the subsequent rows until another TAG populated with in the same group or we reach end of that GROUP.
Current Table Required Table
GROUPID SEQ TAG GROUPID SEQ TAG NEW_TAG
------- --- ---- ------- --- --- --------
1 1 1 1
1 2 1 2
1 3 1 3
1 4 4 1 4 4 4
1 5 1 5 4
1 6 1 6 4
1 7 1 7 4
1 8 1 8 4
2 1 2 1
2 2 2 2
2 3 2 3
2 4 2 4
2 5 5 2 5 5 5
2 6 2 6 5
2 7 2 7 5
2 8 2 8 5
2 9 9 2 9 9 9
2 10 2 10 9
2 11 2 11 9
select
groupid,
seq,
tag,
last_value(tag) ignore nulls over (
partition by groupid
order by seq
) as new_tag
from t
order by groupid, seq;
GRO SEQ TAG NEW_TAG
1 1 - -
1 2 - -
1 3 - -
1 4 4 4
1 5 - 4
1 6 - 4
1 7 - 4
1 8 - 4
2 1 - -
2 2 - -
2 3 - -
2 4 - -
2 5 5 5
2 6 - 5
2 7 - 5
2 8 - 5
2 9 9 9
2 10 - 9
2 11 - 9
19 rows selected.

summarising a 3 months sales report across 2 branches into top 3 product for each month

I have the following REPORT table
m = month,
pid = product_id,
bid = branch_id,
s = sales
m pid bid s
--------------------------
1 1 1 20
1 3 1 11
1 2 1 14
1 4 1 16
1 5 1 31
1 1 2 30
1 3 2 10
1 2 2 24
1 4 2 17
1 5 2 41
2 3 1 43
2 5 1 21
2 4 1 10
2 1 1 5
2 2 1 12
2 3 2 22
2 5 2 10
2 4 2 5
2 1 2 4
2 2 2 10
3 3 1 21
3 5 1 10
3 4 1 44
3 1 1 4
3 2 1 14
3 3 2 10
3 5 2 5
3 4 2 6
3 1 2 7
3 2 2 10
I'd like to have a summary of this sales table
by showing the top 3 sales among the products across all branches.
something like this:
m pid total
---------------------
1 5 72
1 1 50
1 4 33
2 3 65
2 5 31
2 2 22
3 4 50
3 3 31
3 2 24
so on month 1, product #5 has the highest total sales with 72, followed by product #1 is 50.. and so on. if i could separate them into different table for each month would be better
so far what i can do is make a summary for 1 month and shows the entire thing and not top 3.
select pid, sum(s)
from report
where m = 1
group by pid
order by sum(s);
thanks a lot!
Most databases support the ANSI standard window functions. You can do what you want with row_number():
select m, pid, s
from (select r.m, r.pid, sum(s) as s,
row_number() over (partition by m order by sum(s) desc) as seqnum
from report r
group by r.m, r.pid
) r
where seqnum <= 3
order by m, s desc;

SQL or PL/SQL queries to Print sequence of given N numbers

I need help in building the query as the below structured.i have tried using for loop but its just printing as 1 2 3 4 5 6 7 8 9
For example if the N Value is 9 means the output should be like this.
EXAMPLE Output :
0
0 1
0 1 2
0 1 2 3
0 1 2 3 4
0 1 2 3 4 5
0 1 2 3 4 5 6
0 1 2 3 4 5 6 7
0 1 2 3 4 5 6 7 8
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8
0 1 2 3 4 5 6 7
0 1 2 3 4 5 6
0 1 2 3 4 5
0 1 2 3 4
0 1 2 3
0 1 2
0 1
0
To do this just in SQL - albeit with a bind variable if you want to be able to specify n - you need to combine start with the connect by and build from there. This is one way, though I'm pretty sure it can be done without the union:
with t as (
select level as rn, level - 1 as val
from dual
connect by level <= :n + 1
)
select t1.rn as rn,
listagg(t2.val, ' ') within group (order by t2.val) as answer
from t t1
join t t2 on t2.val <= t1.val
group by t1.rn, t1.val
union all
select (2 * (:n + 1)) - t1.rn,
listagg(t2.val, ' ') within group (order by t2.val) as answer
from t t1
join t t2 on t2.val <= t1.val
where t1.rn <= :n
group by t1.rn, t1.val
order by rn;
The CTE generates the numbers 0 to n. The two halves of the union create the mirror halves of the output; the second has the rn <= :n filter to prevent the 'middle' line being duplicated.
With:
var n number;
exec :n := 9;
This gives:
RN ANSWER
------ ----------------------------------------
1 0
2 0 1
3 0 1 2
4 0 1 2 3
5 0 1 2 3 4
6 0 1 2 3 4 5
7 0 1 2 3 4 5 6
8 0 1 2 3 4 5 6 7
9 0 1 2 3 4 5 6 7 8
10 0 1 2 3 4 5 6 7 8 9
11 0 1 2 3 4 5 6 7 8
12 0 1 2 3 4 5 6 7
13 0 1 2 3 4 5 6
14 0 1 2 3 4 5
15 0 1 2 3 4
16 0 1 2 3
17 0 1 2
18 0 1
19 0
Or for 6:
exec :n := 6;
RN ANSWER
------ ----------------------------------------
1 0
2 0 1
3 0 1 2
4 0 1 2 3
5 0 1 2 3 4
6 0 1 2 3 4 5
7 0 1 2 3 4 5 6
8 0 1 2 3 4 5
9 0 1 2 3 4
10 0 1 2 3
11 0 1 2
12 0 1
13 0
You don't really want to see the rn but you can remove that by putting this in a subquery.
This select query gives exact result:
WITH CTE1 AS
(SELECT 9 AS COL FROM DUAL)
,CTE2
AS (
SELECT LEVEL - 1 AS A,
SYS_CONNECT_BY_PATH(LEVEL - 1, ' ') AS B,
COL
FROM DUAL, CTE1
CONNECT BY LEVEL <= COL + 1)
SELECT B
FROM (SELECT A, B FROM CTE2
UNION ALL
SELECT 2 * COL - A, B
FROM CTE2
WHERE A != COL)
ORDER BY A;
OUTPUT:
0
0 1
0 1 2
0 1 2 3
0 1 2 3 4
0 1 2 3 4 5
0 1 2 3 4 5 6
0 1 2 3 4 5 6 7
0 1 2 3 4 5 6 7 8
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8
0 1 2 3 4 5 6 7
0 1 2 3 4 5 6
0 1 2 3 4 5
0 1 2 3 4
0 1 2 3
0 1 2
0 1
0
Using a while loop:
DECLARE
my_limit SIMPLE_INTEGER := 9;
my_step SIMPLE_INTEGER := +1;
i SIMPLE_INTEGER := 0;
s VARCHAR2(32000);
BEGIN
WHILE (i > -1) LOOP
s := '';
FOR j IN 0 .. i LOOP
IF j>0 THEN s := s || ' '; END IF;
s := s || to_char(j);
END LOOP;
dbms_output.put_line(s);
IF (i >= my_limit) THEN my_step := -1; END IF;
i := i + my_step;
END LOOP;
END;
/
I think, that sys_connect_by_path is what you are looking for.
Oracle Doc
SYS_CONNECT_BY_PATH is valid only in hierarchical queries. It returns the path of a column value from root to node, with column values separated by char for each row returned by CONNECT BY condition.
with desired_number as (select 5 as nm from dual)
,tree
as ( select level - 1 as a, sys_connect_by_path(level - 1, ' ') as b, nm
from dual, desired_number
connect by level <= nm + 1)
select *
from (select a, b from tree
union all
select 2 * nm - a, b
from tree
where a != nm)
order by a
Btw, how do you format it like sql?
You may find connect by to be a useful basis for working out the answer.
select level n
from dual
connect by level <= 10
Using three nested for loops:
DECLARE
my_limit SIMPLE_INTEGER := 9;
PROCEDURE one_line(n simple_integer) IS
s VARCHAR2(4000);
BEGIN
FOR j IN 0 .. n LOOP
IF j>0 THEN s := s || ' '; END IF;
s := s || to_char(j);
END LOOP;
dbms_output.put_line(s);
END one_line;
BEGIN
FOR i IN 0 .. my_limit LOOP
one_line(i);
END LOOP;
FOR i IN REVERSE 0 .. my_limit-1 LOOP
one_line(i);
END LOOP;
END;
/
Got it at last:
WITH CTE AS
(
SELECT LEVEL-1 COL
FROM DUAL
CONNECT BY LEVEL <= 10
)
SELECT SYS_CONNECT_BY_PATH(COL, ' ') COL
FROM ( SELECT COL,
ROW_NUMBER() OVER (ORDER BY COL) RN
FROM CTE)
START WITH RN = 1
CONNECT BY PRIOR RN = RN - 1
UNION ALL
SELECT COL FROM(
SELECT RPAD(SYS_CONNECT_BY_PATH(COL, ' ') , '20' )COL
FROM ( SELECT COL,
ROW_NUMBER() OVER (ORDER BY COL) RN
FROM CTE)
START WITH RN = 1
CONNECT BY PRIOR RN = (RN - 1 )
ORDER BY COL DESC
);
Produces This output:
0
0 1
0 1 2
0 1 2 3
0 1 2 3 4
0 1 2 3 4 5
0 1 2 3 4 5 6
0 1 2 3 4 5 6 7
0 1 2 3 4 5 6 7 8
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8
0 1 2 3 4 5 6 7
0 1 2 3 4 5 6
0 1 2 3 4 5
0 1 2 3 4
0 1 2 3
0 1 2
0 1
0

SQL - create pivot table/cross tab from raw data

What is the best way to transform the layout of a flat table into a crosstab/pivot table with SQL.
there is no need to calculations.
The first column of the raw table will be the first column in PV table, second colum of raw will be spitted in columns (12 distinct values) in PVtable. Values will be then the third column in raw table.
I am having difficulties to transform this layout and I guess I am putting to much cases for this making it hard to read/maintain.
does anyone has an idea how to do it?
Many thanks!
Example, below
RAW:
indx rank score
1 1 59
1 2 15
1 3 17
1 4 7
1 5 56
1 6 13
1 7 7
1 8 3
1 9 7
1 10 10
1 11 2
1 12 181
2 2 16
2 3 19
2 4 7
2 5 79
2 6 20
2 7 13
2 8 5
2 9 10
2 10 18
2 11 5
2 12 268
3 3 12
3 4 6
3 5 56
3 6 10
3 7 9
3 8 5
3 9 8
3 10 17
3 11 3
3 12 219
4 4 1
4 5 19
4 6 4
4 7 3
4 8 2
4 9 6
4 10 5
4 11 1
4 12 102
PVtable:
Rank
indx 1 2 3 4 5 6 7 8 9 10 11 12
1 59 15 17 7 56 13 7 3 7 10 2 181
2 - 16 19 7 79 20 13 5 10 18 5 267
3 - - 12 6 56 10 9 5 8 17 3 219
4 - - - 1 19 4 3 2 6 5 1 101
5 - - - - 0 0 0 0 0 0 0 0
6 - - - - - 0 0 0 0 0 0 0
7 - - - - - - 0 0 0 0 0 0
8 - - - - - - - 0 0 0 0 0
9 - - - - - - - - 0 0 0 0
10 - - - - - - - - - 0 0 0
11 - - - - - - - - - - 0 0
12 - - - - - - - - - - - 0
A canonical way that works in just about any database is the conditional aggregation approach:
select indx
max(case when rank = 1 then score end) as rank1,
max(case when rank = 2 then score end) as rank2,
. . .
max(case when rank = 12 then score end) as rank12
from table t
group by indx
order by indx;
In your example, though, it is unclear what indexes of 5 and more all have values of 0.
EDIT:
If you want the values of 5+ to be 0 and to get values from 1-2, then use a left outer join and change the logic in the max():
select n.n as indx,
max(case when rank = 1 then score end) as rank1,
max(case when rank = 2 then score end) as rank2,
. . .
max(case when rank = 12 then score when n.n >= 12 then 0 end) as rank12
from (select 1 as n union all select 2 union all select 3 union all select 4 union all
select 5 union all select 6 union all select 7 union all select 8 union all
select 9 union all select 10 union all select 11 union all select 12
) n left outer join
table t
on n.n = t.indx
group by n.n
order by n.n;