how to generate consecutive records with a given number? - sql

in oracle, is there a built-in function to produce consecutive records with a given number? For example, the number is 100, so that you can generate a result-set with 100 records whose values are 1, 2, 3, 4...100, like the following:
1
2
3
4
...
100
I know store procedure can do this, and I want to know if there are other ways just using sql statements?

select level
from dual
connect by level <= 100

Here is another approach, using model clause. (Oracle 10g and higher).
SQL> select x
2 from dual
3 model
4 dimension by (0 as z)
5 measures (0 as x)
6 rules iterate(101) (
7 x[iteration_number] = iteration_number
8 )
9 ;
X
----------
0
1
2
3
4
5
6
7
8
9
10
11
...
100

Please try using CTE:
WITH numbers(n) AS
(
SELECT 1 FROM dual
UNION ALL
SELECT n + 1 FROM numbers WHERE n < 100
)
SELECT * FROM numbers;

It's traditional to use a hierarchical query:
select level
from dual
connect by level <= 100

Related

HANA SQL - Create multiple rows from one row based on calculation

I have the following 2 records:
Batch
Qty
QtyPer
PackNum
One
60
20
3
Two
20
10
2
I need to find a way to create multiple rows based on Qty/PackNum, such as:
Batch
Qty
QtyPer
PackNum
Level
One
60
20
3
1
One
60
20
3
2
One
60
20
3
3
Two
20
10
2
1
Two
20
10
2
2
I've tried using the WITH command to no avail.
Any Ideas?
Marcus
one alternate is construct as dummy table or dummy sql with below structure and do the cross join on the pack_number. You could also try sql procedure with global/local temporary table
pack number, level
3,1
3,2
3,3
2,1
2,2
or
below is the pseudo sql
select it.*,dd.level from(
select 3 as pack_number , 1 as level from dummy
union all
select 3 as pack_number , 2 as level from dummy
union all
select 3 as pack_number , 3 as level from dummy
union all
select 2 as pack_number , 1 as level from dummy
union all
select 2 as pack_number , 2 as level from dummy
....
) as dummy_data as dd
inner join
input_tab as it
on dd. pack_number = it.pack_number

How to process a column that holds a comma-separated or range string values in Oracle

