INSTEAD OF INSERT trigger & Identity column using view - sql

I'm building a database that will store client data for a company. The tables in the DB are normalized, so I have multiple tables that are linked together using Foreign Key Constraints.
Microsoft Access will be used to interface with the database (as a frontend). To make things simpler, I created a view that joins all the required tables together so that end-users can query information without hassle.
The problem I've run into involves INSERTING information into this view. From my understanding, since I have multiple tables in my view, I have to use a trigger with an INSTEAD OF INSERT statement. I've created the trigger; however, I'm unsure how to work with the ID columns in the tables (these act as keys).
I have a MemberBasicInformation table that contains the client's DOB, Name, Gender, etc AND their MemberID. This ID is an IDENTITY column in the table, so it is generated automatically. The problem that I'm running into is that because the identity is automatically generated, I'm unable to grab the identity value that is generated after the insert into the MemberBasicInformation table and insert it into the other related tables. When I attempt to do so, I end up with a Foreign Key constraint violations.
I've tried using ##Identity and Scope_Identity() to no avail. I've listed my view and trigger to give you an idea of how things are set up. I would greatly appreciate if someone could point me in the right direction. I'm truly at a loss.
MEMBER view:
CREATE VIEW [dbo].[Member]
AS
SELECT
dbo.MemberBasic.MemberId, dbo.MemberBasic.FirstName,
dbo.MemberBasic.MiddleInitial, dbo.MemberBasic.LastName,
dbo.MemberBasic.FullName, dbo.MemberBasic.DateOfBirth,
dbo.Gender.Name AS Gender, dbo.MemberBasic.Address,
dbo.MemberBasic.Address2, dbo.MemberBasic.City,
dbo.MemberBasic.State, dbo.MemberBasic.ZipCode,
dbo.MemberBasic.PhoneNumber,
dbo.MemberBasic.SocialSecurityNumber,
dbo.MemberBasic.DriversLicense,
dbo.MemberBasic.EmployerIdentificationNumber,
dbo.MemberBasic.Notes,
dbo.FieldRep.Name AS FieldRepName,
dbo.MemberDetail.DateAssigned AS FieldRepDateAssigned,
dbo.MemberDetail.CPReceivedOn, dbo.MemberDetail.CredentialedOn,
dbo.MemberEligibility.IsActive, dbo.ICO.Name AS ICO,
dbo.MemberEligibility.StartDate AS EligibilityStartDate,
dbo.MemberEligibility.EndDate AS EligibilityEndDate,
dbo.MemberWorkerCompDetail.ExpirationDate AS WorkerCompExpirationDate,
dbo.MemberWorkerCompDetail.AuditDate AS WorkerCompAuditDate,
dbo.WorkerCompTier.Name AS WorkerCompTier,
dbo.MemberAttachment.AttachmentId,
dbo.MemberAttachment.Data AS AttachmentData
FROM
dbo.MemberAttachment
INNER JOIN
dbo.MemberBasic ON dbo.MemberAttachment.MemberId = dbo.MemberBasic.MemberId
INNER JOIN
dbo.MemberCaregiverAssignment ON dbo.MemberAttachment.MemberId = dbo.MemberCaregiverAssignment.MemberId
INNER JOIN
dbo.MemberDetail ON dbo.MemberBasic.MemberId = dbo.MemberDetail.MemberId
INNER JOIN
dbo.MemberEligibility ON dbo.MemberAttachment.MemberId = dbo.MemberEligibility.MemberId
INNER JOIN
dbo.MemberWorkerCompDetail ON dbo.MemberAttachment.MemberId = dbo.MemberWorkerCompDetail.MemberId
INNER JOIN
dbo.Gender ON dbo.MemberBasic.GenderId = dbo.Gender.GenderId
INNER JOIN
dbo.FieldRep ON dbo.MemberDetail.FieldRepId = dbo.FieldRep.FieldRepId
INNER JOIN
dbo.ICO ON dbo.MemberEligibility.ICOId = dbo.ICO.ICOId
INNER JOIN
dbo.WorkerCompTier ON dbo.MemberWorkerCompDetail.TierId = dbo.WorkerCompTier.TierId
GO
MEMBER trigger:
ALTER TRIGGER [dbo].[InsertNewMember]
ON [dbo].[Member]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO MemberBasic (FirstName, MiddleInitial, LastName, GenderId, DateOfBirth, Address, Address2, City, State, ZipCode, PhoneNumber, SocialSecurityNumber, DriversLicense, EmployerIdentificationNumber, Notes)
SELECT
FirstName, MiddleInitial, LastName, GenderId, DateOfBirth,
Address, Address2, City, State, ZipCode, PhoneNumber,
SocialSecurityNumber, DriversLicense,
EmployerIdentificationNumber, Notes
FROM
inserted
INNER JOIN
Gender ON Gender.Name = Gender;
INSERT INTO MemberDetail (MemberId, FieldRepId, DateAssigned, CPReceivedOn, CredentialedOn)
SELECT
MemberId, FieldRep.FieldRepId, FieldRepDateAssigned,
CPReceivedOn, CredentialedOn
FROM
inserted
INNER JOIN
FieldRep ON FieldRep.Name = FieldRepName;
INSERT INTO MemberEligibility (MemberId, ICOId, StartDate, EndDate)
SELECT
MemberId, ICOId, EligibilityStartDate, EligibilityEndDate
FROM
inserted
INNER JOIN
ICO ON ICO.Name = ICO;
INSERT INTO MemberWorkerCompDetail (MemberId, AuditDate, ExpirationDate, TierId)
SELECT
MemberId, WorkerCompAuditDate, WorkerCompExpirationDate, TierId
FROM
inserted
INNER JOIN
WorkerCompTier ON WorkerCompTier.Name = WorkerCompTier;
INSERT INTO MemberAttachment (MemberId, Data)
SELECT MemberId, AttachmentData
FROM Member
END

