SQL violation of Primary Key constraint on update - sql

--EDITED--
I think I figured out the problem. The original row is being taken to update instead of the latest row therefore the CreationDate is the same and since ID + CreationDate is the primary key it returns a violation. Is there any way to select the latest row instead of the original row when updating records?
Thanks :D
----------------
I got the error violation of primary key constraint but I don't know why because my primary key values are unique.
I can add the record for 'Darren' and update it once. After that I get the error. My trigger works such that when I update an existing record, both the original and edited record are inserted into the table ProcessList so that I am able to see all the changes made to all records.
Tables:
CREATE TABLE dbo.ProcessList
(
TransactionID integer IDENTITY,
IsEdited bit DEFAULT 'FALSE',
ID integer NOT NULL,
Name varchar(30) NOT NULL,
Amount smallmoney NOT NULL,
CreationDate datetime DEFAULT GETDATE(),
ModificationDate datetime,
PRIMARY KEY (ID, CreationDate)
)
CREATE TABLE dbo.ProcessListHist
(
TransactionID integer IDENTITY,
IsEdited bit DEFAULT 'FALSE',
ID integer NOT NULL,
Name varchar(30) NOT NULL,
Amount smallmoney NOT NULL,
CreationDate datetime DEFAULT GETDATE(),
ModificationDate datetime,
PRIMARY KEY (ID, CreationDate)
)
Trigger:
CREATE TRIGGER CloneAfterUpdate ON ProcessList
AFTER UPDATE
AS
IF (UPDATE (Amount) OR UPDATE (NAME))
BEGIN
INSERT INTO ProcessListHist (ID, Name, Amount, CreationDate, ModificationDate, IsEdited)
SELECT
ID, Name, Amount, CreationDate, GETDATE(), 'True'
FROM deleted
UPDATE PL
SET PL.CreationDate = PLH.ModificationDate
FROM ProcessList PL
INNER JOIN deleted ON PL.ID = deleted.ID
AND PL.CreationDate = deleted.CreationDate
INNER JOIN ProcessListHist PLH ON PL.ID = PLH.ID
AND PLH.CreationDate = deleted.CreationDate
INSERT INTO ProcessList (ID, Name, Amount, CreationDate, ModificationDate, IsEdited)
SELECT
ID, Name, Amount, CreationDate, ModificationDate, IsEdited
FROM ProcessListHist
END
Insert/Update statements:
INSERT INTO ProcessList (ID, Name, Amount) VALUES ('1020', 'Darren', '300')
UPDATE ProcessList SET Amount = 1000 WHERE Name = 'Darren'

You logging a transaction on your history table right ? , therefore you need to remove the ID of history table from being unique. Make it TransactionID INT only, not an identity, your problem is because every time you perform an UPDATE , the record is inserted into history table, so if the record already exist meaning to say, if the ID is already existing in the history table , you are not allowed to insert the same ID, that's why you will receiving that error.

Related

How to get old value and new value of multiple field in trigger in SQL server?

I have a table Person:
CREATE TABLE Person
(
ID INT PRIMARY KEY,
FirstName varchar(50),
LastName varchar(50),
Phone varchar(50),
Address varchar(50),
City varchar(50),
PinCode varchar(50),
DateOfBirth DATETIME,
UpdatedOn DATETIME,
UpdatedBy varchar(50)
)
Whenever I insert or update the multiple fields from above table then I want previous value and current value of all updated fields and store that in another table using Trigger. How we can get values of all updated fields.
For example
INSERT INTO Person
VALUES (1, 'first', 'last', '11111', 'add', 'city', 'pin', GETDATE(), GETDATE(), 'ABC')
UPDATE Person
SET FirstName = 'First11',
LastName = 'Last22',
Phone = '1010101010'
WHERE id = 1
When I will hit above commands in both cases I want old and current value and store it in another table. How we can achieve this using triggers?
For SQL Server when you updated ( he delete the old values and then insert the new values )
ALTER TRIGGER [dbo].[YOUR_TRIGGER_NAME_INSERT]
ON [dbo].[YOUR_TABLE]
AFTER INSERT --Here when you insered rows
AS
BEGIN
select * from inserted --get all inserted rows
--Your code
END
And Update
ALTER TRIGGER [dbo].[YOUR_TRIGGER_NAME_UPDATE]
ON [dbo].[YOUR_TABLE]
AFTER UPDATE--Here when you updated rows
AS
BEGIN
select * from inserted --get all inserted (new values) rows
select * from deleted--get all deleted (old values) rows
--Your code
END

SQL Server - Limit maximum of 8 records per week to be inserted for a staff_schedule table for a single staff

