I have a timestamp column in one of my tables. I want to get all entries that are a minute after the very first entry in the table. The timestamp is chronological with respect to the primary id.
To this end I retrieve the first entry in the table and put the value of the time column into a variable. Then I add a minute to this value and create a new variable with the new value. Using this new variable I search for the entry that is less than the adjusted timestamp and retrieve the id of that entry.
So my three variables are v_time, v_adjusted and v_id.
When setting v_time I have the following query,
begin
select time_of
into v_time
from MYTABLE where rownum=1
order by id asc;
exception
when no_data_found then
v_time := null;
end;
For v_adjusted i simply do,
v_adjusted := v_time + 1 / 1440.00;
When setting v_id I have,
begin
select id
into v_id
from MYTABLE where time_of < v_adjusted and rownum=1
order by id desc;
exception
when no_data_found then
v_id:= null;
end;
These operations do not return the correct result. The retrieved id is wrong. I can confirm by executing a query with the new timestamp which returns the correct id. As an example, in my table adding a minute should return id 19 but the value of v_id is 1.
Why am I getting the wrong result?
You need to use subquery:
begin
SELECT id
INTO v_id
FROM (select id
from MYTABLE where time_of < v_adjusted
order by id desc
) sub
where rownum=1;
exception
when no_data_found then
v_id:= null;
end;
or simply max:
begin
select MAX(id)
into v_id
from MYTABLE where time_of < v_adjusted;
exception
when no_data_found then
v_id:= null;
end;
EDIT:
With FETCH (Oracle 12c):
begin
select id
into v_id
from MYTABLE where time_of < v_adjusted;
ORDER BY id DESC
FETCH FIRST 1 ROWS ONLY;
exception
when no_data_found then
v_id:= null;
end;
Related
I have a report with the following structure:
Column 1 ID, Column 2 Approved_Rejected_Status, Column 3 Rep_Id
I'm getting a no data found error while running the following:
DECLARE
V_REP_ID VARCHAR2(100);
V_ROWS_APPROVED_min NUMBER;
V_ROWS_APPROVED_max NUMBER;
V_STAT VARCHAR2(100);
BEGIN
SELECT min(id) INTO V_ROWS_APPROVED_min FROM MY_TABLE;
SELECT max(id) INTO V_ROWS_APPROVED_max FROM MY_TABLE;
FOR i IN V_ROWS_APPROVED_min..V_ROWS_APPROVED_max LOOP
SELECT APPROVED_REJECTED_STATUS INTO V_STAT
FROM MY_TABLE WHERE MY_TABLE.ID =i;
SELECT REP_ID INTO V_REP_ID
FROM MY_TABLE
WHERE MY_TABLE.ID = i;
END LOOP;
END;
I think it has something to do with the ID having non consecutive values perhaps?
(I can't have consecutive values on the report for functionality, neither include an exception cause I need to to perform actions depending on the type_of_change)
Thank you
Edit:
Thank you MT0 for your review/suggestion!
I tried using a cursor for loop and something really weird is happening:
for cur_m in (select id, approved_rejected_status
from bdc_bench_watchlist_t
order by id)
loop
if cur_m.approved_rejected_status = 'Approved' then
--Inserting into a test table:
insert into index_test (ID_COPY, MY_APPROVED_STATUS)
values (cur_m.id, cur_m.approved_rejected_status);
end if;
end loop;
The result brings all the range of IDs (from the min to the max) ok but! the approved_rejected_status column is empty (only the header is showing with null data on the rows). I have no idea why.
If I do a basic select id, approved_rejected_status from the source table it brings everything ok (id and status info ok).
Thank you
Assuming you need to iterate through every number and do something even if there is no row there then just handle the NO_DATA_FOUND exception:
DECLARE
V_REP_ID MY_TABLE.REP_ID%TYPE;
V_ROWS_APPROVED_min MY_TABLE.ID%TYPE;
V_ROWS_APPROVED_max MY_TABLE.ID%TYPE;
V_STAT MY_TABLE.APPROVED_REJECTED_STATUS%TYPE;
BEGIN
SELECT min(id), max(id)
INTO V_ROWS_APPROVED_min, V_ROWS_APPROVED_max
FROM MY_TABLE;
FOR i IN V_ROWS_APPROVED_min..V_ROWS_APPROVED_max LOOP
BEGIN
SELECT APPROVED_REJECTED_STATUS, REP_ID
INTO V_STAT, V_REP_ID
FROM MY_TABLE
WHERE MY_TABLE.ID =i;
EXCEPTION
WHEN NO_DATA_FOUND THEN
V_STAT := NULL; -- Or put default values here.
V_REP_ID := NULL;
END;
-- Do stuff with the stat/rep_id
END LOOP;
END;
If you don't want to iterate through every row and want to skip the missing id values then you could use a cursor and order by id.
I have created a procedure for updating my t_ritm table. First I have select rrcd_qnty (which is my product quantity) of a product id from t_rrcd table. Then I update the rrcd_qnty value in t_ritm table.
Here is my procedure:
procedure update_ritm_new_rate(p_oid in varchar2, p_ritm_rate in varchar2, p_euser in varchar2)
is
nrate varchar2(4);
begin
SELECT rrcd_rate into nrate
FROM (select oid, t_rrcd.rrcd_rate
from t_rrcd
where rrcd_ritm= p_oid
ORDER BY oid DESC )
WHERE rownum <= 1
ORDER BY rownum DESC ;
EXCEPTION
WHEN NO_DATA_FOUND THEN nrate := 0;
update t_ritm
set ritm_rate = nrate, euser = p_euser, edat = sysdate
where oid = p_oid;
commit;
end update_ritm_new_rate;
Some of my product id Quantity was null. so I was getting No_Data_Found error. But when and which product id has Quantity value they were successfully updating. For avoiding No_Data_Found I used EXCEPTION WHEN NO_DATA_FOUND THEN nrate := 0; which solved my no_Data_Found error. But when product id has quantity value they were not updating.
I had search lot of for this issue but not get good solution. What should be the best practice for avoiding No_Data_Found error? Could I pass my value if I don't get any No_Data_Found error?
thank in advance
That's because - if your SELECT returns something, it never reaches UPDATE as it is hidden behind the EXCEPTION handler.
Therefore, enclose it (SELECT) into its own BEGIN-END block, and put UPDATE out of it so that it is executed with whichever NRATE value is used.
PROCEDURE update_ritm_new_rate (p_oid IN VARCHAR2,
p_ritm_rate IN VARCHAR2,
p_euser IN VARCHAR2)
IS
nrate VARCHAR2 (4);
BEGIN
BEGIN --> this
SELECT rrcd_rate
INTO nrate
FROM ( SELECT oid, t_rrcd.rrcd_rate
FROM t_rrcd
WHERE rrcd_ritm = p_oid
ORDER BY oid DESC)
WHERE ROWNUM <= 1
ORDER BY ROWNUM DESC;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
nrate := 0;
END; --> this
UPDATE t_ritm
SET ritm_rate = nrate, euser = p_euser, edat = SYSDATE
WHERE oid = p_oid;
COMMIT;
END update_ritm_new_rate;
I have fixed the issue by adding EXCEPTION WHEN NO_DATA_FOUND THEN nrate := 0; after the update query.
procedure update_ritm_new_rate(p_oid in varchar2, p_ritm_rate in varchar2, p_euser in varchar2)
is
nrate varchar2(4);
begin
SELECT rrcd_rate into nrate FROM (select oid, t_rrcd.rrcd_rate from t_rrcd where rrcd_ritm= p_oid ORDER BY oid DESC )
WHERE rownum <= 1 ORDER BY rownum DESC ;
update t_ritm set ritm_rate = nrate, euser = p_euser, edat = sysdate where oid = p_oid;
commit;
EXCEPTION WHEN NO_DATA_FOUND THEN nrate := 0;
end update_ritm_new_rate;
I have a query:
DECLARE
id_tmp number;
BEGIN
select id into id_tmp from ...;
END;
So there is a possibility that there is no id selected by query. And I have got an error:
ORA-01403: data not found
How to handle this and assign id_tmp with NULL?
Assuming that your query can only return one row or no rows, one way could be handling the NO_DATA_FOUND:
DECLARE
id_tmp number;
BEGIN
begin
select id
into id_tmp
from (
select 1 as id
from dual
where 1=2
);
exception
when NO_DATA_FOUND then
id_tmp := null;
end;
END;
Another way, under the same assumptions, could be by an aggregate function that returns null when the query returns no data:
DECLARE
id_tmp number;
BEGIN
select max(id)
into id_tmp
from (
select 1 as id
from dual
where 1=2
);
END;
Read about errors handling. In your case you should be interested in the predefined error NO_DATA_FOUND. You have to catch the error and assign null to id_tmp there.
I need to create a table if it does not exist, and when it is created add a single row to it.
I'm new to oracle and PL/SQL so I basically need an equivalent of the following T-SQL:
IF OBJECT_ID('my_table', 'U') IS NULL
BEGIN
CREATE TABLE my_table(id numeric(38,0), date datetime)
INSERT INTO my_table
VALUES (NULL, 0)
END
if you want to check table creation
DECLARE count NUMBER;
BEGIN
count := 0;
SELECT COUNT(1) INTO count from user_tables WHERE table_name= 'MY_TABLE';
IF COL_COUNT = 0 THEN
EXECUTE IMMEDIATE 'create table ....';
END IF;
END;
/
A checking for DML .please note you have to sepcify your pk columns and values.
DECLARE count NUMBER;
BEGIN
count := 0;
SELECT COUNT(1) INTO count from MY_TABLE WHERE id= 0 and name='Something';
IF COL_COUNT = 0 THEN
EXECUTE IMMEDIATE 'insert into MY_TABLE (id,name) values(0,''something'') ';
END IF;
END;
/
also note I recomand to specify columns when you insert into a table
Another approach is to use exception logic. I changed field names and types according to Oracle rules
declare
eAlreadyExists exception;
pragma exception_init(eAlreadyExists, -00955);
begin
execute immediate 'CREATE TABLE my_table(id number, dateof date)';
execute immediate 'INSERT INTO my_table VALUES (NULL, sysdate)';
exception when eAlreadyExists then
null;
end;
but may be it is not a good idea to create tables dynamically
In my opinion, you should not be creating objects on the fly. You should think about your design before implementing it.
Anyway, if you really want to do it this way, then you need to do it programmatically in PL/SQL (ab)using EXECUTE IMMEDIATE.
However, I would prefer the CTAS i.e. create table as select if you want to create a table ta once with a single row. For example,
SQL> CREATE TABLE t AS SELECT 1 id, SYSDATE dt FROM DUAL;
Table created.
SQL> SELECT * FROM t;
ID DT
---------- ---------
1 29-MAY-15
SQL>
The table is created permanently.
If you are looking for a temporary table, which you could use to store session specific data , then look at creating Global temporary table.
From documentation,
Use the CREATE GLOBAL TEMPORARY TABLE statement to create a temporary
table. The ON COMMIT clause indicates if the data in the table is
transaction-specific (the default) or session-specific
You can use NOT EXISTS with select statement:
IF NOT EXISTS(SELECT 1 FROM my_table) THEN
CREATE TABLE my_table(id NUMBER, date date);
COMMIT;
INSERT INTO my_table(id, date) values (NULL, O);
COMMIT;
END IF;
UPDATE
According to the comment, I cannot use Exist directly in PL/SQL. So this is another way to do it:
begin
select case
when exists(select 1
from my_table)
then 1
else 0
end into l_exists
from dual;
if (l_exists = 1)
then
-- anything
else
EXECUTE IMMEDIATE 'CREATE TABLE my_table(id NUMBER, date date)';
EXECUTE IMMEDIATE 'INSERT INTO my_table(id, date) values (NULL, O)';
end if;
end;
I have a table with 2 varchar2 columns. I have added new number column to existing table to make this column primary key. This table now includes 3 columns. I gave a try to use anonymous block as following:
declare
cnt number;
begin
select nvl(count(*),0) into cnt from sometable;
for i in 1..cnt
loop
update sometable set id=i where i=rownum;
end loop;
end;
Using this anonymous block updates this table unexpectedly.
My solution was to use the following statement:
create table sometablecopy as select row_number() over(order by sometable.col1) as id, sometable.* from sometable;
Nevertheless I am curios why doesn't anonymous block produce expected primary key values with the help of rownum pseudocolumn? It must be rownum related issue.
Rownum is a pseudocolumn. Its assigned to rows as they are returned from the select. So you can't say "select * from my_table where rownum = 42" since the row with rownum=42 hasn't been defined yet, it will vary depending on your select and predicate (and "select * from my_table where rownum = 1" will return a single row, not the "first" row, whatever that would be). You could do something like (untested):
declare
cursor sel_cur is
select rowid as row_id
from my_table
order by orderby_col;
v_ctr pls_integer := 0;
begin
for rec in sel_cur
loop
v_ctr := v_ctr + 1;
update my_table set pk_col = v_ctr where rowid = rec.row_id;
end loop;
commit;
exception
when others then
rollback;
raise;
end;
This assumes you have sufficient rollback to update the entire table.
Hope that helps.
You cannot use ROWNUM like that (see ROWNUM in SQL).
What you could have done is this:
UPDATE sometable SET id = ROWNUM;