we have the problem in the select clause. How can we control which users can access which documents and present this to a trigger.
Task:
Implement a trigger that guarantees that an entry (insert) in the "Access" table may only be made if the accessing user for the document belonging to the versionid is also entered in the "Access Permission" table.
create or replace trigger access_trg
before insert
on access
referencing new as newRow
for each row
declare accessvar varchar2(32);
begin
dbms_output.put_line('enter access');
dbms_output.put_line(':newRow.user = ' || :newRow.user);
dbms_output.put_line(':newRow.version = ' || :newRow.version);
dbms_output.put_line(':newRow.date = ' || :newRow.date);
dbms_output.put_line(':newRow.art = ' || :newRow.art);
--------------------------???????????????---------------------------
--------------------------???????????????---------------------------
Select z.version into accessvar from access-permission z
where z.user = :newRow.user;
if (:newRow.version in (accessvar))
then
dbms_output.put_line('Access');
else if
raise_application_error(-54781, 'No Access');
end if;
end access_trg
create table version (
versionId varchar2(15) primary key,
createdby references user(userId) not null,
creatdate date not null,
document references document(documentid) not null,
text clob,
unique (createdby, creatdate, document));
create table accesspermission (
user references user(userid) not null,
document references document(documentid) not null,
primary key(user, document));
create table access (
user references user(userid) not null,
version references version(versionid) not null,
date date not null,
type varchar(10) check(type in ('new', 'read')),
primary key (user, version, date));
Tables with inserts
Certainly you have a problem with SELECT; apart from syntax error (table name shouldn't contain a "minus" sign; it's probably an "underline"), you are selecting a column (version) which does not exist in the access_permission table. It is difficult to suggest anything if data model you presented isn't correct.
I presume that - once you fix that - you might hit the TOO_MANY_ROWS error if table contains more than a single row for that :newRow.user. What to do? It depends; one option is to add another condition(s) to where clause, or to use aggregate function (such as max) and select the highest version, maybe to use exists ... can't tell.
Furthermore, you're raising error -54781; Oracle reserved -20000 to -20999 for us, developers. Your value is out of that range, so - you should change it.
Related
I would receive an error:
ORA-02437: cannot validate (%s.%s) - primary key violated
Cause: attempted to validate a primary key with duplicate values or null values
I found it was because I have a stored procedure that increments the ID, but it had failed to do so when it re-ran and had an error related to one of my datatypes. I found I now had a duplicate ID in my database table. All this made sense and I was able to easily rectify it with a DELETE FROM MyTable WHERE ID = x, where x was the offending duplicate ID. The problem I have is the only way I was able to even find the IDs that were duplicated is in the first place is because I did a SELECT * FROM MyTable WHERE ID = x -- where x was one greater than the last ID I could actually see. I found it just by an educated guess. So:
Why can't I see these duplicate IDs when I open the table in Oracle SQL Developer? It only shows the last row as the ID before the duplicates. I don't think it is because of my primary key constraint, since the first line in my stored procedure is to remove that (and put it back, at the end - probably when I got my error), and it was not present when I looked at my table.
Is there some way to make these last IDs that got inserted into the table visible, so I wouldn't have to guess or assume that the duplicate IDs are "hiding" as one greater than the last ID I have in my table, in the future? There is a commit; in my stored procedure, so they should have appeared -- unless, of course, the procedure got hung up before it could run that line of code (highly probable).
Stored procedure that runs:
create or replace
PROCEDURE PRC_MYTABLE_INTAKE(
, EMPLOYEE_ID IN NVARCHAR2
, TITLE_POSITION IN NVARCHAR2
, CREATED_DATE IN DATE
, LAST_MODIFIED IN DATE
) AS
myid integer := 0;
appid integer := 0;
BEGIN
-- disable PK constraint so it can be updated
EXECUTE IMMEDIATE 'ALTER TABLE MYTABLE DROP CONSTRAINT MYTABLE_PK';
COMMIT;
-- assign ID to myid
SELECT ID INTO myid FROM MYTABLE WHERE ROWID IN (SELECT MAX(ROWID) FROM MYTABLE);
-- increment
myid := myid + 1;
-- assign APPLICATION_ID to appid
SELECT APPLICATION_ID INTO appid FROM MYTABLE WHERE ROWID IN (SELECT MAX(ROWID) FROM MYTABLE);
-- increment
appid := appid + 1;
-- use these ids to insert with
INSERT INTO MYTABLE (ID, APPLICATION_ID,
, EMPLOYEE_ID
, TITLE_POSITION
, CREATED_DATE
, LAST_MODIFIED
) VALUES(myid, appid,
, EMPLOYEE_ID
, TITLE_POSITION
, CREATED_DATE
, LAST_MODIFIED
);
COMMIT;
-- re-enable the PK constraint
EXECUTE IMMEDIATE 'ALTER TABLE PASS ADD CONSTRAINT MYTABLE_PK PRIMARY KEY (ID)';
COMMIT;
END;
Here's one problem:
SELECT ID
INTO myid
FROM MYTABLE
WHERE ROWID IN (SELECT MAX(ROWID) FROM MYTABLE)
There is no correlation between ID and ROWID, so you're not getting the maximum current ID, you're just getting the one that happens to be on the row that is furthest from the start of a datafile with a high number.
The code you need is:
SELECT COALESCE(MAX(ID),0)
FROM MYTABLE;
Or better yet, just use a sequence.
No idea why you're dropping the PK either.
Furthermore, when you issue the query:
SELECT APPLICATION_ID INTO appid ...
... that could be for a different row than the one you already got the id for, because a change could have been committed to the table.
Of course another issue is that you can't run two instances of this procedure at the same time either.
For David Aldridge, since he wants to look at code instead of the real reason I posted my question, run this ---
CREATE TABLE YOURSCHEMA.TESTING
(
TEST_ID NVARCHAR2(100) NOT NULL
, TEST_TYPE NVARCHAR2(100) NOT NULL
, CONSTRAINT TEST_PK PRIMARY KEY
(
TEST_ID
)
ENABLE
);
create or replace
PROCEDURE PRC_TESTING_INSERT(
TEST_TYPE IN NVARCHAR2
) AS
testid integer := 0;
BEGIN
-- disable PK constraint so it can be updated
EXECUTE IMMEDIATE 'ALTER TABLE TESTING DROP CONSTRAINT TEST_PK';
COMMIT;
-- assign TEST_ID to testid
SELECT TEST_ID INTO testid FROM TESTING WHERE ROWID IN (SELECT MAX(ROWID) FROM TESTING);
-- increment
testid := testid + 1;
-- use this id to insert with
INSERT INTO TESTING (TEST_ID, TEST_TYPE) VALUES(testid, TEST_TYPE);
COMMIT;
-- re-enable the PK constraint
EXECUTE IMMEDIATE 'ALTER TABLE TESTING ADD CONSTRAINT TEST_PK PRIMARY KEY (TEST_ID)';
COMMIT;
END;
SET serveroutput on;
DECLARE
test_type varchar(100);
BEGIN
test_type := 'dude';
YOURSCHEMA.PRC_TESTING_INSERT(test_type);
-- to verify the variable got set and procedure ran, could do:
--dbms_output.enable;
--dbms_output.put_line(test_type);
END;
Now, because there is no data in the table, the stored procedure will fail with ORA-06512: no data found. If you then try and run it again, you will get ORA-02443: cannot drop constraint - nonexistent constraint, because the EXECUTE IMMEDIATE 'ALTER TABLE TESTING DROP CONSTRAINT TEST_PK'; successfully dropped it, and the procedure never ran the command at the end to re-add it. This is what made me think I needed the commits, but even without them, it still will not complete the whole procedure.
To prove that the procedure DOES run, if given proper data, run this after creating the table, but before creating/running the stored procedure:
INSERT INTO TESTING (TEST_ID, TEST_TYPE)
VALUES ('1', 'hi');
And if you run the proc from a new table (not one with its constraint dropped), it will run fine.
Since mathguy didn't post this as the answer, though I'll credit him for the information...
Answer to why I can't see the duplicates is because the COMMIT does not occur in the procedure when it failed due to a datatype mismatch (which we found was actually in the application's code that sent the variable's values into this procedure, not in the stored procedure, itself). (It's also why I'll mark down anyone that says you don't have to add so many COMMIT lines in this procedure.) The commands were run in the session of the user that starts it - in my case, another session of the same DB user I was logged in with, but started from my application, instead of my SQL Developer session. It also explains why I could do a COMMIT, myself, but it did not affect the application's session - I could not commit any actions ran from another session. Had I ran a COMMIT as an OracleCommand and did an .ExecuteNonQuery on my OracleConnection right after the failure within the catch of my application, I would have seen the rows in SQL Developer without having to do a special query.
So, in short, the only way to see the items was with a direct query using WHERE ID =, find the last ID and increment it, and put it in the query.
Is there anyway that we can set a constraint in database table level to have upper or lower case values for certain columns? When we create a table, we can set NOT NULL to avoid having null values on a column. Same way, can we do that for either uppercase or lower case?
You can do that using a check constraint:
create table foo
(
only_lower varchar(20) not null check (lower(only_lower) = only_lower),
only_upper varchar(20) not null check (upper(only_upper) = only_upper)
);
I had almost same case, tried with check constraint, but if the user is not mentioning it as UPPER() or LOWER() it gives error so I took TRIGGER route as below code.
--creating table
create table user_name (
first_name varchar2(50),
last_name varchar2(50));
--creating trigger
CREATE OR REPLACE TRIGGER TRG_USER_NAME_IU
BEFORE INSERT OR UPDATE ON USER_NAME
FOR EACH ROW
BEGIN
:NEW.FIRST_NAME := UPPER(:NEW.FIRST_NAME);
:NEW.LAST_NAME := UPPER(:NEW.LAST_NAME);
END;
/
Can test and share feedback or comments
I'm wondering is it possible to use a constraint to set the value of one column to be sum of two others. For example given the following tables:
CREATE TABLE Room (
Room_Num NUMBER(3),
Room_Band_ID NUMBER(2),
Room_Type_ID NUMBER(2),
Room_Price NUMBER(4),
PRIMARY KEY (Room_Num),
FOREIGN KEY(Room_Band_ID)
REFERENCES Room_Band(Room_Band_ID),
FOREIGN KEY(Room_Type_ID)
REFERENCES Room_Type(Room_Type_ID)
);
CREATE TABLE Booking (
Booking_ID NUMBER(10) NOT NULL,
GuestID NUMBER(4) NOT NULL,
StaffID NUMBER(2) NOT NULL,
Payment_ID NUMBER(4) NOT NULL,
Room_Num NUMBER(3) NOT NULL,
CheckInDate DATE NOT NULL,
CheckOutDate DATE NOT NULL,
Booking NUMBER(2) NOT NULL,
Price NUMBER(4),
PRIMARY KEY (Booking_ID),
FOREIGN KEY(GuestID)
REFERENCES Guest(GuestID),
FOREIGN KEY(StaffID)
REFERENCES Staff(StaffID),
FOREIGN KEY(Payment_ID)
REFERENCES Payment(Payment_ID),
FOREIGN KEY(Room_Num)
REFERENCES Room(Room_Num)
);
I know it is possible to do something like:
Constraint PriceIs CHECK (Booking.Price=(Room.Room_Price*
(Booking.CheckOutDate - Booking.CheckInDate)));
Is it also possible to set up a constraint that doesn't just ensure that the price is correct, but to calculate the price automatically into the price field for the relevant tuple?
Update,
So I've tried to set up a trigger as follows:
CREATE OR REPLACE trigger PriceCompute
AFTER INSERT ON Booking
FOR each row
BEGIN
UPDATE Booking
SET
SELECT (Room.Room_Price*(Booking.CheckOutDate - Booking.CheckInDate))
INTO
Booking.Price
FROM Booking
JOIN ROOM ON Booking.Room_Num = Room.Room_Num
END;
/
But I'm getting the following errors back:
Can anyone see where I'm going astray here, as its beyond me.
Yes, you can. Here are your options. Listed in order of my personal preference:
You can have a table without this column. And create a view that will be calculating this column on a fly.
You may use oracle virtual columns
create table Room (
...
price NUMBER GENERATED ALWAYS AS (room_price*(checkOut-checkIn)) VIRTUAL,
...)
You may use actual column (same as 2, per Dave Costa):
create table Room (
...
price AS (room_price*(checkOut-checkIn)),
...)
You can write trigger to populate it (like Mat M suggested)
You can write stored procedure, but it will be an overkill in this situation
I think you would have to put a trigger on both tables for whenever the price value of the room is changed or the checkout/in dates are changed, it will update the PriceIs field from your calculation.
If you don't need the calculated portion stored in an actual field, you can always create a view that calculates it whenever you look at the view.
I think the better solution is to use a view that calculates the value on the fly. But regarding your attempt to create a trigger, you should use :new.<column_name> to refer to the values being inserted into the Booking table. You don't need to perform updates and queries on that table to get or modify the values in the row that is being inserted*. You just refer to them as variables. So you would want to do something like:
SELECT (Room.Room_Price*(:new.CheckOutDate - :new.CheckInDate))
INTO
:new.Price
FROM ROOM WHERE :new.Room_Num = Room.Room_Num
*In fact, you can't perform queries or updates on the table whose modification invoked the trigger in the first place. You would get the infamous "mutating table" error if your trigger actually compiled and ran.
I want to create a trigger, in Oracle. When the dateOrdReceived in my order table is updated or inserted the trigger takes this date whatever it may be and updates it by 14 days into another table productList ordDateDelivery so that it equals to
dateOrdReceived + 14 days = new ordDateDelivery
I did have a couple of attempts and guessed I'd need a query which would join my two tables. I also learned that maybe using DATEADD would allow me add 14 days but altogether I can't quite get it right.
My trigger attempt
`CREATE OR REPLACE TRIGGER "PRODUCTLIST_DATE_DELIVERY"
BEFORE
insert or update on "PRODUCTLIST"
for each row
begin
select p.dateOrdRecieved, o.ordDateDelivery
from productList p JOIN orders o
ON p.ordID = o.ordID;
new.OrdDateDelivery := DATEADD(day,14,new.p.dateOrdRecieved)
end;
/
ALTER TRIGGER "PRODUCTLIST_DELIVERY_DATE" ENABLE
and my tables for this trigger are as follows
PRODUCTLIST TABLE
CREATE TABLE "PRODUCTLIST"
( "ORDID" NUMBER(3,0) NOT NULL ENABLE,
"PRODUCTID" NUMBER(3,0) NOT NULL ENABLE,
"QUANTITY" NUMBER(4,2) NOT NULL ENABLE,
"ORDDATEDELIVERY" DATE,
"DISCOUNT" NUMBER(3,0),
"TOTALCOST" NUMBER(4,2),
CONSTRAINT "PK_PRODUCTLIST" PRIMARY KEY ("ORDID", "PRODUCTID") ENABLE
)
/
ALTER TABLE "PRODUCTLIST" ADD CONSTRAINT "FK_ORDERS" FOREIGN KEY ("ORDID")
REFERENCES "ORDERS" ("ORDID") ENABLE
/
ALTER TABLE "PRODUCTLIST" ADD CONSTRAINT "FK_PRODUCTS" FOREIGN KEY ("PRODUCTID")
REFERENCES "PRODUCT" ("PRODUCTID") ENABLE
/
ORDERS TABLE
CREATE TABLE "ORDERS"
( "ORDID" NUMBER(3,0) NOT NULL ENABLE,
"DATEORDRECIEVED" DATE,
"CUSID" NUMBER(3,0) NOT NULL ENABLE,
PRIMARY KEY ("ORDID") ENABLE
)
/
ALTER TABLE "ORDERS" ADD CONSTRAINT "FK_CUSTOMER" FOREIGN KEY ("CUSID")
REFERENCES "CUSTOMER" ("CUSID") ENABLE
/
DATEADD() is not an Oracle function... Oracle's datetime arithmetic is based around the day. If you add 1 to a date it increments the date by one day, adding 1.5 by 36 hours etc.
Now, your trigger.
You can't automatically update or insert a record into another table. The trigger is "on" one table, which means you need to create the DML in order to add or update it into that table.
update productlist
set dateOrdRecieved = :new.OrdDateDelivery + 14
where ordid = :new.ordid
The :new. here references the new data of the table on which the trigger is on. It's a specific "variable" that you can access rather than a general concept of what you're trying to achieve. You can't use it to assign data to other tables directly, though you can use it as a means of doing so.
Next you need to consider where your trigger is. You're looking to update PRODUCTLIST whenever ORDERS is changed, this means that the trigger needs to be on the table ORDERS.
create or replace trigger productlist_date_delivery
before insert or update on orders
for each row
begin
update productlist
set OrdDateDelivery = :new.dateOrdRecieved + 14
where ordid = :new.ordid;
end;
/
Notice a few extra differences to your own:
I use :new. instead of new.
I'm not selecting from the table; there's no need to do this as the data is already available. It's also impossible as you're selecting data that Oracle's trying to update, it forbids this to ensure integrity.
I haven't used cased identifiers. There's no need to do this; Oracle upper-cases everything by default. It's also really painful if everything's not upper case as you have to remember
Every statement ends in a semi-colon.
If you're having problems I recommend Tech on the Net, it has a good basic guide. As always though, there's the documentation on the CREATE TRIGGER statement.
Where could I find any info on that.
a)I'll need to select which column is being searched
b)Select what data will be displayed with check boxes
c)If the selected column(in which you search) is the primary key column, then the form will also display the child table check boxes which, if checked, will display the columns along with the checked parent table columns.
The tables:
CREATE TABLE CHAMPIONS (
CNAME VARCHAR2(15) NOT NULL,
PRICELEVEL NUMBER(1) NOT NULL,
ROLE VARCHAR2(10) NOT NULL,
HPLEVEL NUMBER(2) NOT NULL,
ATKLEVEL NUMBER(2) NOT NULL,
MAGICLEVEL NUMBER(2) NOT NULL,
DIFFLEVEL NUMBER(2) NOT NULL
);
CREATE TABLE SKINS (
SNAME VARCHAR2(20) NOT NULL,
CNAME VARCHAR2(15) NOT NULL,
PRICELEVEL NUMBER(1) NOT NULL);
ALTER TABLE Champions ADD CONSTRAINT pk_Champions PRIMARY KEY (CNAME);
ALTER TABLE Skins ADD CONSTRAINT fk_Skins FOREIGN KEY (CNAME) REFERENCES champions(CNAME);
I don't really care how this is implemented since i don't know which way is possible. Since it's just a 1-1 relationship, some sort of hiding might work.
if the pressed button is cname(the primary key) the first result table will of course have only one row while the second result table will be populated by the data associated with the selected primary key. If any other button is pressed the other table isn't even displayed however the first one might have multiple rows displayed. The columns that are displayed in the results depend on the check boxes
If you don't want to repeat the champion data displayed, create two separated database blocks, and add a relation between them.
If you want to display all data in one multi-row block, you will need a view that joins the data from the two tables.
Your search options will be a non-database block. When you click in the search button, you will need to change the default_where of the blocks, adding the value searched according to the field. For this, you will need to add the trigger pre-query. Something like:
trigger of search button
begin
go_block('champions_block');
execute_query; -- this triggers pre-query of champions_block
end;
trigger pre-query
if :search.search_text is not null then
-- this changes the where of your block
set_block_property('champions_block',default_where, 'cname like %'||:search.search_text||'%');
--
else
-- remove previous where
set_block_property('champions_block',default_where, '');
end if;
The show / hide of fields is more complicated, you will need to show / hide the items using:
set_item_property('name_of_block.name_of_item', visible, property_true); -- false to hide
And also will need to adjust the position of the other fields:
set_item_property('name_of_block.name_of_item', position, pos_x, pos_y);