SQL - Data in two places - sql

Please have a look at the DDL below:
CREATE TABLE Sport (ID int, Description varchar(50),primary key(ID))
CREATE TABLE Audit (AditID int, PersonID int, SportID int, AuditDate datetime,primary key(id))
CREATE Table Person (ID int not null, Name varchar(50), FavouriteSportID int, PRIMARY KEY (id),
FOREIGN KEY (FavouriteSportID) REFERENCES Sport(Id))
INSERT INTO Sport VALUES (1,'Football')
INSERT INTO Sport VALUES (2,'Basketball')
INSERT INTO Sport VALUES (3,'Squash')
INSERT INTO Person VALUES (1,'Ian',1)
INSERT INTO Audit VALUES (1,1,1,'2012-01-01')
INSERT INTO Audit VALUES (2,1,1,'2012-02-01')
INSERT INTO Audit VALUES (3,1,'2012-03-01')
The Audit table shows the Persons favorite sport in the past.
I am conscious that this involves storing the favorite sport in two places i.e. Person.FavouriteSportID and the most recent audit record i.e. audit record ID 3 also shows that Ian's current favorite sport is Squash (because it has the most recent audit record).
I am wondering if there is a better design for this simple requirement.

It's fine. It's good, in fact.
Alternatively, you could use AuditId in place of SportId in Person, as a reference to the latest audit row with the current SportId, but that makes Audit an active part of the current representation. I like your design better, since the current representation is confined to fewer tables, and the audit table is an outrigger. It's also much easier to automate--a simple after update, delete trigger on Person will reliably maintain the audit table with no further effort from anybody.
EDIT
If you don't need to record the current association in the audit table, then don't, and you haven't stored the same info twice. As mentioned, an after update, delete trigger will do nicely
-- syntax assuming SQL Server 2008+, adapt as appropriate
create trigger tr_Person_Audit on Person
after update, delete
as begin
insert Audit (PersonId, SportId, ExpirationDate)
select PersonId, SportId, getdate() -- as expiration date
from (
select PersonId, SportId from deleted except
select PersonId, SportId from inserted -- control for updates where nothing changed
) this
end
If you do need to record the current association in the audit table, it's not quite as clean, but Audit is still an outrigger and you still can automate it with an after insert, update trigger:
-- syntax assuming SQL Server 2008+, adapt as appropriate
create trigger tr_Person_Audit on Person
after insert, update
as begin
insert Audit (PersonId, SportId, EffectiveDate)
select PersonId, SportId, getdate() -- as effective date
from (
select PersonId, SportId from inserted except
select PersonId, SportId from deleted -- control for updates where nothing changed
) this
end

You could also drop the column Person.FavouriteSportID and query the most recent audit when you need it. It's easier to keep the data consistent.
SELECT TOP 1 SportID
FROM Audit
WHERE PersonID = #1
ORDER BY AuditDate DESC
Or
SELECT
Person.*,
(SELECT TOP 1 Audit.SportID
FROM Audit
WHERE Audit.PersonID = Person.PersonID
ORDER BY Audit.AuditDate DESC) AS FavouriteSportID
FROM
Person

Related

From keyword not found where expected error in oracle

Select firstname as name, time as asof, salary as bal into temp employee
from people.person p
where p.id =1;
Need to create a temporary table employee by inserting values from already created table person which belongs to people database but getting error from keyword not found where expected
You'd then use CTAS (Create Table As Select), not an invalid INTO clause; it is used for different purposes.
create table temp_employee as
select firstname as name,
time as asof,
salary as bal
from people.person p
where p.id = 1;
Based on comment you posted, there are several options you might want to consider.
One is to create a permanent table (just like the above example shows). If you'll reuse it, then - in your procedure - first delete its contents (or truncate the table as it is way faster), and then re-populate it:
delete from temp_employee;
-- or truncate table temp_employee;
insert into temp_employee
select firstname, time, salary from people.person
where id = 1;
Another option is to create a true temporary table, e.g.
create global temporary table temp_employee
(name varchar2(30),
time date,
bal number
)
on commit preserve rows;
Whenever you need data in it, just insert it:
insert into temp_employee (name, time, bal)
select firstname as name,
time as asof,
salary as bal
from people.person p
where p.id = 1;
Doing so, its contents will be visible only to you (and nobody else), while table's contents will be kept during the transaction or session (it depends on how you created it - see the on commit preserve/delete rows clause).
What you should not do is to create the table, drop it, then create it again, and so on - in Oracle, we create table once and use it many times.

