I need to put the number 2 in the company that is CODCIA, the sequence is 1,2,3,4 etc. and when putting the number 4 the sequence is reset to 1.
I only have 1 table with a codcia field.
I have it this way:
CODCIA NRODOCTO
------ --------
2 1
2 2
2 3
4 4
4 5
4 6
WITH THIS CODE:
IF (:E.NRODOCTO in (2)) then
BEGIN
SELECT NVL(MAX(NRODOCTO),1200)+1
INTO :E.NRODOCTO
FROM PS91;
EXCEPTION WHEN OTHERS THEN :E.NRODOCTO := 1200;
END;
END IF;
and I need this:
CODCIA NRODOCTO
------ --------
2 1
2 2
2 3
4 1
4 2
4 3
It seems that quite a lot of information is missing in your question; not that you should post absolutely all information, but oversimplifying it also doesn't help.
Anyway: I'd say that you might be using the POST-INSERT trigger and update NRODOCTO column value, because - doing that in a form would require looping through the whole block, paying attention to CODCIA item value and incrementing the appropriate NRODOCTO value, and doing it for all distinct CODCIA values. That's somewhat dull as nobody prevents user from entering mixed CODCIA values.
Here's a SQL*Plus-based example (as I'm not going to create a form for it). The PS91 table most probably doesn't look as simple as that (re-read my first sentence), but that's what you told us.
SQL> create table ps91 (codcia) as
2 (select 2 from dual union all
3 select 2 from dual union all
4 select 2 from dual union all
5 --
6 select 4 from dual union all
7 select 4 from dual
8 );
Table created.
SQL> alter table ps91 add nrodocto number;
Table altered.
SQL> -- POST-INSERT form trigger
SQL> merge into ps91 p
2 using (select rowid rid, row_number() over (partition by codcia order by null) rn
3 from ps91
4 ) x
5 on (p.rowid = x.rid)
6 when matched then update set p.nrodocto = x.rn;
5 rows merged.
SQL> select * from ps91
2 order by codcia, nrodocto;
CODCIA NRODOCTO
---------- ----------
2 1
2 2
2 3
4 1
4 2
SQL>
Related
I have query like this
select 1,2,3 from dual
union all
select 1,2,3 from dual
When I need to add new row, i put another union all, and that's ok. But problem appear when I need several union, for example 20. It is really annoying and not efficient to make another 17 unions. Is there a way (some procedure, function whatever) to make it faster and more elegant?
No problem, easy-peasy.
SQL> select 1, 2, 3
2 from dual
3 connect by level <= 10;
1 2 3
---------- ---------- ----------
1 2 3
1 2 3
1 2 3
1 2 3
1 2 3
1 2 3
1 2 3
1 2 3
1 2 3
1 2 3
10 rows selected.
SQL>
Sometimes it's easier to use json_table in such cases:
select *
from json_table(
'{data:[
[1,2,3,"abc"],
[2,3,4,"def"],
[3,4,5,"xyz"],
]
}'
,'$.data[*]'
columns
a number path '$[0]',
b number path '$[1]',
c number path '$[2]',
d varchar2(30) path '$[3]'
);
Results:
A B C D
---------- ---------- ---------- ------------------------------
1 2 3 abc
2 3 4 def
3 4 5 xyz
A variation on Littlefoot's answer:
select 1, 2, 3
from xmltable('1 to 20');
I made a hierarchical structure table(reply-type board) in Oracle.
However, the difference is that each value refers not directly to the board_num above, but to the number of the final value. When creating a deletion query under these conditions, how can I delete only own sub-values, including themselves? Below is my code.
select rownum"RNUM", board_num"num", board_re_ref"ref", board_re_lev"lev",board_re_seq"seq"
from (
select *
from board
order by BOARD_RE_REF desc, BOARD_RE_SEQ asc
);
RNUM num ref lev seq
--------- ---------- ---------- ---------- ----------
1 6 6 0 0
2 7 6 1 1
3 1 1 0 0
4 5 1 1 1
5 3 1 1 2
6 8 1 2 3
7 2 1 1 4
8 4 1 2 5
when i delete num1, delete 2,3,4,5,8
when i delete num2, delete 4
when i delete num3, delete 8
when i delete num6, delete7
and I have to do this with just one delete query.
I think I finally understood your question. It's like a blog and comment section. When you delete the blog, all its comments should also be deleted. If you delete a comment, then replies to that comment should also be deleted.
This delete statement might be the one you need, based on your sample data:
delete board
where board_num in (
select board_num
from (
select b2.*,
lag(board_re_lev) over (order by board_re_ref desc, board_re_seq) prev_lev,
lag(board_re_seq) over (order by board_re_ref desc, board_re_seq) prev_seq
from (
select b1.*
from board b1
where board_re_ref=(select board_re_ref from board where board_num=&num_del)
and board_re_seq>=(select board_re_seq from board where board_num=&num_del)
and (
(board_re_lev=(select board_re_lev from board where board_num=&num_del) and board_num=&num_del)
or (board_re_lev>(select board_re_lev from board where board_num=&num_del))
)
) b2
)
where board_re_seq=nvl(prev_seq,board_re_seq-1)+1
);
This statement, however, does not support level 1 rows with 2 or more level 2 rows.
It should be easier if you restructure your table and add foreign key constraint that deletes the child records when the parent row is deleted.
I have a table A which represents a valid sequence of numbers, which looks something like this:
| id | start | end | step |
|----|-------|-------|------|
| 1 | 4000 | 4999 | 4 |
| 2 | 3 | 20000 | 1 |
A[1] thus represents the sequence [4000, 4004, 4008, ...4996]
and another B of "occupied" numbers that looks like this:
| id | number | ... |
|-----|--------|-----|
| 1 | 4000 | ... |
| 2 | 4003 | ... |
| ... | ... | ... |
I want to construct a query which using A and B, finds the first unoccupied number for a particular sequence.
I have been trying – and failing – to do, is to generate a list of valid numbers from a row in A and then left outer join table B on B.number = valid_number where B.id is null from which result I could then select min(...).
How about this?
I simplified your test case (END value isn't that high) in order to save space (otherwise, I'd have to use smaller font :)).
What does it do?
CTEs A and B are your sample data
FULL_ASEQ creates a sequence of numbers from table A
if you want what it returns, remove everything from line #17 and - instead of it - run select * from full_aseq
the final query returns the first available sequence number, i.e. the one that hasn't been used yet (lines #19 - 23).
Here you go:
SQL> with
2 a (id, cstart, cend, step) as
3 (select 1, 4000, 4032, 4 from dual union all
4 select 2, 3, 20, 1 from dual
5 ),
6 b (id, cnumber) as
7 (select 1, 4000 from dual union all
8 select 1, 4004 from dual union all
9 select 2, 4003 from dual
10 ),
11 full_aseq as
12 (select a.id, a.cstart + column_value * a.step seq_val
13 from a cross join table(cast(multiset(select level from dual
14 connect by level <= (a.cend - a.cstart) / a.step
15 ) as sys.odcinumberlist))
16 )
17 select f.id, min(f.seq_val) min_seq_val
18 from full_aseq f
19 where not exists (select null
20 from b
21 where b.id = f.id
22 and b.cnumber = f.seq_val
23 )
24 group by f.id;
ID MIN_SEQ_VAL
---------- -----------
1 4008
2 4
SQL>
You can use LEAD to compute the difference between ordered rows in table B. Any row having a difference (to the next row) that exceeds the step value for that sequence is a gap.
Here's that concept, implemented (below). I threw in a sequence ID "3" that has no values in table B, to illustrate that it generates the proper first value.
with
a (id, cstart, cend, step) as
(select 1, 4000, 4032, 4 from dual union all
select 2, 3, 20000, 1 from dual union all
select 3, 100, 200, 3 from dual
),
b (id, cnumber) as
(select 1, 4000 from dual union all
select 1, 4004 from dual union all
select 1, 4012 from dual union all
select 2, 4003 from dual
),
work1 as (
select a.id,
b.cnumber cnumber,
lead(b.cnumber,1) over ( partition by b.id order by b.cnumber ) - b.cnumber diff,
a.step,
a.cstart,
a.cend
from a left join b on b.id = a.id )
select w1.id,
CASE WHEN min(w1.cnumber) is null THEN w1.cstart
WHEN min(w1.cnumber)+w1.step < w1.cend THEN min(w1.cnumber)+w1.step
ELSE null END next_cnumber
from work1 w1
where ( diff is null or diff > w1.step )
group by w1.id, w1.step, w1.cstart, w1.cend
order by w1.id
+----+--------------+
| ID | NEXT_CNUMBER |
+----+--------------+
| 1 | 4008 |
| 2 | 4004 |
| 3 | 100 |
+----+--------------+
You can further improve the results by excluding rows in table B that are impossible for the sequence. E.g., exclude a row for ID #1 having a value of, say, 4007.
I'll ask the obvious and suggest why not use an actual sequence?
SQL> set timing on
SQL> CREATE SEQUENCE SEQ_TEST_A
START WITH 4000
INCREMENT BY 4
MINVALUE 4000
MAXVALUE 4999
NOCACHE
NOCYCLE
ORDER
Sequence created.
Elapsed: 00:00:01.09
SQL> CREATE SEQUENCE SEQ_TEST_B
START WITH 3
INCREMENT BY 1
MINVALUE 3
MAXVALUE 20000
NOCACHE
NOCYCLE
ORDER
Sequence created.
Elapsed: 00:00:00.07
SQL> -- get nexvals from A
SQL> select seq_test_a.nextval from dual
NEXTVAL
----------
4000
1 row selected.
Elapsed: 00:00:00.09
SQL> select seq_test_a.nextval from dual
NEXTVAL
----------
4004
1 row selected.
Elapsed: 00:00:00.08
SQL> select seq_test_a.nextval from dual
NEXTVAL
----------
4008
1 row selected.
Elapsed: 00:00:00.08
SQL> -- get nextvals from B
SQL> select seq_test_b.nextval from dual
NEXTVAL
----------
3
1 row selected.
Elapsed: 00:00:00.08
SQL> select seq_test_b.nextval from dual
NEXTVAL
----------
4
1 row selected.
Elapsed: 00:00:00.08
SQL> select seq_test_b.nextval from dual
NEXTVAL
----------
5
1 row selected.
Elapsed: 00:00:00.08
This question already has answers here:
How to add a sequence column to an existing table with records
(3 answers)
Closed 4 years ago.
There is a table with millions of records which has duplicate records as well. what is the process of creating a new entity as surrogate key (which denotes sequence no.)
E.g table structure
col1 col2
101 A
101 A
101 B
102 A
102 B
I would like to create a new column (col3) - which denotes a seq no.
col1 col2 col3
101 A 1
101 A 2
101 B 3
102 A 1
102 B 2
Please suggest me steps to follow to create surrogate key for existing records(300 million), and even when new records are loaded ( I assume trigger is needed to while inserting).
Just use row_number function to populate col3 :
For already existing records apply :
SQL> create table tab(col1 int , col2 varchar2(1));
Table created
SQL> insert all
2 into tab values(101,'A')
3 into tab values(101,'A')
4 into tab values(101,'B')
5 into tab values(102,'A')
6 into tab values(102,'B')
7 select * from dual;
5 rows inserted
SQL> create table tab_ as
2 select col1, col2,
3 row_number() over (partition by col1 order by col2) as col3
4 from tab;
Table created
SQL> drop table tab;
Table dropped
SQL> alter table tab_ rename to tab;
Table altered
OR Alternatively ( without recreating the table ) :
SQL> create table tab(col1 int , col2 varchar2(1));
Table created
SQL> insert all
2 into tab values(101,'A')
3 into tab values(101,'A')
4 into tab values(101,'B')
5 into tab values(102,'A')
6 into tab values(102,'B')
7 select * from dual;
5 rows inserted
SQL> alter table tab add col3 integer;
Table altered
SQL> declare
2 i pls_integer := 0;
3 begin
4 for c in
5 (
6 select rowid, col1, col2,
7 row_number() over (partition by col1 order by col2) as col3
8 from tab
9 )
10 loop
11 update tab t
12 set t.col3 = c.col3
13 where t.rowid = c.rowid;
14 i:= i+1;
15 if ( ( i mod 10000 ) = 0 ) then commit; end if;
16 end loop;
17 end;
18 commit;
19 /
PL/SQL procedure successfully completed
SQL> select * from tab;
COL1 COL2 COL3
---- ---- -----
101 A 1
101 A 2
101 B 3
102 A 1
102 B 2
5 rows selected
For upcoming (newly inserted) records you may use a trigger as you mentioned :
SQL> create or replace trigger trg_ins_tab
2 before insert on tab
3 referencing new as new old as old for each row
4 declare
5 begin
6 select nvl(max(col3),0) + 1
7 into :new.col3
8 from tab
9 where col1 = :new.col1;
10 end;
11 /
Trigger created
SQL> insert into tab(col1,col2) values(101,'C');
1 row inserted
SQL> select *
2 from tab t
3 order by t.col1, col3;
COL1 COL2 COL3
---- ---- -----
101 A 1
101 A 2
101 B 3
101 C 4
102 A 1
102 B 2
6 rows selected
I have created a query that will output a flat file with header and details.
Now, I want to add a trailer record that will contain the total count of the detail records.
I have correctly counted the total of records using row_number, but it displays the every record.
How can I get the last line so that it will reflect the total count in the trailer line.
This is the code I already created for the headers and detail.
SQL> SELECT filerec FROM (
2 SELECT 'FILENAME' AS filerec, 1 col FROM dual
3 UNION ALL
4 SELECT 'FILEDATE: ' || to_char(SYSDATE,'mm/dd/yyyy') as filerec, 2 col FROM dual
5 UNION ALL
6 SELECT empno || ename AS filerec, NULL col FROM emp
7 ORDER BY 2,1
8 );
This is the output I want to get. (added the last rec, 'TRAILER: 0004')
FILENAME
FILEDATE: 02/27/2015
7369SMITH
7499ALLEN
7521WARD
7566JONES
TRAILER: 0004
There are several ways to do this. I'd prefer to use grouping. See how it can be used. Let we have a recordset:
SQL> select
2 'Row ' || rownum
3 from
4 dual
5 connect by
6 level <= 5;
'ROW'||ROWNUM
--------------------------------------------
Row 1
Row 2
Row 3
Row 4
Row 5
Now we wish to add counting:
SQL> select
2 case
3 when grouping_id(rownum) = 0 then 'Row ' || rownum
4 else 'Total: ' || count(*) || ' row(s)'
5 end
6 from
7 dual
8 connect by
9 level <= 5
10 group by rollup (rownum);
CASEWHENGROUPING_ID(ROWNUM)=0T
------------------------------------------------------
Row 1
Row 2
Row 3
Row 4
Row 5
Total: 5 row(s)
6 rows selected