You can do this by using a table variable to hold the newly inserted IDs and all of the data that you're going to insert into the other tables. You need to do this because there's no way from just the first INSERTs data to join back to inserted in such a manner that you can match up the IDENTITY values with the rows that caused them to be generated.
We also have to abuse MERGE since INSERT doesn't let you include anything other than the target table in its OUTPUT clause.
I'm doing this on a toy example but hopefully you can see how to write it for your full table structures.
First, some tables:
create table dbo.Core (
ID int IDENTITY(-71,3) not null,
A varchar(10) not null,
constraint PK_Core PRIMARY KEY (ID)
)
go
create table dbo.Child1 (
ID int IDENTITY(-42,19) not null,
ParentID int not null,
B varchar(10) not null,
constraint PK_Child1 PRIMARY KEY (ID),
constraint FK_Child1_Core FOREIGN KEY (ParentID) references Core(ID)
)
go
create table dbo.Child2 (
ID int IDENTITY(-42,19) not null,
ParentID int not null,
C varchar(10) not null,
constraint PK_Child2 PRIMARY KEY (ID),
constraint FK_Child2_Core FOREIGN KEY (ParentID) references Core(ID)
)
go
And the view:
create view dbo.Together
with schemabinding
as
select
c.ID,
c.A,
c1.B,
c2.C
from
dbo.Core c
inner join
dbo.Child1 c1
on
c.ID = c1.ParentID
inner join
dbo.Child2 c2
on
c.ID = c2.ParentID
go
And finally the trigger:
create trigger Together_T_I
on dbo.Together
instead of insert
as
set nocount on
declare #tmp table (ID int not null, B varchar(10) not null, C varchar(10) not null);
merge into dbo.Core c
using inserted i
on
c.ID = i.ID
when not matched then insert (A) values (i.A)
output
inserted.ID /* NB - This is the output clauses inserted,
not the trigger's inserted so this is now populated */
,i.B,
i.C
into #tmp;
insert into dbo.Child1(ParentID,B)
select ID,B
from #tmp
insert into dbo.Child2(ParentID,C)
select ID,C
from #tmp
(And I would keep something like that comment in there since statements inside triggers that include OUTPUT clauses tend to be quite confusing since there are two inserted tables in play)
(It's also noteworthy that it doesn't really matter what you put in the ON clause of the MERGE, so long as you're sure that it will fail to make a match. You may prefer to have, say, just 1=0 if you think it makes it clearer. I just went cute on the fact that the trigger's inserted.ID will be NULL)
And now we do our insert:
insert into dbo.Together(A,B,C) values ('aaa','bbb','ccc')
go
select * from dbo.Together
And get the result:
ID A B C
----------- ---------- ---------- ----------
-71 aaa bbb ccc