Insert bulk data into two related tables with foreign keys from another table

I have imported some data to a temp SQL table from an Excel file. Then I have tried to insert all rows to two related tables. Simply like this: There are Events and Actors tables with many to many relationship in my database. Actors are already added. I want to add all events to Events table and then add relation(ActorId) for each event to EventActors tables.
(dbo.TempTable has Title, ActorId columns)
insert into dbo.Event (Title)
Select Title
From dbo.TempTable
insert into dbo.EventActor (EventId, ActorId)
Select SCOPE_IDENTITY(), ActorId --SCOPE_IDENTITY() is for EventId
From dbo.TempTable
When this code ran, all events inserted into Events, but the relations didn't inserted into EventActors because of Foreign Key error.
I think there should be a loop. But I am confused. I don't want to write C# code for this. I know there would be a simple but advanced solution trick for this in SQL Server. Thanks for your help.
Use the output clause to capture the new IDs, with a merge statement to allow capture from both source and destination tables.
Having captured this information, join it back to the temp table for the second insert.
Note you need a unique id per row, and this assumes 1 row in the temp table creates 1 row in both the Event and the EventActor tables.
-- Ensure every row has a unique id - could be part of the table create
ALTER TABLE dbo.TempTable ADD id INT IDENTITY(1,1);
-- Create table variable for storing the new IDs in
DECLARE #NewId TABLE (INT id, INT EventId);
-- Use Merge to Insert with Output to allow us to access all tables involves
-- As Insert with Output only allows access to columns in the destination table
MERGE INTO dbo.[Event] AS Target
USING dbo.TempTable AS Source
ON 1 = 0 -- Force an insert regardless
WHEN NOT MATCHED THEN
INSERT (Title)
VALUES (Source.Title)
OUTPUT Source.id, Inserted.EventId
INTO #NewId (id, EventId);
-- Insert using new Ids just created
INSERT INTO dbo.EventActor (EventId, ActorId)
SELECT I.EventId, T.ActorId
FROM dbo.TempTable T
INNER JOIN #NewId I on T.id = T.id;

Need some help in creating a query in SQL?

