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
Related
My goal is that when the user chooses the product, the application will export the supplier that provides both products. However, if I use a stored procedure, the input must be a fixed value so I can't put a listId like C#.
So, how can I just put a list of Id into the store and I have produced supplier business id of the added productId. See my table below and InventoryOfSentoId is ProductId:
If input is 1 then output is supplierid 1 and 3
If input is 2, 3 then output is supplierid 3
If you are using SQL Server 2014, it is easy to create your own split function. The one I use is the following (but you can google for others that are more efficient, particularly if your list is long):
create FUNCTION [dbo].[fnSplit](
#sDelimiter VARCHAR(5) -- delimiter that separates items
, #sInputList VARCHAR(max) -- List of delimited items
) RETURNS #List TABLE (item VARCHAR(max))
BEGIN
DECLARE #sItem VARCHAR(max)
WHILE CHARINDEX(#sDelimiter,#sInputList,0) <> 0
BEGIN
SELECT
#sItem=RTRIM(LTRIM(SUBSTRING(#sInputList,1,CHARINDEX(#sDelimiter,#sInputList,0)-1))),
#sInputList=RTRIM(LTRIM(SUBSTRING(#sInputList,CHARINDEX(#sDelimiter,#sInputList,0)+LEN(#sDelimiter),LEN(#sInputList))))
IF LEN(#sItem) > 0
INSERT INTO #List SELECT #sItem
END
IF LEN(#sInputList) > 0
INSERT INTO #List SELECT #sInputList -- Put the last item in
RETURN
END
You will then be able to achieve what you want with:
declare #demo table (SupplierID int, InventoryOfSentoID int)
declare #list varchar(10) = '2,3'
declare #ids table (SentoID int)
declare #cnt int
INSERT INTO #demo VALUES (1,1),(1,2),(2,5),(3,3),(3,2),(3,1)
INSERT INTO #ids
SELECT * FROM dbo.fnSplit(',',#list)
SELECT #cnt = COUNT(*) FROM #ids
SELECT SupplierID FROM #demo d
INNER JOIN #ids i
ON i.SentoID = d.InventoryOfSentoID
GROUP BY SupplierID
HAVING COUNT(*) = #cnt
By way of explanation, note the use of the HAVING restriction. This is to ensure that only those suppliers who supply all the elements in the list are returned. If you did not have this restriction, the resultset would include supplierid 1, which from your question I presumed you wanted to exclude.
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
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
Here as my tables (Entier = Integer // Caractère long variable = Varchar) :
http://i.stack.imgur.com/lNjyy.jpg
I created a view V_Enterprise(idContact, phoneNumber, email, name, city, adress)
I tried to create a Trigger on that View to allow users to update the view :
CREATE TRIGGER test
ON V_Entreprise
INSTEAD OF INSERT
AS
DECLARE #T_ContactId INT
BEGIN
INSERT INTO T_Contact
SELECT i.phoneNumber, i.email
FROM Inserted i
SELECT #T_ContactId = ##IDENTITY
INSERT INTO T_Entreprise
SELECT #T_ContactId, i.name, i.city, i.adress
FROM Inserted i
END ;
As I expected, it work on simple inserts, but when I add couples of rows at once, it fails because #T_ContactId only contains the first id. Can someone help me to fix it ? I feel like I should use INNER JOIN inserts but I can't figure out how to deal with it.
OK you should never set scalar variables to a value in inserted or delted in a trigger.
Use the OUTPUT clause instead to get your id values back.
This trigger uses a loop over a cursor and won't require any particular uniqueness in the tables;
CREATE TRIGGER test
ON V_Enterprise
INSTEAD OF INSERT
AS
BEGIN
DECLARE #name VARCHAR(32)
DECLARE #city VARCHAR(32)
DECLARE #address VARCHAR(32)
DECLARE #pn VARCHAR(32)
DECLARE #email VARCHAR(32)
DECLARE cursor1 CURSOR FOR
SELECT name,city,address,phoneNumber,email FROM inserted;
OPEN cursor1;
FETCH NEXT FROM cursor1 INTO #name, #city, #address, #pn, #email;
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO T_Contact (phoneNumber,email) VALUES (#pn, #email);
INSERT INTO T_Enterprise (idcontact,name,city,address) VALUES
(##IDENTITY,#name,#city,#address);
FETCH NEXT FROM cursor1 INTO #name, #city, #address, #pn, #email;
END
CLOSE cursor1;
DEALLOCATE cursor1;
END
GO
I don't know if this is a good way to do it, but you can do this without relying on unique columns or using a cursor using the OUTPUT clause for INSERT. This approach does make use of an in-memory temporary table that could get big with large inserts.
DECLARE #Table table( NewID BIGINT);
INSERT INTO T_Contact (PhoneNumber)
OUTPUT Inserted.ID
INTO #Table
SELECT PhoneNumber FROM inserted WHERE
;
INSERT INTO T_Enterprise (Contact_ID)
SELECT NewID FROM #Table;
If phoneNumber and email are a unique key in T_Contact then you could do this:
CREATE TRIGGER test
ON V_Entreprise
INSTEAD OF INSERT
AS
DECLARE #T_ContactId INT
BEGIN
INSERT INTO T_Contact
SELECT i.phoneNumber, i.email
FROM Inserted i
SELECT #T_ContactId = ##IDENTITY
INSERT INTO T_Entreprise
SELECT
(SELECT idContact FROM T_Contact
WHERE phoneNumber = i.phoneNumber AND email = i.email),
i.name, i.city, i.adress
FROM Inserted i
END ;
I have an ordering system where when a new order is placed it is inserted into my table Orders. From there I want to insert the new id into another table Importance which also needs an id from a third table called ImportanceRating.
Table structures:
Order
OrderId uniqueidentifier
TimeOrderPlaced datetime
ProductId uniqueidentifier
EstimatedDeliveryTime datetime
Importance
FK_OrderId uniqueidentifier
FK_ImpRatingId uniqueidentifier
ImportanceRating
ImpRatingId uniqueidentifier
RatingTitle varchar(50)
All of this I want merged in 1 stored procedure. How would I go about with this?
Links to good guides on the subject is more than welcome.
I'm a SPROC newbie
Could you try this?:
CREATE PROCEDURE AddOrderAndRatingSample
-- These are the values you want to insert
#paramTimeOrderPlaced DATETIME
, #paramProductId INT
, #paramEstimatedDeliveryTime DATETIME
, #paramRatingTitle VARCHAR(50)
AS
BEGIN
DECLARE #siOrderId INT
DECLARE #siImpRatingId INT
-- Assuming that `OrderId` in table `Order` is an `identity column`:
INSERT INTO Order (TimeOrderPlaced, ProductId, EstimatedDeliveryTime)
VALUES(#paramTimeOrderPlaced, #paramProductId, #paramEstimatedDeliveryTime)
SET #siOrderId = SCOPE_IDENTITY()
-- Assuming `ImpRatingId` in table `ImportanceRating` is an `identity column`:
INSERT INTO ImportanceRating (RatingTitle)
VALUES(#paramRatingTitle)
SET #siImpRatingId = SCOPE_IDENTITY()
-- And that both `FK_OrderId` and `FK_ImpRatingId`
-- in table `Importance` are not `identity columns`:
INSERT INTO Importance (FK_OrderId, FK_ImpRatingId)
SELECT #siOrderId, #siImpRatingId
END
Could you please try this way:
DECLARE #OrderId INT
INSERT INTO Order (TimeOrderPlaced, ProductId, EstimatedDeliveryTime)
VALUES(#paramTimeOrderPlaced, #paramProductId, #paramEstimatedDeliveryTime)
SET #OrderId = ##IDENTITY -- Last orderId
INSERT INTO ImportanceRating (RatingTitle)
VALUES(#paramRatingTitle)
INSERT INTO Importance (FK_OrderId, FK_ImpRatingId)
SELECT #OrderId, ##IDENTITY -- Here ##IDENTITY returns last ID of ImportanceRating
-- Each inserting time the global variable ##IDENTITY is set with last IDENTITY value