Confused with Oracle Procedure with sequence, linking errors and filling null fields - sql

I am trying to make a procedure that takes makes potential empty "received" fields use the current date. I made a sequence called Order_number_seq that populates the order number (Ono) column. I don't know how to link errors in the orders table to a entry in the Orders_errors table.
this is what i have so far:
CREATE PROCEDURE Add_Order
AS BEGIN
UPDATE Orders
CREATE Sequence Order_number_seq
Start with 1,
Increment by 1;
UPDATE Orders SET received = GETDATE WHERE received = null;
These are the tables I am working with:
Orders table
(
Ono Number Not Null,
Cno Number Not Null,
Eno Number Not Null,
Received Date Null,
Shipped_Date Date Null,
Creation_Date Date Not Null,
Created_By VARCHAR2(10) Not Null,
Last_Update_Date Date Not Null,
Last_Updated_By VARCHAR2(10) Not Null,
CONSTRAINT Ono_PK PRIMARY KEY (Ono),
CONSTRAINT Cno_FK FOREIGN KEY (Cno)
REFERENCES Customers_Proj2 (Cno)
);
and
Order_Errors table
(
Ono Number Not Null,
Transaction_Date Date Not Null,
Message VARCHAR(100) Not Null
);
Any help is appreciated, especially on linking the orders table errors to create a new entry in OrderErrors table.
Thanks in advance.