I have 6 tables:
Staff ( StaffID, Name )
Product ( ProductID, Name )
Faq ( FaqID, Question, Answer, ProductID* )
Customer (CustomerID, Name, Email)
Ticket ( TicketID, Problem, Status, Priority, LoggedTime, CustomerID* , ProductID* )
TicketUpdate ( TicketUpdateID, Message, UpdateTime, TicketID* , StaffID* )
Question to be answered:
Given a Product ID, remove the record for that Product. When a product is removed all associated FAQ can stay in the database but should have a null reference in the ProductID field. The deletion of a product should, however, also remove any associated tickets and their updates. For completeness deleted tickets and their updates should be copied to an audit table or a set of tables that maintain historical data on products, their tickets and updates. (Hint: you will need to define a additional table or set or tables to maintain this audit information and automatically copy any deleted tickets and ticket updates when a product is deleted). Your audit table/s should record the user which requested the deletion and the timestamp for the deletion operation.
I have created additional maintain_audit table:
CREATE TABLE maintain_audit(
TicketID INTEGER NOT NULL,
TicketUpdateID INTEGER NOT NULL,
Message VARCHAR(1000),
mdate TIMESTAMP NOT NULL,
muser VARCHAR(128),
PRIMARY KEY (TicketID, TicketUpdateID)
);
Addittionally I have created 1 function and trigger:
CREATE OR REPLACE FUNCTION maintain_audit()
RETURNS TRIGGER AS $BODY$
BEGIN
INSERT INTO maintain_audit (TicketID,TicketUpdateID,Message,muser,mdate)
(SELECT Ticket.ID,TicketUpdate.ID,Message,user,now() FROM Ticket, TicketUpdate WHERE Ticket.ID=TicketUpdate.TicketID AND Ticket.ProductID = OLD.ID);
RETURN OLD;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER maintain_audit
BEFORE DELETE
ON Product
FOR EACH ROW
EXECUTE PROCEDURE maintain_audit()
DELETE FROM Product WHERE Product.ID=30;
When I run this all I get this :
ERROR: null value in column "productid" violates not-null constraint
CONTEXT: SQL statement "UPDATE ONLY "public"."faq" SET "productid" = NULL WHERE $1 OPERATOR(pg_catalog.=) "productid""
GUYS,Could you help me in sorting out this problem?
What you probably want is triggers. Not sure what RDBMS you are using, but that's where you should start. I started from zero and had triggers up and running in a somewhat similar situation within an hour.
In case you don't already know, triggers do something after a specific type of query happens on a table, such as an insert, update or delete. You can do any type of query.
Another tip I would give you is not to delete anything, since that could break data integrity. You could just add an "active" boolean field, set active to false, then filter those out in most of your system's queries. Alternatively, you could just move the associated records out to a Products_archive table that has the same structure. Easy to do with:
select * into destination from source where 1=0
Still, I would do the work you need done using triggers because they're so automatic.
create a foreign key for Ticket.product_id, and TicketUpdate.Ticket_id which has ON DELETE CASCADE. This will automatically delete all tickets and ticketupdates when you delete the product.
create an audit table for Product deleters with product_id, user and timestamp. audit tables for ticket,ticketUpdate should mirror them exactly.
create a BEFORE DELETE TRIGGER for table Ticket which copies tickets to the audit table.
Do the same for TicketUpdate
Create an AFTER DETETE Trigger on Products to capture who requested a product be deleted in the product audit table.
In table FAQ create Product_id as a foreign key with ON DELETE SET NULL

Database Triggers: On Insert