I have a staff table and then staff_schedule table which references staff id. I want to limit the number of schedules created for a single staff to 8 records per week. How can I do that?
Thanks in advance.
What I have so far..
CREATE TABLE Staff
(
staffID int,
fullName varchar(100) NOT NULL,
s_category varchar(25),
s_email varchar(50),
s_contactNo int,
speciality varchar(100),
qualifications varchar(250),
pre_employment varchar(200),
salary numeric(8,2),
staff_gender char(1),
CONSTRAINT PK_Staff PRIMARY KEY (staffID),
CONSTRAINT CHK_StaffGender CHECK (staff_gender='M' OR staff_gender='F'),
CONSTRAINT CHK_FullName CHECK (fullName NOT LIKE '%0%' AND fullName NOT LIKE '%1%' AND fullName NOT LIKE '%2%' AND fullName NOT LIKE '%3%' AND fullName NOT LIKE '%4%' AND fullName NOT LIKE '%5%' AND fullName NOT LIKE '%6%' AND fullName NOT LIKE '%7%' AND fullName NOT LIKE '%8%' AND fullName NOT LIKE '%9%'),
CONSTRAINT CHK_SALARY CHECK (salary>0 AND salary<=150000)
);
CREATE TABLE Staff_Allocation
(
allocationId int,
staff_Id int,
branch_Id int,
staff_start_date DateTime,
staff_end_date DateTime,
CONSTRAINT PK_Staff_Allocation PRIMARY KEY (allocationId),
CONSTRAINT FK_Staff_Allocation_Staff FOREIGN KEY (staff_Id) REFERENCES Staff(staffID),
CONSTRAINT FK_Staff_Allocation_Branch FOREIGN KEY (branch_Id) REFERENCES Branch(branchID),
CONSTRAINT CHK_StaffAllocationRotaDaily CHECK (DATEDIFF(hh, staff_start_date, staff_end_date) <=6)
);
A trigger is definitely the way to go for enforcing this constraint. TABLE constraints cannot enforce requirements that span multiple rows.
There are a number of things to test for your requirements
Single row inserted
Multiple rows attempted to be inserted across different staff_ids
Multiple rows attempted to be inserted for same staff_id
Below trigger will satisfy these. I left the date range check as empty as I'm not sure the exact criteria. The inner query collects all the rows from rows being inserted along with all rows already present for staff_ids in the current insert. The outer query aggregates by staff_id after applying the date range criteria. Finally the IF EXISTS check is used to throw an error.
CREATE TRIGGER insert_checkScheduleCount
ON Staff_Allocation AS
BEGIN
IF EXISTS (
SELECT * FROM
(
SELECT staff_id, staff_start_date, staff_end_date
FROM
inserted // relevant columns from the current set of rows
// attempting to be inserted
UNION ALL
SELECT staff_id, staff_start_date, staff_end_date
FROM
Staff_Allocation
WHERE
staff_id IN (SELECT staff_id FROM inserted)
// rows already in the table for staff being touched in this insert
) AS staffRows
WHERE
<apply you criteria on staff_start_date and staff_end_date>
GROUP BY
staff_id
HAVING COUNT(*) > 8
)
BEGIN
RAISERROR(N'More than 8 rows', 16, -1)
END
END
You can create an instead of trigger:
Sample Query for your reference:
CREATE TRIGGER INSTEADOF_INS
ON Staff_Allocation
INSTEAD OF INSERT AS
BEGIN
DECLARE #CNT_ALLOC TINYINT
DECLARE #CNT_ALLOC_TAB TINYINT
DECLARE #ST_DATE DATE
DECLARE #END_DATE DATE
SELECT #ST_DATE=DATEADD(dd, -(DATEPART(dw, GETDATE())-1), GETDATE())
SELECT #END_DATE=DATEADD(dd, 7-(DATEPART(dw, GETDATE())), GETDATE())
SELECT #CNT_ALLOC=COUNT(*)
FROM Staff_Allocation S INNER JOIN INSERTED I
ON S.staff_Id = I.staff_Id AND S.staff_start_date>#ST_DATE AND S.staff_end_date<#END_DATE
IF (#CNT_ALLOC >8 )
BEGIN
RAISERROR (N'8 rows Per Week',
16, 1)
RETURN
END
INSERT INTO Staff_Allocation (allocationId, staff_Id, branch_Id,staff_start_date,staff_end_date)
SELECT allocationId, staff_Id, branch_Id,staff_start_date,staff_end_date
FROM inserted
END
GO
If you do not want to use a trigger you could just use an IF statement
DECLARE #TableCount INT
SELECT #TableCount = COUNT(*)
FROM YourTable
WHERE StartDate BETWEEN GETDATE()-7 AND GETDATE()
AND EndDate BETWEEN GETDATE()-7 AND GETDATE()
IF #TableCount < 8
INSERT...
ELSE
RAISERROR...
RETURN

Update records based on inserted IDs and another non source column in SQL

Probably there is already answer for it, but i couldn't find it... So i have 2 tables and data in third one. Lets name them (Source, Target and UpdateTarget).
I need to insert records from Source to Target, then grab autoincremented IDs from Target and update UpdateTarget table with these IDs based on filters from Source table. I've tried to use OUTPUT, but it gives me an error:
The multi-part identifier "s.EmployeeID" could not be bound.
Here is my current SQL query:
CREATE TABLE dbo.target
(
id INT IDENTITY(1,1) PRIMARY KEY,
employee VARCHAR(32)
);
CREATE TABLE dbo.source
(
id INT IDENTITY(1,1) PRIMARY KEY,
employee VARCHAR(32),
EmployeeID int
);
CREATE TABLE dbo.updateTarget
(
id INT IDENTITY(1,1) PRIMARY KEY,
ExternalID int
);
DECLARE #MyTableVar TABLE
(
id INT,
EmployeeID int
);
INSERT dbo.target (employee)
OUTPUT
inserted.id, -- autoincremented ID
s.EmployeeID -- here i got an error
INTO #MyTableVar
SELECT s.employee
FROM dbo.source AS s
UPDATE dbo.updateTarget
SET ExternalID = data.ID
FROM #MyTableVar data
WHERE updateTarget.ID = data.EmployeeID
DROP TABLE source
DROP TABLE target
DROP TABLE updateTarget
I don't have EmployeeID column in target table.
Is there a way to achieve it without making two queries for each record? Or can you point me to existing answer if there are any?
Thanks!
1) INSERT INTO table variable generated id, and EmployeeId for usage in update
2) MERGE instead of INSERT (it allows to get column EmployeeId from SRC)
3) OUTPUT result, action inserted, getting id from TGT and EmployeeId
INSERT INTO #MyTableVar(id, EmployeeId)
SELECT id, EmployeeId
FROM (
MERGE dbo.target TGT
USING dbo.source SRC
ON TGT.employee = SRC.employee
WHEN NOT MATCHED THEN
INSERT (employee)
VALUES (src.employee)
OUTPUT inserted.id, SRC.EmployeeId)
AS out(id, EmployeeId);;
MERGE gives better OUTPUT options