Using Oracle 12c DB, I have the following table data example that I need assistance with using SQL and PL/SQL.
Table data is as follows:
Table Name: my_data
ID ITEM ITEM_LOC
------- ----------- ----------------
1 Item-1 0,1
2 Item-2 0,1,2,3,4,7
3 Item-3 0-48
4 Item-4 0,1,2,3,4,5,6,7,8
5 Item-5 1-33
6 Item-6 0,1
7 Item-7 0,1,5,8
Using the data above within the my_data table, what is the best way to process this ITEM_LOC as I need to use the values in this column as an individual value, i.e:
0,1 means the SQL needs to return either 0 or 1 or
range values, i.e:
0-48 means the SQL needs to return a value between 0 and 48.
The returned values for both scenarios should commence from lowest to highest and can't be re-used once processed.
Based on the above, it would be great to have a function that takes the ID and returns an individual value from ITEM_LOC that hasn't been used, based on my description above. This could be a comma-separated string value or a range string value.
Desired result for ID = 2 could be 7. For this ID = 2, ITEM_LOC = 7 could not be used again.
Desired result for ID = 5 could be 31. For this ID = 5, ITEM_LOC = 31 could not be used again.
For the ITEM_LOC data that could not be used again, against that ID, I am looking at holding another table to hold this or perhaps separate all data into separate rows with a new column called VALUE_USED.
This query shows how to extract list of ITEM_LOC values based on whether they are comma-separated (which means "take exactly those values") or dash-separated (which means "find all values between starting and end point"). I modified your sample data a little bit (didn't feel like displaying ~50 values if 5 of them do the job).
lines #1 - 6 represent sample data.
the first select (lines #7 - 15) splits comma-separated values into rows
the second select (lines #17 - 26) uses a hierarchical query which adds 1 to the starting value, up to item's end value.
SQL> with my_data (id, item, item_loc) as
2 (select 2, 'Item-2', '0,2,4,7' from dual union all
3 select 7, 'Item-7', '0,1,5' from dual union all
4 select 3, 'Item-3', '0-4' from dual union all
5 select 8, 'Item-8', '5-8' from dual
6 )
7 select id,
8 item,
9 regexp_substr(item_loc, '[^,]+', 1, column_value) loc
10 from my_data
11 cross join table(cast(multiset
12 (select level from dual
13 connect by level <= regexp_count(item_loc, ',') + 1
14 ) as sys.odcinumberlist))
15 where instr(item_loc, '-') = 0
16 union all
17 select id,
18 item,
19 to_char(to_number(regexp_substr(item_loc, '^\d+')) + column_value - 1) loc
20 from my_data
21 cross join table(cast(multiset
22 (select level from dual
23 connect by level <= to_number(regexp_substr(item_loc, '\d+$')) -
24 to_number(regexp_substr(item_loc, '^\d+')) + 1
25 ) as sys.odcinumberlist))
26 where instr(item_loc, '-') > 0
27 order by id, item, loc;
ID ITEM LOC
---------- ------ ----------------------------------------
2 Item-2 0
2 Item-2 2
2 Item-2 4
2 Item-2 7
3 Item-3 0
3 Item-3 1
3 Item-3 2
3 Item-3 3
3 Item-3 4
7 Item-7 0
7 Item-7 1
7 Item-7 5
8 Item-8 5
8 Item-8 6
8 Item-8 7
8 Item-8 8
16 rows selected.
SQL>
I don't know what you meant by saying that "item_loc could not be used again". Used where? If you use the above query in, for example, cursor FOR loop, then yes - those values would be used only once as every loop iteration fetches next item_loc value.
As others have said, it's a bad idea to store data in this way. You very likely could have input like this, and you likely could need to display the data like this, but you don't have to store the data the way it is input or displayed.
I'm going to store the data as individual LOC elements based on the input. I assume the data contains only integers separated by commas, or pairs of integers separated by a hyphen. Whitespace is ignored. The comma-separated list does not have to be in any order. In pairs, if the left integer is greater than the right integer I return no LOC element.
create table t as
with input(id, item, item_loc) as (
select 1, 'Item-1', ' 0,1' from dual union all
select 2, 'Item-2', '0,1,2,3,4,7' from dual union all
select 3, 'Item-3', '0-48' from dual union all
select 4, 'Item-4', '0,1,2,3,4,5,6,7,8' from dual union all
select 5, 'Item-5', '1-33' from dual union all
select 6, 'Item-6', '0,1' from dual union all
select 7, 'Item-7', '0,1,5,8,7 - 11' from dual
)
select distinct id, item, loc from input, xmltable(
'let $item := if (contains($X,",")) then ora:tokenize($X,"\,") else $X
for $i in $item
let $j := if (contains($i,"-")) then ora:tokenize($i,"\-") else $i
for $k in xs:int($j[1]) to xs:int($j[count($j)])
return $k'
passing item_loc as X
columns loc number path '.'
);
Now to "use" an element I just delete it from the table:
delete from t where rowid = (
select min(rowid) keep (dense_rank first order by loc)
from t
where id = 7
);
To return the data in the same format it was input, use MATCH_RECOGNIZE:
select id, item, listagg(item_loc, ',') within group(order by first_loc) item_loc
from t
match_recognize(
partition by id, item order by loc
measures a.loc first_loc,
a.loc || case count(*) when 1 then null else '-'||b.loc end item_loc
pattern (a b*)
define b as loc = prev(loc) + 1
)
group by id, item;
ID ITEM ITEM_LOC
1 Item-1 0-1
2 Item-2 0-4,7
3 Item-3 0-48
4 Item-4 0-8
5 Item-5 1-33
6 Item-6 0-1
7 Item-7 1,5,7-11
Note that the output here will not be exactly like the input, because any consecutive integers will be compressed into a pair.

Populate a numbered row until reaching a specific value with another column

I have a table full of account numbers and period/terms for loan(loan term is in months)
What I need to do is populate a numbered row for each account number that is less than or equal to the loan term. I've attached a screen shot below:
Example
So for this specific example, I will need 48 numbered rows for this account number, as the term is only 48 months.
Thanks for the help!
with
test_data ( account_nmbr, term ) as (
select 'ABC200', 6 from dual union all
select 'DEF100', 8 from dual
)
-- End of simulated inputs (for testing purposes only, not part of the solution).
-- SQL query begins BELOW THIS LINE.
select level as row_nmbr, term, account_nmbr
from test_data
connect by level <= term
and prior account_nmbr = account_nmbr
and prior sys_guid() is not null
order by account_nmbr, row_nmbr -- If needed
;
ROW_NMBR TERM ACCOUNT_NMBR
-------- ---------- ------------
1 6 ABC200
2 6 ABC200
3 6 ABC200
4 6 ABC200
5 6 ABC200
6 6 ABC200
1 8 DEF100
2 8 DEF100
3 8 DEF100
4 8 DEF100
5 8 DEF100
6 8 DEF100
7 8 DEF100
8 8 DEF100
In Oracle 12, you can use the LATERAL clause for the same:
with
test_data ( account_nmbr, term ) as (
select 'ABC200', 6 from dual union all
select 'DEF100', 8 from dual
)
-- End of simulated inputs (for testing purposes only, not part of the solution).
-- SQL query begins BELOW THIS LINE.
select l.row_nmbr, t.term, t.account_nmbr
from test_data t,
lateral (select level as row_nmbr from dual connect by level <= term) l
order by account_nmbr, row_nmbr -- If needed
;

'X' Pattern in SQL

I am trying to generate a X pattern in SQL like below:
100001
010010
001100
010010
100001
It's a Grid Structure of 5x6 : 5 records with a value of 6 digits.
It may have dynamic grid of a given even number like 4, 8.
Any ideas will be really helpful.
I was trying something like below, Pattern:
select rpad('1',level, '1') from dual connect by level <= 5
But this would only form a right angle triangle
1
11
111
1111
11111
Using the recursiveness of a connect is a nice method.
The query below will generate an X in grid.
With a size you can choose by changing the number in the inner query.
select lpad('1',y,'0')||rpad('0',n-y*2,'0')||rpad('1',y,'0') as X
from (
select level as lvl, n, case when level<n/2 then level else n-level end as y
from (select 6 as n from dual)
connect by level < n
)
order by lvl
The subquery will generate numbers based on n.
For example, if n = 6 then the subquery will generate:
lvl n y
--- -- --
1 6 1
1 6 2
3 6 3
4 6 2
5 6 1
Then in the outer query those n and y numbers are used to calculate the amount of 0 to pad to the left, the middle and the right side.
When using 8 for n it generates the following grid:
10000001
01000010
00100100
00011000
00100100
01000010
10000001
To make it work also for the uneven numbers a few (mod)ifications are needed.
select lpad('1',y,'0')||rpad('0',n-y*2,'0')||case when mod(n,2) = 1 and lvl = trunc(n/2)+1 then rpad('0',y-1,'0') else rpad('1',y,'0') end as X
from (
select level as lvl, n, case when level<trunc(n/2)+mod(n,2) then level else n+mod(n,2)-level end as y
from (select 5 as n from dual)
connect by level < (case when mod(n,2) = 1 then n+1 else n end)
) q
order by lvl
For n = 5 that would generate:
10001
01010
00100
01010
10001

Using random value as join condition

I am generating some test-data and use dbms_random. I encountered some strange behavior when using dbms_random in the condition of the JOIN, that I can not explain:
------------------------# test-data (ids 1 .. 3)
With x As (
Select Rownum id From dual
Connect By Rownum <= 3
)
------------------------# end of test-data
Select x.id,
x2.id id2
From x
Join x x2 On ( x2.id = Floor(dbms_random.value(1, 4)) )
Floor(dbms_random.value(1, 4) ) returns a random number out of (1,2,3), so I would have expected all rows of x to be joined with a random row of x2, or maybe always the same random row of x2 in case the random number is evaluated only once.
When trying several times, I get results like that, though:
(1) ID ID2 (2) ID ID2 (3)
---- ---- ---- ---- no rows selected.
1 2 1 3
1 3 2 3
2 2 3 3
2 3
3 2
3 3
What am I missing?
EDIT:
SELECT ROWNUM, FLOOR(dbms_random.VALUE (1, 4))
FROM dual CONNECT BY ROWNUM <= 3
would get the result in this case, but why does the original query behave like that?
To generate three rows with one predictable value and one random value, try this:
SQL> with x as (
2 select rownum id from dual
3 connect by rownum <= 3
4 )
5 , y as (
6 select floor(dbms_random.value(1, 4)) floor_val
7 from dual
8 )
9 select x.id,
10 y.floor_val
11 from x
12 cross join y
13 /
ID FLOOR_VAL
---------- ----------
1 2
2 3
3 2
SQL
edit
Why did your original query return an inconsistent set of rows?
Well, without the random bit in the ON clause your query was basically a CROSS JOIN of X against X - it would have returned nine rows (at least it would have if the syntax had allowed it). Each of those nine rows executes a call to DBMS_RANDOM.VALUE(). Only when the random value matches the current value of X2.ID is the row included in the result set. Consequently the query can return 0-9 rows, randomly.
Your solution is obviously simpler - I didn't refactor enough :)