I've got two tables:
create table customer
(
cust_id
cust_name
)
create table customerphone
(
customerphone_phone,
cust_id,
)
cust_id must exists in both tables, how can i check this?
This cannot be done with Declarative Referential Integrity (DRI) alone. Adding a foreign key constraint is only part of the solution. You will also need to need to wrap a transaction and business logic around the inserts to the 2 tables. I recommend doing this in a stored procedure so that it operates as an atomic operation from the perspective of the application.
Begin Transaction
Logic around inserting a Customer
Logic around inserting CustomerPhone row
If the newly added Customer has a CustomerPhone
Commit Transaction
Else
Rollback Transaction
Make cust_id a primary key on the table customer.
Then make cust_id a foreign key on the table customerphone, which points back to cust_id on customer.
Then you can only add rows to customerphone if the cust_id already exists in customer.
You can use cust_id from customer as PRIMARY KEY, and use a FOREIGN KEY to restrict the cust_id in customerphone.
You can guide yourself using this manual:
https://technet.microsoft.com/en-us/library/ms175464(v=sql.105).aspx
Related
I have two tables, i will leave the create table statements for both of them below.
I would like to know if it is possible to lock the corresponding rows in currency_details when someone is modifying a row on currency and vice versa.
CREATE TABLE IF NOT EXISTS currency (
id VARCHAR(8),
name VARCHAR(20),
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS currency_details (
id VARCHAR(8) REFERENCES currency ON DELETE CASCADE,
info VARCHAR(80),
PRIMARY KEY (id)
);
The only locks that are taken automatically is a FOR KEY SHARE row lock on currency if you insert rows into currency_details.
Other than that, you could write a trigger that performs an appropriate SELECT ... FOR UPDATE on the other table.
But that is an unusual requirement; perhaps it is not really necessary to lock those rows.
I have a database orders table with a foreign key that refers to a customer id in another table. the customer and orders table is a one-to-many relationship. There are several orders in the orders table that do not have a customer id (the foreign key) included in the orders table because the foreign key column was left nullable:
create table orders1
(
orderID int,
customerID int,
customerLName varchar(50),
customerFName varchar(50),
orderTotal float,
primary key (orderID),
foreign key (customerID) references customer1(customerID) -- left nullable
);
I can't go back an fix the entries that have no customer IDs attached, but is there a trigger object I can create that will alert the user if they do not fill in the customer ID value in the future?
No, there is no trigger/code you can write that will be able to guess what customer id a random order should belong to if the insert omits the customer id.
Your best move is to fix the rows that have null customer id and make the FK not nullable.
One reasonable way to fix the rows is to create a dummy customer record and associate the orphaned orders to it. Once that is done, and you alter the column to not null, you will have stopped the rot and can sift through the data to try to figure out what customer those orders actually belong to. You might find the answer in application logs, or perhaps other entities such as payment etc, or maybe the orders are so old nobody cares.
There are 2 tables in the database that contain the following columns:
department table with column dept_no (char(4), not null)
employee table with column dept_no (char(4), null)
The dept_no column needs to be defined as a primary key in the department table and a foreign key in the employee table using a trigger.
I thought this was the correct solution using the deleted and inserted virtual tables to update/delete the foreign key in the corresponding employee table:
CREATE TRIGGER trig_delete_dept_no
ON department
AFTER DELETE
AS
UPDATE employee
SET employee.dept_no = NULL
FROM deleted
WHERE employee.dept_no = deleted.dept_no
CREATE TRIGGER trig_update_dept_no
ON department
AFTER UPDATE
AS
UPDATE e
SET e.dept_no = i.dept_no
FROM employee e
INNER JOIN inserted i ON e.dept_no = i.dept_no
However, when I update the department dept_no row to a different value I do not see the corresponding dept_no update in the employee table:
UPDATE department
SET dept_no = 'd4'
WHERE dept_no = 'd3'
Deleting functions as expected. What am I doing wrong with the update trigger and how can I combine these two triggers into one trigger?
There is an issue in your design. The first thing is you should not use dept_no as PK (Primary Key). You need to have an IDENTITY or GUID column as Primary Key and refer to that column as FK (Foreign Key).
This way you won't need to worry about changing the dept_no.
The second point is you don't need trigger. you can use CASCADE option on DELETE action.
Find more information on CASCADE
Thank you FLICKER and SMor for helping me think through this. I do not believe the assignment wants us to modify the tables by adding IDENTITY or GUID columns and since we are to strictly use Triggers, this is the best solution I can come up with:
CREATE TRIGGER trig_delete_dept_no ON department AFTER DELETE AS
UPDATE employee
SET employee.dept_no = NULL
FROM deleted
WHERE employee.dept_no = deleted.dept_no
CREATE TRIGGER trig_update_dept_no ON department AFTER UPDATE AS
IF UPDATE(dept_no)
BEGIN
IF (SELECT employee.dept_no
FROM employee, inserted
WHERE employee.dept_no = inserted.dept_no) IS NULL
BEGIN
ROLLBACK TRANSACTION
RAISERROR ('Integrity constraint violation, TRIGGER:
trig_update_dept_no, TABLE: department',16,1)
END
ELSE PRINT 'Update successful'
END
This will allow updates to occur in the department as long as there are no orphaned records in the employee table.
I have two tables:
Customer (customerID, firstName, lastName,....)
Account (accountID, currentBalance....customerID) customerID references CUSTOMER.
The tables have a 1:M (Customer:Account), Mandatory-Mandatory relationship.
I am trying to setup a trigger that automatically creates a child row when a parent is inserted, after researching Stack and elsewhere I have managed to create a trigger on the parent that creates a row in the child:
CREATE OR REPLACE TRIGGER CMustHaveAccount
AFTER INSERT ON CUSTOMER
FOR EACH ROW
BEGIN
INSERT INTO ACCOUNT (accountID)
SELECT SEQACCOUNTID.NEXTVAL
FROM dual;
END;
/
All my attempts to set the FK in Account as the new PK in Customer are failing, I tried a number of triggers, the most promising being:
CREATE OR REPLACE TRIGGER AMustHaveCustomer
AFTER INSERT ON CUSTOMER
FOR EACH ROW
BEGIN
INSERT INTO ACCOUNT (customerID)
SELECT :new.customerID
FROM CUSTOMER;
END;
/
This trigger throws back the error
ORA-04091: table .CUSTOMER is mutating, trigger/function may not see
it
.
If I change the trigger to BEFORE, it gives error ORA-01400: cannot insert NULL into ("ACCOUNT"."ACCOUNTID"). I am assuming because technically the insert has not been completed so the PK I am inserting into Customer does not yet exist.
I want to have a trigger(s) that inserts a value into Account, with a primary key from my sequence, when a row is created in Customer, and for the PK customerID to automatically be assigned to customerID in ACCOUNT as the foreign key.
I am just learning SQL and Databases, please excuse me if the answer is obvious.
Help greatly appreciated!
The foreign key of ACCOUNT is the primary key of CUSTOMER, so this should work for you. Note the :new keyword, which is how to reference values in the current record and so avoid the "mutating table" error.
CREATE OR REPLACE TRIGGER CMustHaveAccount
AFTER INSERT ON CUSTOMER
FOR EACH ROW
BEGIN
INSERT INTO ACCOUNT (accountID, currentBalance, customerID)
values ( SEQACCOUNTID.NEXTVAL, 0, :NEW.customerID);
END;
/
I am trying to figure out the best way to relate these tables together. Suppose I have the following tables:
tblPerson
tblGroup
tblResource
Each row in each of these tables can have multiple email addresses associated with them so I would want a separate table and relate it back.
Are there methods to have a single table (tblEmail) relate back to each of the tables. I thought of using a uniqueidentifier field in each of the parent tables and using that as a key in the email table. It would be guaranteed unique. I just wouldn't be able to create a FK in the email table to preserve integrity. That is manageable though.
Is there a fancy way to do this? I am creating these tables in SQL 2008 R2.
Thank you
Karl
While it may be tempting to try and use a single email table with a ParentType (Person/Group/Resource) and ParentID, this is dangerous and means you can't have the relationship defined in SQL (unless there's some feature I'm unaware of?).
If you want to have referential integrity in SQL you really need to create 3 tables, one for each parent table.
CREATE TABLE dbo.PersonEmail (
ID int IDENTITY PRIMARY KEY,
PersonID int,
EmailAddress varchar(500)
)
CREATE TABLE dbo.GroupEmail (
ID int IDENTITY PRIMARY KEY,
GroupID int,
EmailAddress varchar(500)
)
CREATE TABLE dbo.ResourceEmail (
ID int IDENTITY PRIMARY KEY,
ResourceID int,
EmailAddress varchar(500)
)
If you think you might extend your Email table to later include a DisplayName, and perhaps a BounceCount and others, create a table for Email and create many-to-many join tables to link them to Person/Group/Resource.
Be aware that edits might impact multiple links, you'll have to decide how you want to handle that.
This is a core part of SQL. In a proper relational design, you don't relate email addresess to perosns, groups, or resources -- you relate the persons, groups, and resources TO the email.
So, with an email table of:
CREATE TABLE dbo.tblEmail (
emailID int IDENTITY PRIMARY KEY,
email varchar(500)
)
If you only need one email per entity, you would just insert an emailID on each of the other fields that model something that may need an email.
ALTER TABLE dbo.tblPerson
ADD emailID int REFERENCES dbo.tblEmail(emailID);
ALTER TABLE dbo.tblGroup
ADD emailID int REFERENCES dbo.tblEmail(emailID);
ALTER TABLE dbo.tblResource
ADD emailID int REFERENCES dbo.tblEmail(emailID);
If you need multiple email addresses per entity, you need to insert an additional table, to interpolate the set of email addresses to a particular address. (I wouldn't do this unless you have a technical reason to handle the addresses individually, such as a bulk-email system where you want to avoid duplicates if someone uses the same email for their own use and their organization's use.)
CREATE TABLE dbo.tblEmail (
emailID int IDENTITY PRIMARY KEY
)
CREATE TABLE dbo.tblEmailAddress (
eAddrID IDENTITY PRIMARY KEY,
eAddr varchar(500)
)
CREATE TABLE dbo.tblEmailSet (
emailID int REFERENCES dbo.tblEmail(emailID),
eAddrID int REFERENCES dbo.tblEmailAddresses(eAddrID),
)
In order to, say, return a list of all emails to any Person, Group, or Resource named "Smith", you'd run the query below:
SELECT DISTINCT A.eAddr
FROM (
SELECT emailID FROM dbo.tblPerson WHERE Name = 'Smith'
UNION
SELECT emailID FROM dbo.tblGroup WHERE Name = 'Smith'
UNION
SELECT emailID FROM dbo.tblResource WHERE Name = 'Smith'
) AS PGR
INNER JOIN dbo.tblEmailSet AS S
ON PGR.emailID = S.emailID
INNER JOIN dbo.tblEmailAddress AS A
ON S.eAddrID = A.eAddrID
That ugly UNION, btw, is one of the reasons why you really don't want to do this unless you have a technical need to retrieve the data uniquely. While I've done this sort of many-to-many-to-many join on occasion, in this particular instance it's kind of a "code smell" and an indicator that instead of tracking "People", "Groups", and "Resources", you should be tracking "Contacts" with a "type" indicator to tell if a contact is a Person, a Group, or a Resource.
(Or maybe you never need to grab a bunch of email addresses, and just want a single table of emails you can check for whitelisting...)
So you want to have possibly multiple Emails per Person/Group/Resource, and you want all those emails in one table, am I correct ?
To do that, I would create a table dbo.EmailAddress such as this :
CREATE TABLE dbo.EmailAddress
(
EmailID BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY
,EmailAddress VARCHAR(250) NOT NULL
CONSTRAINT UK_EmailAddress UNIQUE(EmailAddress) --to ensure that you never insert twice the same email address
)
Then I would create the relation between your Person/Group/Resource and you emails using another table :
CREATE TABLE dbo.EmailAddressParentXRef
(
EmailID INT NOT NULL REFERENCES dbo.EmailAddress(EmailID)
,ParentTypeID INT NOT NULL
,PersonID INT NULL REFERENCES dbo.tblPerson(PersonID)
,GroupID INT NULL REFERENCES dbo.tblGroup(GroupID)
,ResourceID INT NULL REFERENCES dbo.tblResource(ResourceID)
CONSTRAINT UK_EmailID_ParentTypeID UNIQUE(EmailID,ParentTypeID) --to make sure you don't put the same EmailID for the same type of Parent (e.g. EmailID=12 twice for an Account)
)
There you would have referential integrity + some checks to avoid duplicates when you load data. Note that I didn't put a check to make sure you actually fill in either the PersonID, GroupID or ResourceID. This can be added in different ways, but if you understand the principle of this table, you shouldn't load any line without those references (or they will just be useless).
A lot more checks can be added based on this, to take care of every type of duplication/error you might create when loading the data, but you get the point.