This is a simple example.
I want to insert data in Table1 (Name, Age, Sex). This table has an automatically increasing serial#(int) on insertion of data.
I want to put a trigger on Table1 insert, so that after inserting data, it picks up the serial#(int) from Table1 and puts Serial# and Name to Table2 and Serial# and some other data in Table3.
Is it possible via triggers?
or, should I pick (last) Serial from table1 and call insert on other tables by increasing it manually, in same SP I used to insert in Table1?
Which approach is better?
EDIT 1:
Suppose table:
Serial | UID | Name | Age | Sex | DateTimeStamp
(int | uniqueidentifier | nvarchar | smallint | nchar | DateTime )
Default NewID() and Default GetDate() as UID and DateTimeStamp, would INSERTED table have Datetime-Of-Insertion in DatetimeStamp field? Meaning, I originally didn't enter any of Serial, GUID or DatetimeStamp, will they occur in INSERTED table?
EDIT 2:
Can you point me towards good books/articles on triggers. I read mastering SQL server 2005, didn't get much out of it. Thanks!
Sure you can do this with a trigger - something like:
CREATE TRIGGER trg_Table1_INSERT
ON dbo.Table1 AFTER INSERT
AS BEGIN
INSERT INTO dbo.Table2(SerialNo, Name)
SELECT SerialNo, Name
FROM Inserted
INSERT INTO dbo.Table3(SomeOtherCol)
SELECT SomeOtherCol
FROM Inserted
END
or whatever it is you need to do here....
It's important to understand that the trigger will be called once per statement - not once per row inserted. So if you have a statement that inserts 10 rows, your trigger gets called once, and the pseudo-table Inserted will contain those 10 rows that have been inserted in the statement.
Yes, this is possible with triggers.
When you use an INSERT trigger, you have access to the INSERTED logical table that represents the row to be inserted, with the value of the new ID it in.
Yes, it is possible by trigger, but keep in mind that TRIGGER doesn't take any input and doesn't provide any output, so you only can collect your desired data by querying in the trigger, however to satisfy insertion into your Table2 and Table3
CREATE TRIGGER tr_YourDesiredTriggerName ON Table1
FOR INSERT
AS
BEGIN
-- Inserting data to Table2
INSERT INTO Table2( Serial, Name)
SELECT i.Serial, i.Name
FROM Table1 AS t1 INNER JOIN Inserted AS i ON t1.Serial = i.Serial
AND i.Serial NOT IN ( SELECT t2.Serial FROM Table2 AS t2 )
-- Inserting data to Table3
INSERT INTO Table3( Serial, OtherData) -- select from other table
SELECT i.Serial, OtherData
FROM OtherTable AS ot INNER JOIN Inserted AS i ON ot.Serial = i.Serial
AND i.Serial NOT IN ( SELECT t3.Serial FROM Table3 AS t3 )
END
If you don't have control over the source of the insert then use a trigger. If you do have control over the source of the inserts then you can modify your insert process to also add the secondary table rows.
Will you also have control over future inserts? What if another insert gets created for this table? If you use a trigger then the secondary inserts would also get handled automatically. If not then the second insert process could possibly leave out the secondary inserts. Maybe that would be good or bad depending on your application.

How to Create Trigger to Keep Track of Last Changed Data

CREATE TABLE Member
(
memberID - PK
memberName
dateRegistered - one time process
);
CREATE TABLE MemberLastChanged
(
memberID
memberName
dateEntered
);
If by any chance a user changes his member name, i need to keep track of the currently changed memberName in a history table.
For example, current info is:
memberID: 5534 memberName: james
User changes it to:
memberID: 5534 memberName:
mark
By now, "Member" will hold current values:
5534 and mark
AND
"MemberLastChanged" will hold:
5534 and james
How can i achieve this in t-sql using trigger?
CREATE TRIGGER TRG_Member_U ON Member FOR UPDATE
AS
SET NOCOUNT ON
INSERT MemberLastChanged (memberID, memberName)
SELECT
D.memberID, D.memberName
FROM
DELETED D JOIN INSERTED I ON D.memberID = I.memberID
WHERE
D.memberName <> I.memberName
GO
Also, add a default of GETDATE to dateRegistered so it's recorded automatically.
This also filters out dummy updates by comparing new and old values (INSERTED vs DELETED).
INSERTED and DELETED are special tables available only in trigger.
You create an UPDATE trigger - triggers have access to two logical tables that have an identical structure to the table they are defined on:
INSERTED, which is the new data to go into the table
DELETED, which is the old data the is in the table
See this MDSN article on using these logical tables.
With this data you can populate your history table.
CREATE TRIGGER trg_Member_MemberUpdate
ON dbo.Member AFTER UPDATE
AS
INSERT INTO dbo.MemberLastChanged(memberID, memberName)
SELECT d.MemberID, d.MemberName
FROM DELETED d
You want to have an AFTER UPDATE trigger on your users table - something like:
CREATE TRIGGER trg_MemberUpdated
ON dbo.Member AFTER UPDATE
AS BEGIN
IF UPDATE(memberName)
INSERT INTO
dbo.MemberLastChanged(memberID, memberName, dateEntered)
SELECT
d.MemberID, d.MemberName, GETDATE()
FROM
Deleted d
END
Basically, this trigger checks to see whether the memberName property was updated; if so, a row with the old values (which are available in the Deleted pseudo table inside the UPDATE trigger) is inserted into MemberLastChanged