Trigger After Update SQL

I have Customer table. To simplify lets say i have two columns
Id
Name
I have a second table (Log) that I want to update ONLY when the Id column of my customer changes. Yes you heard me right that the primary key (Id) will change!
I took a stab but the NewId that gets pulled is the first record in the Customer table not the updated record
ALTER TRIGGER [dbo].[tr_ID_Modified]
ON [dbo].[customer]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF UPDATE (Id)
BEGIN
UPDATE [log]
SET NewId = Id
FROM customer
END
END
Many would make the argument that if you are changing PK values, you need to rethink the database/table design. However, if you need a quick & dirty fix, add a column to the customer table that is unique (and not null). Use this column to join between the [inserted] and [deleted] tables in your update trigger. Here's a sample script:
CREATE TABLE dbo.Customer (
Id INT CONSTRAINT PK_Customer PRIMARY KEY,
Name VARCHAR(128),
UQColumn INT IDENTITY NOT NULL CONSTRAINT UQ_Customer_UQColumn UNIQUE
)
CREATE TABLE dbo.[Log] (
CustomerId INT NOT NULL,
LogMsg VARCHAR(MAX)
)
INSERT INTO dbo.Customer
(Id, Name)
VALUES
(1, 'Larry'),
(2, 'Curley'),
(3, 'Moe')
INSERT INTO dbo.[Log]
(CustomerId, LogMsg)
VALUES
(1, 'Larry is cool'),
(1, 'Larry rocks'),
(2, 'Curley cracks me up'),
(3, 'Moe is mean')
CREATE TRIGGER [dbo].[tr_Customer_Upd]
ON [dbo].[customer]
FOR UPDATE
AS
BEGIN
UPDATE l
SET CustomerId = i.Id
FROM inserted i
JOIN deleted d
ON i.UQColumn = d.UQColumn
JOIN [Log] l
ON l.CustomerId = d.Id
END
SELECT *
FROM dbo.[Log]
UPDATE dbo.Customer
SET Id = 4
WHERE Id = 1
SELECT *
FROM dbo.[Log]