Here is answer to your question "Getting Identity values to use as FK in an INSTEAD OF trigger"
https://dba.stackexchange.com/questions/34258/getting-identity-values-to-use-as-fk-in-an-instead-of-trigger

Related

Select Statement in SQL inheritance

CREATE TABLE User(
UserID int primary key,
Name varchar,
type int
);
CREATE TABLE Student(
UserID int primary key references User(UserID),
marks int
);
CREATE TABLE Lecture(
UserID int primary key references User(UserID),
salary int
);
Can someone help with with select statement for Student or lecture.
Both Lecture and Student tables are inheriting from User table,So I need to know how insert data and select data from these tables.
To query the tables, this would work:
SELECT u.[Name]
, s.marks
, l.salary
FROM [User] u
INNER JOIN Student s
ON u.UserId = s.UserId
INNER JOIN Lecture l
ON u.UserId = l.UserId;
However, if there are no records in the Student / Lecture tables yet, you should use LEFT Join instead.
As for inserting the data, you would need to use SCOPE_IDENTITY().
Insert into [User] (Name) values ('Melvin')
Get the identity of the UserId
DECLARE #userId INT;
SELECT #userId = SCOPE_IDENTITY ();
INSERT INTO Student
(
UserID
, Marks
)
VALUES
(userId, 5);
Update: Just noticed this was SQL Lite, which I'm not so familiar with, but it looks like it supports last_insert_rowid() instead of SCOPE_IDENTITY (), but you should get the gist of it.
If you want to select Student X:
SELECT *
FROM Student
WHERE UserID = X
If you want to select all Students and their User data, you'll want something like:
SELECT *
FROM User
JOIN Student ON User.UserID = Student.UserID
I don't understand your question but I think you need something like that
select Name, marks
from User as u, Student as s
inner join u.UserID == s.UserID;

SQL cross-reference table self-reference

