update value into another table using trigger - sql-server

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

Related

Adding constraints to list items in SQL Server 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

How to fill in the new created column efficiently?

I Have a table below and I will create a new column say named 'Amount'. The existing column 'Id' is foreign key and link the information in a table say 'Loan'('Id' is the primary key of table 'Loan' ). The simple thing I wanna do is assign each new created Amount cell with the right amount obtain from table 'Loan', mapping with 'Id'. I currently use local variable and self-created table type to find the amount value in 'Loan' case by case. Is there any other more efficient way to execute the same operation? Many thanks.
MyTable was as below:
My code was as followed:
ALTER TABLE MyTable
ADD Amount MONEY
CREATE TYPE ListofID AS TABLE (idx INT IDENTITY(1,1), ID VARCHAR(255))
DECLARE #Table_ID_List ListofID
INSERT #Table_ID_List (
ID )
SELECT Id FROM MyTable
DECLARE #i INT
DECLARE #cnt INT
SELECT #i = min(idx) - 1, #cnt = max(idx) FROM #Table_ID_List
DECLARE #app VARCHAR(255)
WHILE #i<#cnt
BEGIN
SELECT #i = #i + 1
SELECT #app = (SELECT ID FROM #Table_ID_List WHERE idx = #i)
UPDATE MyTable
SET Amount =(SELECT Amount FROM Loan WHERE Id = #app)
WHERE Id = #app
END
Depending on the size of your table, you may want to use the SWITCH statement to transfer the whole table at once to the new schema.
https://sqlperformance.com/2012/08/t-sql-queries/t-sql-tuesday-schema-switch-a-roo
I may have missed your question, but I believe you can do this directly via an Update statement. Something along the lines of:
ALTER TABLE MyTable ADD Amount MONEY
UPDATE
t1
SET
t1.Amount = t2.Amount
FROM
MyTable t1
JOIN Loan t2
ON t1.Id = t2.Id
Wouldn't that do the trick?

How to update N rows when N is in a from (select N from #myVar)

I'm developing this stored procedure on SQL Server 2012.
The stored procedure will update Quantity rows in EXTERNAL_CODES table for each row in #newBatches parameter. It's like a loop, I will need to create a new row in BATCHES table for each row in #newBatches parameter.
And then, I have to update Quantity rows in EXTERNAL_CODES table with each batchId created.
CREATE PROCEDURE [dbo].[CreateBatchAndKeepExternalCodes]
#newBatches as dbo.CreateBatchList READONLY,
#productId int
AS
set nocount on;
declare #lowestCodeLevel tinyint;
-- ======== VALIDATION ==========
if ((select count(name) from #newBatches) = 0)
return -112;
-- ====== CODE ========
-- Get lowest aggregation level.
set #lowestCodeLevel =
(select min(c.application_code)
from CHINA_CODES_HEADER c, PRODUCTS p
where p.Id = #productId and c.DRUG_TEN_SEATS = p.PRODUCT_CODE);
begin transaction;
insert into BATCHES (PRODUCT_ID, NAME, CREATED)
select #productId, Name, CAST(SYSDATETIMEOFFSET() as nvarchar(50))
from #newBatches;
update top(t.Quantity) EXTERNAL_CODES
set BATCH_ID = (select ID from BATCHES where NAME = t.Name)
, USED = 1
from (select Name, Quantity from #newBatches) t
where PRODUCT_ID = #productId and CODE_LEVEL = #lowestCodeLevel;
commit transaction;
RETURN 0
I get an error on this update:
update top(t.Quantity) EXTERNAL_CODES
set BATCH_ID = (select ID from BATCHES where NAME = t.Name)
, USED = 1
from (select Name, Quantity from #newBatches) t
where PRODUCT_ID = #productId and CODE_LEVEL = #lowestCodeLevel;
The error is here: update top(t.Quantity). It can't find t.Quantity.
dbo.CreateBatchList is:
CREATE TYPE [dbo].[CreateBatchList] AS TABLE
(
Name nVARCHAR(20),
Quantity int
)
My problem is that I can't set to update Quantity rows. Any idea?
The error (or warning) message is:
SQL71005: The reference to the column t.Quantity could not be resolved.
Maybe I could use MERGE.
Your update statement is quite confusing. If for instance #newBatches table has multiple lines, then you are saying, pick all the Quantity from #newBatches in Top?
Anyway, I think the solution is to use a loop to use each line from #newBatches to update. I have modified your code in order to test it on my side, and have replaced all the tables with Table Variables. You may find it helpful.
But still without any Order By clause and without knowing actual business logic, I can't say this solution is correct.
DECLARE #productID int;
DECLARE #lowestCodeLevel int;
DECLARE #EXTERNAL_CODES table(BATCH_ID varchar(100), USED bit, PRODUCT_ID int, CODE_LEVEL int);
DECLARE #BATCHES table(ID int, NAME varchar(100));
DECLARE #newBatches table(Name nVARCHAR(20), Quantity int);
-- we don't know at this point whether #newBatches has some column
-- through which we can uniquely identify a row
-- that is why we are creating this new table in which we have Row_ID column
-- through which we can extract each line
DECLARE #newBatchesWithRowID table(Row_ID int not null identity, Name nVarchar(20), Quantity int);
INSERT INTO #newBatchesWithRowID(Name, Quantity)
SELECT Name, Quantity
FROM #newBatches;
DECLARE #prvRow_ID int;
-- loop to iterate in #newBatchesWithRowID table
WHILE(1 = 1)
Begin
DECLARE #row_ID int = NULL;
DECLARE #Name varchar(100);
DECLARE #Quantity int;
SELECT TOP 1 #row_ID = Row_ID
, #Quantity = Quantity
, #Name = Name
FROM #newBatchesWithRowID
WHERE Row_ID > #prvRow_ID OR #prvRow_ID IS NULL
ORDER BY Row_ID;
If #row_ID IS NULL Break;
SET #prvRow_ID = #row_ID;
update top(#Quantity) #EXTERNAL_CODES
set BATCH_ID = (select ID from #BATCHES where NAME = #Name)
, USED = 1
where PRODUCT_ID = #productId and CODE_LEVEL = #lowestCodeLevel;
END

Insert random text into columns from a reference table variable

I have a table ABSENCE that has 40 employee ids and need to add two columns from a table variable, which acts as a reference table. For each emp id, I need to randomly assign the values from the table variable. Here's the code I tried without randomizing:
USE TSQL2012;
GO
DECLARE #MAX SMALLINT;
DECLARE #MIN SMALLINT;
DECLARE #RECODE SMALLINT;
DECLARE #RE CHAR(100);
DECLARE #rearray table (recode smallint,re char(100));
insert into #rearray values (100,'HIT BY BEER TRUCK')
,(200,'BAD HAIR DAY')
,(300,'ASPIRIN OVERDOSE')
,(400,'MAKEUP DISASTER')
,(500,'GOT LOCKED IN THE SALOON')
DECLARE #REFCURSOR AS CURSOR;
SET #REFCURSOR = CURSOR FOR
SELECT RECODE,RE FROM #REARRAY;
OPEN #REFCURSOR;
SET #MAX = (SELECT DISTINCT ##ROWCOUNT FROM ABSENCE);
SET #MIN = 0;
ALTER TABLE ABSENCE ADD CODE SMALLINT, REASONING CHAR(100);
WHILE (#MIN <= #MAX)
BEGIN
FETCH NEXT FROM #REFCURSOR INTO #RECODE,#RE;
INSERT INTO ABSENCE (CODE, REASONING) VALUES (#RECODE,#RE);
SET #MIN+=1;
END
CLOSE #REFCURSOR
DEALLOCATE #REFCURSOR
SELECT EMPID,CODE,REASONING FROM ABSENCE
Though am inserting into two columns only, it is attempting to insert into empid (which has already been filled) and as it cannot be NULL, the insertion fails.
Also, how to randomize the values from the REARRAY table variable to insert them into the ABSENCE table?
Since this is a small dataset, one approach might be to use CROSS APPLY with a SELECT TOP(1) ... FROM #rearray ORDER BY NEWID() approach. This will essentially join your ABSENCE table with your reference table in an UPDATE statement, selecting a random row each time in the join. In full, it would look like:
UPDATE ABSENCE
SET col1 = x1.recode, col2 = x2.recode
FROM ABSENCE a
CROSS APPLY (SELECT TOP(1) * FROM #rearray ORDER BY NEWID()) x1(recode, re)
CROSS APPLY (SELECT TOP(1) * FROM #rearray ORDER BY NEWID()) x2(recode, re)

Stored procedure inserting values into a table using Table-Valued Parameters

Given the following simple structure:
TABLE: Product (ProductId, ProductName)
TABLE: Category (CategoryId, CategoryName)
LINK TABLE: ProductId,CategoryId
I have a table type which I want to pass to a stored procedure to insert the values into another table if they don't exist.
CREATE TYPE StringList_TBLType AS TABLE (s NVARCHAR(255) NOT NULL PRIMARY KEY)
I want to do the following in a stored procedure where I pass in a ProductName, and the StringList_TBLType of Category Names
select all the strings from my StringList_TBLType
Insert the string into the Category TABLE if it does not exist
Get the ID of the inserted or already existing Category
Insert the ProductId, and CategoryId into the LINK TABLE.
I could probably struggle along and get something working, but I have little experience with MS SQL, and stored procedures in general, and am scared that I would end up writing a very inefficient way of doing this.
You can use the MERGE statement to capture the category IDs.
DECLARE #changes TABLE (ChangeID VARCHAR(10), Id INTEGER);
DECLARE #JustSomeRandomVariable INT;
MERGE Category AS TARGET
USING #data AS SOURCE
ON TARGET.Category = SOURCE.s
WHEN NOT MATCHED THEN
INSERT ([Category])
VALUES (SOURCE.s)
WHEN MATCHED THEN
UPDATE SET #JustSomeRandomVariable = 1
OUTPUT $action, inserted.Id INTO #changes;
The random variable in the merge statement makes sure that updates get logged into the #changes table variable.
Now you can use the #changes to update your link table.
INSERT INTO Link SELECT ProductID, ChangeID FROM #changes
Just retrieve the required ProductID with a simple select query.
EDIT:
This could potentially result in double records in the Link table. You might need to tweak it a bit, perhaps use the MERGE statement for inserting into the Link table aswell.
#data is the StringList_TBLType paramter of your procedure.
This is what I would suggest (not tested)
CREATE PROCEDURE AddProductToCategories
#productname nvarchar(255),
#categories StringList_TBLType READONLY
AS
BEGIN
DECLARE #productId bigint --change to datatype of Product.ProductId
SET #productId = (SELECT TOP 1 ProductId FROM Product WHERE ProductName = #productname) --What to do if your product names are not unique?
IF #productId is not NULL
BEGIN
DECLARE cur CURSOR FOR (
SELECT cat.CategoryName, cat.CategoryId, c.s
FROM #categories c
RIGHT OUTER JOIN Category cat on c.s = cat.CategoryName
)
DECLARE #id as bigint --change to datatype of Category.CategoryId
DECLARE #name as nvarchar(255) --change to datatype of Category.CategoryName
DECLARE #categoryNameToAdd as nvarchar(255)
OPEN cur
FETCH NEXT FROM cur INTO #name, #id, #categoryNameToAdd
WHILE ##FETCH_STATUS = 0
BEGIN
IF #id is NULL
BEGIN
--category does not exist yet in table Category
INSERT INTO Category (CategoryName) VALUES (#categoryNameToAdd)
SET #id = SCOPE_IDENTITY()
END
INSERT INTO ProductsCategories --your link table
(ProductId, CategoryId)
VALUES
(#productId, #id)
FETCH NEXT FROM cur INTO #name, #id, #categoryNameToAdd
END --cursor
CLOSE cur
DEALLOCATE cur
END --IF #productId
END --sproc

Resources