I am trying to add a constraint to cust_birthdate, make sure the customer is more than 18 years old.
Here is my create table statement
create table customer(
cust_id char(5) NOT NULL,
cust_name varchar(30),
cust_birthdate date,
primary key(cust_id)
);
I found out cannot use SYSDATE in constraint, but I found a trigger online that fit my situation.
Here is the trigger I modified:
CREATE OR REPLACE TRIGGER trgCustomer
BEFORE INSERT ON customer
FOR EACH ROW
BEGIN
IF(ADD_MONTHS(:new.cust_birthdate, 18 * 12) < sysdate ) THEN
RAISE_APPLICATION_ERROR(-20001, 'Customer must be at least 18 years old.' );
END IF;
END;
/
However, the trigger will not work when I insert something like this:
insert into customer values('C0001', 'Chek Wei', TO_DATE('20-OCT-2016', 'dd-MON-yyyy'));
The 'Chek Wei' customer is less than 18 years old, but no error message is shown.
I do not yet learn trigger but I since I cannot use SYSDATE on constraint, I have no choice.
What's wrong with my trigger?
I am using Oracle Database Express Edition 11g Release 2
Any help is appreciated.
Isn't that logic reversed? you want only when you add 18 years to that date is
GREATER than sysdate to fire the error......
There you go.... :)
Related
Here is the conceptual model:
In the driver table, the totalTripMade attribute are all null, but the trip table contain all the trips the driver made, so I need to create a trigger such that when a record is inserted into trip, the trigger will update the totalTripMade by counting all the trips using the l#
create or replace trigger updateTrip
AFTER INSERT
on TRIP
for each row
declare
PRAGMA AUTONOMOUS_TRANSACTION;
totalTrips number;
begin
UPDATE DRIVER
SET TOTALTRIPMADE = (SELECT COUNT(L#) FROM TRIP WHERE L# =:NEW.L#)
WHERE L#=:NEW.L#;
SELECT COUNT(L#) into totalTrips FROM TRIP WHERE L# =:NEW.L#;
DBMS_OUTPUT.PUT_LINE(:NEW.L# || ' ' || totalTrips);
commit;
end;
/
SHOW ERRORS;
select * from DRIVER;
--
insert into TRIP values(109, 10001, 'SST005', sysdate );
insert into TRIP values(110, 10001, 'SST005', sysdate );
insert into TRIP values(111, 10001, 'SST005', sysdate );
--
select * from DRIVER;
select * from TRIP;
so the driver 10001 has made 27 trips before these 3 insert statement as there are 27 records based on the l# 10001, but after each insert, this code "DBMS_OUTPUT.PUT_LINE(:NEW.L# || ' ' || totalTrips);" keeps giving me 27 for 3 times, it should be 28, 29 then 30, may I know why is my trigger not counting the records based on the l# correctly in the trip table?
This is the output after running those insert statements:
SQL> insert into TRIP values(109, 10001, 'SST005', sysdate );
10001 27
1 row created.
SQL> insert into TRIP values(110, 10001, 'SST005', sysdate );
10001 27
1 row created.
SQL> insert into TRIP values(111, 10001, 'SST005', sysdate );
10001 27
1 row created.
I think the mistake is here:
I need to create a trigger such that when a record is inserted into trip, the trigger will update the totalTripMade by counting all the trips using the l#
I don't thing you do need to create such a trigger. If you need to count the total number of trips made by a driver, query the trips table directly using something like SELECT COUNT(*) FROM TRIP WHERE L# = <driver_number>. It won't slow the database down even if you have a lot of rows, as long as you have an index on the L# column.
There are a number of reasons why using a trigger like this isn't a good idea. Firstly, what happens if you insert multiple rows at the same time? If you insert 10 rows, should the trigger fire 10 times, or just once at the end?
Also, what happens if you update a row in the TRIP table to change the driver number, or delete a row in this table? Are you handling these cases?
But the single biggest reason why you shouldn't do something like this is that it breaks concurrency. Suppose a driver has 27 trips recorded in the database. Suppose that you are attempting to add three trips to this driver, and also someone else is attempting to add three different trips to the same driver at the same time. Neither of you can see each other's trips until you commit, both of you can only see the 27 trips that are already there. In your session, the trigger would attempt to set the total trips for this driver to 30, because your session can see the 27 existing trips and your three new trips, and in the other person's session, the trigger would also attempt to set the total trips for this driver to 30 as well, because this session sees the 27 existing trips and their three new trips, but not your three new trips. Once you have both committed, the driver then has 33 trips in the database, but their totalTripsMade is incorrectly showing as 30.
To answer your question
may I know why is my trigger not counting the records based on the l# correctly in the trip table?
I'm guessing this is because while working on this trigger you hit an ORA-04091 table is mutating error, saw a Google search result for this error that said to use PRAGMA AUTONOMOUS_TRANSACTION, so you added that to your trigger and the error went away. This pragma causes your trigger to run in an independent session: it can't see the trips you've inserted and so it prints 27 because it can only see the trips that are already there.
Compound triggers are one approach to avoiding ORA-04091 errors without using PRAGMA AUTONOMOUS_TRANSACTION. There's an example on using compound triggers here. However, you will also have to write similar triggers for UPDATE and DELETE, and compound triggers won't fix the concurrency problem I mentioned above.
However, I recommend getting rid of this trigger (and probably also the totalTripMade column in the DRIVER table as well). If you need to count the number of trips made by a driver, do this by counting rows in the TRIP table. It may come across as harsh for me to ask you to get rid of your code, but ultimately you are attempting to do something that won't work. I've seen other questions on StackOverflow where people have attempted to do similar things, so you're not alone in attempting something like this. At an intuitive level, adding these counts to the table seems like a reasonable thing to want to do. However, once you get to understand how the database works, you see the flaws in this.
I've created and worked with Triggers in Oracle for years however I'm unable to wrap my head around how to update a field when inserting data into a sqlite database.
All I want to do is create a trigger that automatically inserts the current DateTime into a column in the sqlite database named 'createdDate' for ANY record that is inserted.
What is the best approach to accomplish this?
Below is what I've attempted without success.
CREATE TRIGGER outputLogDB_Created_Trig
BEFORE INSERT
ON outputLog
WHEN NEW.createdDate IS NULL
BEGIN
SELECT CASE WHEN NEW.createdDate IS NULL THEN NEW.createdDate = datetime('now', 'localtime') END;
END;
The above is almost a replica of how I would implement my triggers in Oracle with some modifications of course for sqlite. The logic is basically identical.
What am I missing?
Later Edit - I can get it to work if I instead use AFTER INSERT and not using FOR EACH ROW
CREATE TRIGGER outputLog_Created_Trig
AFTER INSERT
ON outputLog
WHEN New.createdDate IS NULL
BEGIN
UPDATE outputLog
SET createdDate = datetime('now', 'localtime')
WHERE outputLog_ID = New.rowid;
END;
But why can't I just insert the record using the new value while I'm inserting it? Am I ONLY able to get this in there using an Update AFTER I've already inserted the record?
The issue I have with this is the fact that I'd like to have a NOT NULL constraint on the createdDate column. Perhaps I'm simply used to how I've done it for years in Oracle? I realize the Trigger 'should' take care of any record and force this field to NEVER be NULL. It's just that NOT being able to add the constraint for some reason makes me uneasy. Do I need to let go of this worry?
Thanks to Shawn pointing me toward an easy simple solution to my problem. All that is needed in a SQLite database to insert the current Date/Time for each record being inserted is to set the DEFAULT value on that column to CURRENT_TIMESTAMP.
Since I wanted the timestamp in my own local time see below my create table script that is the solution to my problem.
CREATE TABLE outputLog (
outputLog_ID INTEGER PRIMARY KEY ASC ON CONFLICT ROLLBACK AUTOINCREMENT
NOT NULL ON CONFLICT ROLLBACK,
outputLog TEXT,
created DATETIME DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime') )
NOT NULL )
;
I have a task to load existing SQL Server table to Teradata temporal table. Existing table is a type 2 table and has many versions of record. I need to load them into teradata temporal table. I am planning to load version 1 1st and then update all other versions one by one.
Difficulties i am having is that in existing table every record has start time and end time. I need to update that time in teradata temporal table as validity.
1st I am trying to insert and while insert i am not able to insert end time as less than current time. It report error as "Check constraint violation". Below is sample piece of code for creating table and inserting.
I am yet to test updates as not able to do 1st step.
CREATE multiset TABLE EDW_T.edw_Contracts_History_Test
(
ID INTEGER,
Validity PERIOD(TIMESTAMP(3)) NOT NULL AS VALIDTIME
);
insert into EDW_T.edw_Contracts_History_Test(id,Validity) values(
1,period(cast('1996-01-20 05.00.00.000' as TIMESTAMP(3)), cast('2016-06-23 21.52.20.000' as TIMESTAMP(3))))
--this pass as 2016 is greater than current date
insert into EDW_T.edw_Contracts_History_Test(id,Validity) values(
1,period(cast('1996-01-20 05.00.00.000' as TIMESTAMP(3)), cast('2015-06-23 21.52.20.000' as TIMESTAMP(3))))
--This fails as i m trying to give end time less than current date.
Is there anyway to give end time as less than current date. any way to disable this constraint for time and then enable.
Please help. Thanks!
To insert history rows you should use Sequnce Valid Time Modifier...
Eg :
SEQUENCED VALIDTIME
insert into EDW_T.edw_Contracts_History_Test(id,Validity) values(
1,period(cast('1996-01-20 05.00.00.000' as TIMESTAMP(3)), cast('2015-06-23 21.52.20.000' as TIMESTAMP(3))));
I have used information on this site for awhile, and now have a large project that I need some of your expertise on. I work within an Oracle 11g environment and also have Cognos Report Studio 10.1
I am building a multitab report that displays an analysis of a department's outbound orders. I have created a custom table that holds approximately 30 columns of data. From here, there are over 150 calculations that must be performed on the table daily. An example of these calculations are
1) How many orders received today?
2) Of the orders received today, how many shipped same day?
3) How many orders are on hold?
Basically 100s of 4-5 line queries on the core table. I have thought about creating a second table and with the use of WITH clauses, performing the calculations in a procedure and inserting into the table.
To get to the question, has anyone written a procedure/package that performed a large amount of calculations and is there any links/webpages that can be suggested? My searches have not led me to any examples of a report of this nature being created and am wanting to make sure that my approach is as efficient as possible. Thanks in advance for any information/resources.
A table and stored procedure is a great idea. Some considerations:
Will you be archiving these facts per day.
Ie, do you want to be able to lookup the orders that were on hold 5 days ago, or is all of the data in the 150 questions only relevant for the current date?
You can have a table with 150 columns, one for each question. That may make sense if you are archiving data and need one record per day.
The alternative is to create an order fact table with just two or three fields:
Fact_Name VARCHAR2(30)
Order_Fact NUMBER(10,2)
Last_Update_Date DATE
Your oracle stored procedure would query and update the facts one at a time:
insert into ORDER_FACTS
select "ORDERS_RECEIVED" fact_name, count(*) order_fact, sysdate
from ORDER_TABLE
where rcv_date = trunc(sysdate);
commit;
If you are only wanting to keep one record per fact, you would do an update.
If the answer to some of your questions is not a number, you may need to keep a separate table for facts with VARCHAR2 type.
If you like the sound of this solution, I can setup an example procedure tomorrow.
Edit:
Since you are storing 30 days worth of data, I would create the table at the Date detail level, and have 1 column to store each result. For my example I just included 3 columns so you could get the idea. First, create the table to hold your order facts.
create table order_facts
( DT DATE,
ORD_RCV NUMBER,
SAME_DAY_SHIPPED NUMBER,
ON_HOLD NUMBER);
I suggest the DT field stores the date alone. This will make the table easier to join with your calendar table, which you can use to join these facts to other reports easily if they are based on tables with Calendar in their star schema.
Next, create the procedure to update them:
CREATE OR REPLACE PROCEDURE CALC_ORDER_FACTS ( iDate date := trunc(sysdate), iPurgeDays number := 0)
IS
ddate DATE;
dummy DATE;
/*
Calc_order_facts
Date Author Note
04/11/2013 XXX Created this procedure
Param iDate (optional, default trunc(sysdate)
Specify Date to calculate order facts for
Param iPurgeDays number (optional, default 0)
Specify how many days to retain data. Data older than iPurgeDays will be deleted.
If 0, purging is disabled.
*/
BEGIN
ddate := iDate;
IF iPurgeDays > 0 THEN
dbms_output.put_line('Purging data more than ' || to_char(iPurgeDays) || ' days old.');
begin
delete ORDER_FACTS
where DT < trunc(ddate-iPurgeDays);
commit;
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line('Purge found no data.');
WHEN OTHERS THEN
-- Consider logging the error and then re-raise
dbms_output.put_line('Purged failed, rollling back...');
rollback;
END;
END IF;
-- If date does not already exist in table, insert it
begin
select dt
into dummy
from order_facts
where dt = ddate;
EXCEPTION
WHEN NO_DATA_FOUND THEN
insert into order_facts
values (ddate, null, null, null);
commit;
END;
-- ORD_RCV
-- Calculate Orders received
update order_facts
set ord_rcv =
(select count(*) ord_rcv
from ORDER_TABLE
where rcv_date = ddate)
where dt = ddate;
commit;
-- SAME_DAY_SHIPPED
-- Calculate Orders received and shipped on ddate
update order_facts
set same_day_shipped =
(select count(*) same_day_shipped
from order_table
where rcv_dt = ddate
and ship_dt = ddate)
where dt = ddate;
commit;
-- ON_HOLD
-- Total orders on_hold
-- This method applies if you are only concerned with total on hold
update order_facts
set on_hold =
(select count(*) ON_HOLD
from order_table
where status = 'HOLD')
where dt = ddate;
commit;
END CALC_ORDER_FACTS;
What should i research in order to accomplish this task?
I am using MS-SQL & VB.Net
I have a SQL table that stores startdate and enddate. I want to run a query on that table every 10 minutes in order to check if the enddate is greater than today and if it is I would like to add a 1 to another column if it is not I would like to add a 0.
How can I achieve this?
Your question seems like you're asking for the wrong thing.
It sounds like you are looking for a trigger to fire off and update a calculated field. So it sounds like you want to setup a database trigger on the table and have that trigger fire on insert or update
USE [<database_name>]
GO
/****** Object: Trigger [dbo].[tr_<tablename>_endDateCheck] Script Date: 03/16/2011 12:42:24 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[tr_<tablename>_endDateCheck]
ON [dbo].[<tablename>]
AFTER INSERT,UPDATE
AS
BEGIN
UPDATE [<tablename>] SET isEnded = CASE WHEN (SELECT endDate FROM inserted WHERE id = inserted.id).endDate > GetDate() THEN 1 ELSE 0 END WHERE id IN (SELECT ID FROM inserted)
END
GO
Or something very similar to this anyway
You then put a scheduled job that will run at 0001 hour and update all the records which had endDate = yesterday's date
UPDATE [<tablename>] SET isEnded = 1 WHERE isEnded = 0 AND endDate BETWEEN DATEADD(DAY, -1, GETDATE()) AND GETDATE()
edit: or is endDate actually time specific as well?
Perhaps in this case you should be using a view to select your data instead with a view definition of:
SELECT *, CASE WHEN endDate > GetDate() THEN 1 ELSE 0 END AS HasEnded
FROM [<TableName>]
Edit2: fixed issue with the scheduled job which was not correct for endDate values that include a time value
Create a SQL Server job to run your update query.
UPDATE YourTable
SET AnotherColumn = AnotherColumn + 1
WHERE enddate > DATEADD(dd, DATEDIFF(dd,0,GETDATE()), 0) /* Today's date at midnight */
You can write a SQL Job, or if you want write a service to run against the database, if you are more comfortable in code or the actions require more extensive business rules.