Contrary to Martin Drautzburg's answer, there is no foreign key for the order number on the Order_Errors table. There is an Ono column which appears to serve that purpose, but it is not a foreign as far as Oracle is concerned. To make it a foreign key, you need to add a constraint much like the Cno_FK on Orders. An example:
CREATE TABLE Order_Errors
(
Ono Number Not Null,
Transaction_Date Date Not Null,
Message VARCHAR(100) Not Null,
CONSTRAINT Order_Errors_Orders_FK FOREIGN KEY (Ono) REFERENCES Orders (Ono)
);
Or, if your Order_Errors table already exists and you don't want to drop it, you can use an ALTER TABLE statement:
ALTER TABLE Order_Errors
ADD CONSTRAINT Order_Errors_Orders_FK FOREIGN KEY (Ono) REFERENCES Orders (Ono)
;
As for the procedure, I'm inclined to say what you're trying to do does not lend itself well to a PROCEDURE. If your intention is that you want the row to use default values when inserted, a trigger is better suited for this purpose. (There is some performance hit to using a trigger, so that's a consideration.)
-- Create sequence to be used
CREATE SEQUENCE Order_Number_Sequence
START WITH 1
INCREMENT BY 1
/
-- Create trigger for insert
CREATE TRIGGER Orders_Insert_Trigger
BEFORE INSERT ON Orders
FOR EACH ROW
DECLARE
BEGIN
IF :NEW.Ono IS NULL
THEN
SELECT Order_Number_Sequence.NEXTVAL INTO :NEW.Ono FROM DUAL;
END IF;
IF :NEW.Received IS NULL
THEN
SELECT CURRENT_DATE INTO :NEW.O_Received FROM DUAL;
END IF;
END;
/
This trigger will then be executed on every single row inserted into the Orders table. It checks if the Ono column was NULL and replaces it with an ID from the sequence if so. (Be careful that you don't ever provide an ID that will later be generated by the sequence; it will get a primary key conflict error.) It then checks if the received date is NULL and sets it to the current date, using the CURRENT_DATE function (which I believe was one of the things you were trying to figure out), if so.
(Side note: Other databases may not require a trigger to do this and instead could use a default value. I believe PostgreSQL, for instance, allows the use of function calls in its DEFAULT clauses, and that is how its SERIAL auto-increment type is implemented.)
If you are merely trying to update existing data, I would think the UPDATE statements by themselves would suffice. Is there a reason this needs to be a PROCEDURE?
One other note. Order_Errors has no primary key. You probably want to have an auto-incrementating surrogate key column, or at least create an index on its Ono column if you only ever intend to select off that column.

There are a number of confusing things in your question:
(1) You are creating a sequence inside a procedure. Does this even compile?
(2) Your procedure does not have any parameters. It just updates the RECEIVED column of all rows.
(3) You are not telling us what you want in the MESSAGE column.
My impression is that you should first go "back to the books" before you ask questions here.
As for your original question
how to link errors in the orders table to a entry in the Orders_errors
table.
This is aleady (correctly) done. The Orders_error table contains an ONO foreign key which points to an order.

Related

SQL - How do you use a user defined function to constrain a value between 2 tables

First here's the relevant code:
create table customer(
customer_mail_address varchar(255) not null,
subscription_start date not null,
subscription_end date, check (subscription_end !< subcription start)
constraint pk_customer primary key (customer_mail_address)
)
create table watchhistory(
customer_mail_address varchar(255) not null,
watch_date date not null,
constraint pk_watchhistory primary key (movie_id, customer_mail_address, watch_date)
)
alter table watchhistory
add constraint fk_watchhistory_ref_customer foreign key (customer_mail_address)
references customer (customer_mail_address)
on update cascade
on delete no action
go
So i want to use a UDF to constrain the watch_date in watchhistory between the subscription_start and subscription_end in customer. I can't seem to figure it out.
Check constraints can't validate data against other tables, the docs say (emphasis mine):
[ CONSTRAINT constraint_name ]
{
...
CHECK [ NOT FOR REPLICATION ] ( logical_expression )
}
logical_expression
Is a logical expression used in a CHECK constraint and returns TRUE or
FALSE. logical_expression used with CHECK constraints cannot
reference another table but can reference other columns in the same
table for the same row. The expression cannot reference an alias data
type.
That being said, you can create a scalar function that validates your date, and use the scalar function on the check condition instead:
CREATE FUNCTION dbo.ufnValidateWatchDate (
#WatchDate DATE,
#CustomerMailAddress VARCHAR(255))
RETURNS BIT
AS
BEGIN
IF EXISTS (
SELECT
'supplied watch date is between subscription start and end'
FROM
customer AS C
WHERE
C.customer_mail_address = #CustomerMailAddress AND
#WatchDate BETWEEN C.subscription_start AND C.subscription_end)
BEGIN
RETURN 1
END
RETURN 0
END
Now add your check constraint so it validates that the result of the function is 1:
ALTER TABLE watchhistory
ADD CONSTRAINT CHK_watchhistory_ValidWatchDate
CHECK (dbo.ufnValidateWatchDate(watch_date, customer_mail_address) = 1)
This is not a direct link to the other table, but a workaround you can do to validate the date. Keep in mind that if you update the customer dates after the watchdate insert, dates will be inconsistent. The only way to ensure full consistency in this case would be with a few triggers.

Oracle: stored procedure error

I'm really new to SQL and I must create a stored procedure.
My idea is that I want to type my PaperRoll_ID and to get the "Worker_ID". Since my PaperRoll_ID values in the table are from 1 to 500 and my Worker_id values are from 1500 to 2000, I want to make it so that PaperRoll_ID is equal to the Worker_id index, not value by index (I mean, index 1 is the first Worker_id I added, 2 is the second and so on till 500 (the number of workers)). PaperRoll_ID is located in the table invoice_Paper and Worker_id in the table machine_operator.
Sorry if it's hard to understand, but I lack a lot of knowledge in SQL, so it's a bit hard for me to express myself.
create or replace PROCEDURE name_worker(pi IN NUMBER, mi OUT NUMBER) IS
BEGIN
Select q.worker_ID2 INTO mi
from invoice_paper z,machine_operator o
where z.PaperRoll_ID=pi AND o.WORKER_ID2 = q.worker_ID2;
END;
The tables are
create Table invoice_paper(
PaperRoll_ID Number(10) constraint ppr_id not null,
Single_Layer Varchar(20) Default 'None in stock',
Double_Layer Varchar(20) Default 'None in stock',
Manufacturer_FactoryID Integer,
primary key(PaperRoll_ID),
Constraint pprid_invoice Foreign key (Manufacturer_FactoryID) References Paper_Factory(Factory_ID)
);
create table machine_operator(
Insurence_ID number(10) constraint in_numb not null,
Worker_ID2 number(10) constraint worka_id not null,
operator_name Varchar(20),
Email Varchar(30),
Primary key (Insurence_ID, Worker_ID2),
Constraint wka_id Foreign key(Worker_ID2) References worker(worker_id)
);
"I want to make it so that PaperRoll_ID is equal to the Worker_id index, not value by index (I mean, index 1 is the first Worker_id I added, 2 is the second and so on till 500 (the number of workers)"
That's really not how relational databases work. You should enforce such a relationship with a foreign key between invoice_paper and machine_operator, probably by adding Worker_ID2 column to invoice_paper (*).
Beyond that it's not clear what you are trying to achieve with your procedure, so it's hard to suggest anything better. However, let's assume what you want to do is get the next free worker for assignment to invoice_paper.
create or replace PROCEDURE name_worker(mi OUT NUMBER) IS
BEGIN
Select min(o.worker_ID2) INTO mi
from machine_operator o
where o.WORKER_ID2 not in (select p.worker_ID2
from invoice_paper p)
;
END;
Kept as a procedure for ease of comparison, although this sort of thing is usually written as a FUNCTION with a return value instead.
(*) Just noticed that machine_operator has a compound primary key. If this is correct the foreign key would have to be (Insurence_ID, Worker_ID2) which is ugly. It these situations it is better to have a single column surrogate primary key and enforce the compound key with an additional unique constraint.

PL/SQL function that returns a value from a table after a check

I am new to php and sql and I am building a little game to learn a little bit more of the latter.
This is my simple database of three tables:
-- *********** SIMPLE MONSTERS DATABASE
CREATE TABLE monsters (
monster_id VARCHAR(20),
haunt_spawn_point VARCHAR(5) NOT NULL,
monster_name VARCHAR(30) NOT NULL,
level_str VARCHAR(10) NOT NULL,
creation_date DATE NOT NULL,
CONSTRAINT monster_id_pk PRIMARY KEY (monster_id)
);
-- ****************************************
CREATE TABLE spawntypes (
spawn_point VARCHAR(5),
special_tresures VARCHAR (5) NOT NULL,
maximum_monsters NUMBER NOT NULL,
unitary_experience NUMBER NOT NULL,
CONSTRAINT spawn_point_pk PRIMARY KEY (spawn_point)
);
-- ****************************************
CREATE TABLE fights (
fight_id NUMBER,
my_monster_id VARCHAR(20),
foe_spawn_point VARCHAR(5),
foe_monster_id VARCHAR(20) NOT NULL,
fight_start TIMESTAMP NOT NULL,
fight_end TIMESTAMP NOT NULL,
total_experience NUMBER NOT NULL
loot_type NUMBER NOT NULL,
CONSTRAINT my_monster_id_fk FOREIGN KEY (my_monster_id)
REFERENCES monsters (monster_id),
CONSTRAINT foe_spawn_point_fk FOREIGN KEY (foe_spawn_point)
REFERENCES spawntypes (spawn_point),
CONSTRAINT fight_id_pk PRIMARY KEY (fight_id)
);
Given this data how can I easily carry out this two tasks:
1) I would like to create a pl/sql function that passing only a fight_id as a parameter and given the foe_spawn_point (inside the fight table) return the unitary_experience that is related to this spawn point referencing the spawntypes table, how can I do it? :-/ [f(x)]
In order to calculate the total experience earned from a fight (unitary_experience * fight_length) I have created a function that given a particular fight will subtract the fight_end with the fight_start so now I know how long the fight lasted. [f(y)]
2) is it possible to use this two functions (multiply the result that they returns) during the database population task?
INSERT INTO fights VALUES(.... , f(x) * f(y), 'loot A');
in order to populate all the total_experience entries inside the fights table?
thank you for your help
In SQL, you don't generally talk about building functions to do things. The building blocks of SQL are queries, views, and stored procedures (most SQL dialects do have functions, but that is not the place to start).
So, given a variable with $FIGHTID you would fetch the unitary experience with a simple query that uses the join operation:
select unitary_experience
from fight f join
spawnTypes st
on st.spawn_point = f.foe_spawn_point
where fightid = $FIGHTID
If you have a series of values to insert, along with a function, I would recommend using the select form of insert:
insert into fights(<list of columns, total_experience)
select <list of values>,
($FIGHT_END - $FIGHT_START) * (select unitary_experience from spawnTypes where spawnType ='$SPAWN_POINT)
One comment about the tables. It is a good idea for all the ids in the table to be integers that are auto-incremented. In Oracle you do this by creating a sequence (and it is simpler in most other databases).

Dataintegrity between tables in SQL Server

Is it possible to add data integrity between columns in different tables in SQL Server?
I have table Pay with column Date and table Orders with column DateofOrder. And I would like to add the data integrity so the Date cannot be earlier than the DateofOrder.
And when the user insert there the same date or even earlier database would show error.
I think you mean something like this, here done with a trigger;
CREATE TRIGGER trig_pay ON Pay
FOR INSERT, UPDATE
AS
IF EXISTS(SELECT *
FROM [Order] o
JOIN inserted i
ON o.id = i.payment_id
WHERE DateOfOrder>[date])
BEGIN
RAISERROR ('Sorry, Dave', 16, 1)
ROLLBACK;
RETURN;
END
INSERT INTO [Order] values (1, GETDATE()); -- Order today
INSERT INTO Pay values (1, DATEADD(dd, -1, getdate())); -- Pay yesterday
> Sorry, Dave
Yes, you can do it by using INSTEAD OF INSERT Trigger.
You would have to use an INSTEAD OF or AFTER trigger to enforce this, you can't do it declaratively. Well you could use a check constraint with a TVF or something but I've never tried that.
I'd show sample code but I'm not sure what payroll has to do with orders. If a new order comes in, what pay date must be later than or equal to the order date? Is there some other column that relates these two tables?
It is possible without resorting to triggers.
The idea is to add the DateofOrder in the Orders table to its existing key -- let's call it order_id -- to create a compound superkey, then reference this superkey (rather than the simple key solely order_id) in the Pay table.
Here are the bare bones:
CREATE TABLE Orders
(
order_id CHAR(10) NOT NULL,
DateofOrder DATE NOT NULL,
UNIQUE (order_id), -- simple candidate key
UNIQUE (DateofOrder, order_id) -- compund superkey
);
CREATE TABLE Pay
(
order_id CHAR(10) NOT NULL,
DateofOrder DATE NOT NULL,
FOREIGN KEY (DateofOrder, order_id)
REFERENCES Orders (DateofOrder, order_id),
DateOfPayment DATE NOT NULL,
CHECK (DateofOrder < DateOfPayment),
UNIQUE (order_id)
);

Constraint for only one record marked as default

How could I set a constraint on a table so that only one of the records has its isDefault bit field set to 1?
The constraint is not table scope, but one default per set of rows, specified by a FormID.
Use a unique filtered index
On SQL Server 2008 or higher you can simply use a unique filtered index
CREATE UNIQUE INDEX IX_TableName_FormID_isDefault
ON TableName(FormID)
WHERE isDefault = 1
Where the table is
CREATE TABLE TableName(
FormID INT NOT NULL,
isDefault BIT NOT NULL
)
For example if you try to insert many rows with the same FormID and isDefault set to 1 you will have this error:
Cannot insert duplicate key row in object 'dbo.TableName' with unique
index 'IX_TableName_FormID_isDefault'. The duplicate key value is (1).
Source: http://technet.microsoft.com/en-us/library/cc280372.aspx
Here's a modification of Damien_The_Unbeliever's solution that allows one default per FormID.
CREATE VIEW form_defaults
AS
SELECT FormID
FROM whatever
WHERE isDefault = 1
GO
CREATE UNIQUE CLUSTERED INDEX ix_form_defaults on form_defaults (FormID)
GO
But the serious relational folks will tell you this information should just be in another table.
CREATE TABLE form
FormID int NOT NULL PRIMARY KEY
DefaultWhateverID int FOREIGN KEY REFERENCES Whatever(ID)
From a normalization perspective, this would be an inefficient way of storing a single fact.
I would opt to hold this information at a higher level, by storing (in a different table) a foreign key to the identifier of the row which is considered to be the default.
CREATE TABLE [dbo].[Foo](
[Id] [int] NOT NULL,
CONSTRAINT [PK_Foo] PRIMARY KEY CLUSTERED
(
[Id] ASC
) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[DefaultSettings](
[DefaultFoo] [int] NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[DefaultSettings] WITH CHECK ADD CONSTRAINT [FK_DefaultSettings_Foo] FOREIGN KEY([DefaultFoo])
REFERENCES [dbo].[Foo] ([Id])
GO
ALTER TABLE [dbo].[DefaultSettings] CHECK CONSTRAINT [FK_DefaultSettings_Foo]
GO
You could use an insert/update trigger.
Within the trigger after an insert or update, if the count of rows with isDefault = 1 is more than 1, then rollback the transaction.
CREATE VIEW vOnlyOneDefault
AS
SELECT 1 as Lock
FROM <underlying table>
WHERE Default = 1
GO
CREATE UNIQUE CLUSTERED INDEX IX_vOnlyOneDefault on vOnlyOneDefault (Lock)
GO
You'll need to have the right ANSI settings turned on for this.
I don't know about SQLServer.But if it supports Function-Based Indexes like in Oracle, I hope this can be translated, if not, sorry.
You can do an index like this on suposed that default value is 1234, the column is DEFAULT_COLUMN and ID_COLUMN is the primary key:
CREATE
UNIQUE
INDEX only_one_default
ON my_table
( DECODE(DEFAULT_COLUMN, 1234, -1, ID_COLUMN) )
This DDL creates an unique index indexing -1 if the value of DEFAULT_COLUMN is 1234 and ID_COLUMN in any other case. Then, if two columns have DEFAULT_COLUMN value, it raises an exception.
The question implies to me that you have a primary table that has some child records and one of those child records will be the default record. Using address and a separate default table here is an example of how to make that happen using third normal form. Of course I don't know if it's valuable to answer something that is so old but it struck my fancy.
--drop table dev.defaultAddress;
--drop table dev.addresses;
--drop table dev.people;
CREATE TABLE [dev].[people](
[Id] [int] identity primary key,
name char(20)
)
GO
CREATE TABLE [dev].[Addresses](
id int identity primary key,
peopleId int foreign key references dev.people(id),
address varchar(100)
) ON [PRIMARY]
GO
CREATE TABLE [dev].[defaultAddress](
id int identity primary key,
peopleId int foreign key references dev.people(id),
addressesId int foreign key references dev.addresses(id))
go
create unique index defaultAddress on dev.defaultAddress (peopleId)
go
create unique index idx_addr_id_person on dev.addresses(peopleid,id);
go
ALTER TABLE dev.defaultAddress
ADD CONSTRAINT FK_Def_People_Address
FOREIGN KEY(peopleID, addressesID)
REFERENCES dev.Addresses(peopleId, id)
go
insert into dev.people (name)
select 'Bill' union
select 'John' union
select 'Harry'
insert into dev.Addresses (peopleid, address)
select 1, '123 someplace' union
select 1,'work place' union
select 2,'home address' union
select 3,'some address'
insert into dev.defaultaddress (peopleId, addressesid)
select 1,1 union
select 2,3
-- so two home addresses are default now
-- try adding another default address to Bill and you get an error
select * from dev.people
join dev.addresses on people.id = addresses.peopleid
left join dev.defaultAddress on defaultAddress.peopleid = people.id and defaultaddress.addressesid = addresses.id
insert into dev.defaultaddress (peopleId, addressesId)
select 1,2
GO
You could do it through an instead of trigger, or if you want it as a constraint create a constraint that references a function that checks for a row that has the default set to 1
EDIT oops, needs to be <=
Create table mytable(id1 int, defaultX bit not null default(0))
go
create Function dbo.fx_DefaultExists()
returns int as
Begin
Declare #Ret int
Set #ret = 0
Select #ret = count(1) from mytable
Where defaultX = 1
Return #ret
End
GO
Alter table mytable add
CONSTRAINT [CHK_DEFAULT_SET] CHECK
(([dbo].fx_DefaultExists()<=(1)))
GO
Insert into mytable (id1, defaultX) values (1,1)
Insert into mytable (id1, defaultX) values (2,1)
This is a fairly complex process that cannot be handled through a simple constraint.
We do this through a trigger. However before you write the trigger you need to be able to answer several things:
do we want to fail the insert if a default exists, change it to 0 instead of 1 or change the existing default to 0 and leave this one as 1?
what do we want to do if the default record is deleted and other non default records are still there? Do we make one the default, if so how do we determine which one?
You will also need to be very, very careful to make the trigger handle multiple row processing. For instance a client might decide that all of the records of a particular type should be the default. You wouldn't change a million records one at a time, so this trigger needs to be able to handle that. It also needs to handle that without looping or the use of a cursor (you really don't want the type of transaction discussed above to take hours locking up the table the whole time).
You also need a very extensive tesing scenario for this trigger before it goes live. You need to test:
adding a record with no default and it is the first record for that customer
adding a record with a default and it is the first record for that customer
adding a record with no default and it is the not the first record for that customer
adding a record with a default and it is the not the first record for that customer
Updating a record to have the default when no other record has it (assuming you don't require one record to always be set as the deafault)
Updating a record to remove the default
Deleting the record with the deafult
Deleting a record without the default
Performing a mass insert with multiple situations in the data including two records which both have isdefault set to 1 and all of the situations tested when running individual record inserts
Performing a mass update with multiple situations in the data including two records which both have isdefault set to 1 and all of the situations tested when running individual record updates
Performing a mass delete with multiple situations in the data including two records which both have isdefault set to 1 and all of the situations tested when running individual record deletes
#Andy Jones gave an answer above closest to mine, but bearing in mind the Rule of Three, I placed the logic directly in the stored proc that updates this table. This was my simple solution. If I need to update the table from elsewhere, I will move the logic to a trigger. The one default rule applies to each set of records specified by a FormID and a ConfigID:
ALTER proc [dbo].[cpForm_UpdateLinkedReport]
#reportLinkId int,
#defaultYN bit,
#linkName nvarchar(150)
as
if #defaultYN = 1
begin
declare #formId int, #configId int
select #formId = FormID, #configId = ConfigID from csReportLink where ReportLinkID = #reportLinkId
update csReportLink set DefaultYN = 0 where isnull(ConfigID, #configId) = #configId and FormID = #formId
end
update
csReportLink
set
DefaultYN = #defaultYN,
LinkName = #linkName
where
ReportLinkID = #reportLinkId