Adding constraints to list items in SQL Server database - database

I have table holding items for a given list id in my Ms Sql server database (2008R2).
I would like to add constraints so that no two list ids have same item list. Below illustrate my schema.
ListID , ItemID
1 a
1 b
2 a
3 a
3 b
In above example ListID 3 should fail. I guess you can't put constarint/check within the database itself (Triggers,check) and the logic constaint can only be done from the frontend?
Thanks in advance for any help.

Create a function that performs the logic you want and then create a check constraint or index that leverages that function.
Here is a functional example, the final insert fails. The function is evaluated row by row, so if you need to insert as a set and evaluate after, you'd need to do an "instead of" trigger:
CREATE TABLE dbo.Test(ListID INT, ItemID CHAR(1))
GO
CREATE FUNCTION dbo.TestConstraintPassed(#ListID INT, #ItemID CHAR(1))
RETURNS TINYINT
AS
BEGIN
DECLARE #retVal TINYINT = 0;
DECLARE #data TABLE (ListID INT, ItemID CHAR(1),[Match] INT)
INSERT INTO #data(ListID,ItemID,[Match]) SELECT ListID,ItemID,-1 AS [Match] FROM dbo.Test
UPDATE #data
SET [Match]=1
WHERE ItemID IN (SELECT ItemID FROM #data WHERE ListID=#ListID)
DECLARE #MatchCount INT
SELECT #MatchCount=SUM([Match]) FROM #data WHERE ListID=#ListID
IF NOT EXISTS(
SELECT *
FROM (
SELECT ListID,SUM([Match]) AS [MatchCount]
FROM #data
WHERE ListID<>#ListID
GROUP BY ListID
) dat
WHERE #MatchCount=[MatchCount]
)
BEGIN
SET #retVal=1;
END
RETURN #retVal;
END
GO
ALTER TABLE dbo.Test
ADD CONSTRAINT chkTest
CHECK (dbo.TestConstraintPassed(ListID, ItemID) = 1);
GO
INSERT INTO dbo.Test(ListID,ItemID) SELECT 1,'a'
INSERT INTO dbo.Test(ListID,ItemID) SELECT 1,'b'
INSERT INTO dbo.Test(ListID,ItemID) SELECT 2,'a'
INSERT INTO dbo.Test(ListID,ItemID) SELECT 2,'b'
Related

Related

How can I update the rows that are existed before the insert using Trigger ( SQL Server )?

I'm looking for a method to update old rows before an insert or update using a Trigger ,
For example , I have this table
ID PersonID Name Status
1 001 Alex False
2 002 Mark True
What I need exactly is that when I insert in this table a new row (3,003,Jane,True) , the column status should be affected to False ( all old rows ) only the new row will have True
So the expected result when applying the trigger will be like this :
ID PersonID Name Status
1 001 Alex False
2 002 Mark False
3 003 Jane True
How can I do this?
What I have tried:
ALTER TRIGGER [dbo].[dbo.TR_SetStatus] ON [dbo].[Person]
after INSERT
NOT FOR REPLICATION
AS
DECLARE #CursorTestID INT = 1;
DECLARE #RowCnt BIGINT = 0;
BEGIN
DECLARE #count INT;
SELECT #RowCnt = COUNT(*) FROM Person;
WHILE #CursorTestID <= #RowCnt
BEGIN
update Person set status=0
SET #CursorTestID = #CursorTestID + 1
END
END
I have two questions:
How can I update the rows that are existed before the insert using Trigger ( SQL Server )?
How can I pass a parameter to a trigger? (as an example PersonID)
DROP TABLE IF EXISTS dbo.Test;
CREATE TABLE dbo.Test
(
id tinyint identity(1,1)not null primary key,
person_id char(3)not null,
name varchar(50)not null,
status varchar(5) not null
)
insert dbo.Test(person_id,name,status)
values('001','alex','false'),('002','mark','true');
go
SELECT *FROM DBO.Test
DROP TRIGGER IF EXISTS dbo.II_Test;
GO
CREATE TRIGGER dbo.II_Test
ON dbo.Test
INSTEAD OF INSERT
AS
BEGIN
UPDATE DBO.Test SET status='FALSE';
INSERT DBO.Test(person_id,name,status)
SELECT I.person_id,I.name,I.status
FROM inserted AS I
END
GO
insert dbo.Test(person_id,name,status)
values('003','JANE','true');
select * from dbo.Test
Could you please check if the above is suitable for you
How can I update the rows that are existed before the insert using Trigger ( SQL Server )
For example, you can use INSTEAD OF-trigger
How can I pass a parameter to a trigger? (as an example PersonID)
This is not supported at all. If you need parameters the better way, I guess, is to use stored procedure
Finally, I solved my problem ( the first question ) :
ALTER TRIGGER [dbo].[dbo.TR_SetActive] ON [dbo].[test]
after INSERT
NOT FOR REPLICATION
AS
BEGIN
update dbo.test set status=0 WHERE Id < (SELECT MAX(Id) FROM dbo.test)
END
For the second question , I have used to get the last record as parameter:
set #PersonId = (select PersonId from inserted)
Can be simplfy with :
CREATE TRIGGER dbo.E_I_Test
ON dbo.Test
FOR INSERT
AS
BEGIN
UPDATE T
SET status = CASE WHEN I.id IS NULL THEN 0 ELSE 1 END
FROM dbo.Test as T
LEFT OUTER JOIN inserted AS I
ON T.id = I.id;
BEWARE.... status is a reserved Transact SQL keyword. Should not be use for any SQL identifier (table, name, column neme...)
Corrections made...

Creating a temporary table within a TRIGGER with GROUP BY

I need to create and use a temporary table with GROUP BY clause within a trigger, but I'm having difficulties doing so.
My attempt:
Here I'm trying to use two temporary tables which are dropped after the trigger reach an end.
First I create a #Temptable and the trigger.
CREATE TABLE #TempTable (admID smallint, diagID smallint);
CREATE TRIGGER tr_newTest
ON Adm_Diag
FOR INSERT
AS
BEGIN
...
END
Since the table inserted only contains rows for a current INSERT and UPDATE statements I'm passing several INSERT and UPDATE statements to #TempTable.
DECLARE #admID smallint
SELECT #admID = Adm_ID
FROM inserted
DECLARE #diagID smallint
SELECT #diagID=Diag_ID
FROM inserted
INSERT INTO #TempTable VALUES (#admID, #diagID)
Now with this data I want to create a temporary table that groups the rows of #TempTable:
SELECT *
INTO #TempGroupTable
FROM
(
SELECT admID, COUNT(*) as Diag
FROM #TempTable
GROUP BY admID
) t1
WHERE Diag > 2
The whole script
CREATE TABLE #TempTable (admID smallint, diagID smallint);
CREATE TRIGGER tr_newTest
ON Adm_Diag
FOR INSERT
AS
BEGIN
DECLARE #admID smallint
SELECT #admID = Adm_ID
FROM inserted
DECLARE #diagID smallint
SELECT #diagID=Diag_ID
FROM inserted
INSERT INTO #TempTable VALUES (#admID, #diagID)
-- Below I'm tring to create #TempGroupTable
SELECT *
INTO #TempGroupTable
FROM
(
SELECT admID, COUNT(*) as Diag
FROM #TempTable
GROUP BY admID
) t1
WHERE Diag > 2
END
After executing the trigger I get an error:
Msg 208, Level 16, State 0, Line 41 Invalid object name
'#TempGroupTable'.
How can I create #TempGroupTable?
Not quote sure what you are trying to do but would a global temporary tables which starts with ## work for you? So make the #TempGroupTable into ##TempGroupTable?
Why not dispense with all the overhead of temp tables and variables? Try:
CREATE TRIGGER tr_newTest
ON Adm_Diag
INSTEAD OF INSERT
AS
BEGIN
INSERT INTO Adm_Diag (adminID, Diag)
SELECT admID, COUNT(*) as Diag
FROM inserted
GROUP BY admID
END

update value into another table using trigger

Create table data with column StudentId (varchar type), Marks (Double). Create table data1 with column StudentId (varchar type), OldMarks (Double),NewMarks,Date.
Create trigger on data table.If mark is changed,create entry in data1 table for student with old marks,new marks & current date.
Here is the code I've tried:
CREATE TRIGGER marksss ON [dbo].[data] after UPDATE
AS declare #studentid int;
declare #marks int;
declare #xyz int;
declare #newmarks int;
declare #oldmarks int;
select #studentid=i.student_id from inserted i;
--to fetch inserted values
select #marks=i.marks from inserted i;
begin if update(marks) --set #oldmarks=#mark set #newmarks=#marks
insert into data1(student_id,new_marks,old_marks,date)
values (#studentid,#newmarks,#oldmarks,getdate()enter code here);
end
go
the problem is that it does not display old marks
I've managed to get what you want. First of all, you want to use an instead of trigger instead. Oracle has a before trigger which is what you ideally need however MSSQL doesn't have this feature so we have to do the passed in update manually too...
Here is the code with the table setup that I used, just changed to suit your needs.
CREATE TABLE A (ID INT IDENTITY PRIMARY KEY, SCORE INT)
CREATE TABLE B (ID INT FOREIGN KEY REFERENCES A(ID), SCORE INT, OLDSCORE INT, [date] DATETIME)
GO
CREATE TRIGGER marksss ON A INSTEAD OF UPDATE
AS
BEGIN
IF (SELECT A.SCORE FROM A INNER JOIN INSERTED I ON I.ID = A.ID) != (SELECT I.SCORE FROM INSERTED I)
BEGIN
INSERT INTO B(ID,SCORE,OLDSCORE,[date])
SELECT I.ID, I.SCORE, A.SCORE, GETDATE()
FROM INSERTED I
INNER JOIN A ON I.ID = A.ID
END
BEGIN
UPDATE A
SET SCORE = (SELECT I.SCORE FROM INSERTED I)
END
END

Bulk insert from a table to another

We have 2 tables in SQL Server 2008 R2. Periodically, we have to insert a batch of records from Table A to Table B. While the inserting, Table B still able to SELECT & UPDATE. Currently, we use INSERT..SELECT to copy from Table A to Table B. But the problem is while inserting, sometimes will cause UPDATE statement to TABLE B timeout.
Is there a better bulk insert solution from a table to another that won't cause blocking?
They most obvious solution is to use smaller batches as Stanley suggested. If this is really not an option you could explore '(transaction level) snapshot isolation.
1 Set the transaction timeout to a large enough value, so that the statement no longer goes in timeout.
2 Use CURSOR and do it row by row
3 Try this way of doing things. Requires a row identifier (IDENTITY for instance), best to have a PK or INDEX on that field:
SET NOCOUNT ON;
CREATE TABLE #A(
row_id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
data INT NOT NULL
);
CREATE TABLE #B(
row_id INT NOT NULL PRIMARY KEY,
data INT NOT NULL
);
-- TRUNCATE TABLE #B; -- no truncate needed since you just want to add rows, not copy the whole table
DECLARE #batch_size INT;
SET #batch_size = 10000;
DECLARE #from_row_id INT;
DECLARE #to_row_id INT;
-- You would use this to establish the first #from_row_id if you wanted to copy the whole table
-- SELECT
-- #from_row_id=ISNULL(MIN(row_id),-1)
-- FROM
-- #A AS a;
SELECT
#from_row_id=ISNULL(MAX(row_id),-1)
FROM
#B AS b;
IF #from_row_id=-1
SELECT
#from_row_id=ISNULL(MIN(row_id),-1)
FROM
#A AS a;
ELSE
SELECT
#from_row_id=ISNULL(MIN(row_id),-1)
FROM
#A AS a
WHERE
row_id>#from_row_id;
WHILE #from_row_id>=0
BEGIN
SELECT
#to_row_id=ISNULL(MAX(row_id),-1)
FROM
(
SELECT TOP(#batch_size)
row_id
FROM
#A AS a
WHERE
row_id>=#from_row_id
) AS row_ids
IF #to_row_id=-1
BEGIN
INSERT
#B
SELECT
*
FROM
#A AS a
WHERE
row_id>=#from_row_id;
BREAK;
END
ELSE
INSERT
#B
SELECT
*
FROM
#A AS a
WHERE
row_id BETWEEN #from_row_id AND #to_row_id;
SELECT
#from_row_id=ISNULL(MIN(row_id),-1)
FROM
#A AS a
WHERE
row_id>#to_row_id;
END
DROP TABLE #B;
DROP TABLE #A;

Inserting batch of rows into two tables in SQL Server 2008

I have a requirement to insert multiple rows into table1 and at the same time insert a row into table2 with a pkID from table1 and a value that comes from a SP parameter.
I created a stored procedure that performs a batch insert with a table valued parameter which contains the rows to be inserted into table1. But I have a problem with inserting the row into table2 with the corresponding Id (identity) from table1, along with parameter value that I have passed.
Is there anyone who implemented this, or what is the good solution for this?
CREATE PROCEDURE [dbo].[oSP_TV_Insert]
#uID int
,#IsActive int
,#Type int -- i need to insert this in table 2
,#dTableGroup table1 READONLY -- this one is a table valued
AS
DECLARE #SQL varchar(2000)
DECLARE #table1Id int
BEGIN
INSERT INTO dbo.table1
(uID
,Name
,Contact
,Address
,City
,State
,Zip
,Phone
,Active)
SELECT
#uID
,Name
,Contact
,Address
,City
,State
,Zip
,Phone
,Active
,#G_Active
FROM #dTableGroup
--the above query will perform batch insert using the records from dTableGroup which is table valued
SET #table1ID = SCOPE_IDENTITY()
-- this below will perform inserting records to table2 with every Id inserted in table1.
Insert into table2(#table1ID , #type)
You need to temporarily store the inserted identity values and then create a second INSERT statement - using the OUTPUT clause.
Something like:
-- declare table variable to hold the ID's that are being inserted
DECLARE #InsertedIDs TABLE (ID INT)
-- insert values into table1 - output the inserted ID's into #InsertedIDs
INSERT INTO dbo.table1(ID, Name, Contact, Address, City, State, Zip, Phone, Active)
OUTPUT INSERTED.ID INTO #InsertedIDs
SELECT
#ID, Name, Contact, Address, City, State, Zip, Phone, Active, #G_Active
FROM #dTableGroup
and then you can have your second INSERT statement:
INSERT INTO dbo.table2(Table1ID, Type)
SELECT ID, #type FROM #InsertedIDs
See the MSDN docs on the OUTPUT clause for more details on what you can do with the OUTPUT clause - one of the most underused and most "unknown" features of SQL Server these days!
Another approach using OUTPUT clause and only one statement for inserting data in both destination tables:
--Parameters
DECLARE #TableGroup TABLE
(
Name NVARCHAR(100) NOT NULL
,Phone VARCHAR(10) NOT NULL
);
DECLARE #Type INT;
--End Of parameters
--Destination tables
DECLARE #FirstDestinationTable TABLE
(
FirstDestinationTableID INT IDENTITY(1,1) PRIMARY KEY
,Name NVARCHAR(100) NOT NULL
,Phone VARCHAR(10) NOT NULL
);
DECLARE #SecondDestinationTable TABLE
(
SecondDestinationTable INT IDENTITY(2,2) PRIMARY KEY
,FirstDestinationTableID INT NOT NULL
,[Type] INT NOT NULL
,CHECK([Type] > 0)
);
--End of destination tables
--Test1
--initialization
INSERT #TableGroup
VALUES ('Bogdan SAHLEAN', '0721200300')
,('Ion Ionescu', '0211002003')
,('Vasile Vasilescu', '0745600800');
SET #Type = 9;
--execution
INSERT #SecondDestinationTable (FirstDestinationTableID, [Type])
SELECT FirstINS.FirstDestinationTableID, #Type
FROM
(
INSERT #FirstDestinationTable (Name, Phone)
OUTPUT inserted.FirstDestinationTableID
SELECT tg.Name, tg.Phone
FROM #TableGroup tg
) FirstINS
--check records
SELECT *
FROM #FirstDestinationTable;
SELECT *
FROM #SecondDestinationTable;
--End of test1
--Test2
--initialization
DELETE #TableGroup;
DELETE #FirstDestinationTable;
DELETE #SecondDestinationTable;
INSERT #TableGroup
VALUES ('Ion Ionescu', '0210000000')
,('Vasile Vasilescu', '0745000000');
SET #Type = 0; --Wrong value
--execution
INSERT #SecondDestinationTable (FirstDestinationTableID, [Type])
SELECT FirstINS.FirstDestinationTableID, #Type
FROM
(
INSERT #FirstDestinationTable (Name, Phone)
OUTPUT inserted.FirstDestinationTableID
SELECT tg.Name, tg.Phone
FROM #TableGroup tg
) FirstINS
--check records
DECLARE #rc1 INT, #rc2 INT;
SELECT *
FROM #FirstDestinationTable;
SET #rc1 = ##ROWCOUNT;
SELECT *
FROM #SecondDestinationTable;
SET #rc2 = ##ROWCOUNT;
RAISERROR('[Test2 results] #FirstDestinationTable: %d rows; ##SecondDestinationTable: %d rows;',1,1,#rc1,#rc2);
--End of test1
Since you need all inserted identity values, look at the output clause of the insert statement: http://msdn.microsoft.com/en-us/library/ms177564.aspx

Resources