I am working on a project where I have a table of
all_names(
team_name TEXT,
member_name TEXT,
member_start INT,
member_end INT);
What I have been tasked with is creating a table of
participants(
ID SERIAL PRIMARY KEY,
type TEXT,
name TEXT);
which contains all team and member names as their own entries. Type may be either "team" or "member".
To compliment this table of participants I am trying to create a cross-reference table that allows a member to be referenced to a team by ID and vice versa. My table looks like this:
belongs_to(
member_id INT REFERENCES participants(ID),
group_id INT REFERENCES participants(ID),
begin_year INT,
end_year INT,
PRIMARY KEY (member_id, group_id);
I am unsure of how to proceed and populate the table properly.
The select query I have so far is:
SELECT DISTINCT ON (member_name, team_name)
id, member_name, team_name, member_begin_year, member_end_year
FROM all_names
INNER JOIN artists ON all_names.member_name = participants.name;
but I am unsure of how to proceed. What is the proper way to populate the cross reference table?
Probably the easiest solution is to use a few statements. Wrap this is a transaction to make sure you don't get concurrency issues:
BEGIN;
INSERT INTO participants (type, name)
SELECT DISTINCT 'team', team_name
FROM all_names
UNION
SELECT DISTINCT 'member', member_name
FROM all_names;
INSERT INTO belongs_to
SELECT m.id, g.id, a.member_start, a.member_end
FROM all_names a
JOIN participants m ON m.name = a.member_name
JOIN participants g ON g.name = a.team_name;
COMMIT;
Members that are part of multiple teams get all of their memberships recorded.

SQL Simple SELECT Query

create table Person(
SSN INT,
Name VARCHAR(20),
primary key(SSN)
);
create table Car(
PlateNr INT,
Model VARCHAR(20),
primary key(PlateNr)
);
create table CarOwner(
SSN INT,
PlateNr INT,
primary key(SSN, PlateNR)
foreign key(SSN) references Person (SSN),
foreign key(PlateNr) references Car (PlateNr)
);
Insert into Person(SSN, Name) VALUES ('123456789','Max');
Insert into Person(SSN, Name) VALUES ('123456787','John');
Insert into Person(SSN, Name) VALUES ('123456788','Tom');
Insert into Car(PlateNr, Model) VALUES ('123ABC','Volvo');
Insert into Car(PlateNr, Model) VALUES ('321CBA','Toyota');
Insert into Car(PlateNr, Model) VALUES ('333AAA','Honda');
Insert into CarOwner(SSN, PlateNr) VALUES ('123456789','123ABC');
Insert into CarOwner(SSN, PlateNr) VALUES ('123456787','333AAA');
The problem I'm having is the SELECTE query I wanna make. I wan't to be able to SELECT everything from the Person and wan't the include the PlateNr of the car he's the owner of, an example:
PERSON
---------------------------------
SSN NAME Car
123456789 Max 123ABC
123456787 John 3338AAA
123456788 Tom
----------------------------------
So, I want to be able to show everything from the Person table and display the content of CarOwner aswell if the person is in fact a CarOwner. What I have so far is: "SELECT * from Person, CarOwner WHERE Person.SSN = CarOwner.SSN;". But this obviously results in only showing the person(s) that are CarOwners.
Hope I explained me well enough, Thanks.
Try this:
SELECT p.*, c.*
FROM Person p
LEFT OUTER JOIN CarOwner co
ON p.SSN = co.SSN
LEFT OUTER JOIN Car c
ON co.PlateNr = c.PlateNr
Show SQLFiddle
P.S. I've changed the type of your primary key PlateNr (in varchar and not in int)
select ssn, name, car
from Person p
LEFT OUTER JOIN CarOwner co
ON p.SSN = co.SSN
LEFT OUTER JOIN Car c
ON co.PlateNr = c.PlateNr

Trying to develop the syntax to update one table with values from another table based on a join of a third table

Test case below shows construction of tables. I need to update the address data in tab_1 based on a join on tab_2 and _3. The update script shown returns 'missing right parenthesis', and I am certain it points to an error in the syntax. Would appreciate any help or guidance in getting the statement to properly update the base table.
create table tab_1(address varchar2(25), city varchar2(25), state varchar2(2),zip varchar2(10), office_id varchar2(25));
create table tab_2 (company varchar2(25), office varchar2(25), address_id varchar2(5), office_id varchar2(5));
create table tab_3 (address_id varchar2(5), address varchar2(25), city varchar2(25), state varchar2(2),zip varchar2(10));
insert into tab_1(office_id) values(46);
insert into tab_2(company, office, address_id, office_id)
values('Stone', 'north', '45', '15');
insert into tab_3(address_id, address, city, state, zip)
values('15', '12Main', 'York', 'NY', '12345');
ALTER TABLE TAB_1 ADD
CONSTRAINT tab_1_PK
PRIMARY KEY (OFFICE_ID)
ENABLE
VALIDATE;
ALTER TABLE TAB_2 ADD
CONSTRAINT tab_2_PK
PRIMARY KEY (OFFICE_ID)
ENABLE
VALIDATE;
ALTER TABLE TAB_3 ADD
CONSTRAINT tab_3_PK
PRIMARY KEY (ADDRESS_ID)
ENABLE
VALIDATE;
update (select tab_3.address, tab_3.city, tab_3.state, tab_3.zip, tab_1.address, tab_1.city, tab_1.state, tab_1.zip
FROM
INNER JOIN tab_1 ON (tab_1.office_id=tab_2.office.id)
INNER JOIN tab_3 ON (tab_2.address_id = tab_3.address_id))
SET tab_1.address=tab_3.address, tab_1.city=tab_3.city, tab_1.state=tab_3.state, tab_1.zip=tab_3.zip;
UPDATE ( SELECT src.x src_x, src.y src_y , tgt.x tgt_x, tgt.y tgt_y FROM src
INNER JOIN tgt ON ( src.id = tgt.id ) ) SET tgt_x = src_x , tgt_y = src_y
*******************************************************
UPDATE tab_1
SET (address,
city,
state,
zip) =
(SELECT (address, city, state, zip)
FROM tab_3, tab2
WHERE tab_1.office_id = tab_2.office_id
AND tab_2.address_id = tab_3.address_id);
Your first two update statements are incomplete - they don't even specify which table to update. They're so far off I'm afraid I'm going to ignore them as I don't see how they're salvagable *8-)
Your third as an extra set of parentheses; you don't need them (and they aren't valid) around the list of columns you're selecting in the subquery, it's trying to interpret that as a further subquery which doesn't exist. You also have a typo in one of the table names:
UPDATE tab_1
SET (address, city, state, zip) =
(SELECT address, city, state, zip
FROM tab_3, tab_2
WHERE tab_1.office_id = tab_2.office_id
AND tab_2.address_id = tab_3.address_id);
I'd suggest you use modern join syntax, especially if this is fairly new and you haven't learned (arguably) bad habits yet:
UPDATE tab_1
SET (address, city, state, zip) =
(SELECT address, city, state, zip
FROM tab_2
JOIN tab_3
ON tab_3.address_id = tab_2.address_id
WHERE tab_2.office_id = tab_1.office_id);

