There should be script that checks count() in one table and if count() is NULL then Exit, else sleep for some time and then proceed again with checking table while count(*) is NULL. My efforts were in vain:
declare
v_cnt pls_integer;
begin
while v_cnt >0
loop
select count(*) into v_cnt from TestTable;
if v_cnt is Null
then
exit;
else
dbms_lock.sleep(6);
dbms_output.put_line('Count is greater then Null. Current values: ' || v_cnt);
end loop;
dbms_output.put_line('TestTable does not have any data');
end;
Count always returns a number, not null. Change the if to zero instead of null and it should work. And I changed your logic around so it does not kick off if there are zero records. Unless you are deleting records from the table I have assumed that not having any records will only happen once. If you are deleting records and table could have zero records many times then kick this procedure off with a job that runs as often as you like and remove the while loop.
declare
v_cnt pls_integer;
begin
select count(*) into v_cnt from TestTable;
if v_cnt > 0 then
while v_cnt > 0
loop
select count(*) into v_cnt from TestTable;
dbms_lock.sleep(6);
dbms_output.put_line('Count is greater than zero . Current values: ' || v_cnt);
end loop;
else
dbms_output.put_line('TestTable does not have any data');
end if;
end;
Related
Currently i am implementing a procedure, which creates a couple of rows in some related tables out of a template. So my Procedure consists of a SAVEPOINT followed by some INSERT statements on different tables, and a Cursor to insert some more rows to other tables while referencing on the newly created primary keys.
Each of those tables has an BEFORE INSERT/UPDATE trigger defined which has the purpose to:
Get a new primary key from a sequencer if it is not defined in the INSERT statement (there are cases where I need to set the Primary key explicitely to reference it later on in the same transaction)
Set some default values if they are NULL
Set auditing fields (last_change_date, last_change_user, etc..)
The transaction fails with ORA-04091: table is mutating, trigger/function may not see it
I am understanding, that I could Workaround this, by declaring PRAGMA AUTONOMOUS TRANSACTION in each Trigger, but my Transaction would not be atomic any more then, as it is the requirement that all those datasets should be created/inserted as a whole or None of them.
So what am I doing wrong in the design of my database?
UPDATE: This is the Code of the trigger
CREATE TRIGGER TRG_AUFTRAG_B_IU
BEFORE INSERT OR UPDATE
ON AUFTRAG
FOR EACH ROW
BEGIN
IF INSERTING THEN
IF :new.id is NULL or :new.id = 0 THEN
SELECT SEQ_AUFTRAG.nextval into :new.id from dual;
END IF;
IF :new.nummer is NULL or :new.nummer = 0 THEN
SELECT nvl(MAX(NUMMER),0)+1 INTO :new.nummer FROM AUFTRAG WHERE EXTRACT(YEAR from DATUM) = EXTRACT(YEAR from :new.DATUM);
END IF;
--DEFAULT Values
IF :new.BETR_GRENZWERTE_RELEVANT is NULL THEN
SELECT 0 INTO :new.BETR_GRENZWERTE_RELEVANT FROM dual;
END IF;
IF :new.DOKUMENTE_ABGELEGT is NULL THEN
SELECT 0 INTO :new.DOKUMENTE_ABGELEGT FROM dual;
END IF;
IF :new.EXT_ORG is NULL or :new.EXT_ORG < 1 THEN
SELECT 1 INTO :new.EXT_ORG FROM dual;
END IF;
:new.ERSTELLT_VON := nvl(:new.ERSTELLT_VON,user);
:new.ERSTELLT_DATUM := nvl(:new.ERSTELLT_DATUM,sysdate);
END IF;
:new.GEAENDERT_VON := user;
:new.GEAENDERT_DATUM := sysdate;
END;
You can write it more compact like this:
CREATE TRIGGER TRG_AUFTRAG_B_IU
BEFORE INSERT OR UPDATE
ON AUFTRAG
FOR EACH ROW
BEGIN
IF INSERTING THEN
:new.id = NVL(NULLIF(:new.id, 0), SEQ_AUFTRAG.nextval);
--DEFAULT Values
:new.BETR_GRENZWERTE_RELEVANT := NVL(:new.BETR_GRENZWERTE_RELEVANT, 0);
:new.DOKUMENTE_ABGELEGT := NVL(:new.DOKUMENTE_ABGELEGT, 0);
IF :new.EXT_ORG is NULL or :new.EXT_ORG < 1 THEN
:new.EXT_ORG := 1;
END IF;
:new.ERSTELLT_VON := nvl(:new.ERSTELLT_VON,user);
:new.ERSTELLT_DATUM := nvl(:new.ERSTELLT_DATUM,sysdate);
END IF;
:new.GEAENDERT_VON := user;
:new.GEAENDERT_DATUM := sysdate;
END;
Only "problem" is this part
IF :new.nummer is NULL or :new.nummer = 0 THEN
SELECT nvl(MAX(NUMMER),0)+1 INTO :new.nummer
FROM AUFTRAG
WHERE EXTRACT(YEAR from DATUM) = EXTRACT(YEAR from :new.DATUM);
END IF;
This one you should put into your procedure or in a statement trigger (i.e. without FOR EACH ROW clause) like this:
CREATE TRIGGER TRG_AUFTRAG_B_A
AFTER INSERT ON AUFTRAG
BEGIN
UPDATE
(SELECT ID, NUMMER,
ROW_NUMBER() OVER (PARTITION BY EXTRACT(YEAR from DATUM) ORDER BY ID) as N
FROM AUFTRAG)
SET NUMMER = N
WHERE NUMMER IS NULL;
END;
How to use BEGIN TRANSACTION with while loop in SQL Server?
This query never finishes perhaps because it stops and look for COMMIT TRANSACTION after inserting one row (when #cnt = 1) but I don't want to COMMIT TRANSACTION because I want to see results before committing.
BEGIN TRANSACTION
DECLARE #cnt INT = 0;
WHILE #cnt <= 100
BEGIN
DECLARE #offset INT = 1
INSERT INTO totalSales (col1, col2)
SELECT
'Col1', ROW_NUMBER() OVER (ORDER BY col2) + #offset
FROM
sales
SET #cnt = #cnt + 1;
END;
So how I can check result before commit in while loop?
You should create a BEGIN TRAN outer (general), and inside loop while create a BEGIN TRAN inner (with a trans name).
Inside loop, if are conditions to rollbacks only for this iteration i use SAVE TRAN savepoint for not lose previous trans.
I 've created an example tests in loop while with conditional inserts and rollback savepoint:
declare #num int
set #num = 0
--drop table #test
create table #test (
valor Varchar(100)
)
begin tran
while #num <= 5
begin
begin transaction tran_inner
insert into #test (valor) values ('INSERT 1 INNER -> ' + convert(varchar(10),#num))
save transaction sv_inner
insert into #test (valor) values ('INSERT 2 EVEN - SAVEPOINT -> ' + convert(varchar(10),#num))
if #num % 2 = 0 begin
commit transaction sv_inner
end else begin
rollback transaction sv_inner
end
insert into #test (valor) values ('INSERT 3 INNER -> ' + convert(varchar(10),#num))
set #num = #num + 1
if ##trancount > 0 begin
commit transaction tran_inner
end
end
select valor from #test;
if ##trancount > 0 begin
commit tran
end
Return rows: 1, 2 if iteration even, and 3.
In the same batch (within the same transaction) you can simply issue a SELECT command to see the updated content of the table. Changes will be persisted when the COMMIT TRANSACTION statement is executed or reverted on ROLLBACK.
CREATE TABLE test (id INT IDENTITY(1,1), x VARCHAR(32));
GO
BEGIN TRANSACTION;
INSERT INTO test (x) VALUES ('a');
INSERT INTO test (x) VALUES ('b');
SELECT * FROM test;
ROLLBACK TRANSACTION;
Example: http://sqlfiddle.com/#!6/e4910/2
Alternatively you can use the INSERT INTO .. OUTPUT construct to output the result of the INSERT statement.
Docs: https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql
Outside the batch (using a second connection), you can use READ UNCOMMITTED isolation level to be able to read records not committed yet.
Docs: https://technet.microsoft.com/en-us/library/ms189122(v=sql.105).aspx
If you are saying it never finishes it sounds to me like you are getting some blocking going on because that loop runs just fine.
https://www.mssqltips.com/sqlservertip/2429/how-to-identify-blocking-in-sql-server/
I HIGHLY recommend using Adam Machanic's sp_WhoIsActive for this as well: http://whoisactive.com
Hey guys trying to use a loop to cycle though days so my script doesn't fail the loop fails to execute each time I am not sure what I am doing wrong however i know for a fact the select statement is fine and working its just the loop i have trouble with also is it possible to have the loop check the max date in the table delete only the most recent day in case of incomplete data then add only whats needed to date?
Error line 41, column 4:
PL/SQL: ORA-00933: SQL command not properly ended
ORA-06550: line 7m Column 1:
Pl/sql statement ignored
delete Target_table
commit;
DECLARE
i_date date;
BEGIN
i_date := '01-Jan-2014';
WHILE i_date < sysdate LOOP
insert into Target_table
Select field_1,
field_2
From Data_table_1
LEFT JOIN Data_table_2
ON Data_table_1.ACCOUNT_ID=Data_table_2.account_id
Where Data_table_1.Date >= i_date
and Data_table_1.Date < i_date+1
and Data_table_1.COST_CENTRE In ('1','2','3','4','5','6','7','8','9','10')
And Data_table_1.field_3 In ('C', 'D')
And Data_table_1.field_4 Is Null
And Data_table_2.field_5 in (1,2)
END;
commit;
i_date := i_date +1;
END LOOP;
END
Just want to mention that the most efficent way is write the query in in SQL whenever possible, when PL/SQL can be avoided. (There is an overhead involved in the context switch between SQL and PL/SQL blocks). This is how i would write the query. The new block, using CONNECT by would generate all dates between i_date and sysdate
DECLARE
i_date DATE;
BEGIN
DELETE Target_table;
SELECT field_1,
field_2
FROM Data_table_1
JOIN (SELECT i_date + level -1 as i_date_new
FROM DUAL
CONNECT BY LEVEL<=TRUNC(sysdate)-i_date
)dates_generated
ON Data_table_1.Date_field1 >= dates_generated.i_date_new
AND Data_table_1.Date_field1 < dates_generated.i_date_new+1
LEFT JOIN Data_table_2
ON Data_table_1.ACCOUNT_ID =Data_table_2.account_id
WHERE Data_table_1.COST_CENTRE IN ('1','2','3','4','5','6','7','8','9','10')
AND Data_table_1.field_3 IN ('C', 'D')
AND Data_table_1.field_4 IS NULL
AND Data_table_2.field_5 IN (1,2);
END;
I have made some modifications in your code. I think this should work. I have also mentioned comments for which i have made changes.
DECLARE
i_date DATE;
BEGIN
DELETE Target_table; -- Inserted inside the Anonymous block
i_date := to_date('10-Nov-2015','DD-MON-YYYY'); -- Specified Date format
WHILE i_date < sysdate
LOOP
INSERT INTO Target_table
SELECT field_1,
field_2
FROM Data_table_1
LEFT JOIN Data_table_2
ON Data_table_1.ACCOUNT_ID =Data_table_2.account_id
WHERE Data_table_1.Date_field1 >= i_date
AND Data_table_1.Date_field1 < i_date+1
AND Data_table_1.COST_CENTRE IN ('1','2','3','4','5','6','7','8','9','10')
AND Data_table_1.field_3 IN ('C', 'D')
AND Data_table_1.field_4 IS NULL
AND Data_table_2.field_5 IN (1,2);
dbms_output.put_line(i_date);
i_date := i_date +1;
END LOOP;
COMMIT; -- Added commit at the end
END;
I need to make a trigger that checks if an airplane seat is taken before a customer can be inserted into the table.
I have the following Trigger so far:
CREATE TRIGGER CheckIfSeatIsUnique
ON PassagierVoorVlucht
AFTER insert, update
AS
BEGIN
IF ##ROWCOUNT = 0 RETURN
SET NOCOUNT ON
BEGIN TRY
IF EXISTS (SELECT 1 FROM PassagierVoorVlucht P Join inserted I on P.vluchtnummer=i.vluchtnummer Where P.stoel = I.stoel)
BEGIN
RAISERROR('The chosen seat is taken', 16, 1)
END
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
DECLARE #ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE()
DECLARE #ErrorSeverity INT = ERROR_SEVERITY()
DECLARE #ErrorState INT = ERROR_STATE()
RAISERROR (#ErrorMessage, #ErrorSeverity, #ErrorState)
END CATCH
END
The problem I have, is that the trigger checks if the seat is taken AFTER the insert was done, So the seat will always be taken no matter what.
Is there some way to check if the seat is taken before the insert is done?
Edit: It must also be possible to enter NULL on seat, because the seatnumber isn't known till a few days before the flight
If you have a unique identifier on the table, you can join it into the EXISTS() query to filter out any records that were attempted to insert.
The fiddle below examples this, though it assumes you're taking care of an null handling you need to outside of this.
http://sqlfiddle.com/#!6/93a8a
CREATE TRIGGER CheckIfUnique_mydata_value ON dbo.data
AFTER insert, update
AS
BEGIN
--check if we passed multiple values
IF EXISTS(SELECT 1 FROM inserted GROUP BY value HAVING COUNT(*) > 1)
BEGIN
RAISERROR('You tried to insert a duplicate value within the result set. Ensure you only pass unique values!', 16,1)
END
--check if we inserted a value that already exists that is not me (works for updates on me too!)
IF EXISTS(SELECT 1 FROM dbo.data m INNER JOIN inserted i ON m.value = i.value AND m.id <> i.id)
BEGIN
RAISERROR('Duplicate Value found',16,1)
END
END;
Can I do something like the following in PLSQL?
if (some_query) then
dbms_output.put_line('Your query returned at least 1 row.');
else
dbms_output.put_line('Your query returned no rows.');
end if;
My use case is I want to check if a value already exists in my database. If the value already exists then I will do something different than if the value doesn't exist at all.
If you a checking for existence of a record, then you can select COUNT(*) from the source table based on the key as it will always return a value you can check:
SQL> set serverout on
SQL> DECLARE
2 v_check NUMBER;
3 BEGIN
4 SELECT COUNT(*)
5 INTO v_check
6 FROM DUAL
7 WHERE DUMMY = 'X'
8 AND ROWNUM = 1;
9
10 if (v_check = 0) then
11 dbms_output.put_line('Your query returned no rows.');
12 else
13 dbms_output.put_line('Your query returned at least 1 row.');
14 end if;
15 END;
16 /
Your query returned at least 1 row.
PL/SQL procedure successfully completed.
SQL>
You'd have to do something like
BEGIN
SELECT 1
INTO l_foo
FROM dual
WHERE EXISTS (<<some query>>);
dbms_output.put_line( 'Your query returned at least 1 row' );
EXCEPTION
WHEN no_data_found
THEN
dbms_output.put_line( 'Your query returned 0 rows' );
END;
If the query isn't too expensive, it will be almost as efficient and probably a bit easier to maintain if you do something simpler
SELECT COUNT(*)
INTO l_foo
FROM (<<some query>>)
WHERE rownum = 1;
IF( l_foo = 1 )
THEN
dbms_output.put_line( 'Your query returned at least row.' );
ELSE
dbms_output.put_line( 'Your query returned 0 rows.' );
END IF;
Exactly the same, no. But there are several ways you can fake it:
If you only need to do one thing do it inside an implicit cursor that confirms whether your value exists.
for i in ( some_query ) loop
do_something;
end loop;
You can also set a value in here and use it in an if
for i in ( some_query ) loop
result := True;
end loop;
if result then
do_something;
else
do_something_else;
end if;
Or you can use an explicit cursor and catch the no_data_found error that'll be raised
declare
cursor c_blah is
select my_value
from my_table
where id = my_id
;
my_value varchar2(4000);
begin
open c_blah(my_id);
fetch c_blah into my_value;
close c_blah;
do_something;
exception when no_data_found then
do_something_else;
end;
Since your use case behaves identically whether the query returns one row or a thousand, use EXISTS:
DECLARE
l_dummy VARCHAR2(1);
BEGIN
SELECT NULL
INTO l_dummy
FROM DUAL
WHERE EXISTS (SELECT NULL
FROM <some_query>);
DBMS_OUTPUT.PUT_LINE('Your query returned at least one row.');
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Your query returned no rows.');
END;