Exceeding the permissible coordinate zone - sql

I work in apex oracle, I have a table with geo-coordinates and objects.
When I seal this object, I fill out the form. When I fill out this form, I select an object from the LoV from the "test1" table, also in the form of automatically pulling my location into cells P1_long, and P1_lati. I wish that when I stored this form in the "test2" table, I calculated the difference of geo-coordinates between the geodata that is in the "test1" table and in my form. And if this difference is more than -+0.001 then an entry was made in the table "ero_tabel" that the geo-coordinates are violated
I created a dynamic promotion, and I'm trying to write something like this
CREATE TABLE test1
(
objects_name varcahar2(10),
geo-latit varcahar2(20),
geo-long varcahar2(20)
);
INSERT INTO test1
VALUES ('House', '60.2311699999996', '12.454977900000003');
CREATE TABLE test2
(
id number(),
data_seals date(),
objects_name varcahar2(10),
geo-latit varcahar2(20),
geo-long varcahar2(20)
);
CREATE TABLE eror_table
(
id number(),
data_err date(),
objects_name varcahar2(10),
);
Declare
Begin
if (to_number(:P1_long) - (select geo-long from test1 where
objects_name = P1_SEALING_OBJECT )) > 0.001 then
INSERT INTO eror_table
VALUES ('1', sysdate, 'P1_SEALING_OBJECT);
end if;
end;

I can see a few issues (need a variable to hold the data, a query can not be directly used in IF) in your code and Updated your code block as follows:
DECLARE
LV_GEO_LONG TEST1.GEO_LONG%TYPE; -- variable to hold geo_long
BEGIN
SELECT
GEO_LONG
INTO LV_GEO_LONG -- storing geo_long value to newly defined variable
FROM
TEST1
WHERE
OBJECTS_NAME = P1_SEALING_OBJECT;
IF ABS(TO_NUMBER(:P1_LONG) - LV_GEO_LONG) > 0.001 THEN -- checking for the difference
INSERT INTO EROR_TABLE VALUES (
'1',
SYSDATE,
P1_SEALING_OBJECT
);
END IF;
END;
/
Cheers!!

Related

How to restrict any DML operation in a stored procedure

I am trying to insert some records into a table for a particular month. How do I restrict any DML operations on that table for rest of the other months in a stored procedure (without any trigger or constraints). Please help me on this, Thanks in advance.
Pass the "particular month" as a parameter and use it in code throughout the procedure, most probably in WHERE clauses or IFs, CASEs etc.
Create 2 users:
The first (we can call it DATA_OWNER) will own the tables containing the data and the stored procedures/packages that perform the DML on those tables; and
The second (we can call it END_USER) will be the database user that your end users connect as.
Then you can create the tables:
CREATE TABLE data_owner.table_name (
id NUMBER
GENERATED ALWAYS AS IDENTITY
PRIMARY KEY,
datetime DATE
NOT NULL,
value NUMBER
);
CREATE TABLE data_owner.table_name_insert_bounds (
start_datetime DATE,
end_datetime DATE,
CONSTRAINT table_name_insert_bounds__pk PRIMARY KEY ( start_datetime, end_datetime ),
CONSTRAINT table_name_insert_bounds__chk CHECK ( start_datetime <= end_datetime )
);
And a package containing your stored procedures:
CREATE PACKAGE data_owner.table_name_management_pkg IS
PROCEDURE add_value(
i_datetime IN TABLE_NAME.DATETIME%TYPE,
i_value IN TABLE_NAME.VALUE%TYPE
);
END;
/
CREATE PACKAGE BODY data_owner.table_name_management_pkg IS
PROCEDURE add_value(
i_datetime IN TABLE_NAME.DATETIME%TYPE,
i_value IN TABLE_NAME.VALUE%TYPE
)
IS
valid_datetime NUMBER;
BEGIN
SELECT CASE
WHEN EXISTS(
SELECT 1
FROM table_name_insert_bounds
WHERE i_datetime BETWEEN start_datetime AND end_datetime
)
THEN 1
ELSE 0
END
INTO valid_datetime
FROM DUAL;
IF valid_datetime = 0 THEN
RAISE_APPLICATION_ERROR(
-20000,
'Date-time is outside of current insertion range.'
);
END IF;
INSERT INTO table_name ( datetime, value )
VALUES ( i_datetime, i_value );
END;
END;
/
And then grant permissions to the end user:
GRANT SELECT ON data_owner.table_name TO end_user;
GRANT EXECUTE ON data_owner.table_name_management_pkg TO end_user;
Then the end user can only perform DML actions via the stored procedure in the package (they have no permissions to perform DML directly on the table bypassing the procedure) but could also SELECT all the data and the data owner can set the date bounds which the end user is able to insert within (and the end user cannot modify these bounds).
For example:
If the data owner sets these boundaries:
INSERT INTO table_name_insert_bounds ( start_datetime, end_datetime )
VALUES ( DATE '2020-01-01', DATE '2020-02-01' );
Then the end user can run:
BEGIN
data_owner.table_name_management_pkg.ADD_VALUE( DATE '2020-01-10', 42 );
END;
/
and one row will be inserted but if they try to:
BEGIN
data_owner.table_name_management_pkg.ADD_VALUE( DATE '2020-02-02', 13 );
END;
/
Then will get the exception:
ORA-20000: Date-time is outside of current insertion range.
db<>fiddle here

oracle constraints datatype

I have created a table with datatype smallint.
create table test(
A smallint,
constraints ACHECK check(A between 1 and 5));
I want to add constraint that only allow users to add integer value range between 1~5.
But, even with the constraints, I am still able to insert floating point value which gets round up automatically.
insert into test values(3.2);
How do I make this code to show an error?
I am not allow to change datatype.
You cannot do what you want easily. Oracle is converting the input value 3.2 to an integer. The integer meets the constraint. The value 3 is what gets inserted. The conversion happens behind the scenes. The developers of Oracle figured this conversion is a "good thing".
You could get around this by declaring the column as a number and then checking that it is an integer:
create table test (
A number,
constraints ACHECK check(A between 1 and 5 and mod(A, 1) = 0)
);
As far as the requirement is NOT TO CHANGE THE DATA TYPE, but it doesn't say anything regarding creating new objects, I came up with a very complicated solution which does the trick but I would have preferred by far to change the data type to number and use a normal constraint.
The main problem here is that the round up of the value is done after parsing the statement, but before executing. As is an internal mechanism , you can't do anything about it. You can easily see that happening if you use a trigger and display the value of :NEW before inserting or updating the column.
However, there is a trick. F.G.A. got the original value passed to the statement before parsing. So, using a policy with a handler and two triggers make the trick.
Let me go into details
SQL> create table testx ( xsmall smallint );
Table created.
SQL> create table tracex ( id_timestamp timestamp , who_was varchar2(50) , sqltext varchar2(4000) );
Table created.
SQL> create or replace procedure pr_handle_it (object_schema VARCHAR2, object_name VARCHAR2, policy_name VARCHAR2)
is
begin
-- dbms_output.put_line('SQL was: ' || SYS_CONTEXT('userenv','CURRENT_SQL'));
insert into tracex values ( systimestamp , sys_context('userenv','session_user') , sys_context('userenv','current_sql') );
commit;
end;
/
Procedure created.
SQL> BEGIN
DBMS_FGA.ADD_POLICY(
object_schema => 'MYSCHEMA',
object_name => 'TESTX',
policy_name => 'MY_NEW_POLICY',
audit_condition => null,
audit_column => 'XSMALL',
handler_schema => 'MYSCHEMA',
handler_module => 'PR_HANDLE_IT',
enable => true,
statement_types => 'INSERT, UPDATE, DELETE'
);
END;
/
PL/SQL procedure successfully completed.
SQL> create or replace trigger trg_testx before insert or update on testx
referencing new as new old as old
for each row
begin
if inserting or updating
then
dbms_output.put(' New value is: ' || :new.xsmall);
dbms_output.put_line('TRIGGER : The value for CURRENT_SQL is '||sys_context('userenv','current_sql'));
insert into tracex values ( systimestamp , sys_context('userenv','session_user') , sys_context('userenv','current_sql') );
end if;
end;
/
Trigger created.
SQL> create or replace trigger trg_testx2 after insert or update on cpl_rep.testx
referencing new as new old as old
for each row
declare
v_val pls_integer;
begin
if inserting or updating
then
select regexp_replace(sqltext,'[^0-9]+','') into v_val
from ( select upper(sqltext) as sqltext from tracex order by id_timestamp desc ) where rownum = 1 ;
if v_val > 5
then
raise_application_error(-20001,'Number greater than 5 or contains decimals');
end if;
end if;
end ;
/
Trigger created.
These are the elements:
-One trace table to get the query before parsing
-One FGA policy over update and insert
-One procedure for the handler
-Two triggers one before ( got the query and the original value ) and one after to evaluate the value from the statement not the one rounded up.
Due to the fact that the triggers are evaluating in order, the before one inserts the original value with the decimal and it does it before the round up, the after analyses the value stored in the trace table to raise the exception.
SQL> insert into testx values ( 1 ) ;
1 row created.
SQL> insert into testx values ( 5 ) ;
1 row created.
SQL> insert into testx values ( 2.1 ) ;
insert into testx values ( 2.1 )
*
ERROR at line 1:
ORA-20001: Number greater than 5 or it contains decimals
ORA-06512: at "CPL_REP.TRG_TESTX2", line 10
ORA-04088: error during execution of trigger 'CPL_REP.TRG_TESTX2'
SQL> insert into testx values ( 6 ) ;
insert into testx values ( 6 )
*
ERROR at line 1:
ORA-20001: Number greater than 5 or it contains decimals
ORA-06512: at "CPL_REP.TRG_TESTX2", line 10
ORA-04088: error during execution of trigger 'CPL_REP.TRG_TESTX2'
Summary: As #Gordon Linoff said there was no easy way to achieve what was asked. I believe the method is very complicated for the requirement. I just came up with it for the purpose to show that it was possible after all.

How to find the source table rows used by a big complex query

We have a huge Oracle SQL query in our project which is using many views and tables for source data.
Is there any way to find the list of rows fetched from each source table by this big query when I run it?
Basically what we are trying to do is to create the bare minimum number of rows in the source tables so that the outer most big query returns at least a single record.
I have tried to run the smaller queries individually. But it is really time consuming and tedious. So I was wondering if there is a smarter way of doing this.
You can use Fine Grained Access Control. Here is how you might do it:
Step 1: Create a table to hold the list of rowids (i.e., the results you are looking for in this exercise)
CREATE TABLE matt_selected_rowids ( row_id rowid );
Step 2: Create a FGAC handler that will add a predicate whenever a base table is selected.
CREATE OR REPLACE PACKAGE matt_fgac_handler IS
FUNCTION record_rowid ( p_rowid rowid ) RETURN NUMBER DETERMINISTIC;
FUNCTION add_rowid_predicate (d1 varchar2, d2 varchar2 ) RETURN VARCHAR2;
END matt_fgac_handler;
CREATE OR REPLACE PACKAGE BODY matt_fgac_handler IS
FUNCTION record_rowid ( p_rowid rowid ) RETURN NUMBER DETERMINISTIC IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO matt_selected_rowids( row_id ) values ( p_rowid );
COMMIT;
RETURN -1;
END record_rowid;
FUNCTION add_rowid_predicate (d1 varchar2, d2 varchar2 ) RETURN VARCHAR2 IS
BEGIN
RETURN 'matt_fgac_handler.record_rowid (rowid) = -1';
END add_rowid_predicate;
END matt_fgac_handler;
Step 3: Add a policy to each base table used by your view (you can get the list by using DBA_DEPENDENCIES recursively, or just doing an explain plan and eyeballing it).
E.g.,
CREATE TABLE matt_table ( a number, b varchar2(80) );
CREATE INDEX matt_table_n1 on matt_table(a);
insert into matt_table values (1,'A');
insert into matt_table values (2,'B');
insert into matt_table values (3,'C');
insert into matt_table values (3,'D');
insert into matt_table values (3,'E');
insert into matt_table values (3,'F');
insert into matt_table values (4,'G');
insert into matt_table values (4,'H');
COMMIT;
BEGIN
DBMS_RLS.ADD_POLICY('APPS','MATT_TABLE','record_rowids_policy', NULL, 'matt_fgac_handler.add_rowid_predicate', 'select');
END;
So, at this point, whenever a user selects from my table, Oracle is automatically going to add a condition that calls my record_rowid function for each rowid.
For example:
delete from matt_selected_rowids;
SELECT /*+ INDEX */ * FROM matt_table where a = 2;
-- This gives your the rowids selected...
select r.row_id, o.object_name from matt_selected_rowids r left join dba_objects o on o.object_id =dbms_rowid.rowid_object(row_id);

oracle inline function called multiple times

When calling a function via an inline select statement, when the function is returning a custom type, Oracle seems to execute the function equal to the number of arguments +1. This seems to happen when the select is included as a CTAS or an insert/select.
Has anyone seen this before? Is this an Oracle bug? I would expect the function to be called once per row in the table.
--Inline function gets called for the number of arguments +1
--drop table t
create table t(
id number,
l_blob blob
);
insert into t values(1, utl_raw.cast_to_raw('SampleString'));
COMMIT;
create table tmp_ts (c1 timestamp);
create or replace type test_type as object(
c1 varchar2(32)
,c2 varchar2(32)
);
/
create or replace FUNCTION test_function (p_blob blob, p_date date)
RETURN test_type
IS
BEGIN
--This could also be a DBMS_OUTPUT.PUT_LINE statement
insert into tmp_ts VALUES (systimestamp);
return test_type(null,null);
END test_function;
/
--0
select count(*) from tmp_ts;
--Call function on 1 row table - function should just insert 1 row into tmp_ts
create table tst_table as
select test_function(l_blob, '25-JAN-09') as c1
from t;
--it actually inserts 3
select count(*) from tmp_ts;
Example where increasing the argument call for the type increases the number of time the function is executed
--Same example with more arguements - 6 arguements here
--Inline function gets called for the number of arguments +1
--drop table t
create table t2(
id number,
l_blob blob
);
insert into t2 values(1, utl_raw.cast_to_raw('SampleString'));
COMMIT;
create table tmp_ts2 (c1 timestamp);
create or replace type test_type2 as object(
c1 varchar2(32)
,c2 varchar2(32)
,c3 varchar2(32)
,c4 varchar2(32)
,c5 varchar2(32)
,c6 varchar2(32)
);
/
create or replace FUNCTION test_function2 (p_blob blob, p_date date)
RETURN test_type2
IS
BEGIN
insert into tmp_ts2 VALUES (systimestamp);
return test_type2(null,null,null,null,null,null);
END test_function2;
/
--0
select count(*) from tmp_ts2;
--Call function on 1 row table - function should just insert 1 row into tmp_ts
create table tst_table2 as
select test_function2(l_blob, '25-JAN-09') as c1
from t;
--it actually inserts 7
select count(*) from tmp_ts2;
Any help/feedback is greatly appreciated.
First: It is a bug that you can even perform a DML inside a function which is called in a SELECT Statement. This should raise an exception.
Otherwise Oracle makes absolutely no guarantee how often Functions in a SQL-Select are executed, it could be once per row, ten times per row or just once for the whole query (with caching) - so however often it is called, this conforms to the specifications.
In this special case it will call the function for each attribute of the returning type, since oracle will not insert the object type as one memory-structure, but use the function like a table with multiple columns and read each column individually like this:
INSERT VALUES ( myFunc(x).attribute1, myFunc(x).attribute2 );
The important part: Never make any assumptions about how often a FUNCTION is called when you use it in an SQL Statement!!! At any time the function could be called again by the optimizer, maybe for sampling or caching...
Preferred solution: Pipelined Functions - a pipelined Function can be called like a Table and will only be called once. You can pass in a cursor which the function uses for input processing and do the whole data-transformation and logging and everything in the function.

Oracle SQL ORA-01403: no data found error

Hi there I already seen other posts with the same error code, but I can't figure it out.
I have this table 'NOLEGGIO' created in this way:
CREATE TABLE NOLEGGIO(
idNoleggio INT PRIMARY KEY,
dataNoleggio DATE,
dataRestituzione DATE,
dataRestituito DATE,
CF CHAR(16) NOT NULL,
prezzo NUMBER(4),
--SEVERAL CONSTRAINTS...
All I want to do now is a trigger that sets a 'dataRestituzione' := :NEW.dataNoleggio + INTERVAL '3' DAY; (that means returnDate := :NEW.rentalDATE ) IF the date of membership is < than a specific date.
I show you my 'TESSERATO' table (tesserato stands for membership)
CREATE TABLE TESSERATO(
numTessera INT NOT NULL UNIQUE,
dataTesseramento DATE,
dataScadenza DATE,
CF CHAR(16) PRIMARY KEY,
-- CONSTRAINT...
If I execute the query outside my trigger (coming next) it works (because I have datas in the fields i'm looking at) but if I insert this query in the trigger, it doesn't work!
This is the trigger:
CREATE OR REPLACE TRIGGER TR_NOLEGGIO
BEFORE INSERT ON NOLEGGIO
FOR EACH ROW
DECLARE
DATAT DATE;
BEGIN
:NEW.idNoleggio := id_noleggio.NEXTVAL;
SELECT T.dataTesseramento INTO DATAT
FROM NOLEGGIO N JOIN TESSERATO T ON N.CF=T.CF
WHERE DATAT < TO_DATE('27/02/2014','DD/MM/YYYY');
/* Here I've even tried to do something like:
IF DATAT < TO_DATE.... THEN . But it doesn't work either.
However the query that actually works if I execute outside the trigger is the SELECT above.
*/
:NEW.dataRestituzione := :NEW.dataNoleggio + INTERVAL '3' DAY;
END;
/
It says No data Found error, while there are datas in the rows instead!! (In fact doing the select outside the trigger matches several rows).
It's definitely driving me crazy ! Cannot understand what I do wrong.
Thank you in advance for anyone that get involved into this.
Insert staments for the two tables
-- NOLEGGIO
INSERT INTO NOLEGGIO VALUES(001,'18-OTT-2013','20-OTT-2013',NULL,'P3SDTI85A15H501H',10);
INSERT INTO NOLEGGIO VALUES(002,'15-NOV-2013','19-NOV-2013',NULL,'CNTNDR89T42F839M',700);
--idRental,dateRental,dateReturn,dateReturned,SSN,price)
-- TESSERATO
INSERT INTO TESSERATO(dataTesseramento,dataScadenza,CF) VALUES('07-set-2013','07-set-2014','RDLVRT70M08F205K');
-- SEVERAL INSERTS MORE
-- N.B. the numTessera is made with a sequence in another trigger
New Answer Following Comments
I have put together a test script for this. The new code used for the trigger seems to work correctly updating the return date if a valid membership exists within the date requirements set. Feel free to just take the trigger code and discard the rest, I have just included this as it is what I have used to verify that the trigger performs an update when it should:
CAUTION: I am dropping tables in this test to make it rerunable, so i would only recommend using the full script in a test environment
/**************** R U N O N C E ********************/
--CREATE OR REPLACE SEQUENCE id_noleggio
-- MINVALUE 0
-- MAXVALUE 1000000000
-- START WITH 1
-- INCREMENT BY 1
-- CACHE 20;
/********************************************************/
/****************** R E R U N A B L E ****************/
drop table NOLEGGIO;
drop table TESSERATO;
CREATE TABLE NOLEGGIO(
idNoleggio INT PRIMARY KEY,
dataNoleggio DATE,
dataRestituzione DATE,
dataRestituito DATE,
CF CHAR(16) NOT NULL,
prezzo NUMBER(4));
CREATE TABLE TESSERATO(
numTessera INT NOT NULL UNIQUE,
dataTesseramento DATE,
dataScadenza DATE,
CF CHAR(16) PRIMARY KEY);
-- TESSERATO
INSERT INTO TESSERATO(numTessera, dataTesseramento, dataScadenza, CF) VALUES(1, '15-NOV-2013','15-NOV-2014','ABCDEFGHI0000001');
INSERT INTO TESSERATO(numTessera, dataTesseramento, dataScadenza, CF) VALUES(2, '01-MAR-2014','01-MAR-2015','ABCDEFGHI0000002');
-- SEVERAL INSERTS MORE
-- N.B. the numTessera is made with a sequence in another trigger
CREATE OR REPLACE TRIGGER TR_NOLEGGIO
BEFORE INSERT ON NOLEGGIO
FOR EACH ROW
DECLARE
CUT_OFF_DATE DATE := TO_DATE('27/02/2014','DD/MM/YYYY');
MEMBER_EXISTS VARCHAR2(1) := 'N';
DATAT DATE;
BEGIN
:NEW.idNoleggio := id_noleggio.NEXTVAL;
-- membership exists
SELECT 'Y', T.dataTesseramento
INTO MEMBER_EXISTS, DATAT
FROM TESSERATO T
WHERE T.CF = :NEW.CF
AND T.dataTesseramento < CUT_OFF_DATE;
-- if value returned from query above is not null...
if MEMBER_EXISTS = 'Y' then
:NEW.dataRestituzione := :NEW.dataNoleggio + INTERVAL '3' DAY;
end if;
exception
when no_data_found then
-- e.g. if there are no records in the TESSERATO table with the same CF value
null; -- no action required, this will just stop an error being flagged
END;
/
-- test trigger
-- should set dataRestituzione (a valid membership exists within date requirements)
INSERT INTO NOLEGGIO VALUES(004, '01-Mar-2014', NULL, NULL, 'ABCDEFGHI0000001', 20); -- should set dataRestituzione
-- should not set dataRestituzione (membership too recent)
INSERT INTO NOLEGGIO VALUES(004, '01-Mar-2014', NULL, NULL, 'ABCDEFGHI0000002', 30);
-- should not set dataRestituzione (no record of membership in TESSERATO table)
INSERT INTO NOLEGGIO VALUES(1, '18-OCT-2013', NULL, NULL, 'P3SDTI85A15H501H', 10);
INSERT INTO NOLEGGIO VALUES(2, '15-NOV-2013', NULL, NULL, 'CNTNDR89T42F839M', 700);
--idRental,dateRental,dateReturn,dateReturned,SSN,price)
-- look at results
select * from TESSERATO;
select * from NOLEGGIO;
I think that the key problem with the way that you were trying to do this before is that you were joining to the NOLEGGIO table to retrieve data that had not yet been inserted.
Previous Answer
Try chaining the line:
WHERE DATAT < TO_DATE('27/02/2014','DD/MM/YYYY');
to:
WHERE T.dataTesseramento < TO_DATE('27/02/2014','DD/MM/YYYY');
It looks like you are using this variable for a where condition before you have assigned a value to it i.e. it doesn't know the value if DATAT until the query has completed, but you are trying to use this value within the query.