Writing a complex trigger

I am using SQL Server 2000. I am writing a trigger that is executed when a field Applicant.AppStatusRowID
Table Applicant is linked to table Location, table Company & table AppStatus.
My issue is creating the joins in my query.
When Applicant.AppStatusRowID is updated, I want to get the values from
Applicant.AppStatusRowID, Applicant.FirstName, Applicant.Lastname, Location.LocNumber, Location.LocationName, Company.CompanyCode, AppStatus.DisplayText
The joins would be :
Select * from Applicant A
Inner Join AppStatus ast on ast.RowID = a.AppStatusRowID
Inner Join Location l on l.RowID = a.LocationRowID
Inner Join Company c on c.RowID = l.CompanyRowID
This is to be inserted into an Audit table (fields are ApplicantID, LastName, FirstName, Date, Time, Company, Location Number, Location Name, StatusDisposition, User)
My issue is the query for the inner join...
First lets introduce you to the inserted and deleted pseudotables which are available only in triggers. Inserted has new values and delted has old values or records being deleted.
You do not want to insert all records into your audit table only those in inserted.
So to insert into an audit table you might want something like inside the trigger code:
insert Myaudittable (<insert field list here>)
Select <insert field list here> from Inserted i
Inner Join AppStatus ast on ast.RowID = i.AppStatusRowID
Inner Join Location l on l.RowID = i.LocationRowID
Inner Join Company c on c.RowID = l.CompanyRowID
I would personally add columns for old and new values, a column for the type of change and what the date of the change and what user made the change, but you I'm sure have your own requirement to follow.
Suggest you read about triggers in Books online as they can be tricky to get right.
Here's one way to test and debug trigger that I often use. First I create temp tables names #delted and #inserted that have the sturcture of the table I'm going to put the trigger on. Then I write the code to use those instead of the deleted or inserted tables. That wa y I can look at things as I go and make sure everything is right before I change the code to a trigger. Example below with you code added in and modified slightly:
Create table #inserted(Rowid int, lastname varchar(100), firstname varchar(100), appstatusRowid int)
Insert #inserted
select 1, 'Jones', 'Ed', 30
union all
select 2, 'Smith', 'Betty', 20
Create table #deleted (Rowid int, lastname varchar(100), firstname varchar(100), appstatusRowid int)
Insert #deleted
select 1, 'Jones', 'Ed', 10
union all
select 2, 'Smith', 'Betty', 20
--CREATE TRIGGER tri_UpdateAppDisp ON dbo.Test_App
--For Update
--AS
--If Update(appstatusrowid)
IF exists (select i.appstatusRowid from #inserted i join #deleted d on i.rowid = d.rowid
Where d.appstatusrowid <> i.appstatusrowid)
BEGIN
--Insert AppDisp(AppID, LastName, FirstName, [DateTime],Company,Location,LocationName, StatusDisp,[Username])
Select d.Rowid,d.LastName, d.FirstName, getDate(),C.CompanyCode,
l.locnum,l.locname, ast.Displaytext, SUSER_SNAME()+' '+User
From #deleted d
Join #inserted i on i.rowid = d.rowid
--From deleted d
--Join inserted i on i.rowid = d.rowid
Inner join Test_App a with (nolock) on a.RowID = d.rowid
inner join location l with (nolock) on l.rowid = d.Locationrowid
inner join appstatus ast with (nolock) on ast.rowid = d.appstatusrowid
inner join company c with (nolock) on c.rowid = l.CompanyRowid
Where d.appstatusrowid <> i.appstatusrowid)
end
Once you get the data for the select correct, then it is easy to uncomment out the trigger code and the insert line and change #deleted or #inserted to deleted or inserted.
You'll note I had two records in the temp tables, one of which met your condition and one of which did not. This allows you to test mulitple record updates as well as results that meet the condition and ones that don't. All triggers should be written to handle multiple records as they are not fired row-by-row but by batch.