Need Help Writing SQL Query in Postgresql

I've been trying to write this query but can't seem to get it right for some reason or another..
What I need to do is:
Change the status of a question to 'closed' if there has not been an update associated with this question inserted into the qUpdateTable in the last 24 hours.
I only want it to be closed if a staff member has replied to it at least once.
You can determine if a staff member or a user has replied to the question by checking the qUpdateTable and seeing if a the StaffID field is empty or has a value for that particular tickets updates. If there is a staffID then it has been updated by a staff member, however if it does not then the qUpdate was done by a user.
Essentialy the way this works is a user posts a question by inserting into the Question table, and replies are made by inserting into the qUpdate table and linked to the original question using the foreign key - "QuestionID".
The tables:
CREATE TABLE Staff
(
ID INTEGER NOT NULL PRIMARY KEY,
Name VARCHAR(40) NOT NULL
);
CREATE TABLE Customer
(
ID INTEGER NOT NULL PRIMARY KEY,
Name VARCHAR(40) NOT NULL,
Email VARCHAR(40) NOT NULL
);
CREATE TABLE Product
(
ID INTEGER NOT NULL PRIMARY KEY,
Name TEXT NOT NULL
);
CREATE TABLE Question
(
ID INTEGER NOT NULL PRIMARY KEY,
Problem VARCHAR(1000),
Status VARCHAR(20) NOT NULL DEFAULT 'open',
Priority INTEGER NOT NULL,
LoggedTime TIMESTAMP NOT NULL,
CustomerID INTEGER NOT NULL,
ProductID INTEGER NOT NULL,
FOREIGN KEY (ProductID) REFERENCES Product(ID),
FOREIGN KEY (CustomerID) REFERENCES Customer(ID),
CHECK (Status IN ('open','closed') AND Priority IN (1,2,3))
);
CREATE TABLE qUpdate
(
ID INTEGER NOT NULL PRIMARY KEY,
Message VARCHAR(1000) NOT NULL,
UpdateTime TIMESTAMP NOT NULL,
QuestionID INTEGER NOT NULL,
StaffID INTEGER,
FOREIGN KEY (StaffID) REFERENCES Staff(ID),
FOREIGN KEY (QuestionID) REFERENCES Question(ID)
);
Some sample inserts:
INSERT INTO Customer (ID, Name, Email) VALUES (1, 'testname1', 'testemail1');
INSERT INTO Customer (ID, Name, Email) VALUES (2, 'testname2', 'testemail2');
INSERT INTO Staff (ID, Name) VALUES (1, 'Don Keigh');
INSERT INTO Product (ID, Name) VALUES (1, 'Xbox');
INSERT INTO Question (ID, Problem, Status, Priority, LoggedTime, CustomerID, ProductID)
VALUES (1, 'testproblem1', 'open', 3, '2012-04-14 09:30', 2, 1);
INSERT INTO Question (ID, Problem, Status, Priority, LoggedTime, CustomerID, ProductID)
VALUES (2, 'testproblem2', 'open', 3, '2012-04-14 09:30', 2, 1);
INSERT INTO qUpdate (ID, Message, UpdateTime, StaffID, QuestionID) VALUES (2, 'testmessage1','2012-07-12 14:27', 1, 1);
INSERT INTO qUpdate (ID, Message, UpdateTime, QuestionID) VALUES (3, 'testmessage1','2012-06-18 19:42', 2);
What I've done so far (which obviously doesn't work)
UPDATE Question
SET Status = 'closed'
WHERE EXISTS
(SELECT qUpdate.QuestionID
MAX(qUpdate.UpdateTime - Now() = INTERVAL '1 day') FROM qUpdate
LEFT JOIN Question ON qUpdate.QuestionID = Question.ID
WHERE qUpdate.StaffID IS NOT NULL);
I realise my explanation may be a bit confusing so if you need more info post and I'll reply ASAP
UPDATE Question
SET Status = 'closed'
where
-- this clause asserts there's at least one staff answer
exists (
select null from qUpdate
where qUpdate.QuestionID = Question.ID
and StaffID is not null
)
-- this clause asserts there's been no update in the last 24 hours
and not exists (
select null from qUpdate
where qUpdate.QuestionID = Question.ID
and qUpdate.UpdateTime > (now() - interval '24 hours')
)
and Status = 'open';
You'll almost certainly want an index on qUpdate(QuestionID) or possibly qUpdate(QuestionID,UpdateTime) or qUpdate(QuestionID,StaffID) to get good performance on the subselects in the exists() tests.