SQL Server: check constraint if relationship exists, else insert - sql-server

I am trying to create a constraint to validate if a relation exists
I have tried to create a procedure and then use it in check constraint. Apparently that does not seem to work.
These are my tables:
STOCKITEMS table:
StockItemId INT
StockItemName VARCHAR
ColorId INT
COLOR table:
ColorId INT
ColorName VARCHAR
This is my stored procedure:
CREATE PROCEDURE USP_ValidateColor
(#Color NVARCHAR(50))
AS
IF NOT EXISTS(SELECT ColorName FROM WareHouse.Colors WHERE ColorName = #Color)
BEGIN
DECLARE #Id INT
SET #Id = (SELECT TOP(1) ColorId + 1 FROM Warehouse.Colors
ORDER BY ColorId DESC)
INSERT INTO Warehouse.Colors
VALUES (#Id, #Color)
PRINT 'Does not exist';
END;
ELSE
PRINT 'Exists';
So if a user insert into the table stock items, I want a check that checks if the colorId already exists in the color table
If it does not, then insert that colorname into colors and. I was thinking about using a constraint check with my procedure, but can't fix the query.

Don't use an SP to check a constraint, use a foreign key:
CREATE TABLE Colour (ColourID int PRIMARY KEY, --This should really have a name
ColourName varchar(20));
CREATE TABLE StockItem (StockItemID int PRIMARY KEY, --This should really have a name too
StockItemName varchar(20),
ColourID int);
ALTER TABLE dbo.StockItem ADD CONSTRAINT Colour_FK FOREIGN KEY (ColourID) REFERENCES dbo.Colour(ColourID);
Then, if you try to insert something into the StockItem table, it'll fail unless the colour exists:
INSERT INTO dbo.Colour (ColourID,
ColourName)
VALUES (1,'Green'),(2,'Blue');
GO
INSERT INTO dbo.StockItem (StockItemID,
StockItemName,
ColourID)
VALUES(1,'Paint',1); --works
GO
INSERT INTO dbo.StockItem (StockItemID,
StockItemName,
ColourID)
VALUES (1,'Wood Panels',3); --fails
GO
--clean up
DROP TABLE dbo.StockItem;
DROP TABLE dbo.Colour;

For checking, use a UNIQUE check constraint. If you want to insert a color only if it doesn't exist, use INSERT .. FROM .. WHERE to check for existence and insert in the same query.
The only "trick" is that FROM needs a table. This can be fixed using a table value constructor to create tables out of the values to insert. If the stored procedure accepts a table-valued parameter, there's no problem.
This example uses a LEFT JOIN to insert non-matching values :
declare #colors table (Color nvarchar(10) UNIQUE)
insert into #colors VALUES ('green')
select * from #colors;
insert into #Colors (Color)
select new.Color
from (VALUES ('red'),
('green')) new(Color)
left outer join #Colors old on old.Color=new.Color
where old.Color is NULL
-- (1 row affected)
insert into #Colors (Color)
select new.Color
from (VALUES ('red'),
('green')) new(Color)
left outer join #Colors old on old.Color=new.Color
where old.Color is NULL
-- (0 rows affected)
select * from #colors;
-- green
-- red
The same using a subquery:
insert into #Colors (Color)
select new.Color
from
(VALUES ('red'),
('green')) new(Color)
where not exists (select 1
from #colors
where color=new.Color);
By using the UNIQUE constraint we ensure that duplicate entries can't be inserted

Related

I would like to sum all of the data in a specific table column, if the appropriate ID of that table exists in another one

I would like to SUM a value called amount from table 1, the considered, to be summed, values should only be the ones presenting in table 2. Meaning that, for the amount of row 1 in table 1 to be considered in the sum. the ID of that row 1 should be present in table 2.
Thanks,
This might be the answer but you really should have put some example tables in your example. I fancied helping as have 10 mins, this example you can run.
You can see that table 1 is referenced twice from Table2 and 3 just the once ,so the result ignores multiple occurrences, hence the WHERE EXISTS syntax.
This sums up all the numbers in Table1 that are referenced in Table2.
BEGIN TRANSACTION
BEGIN TRY
CREATE TABLE #Table1 (
Id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
[Number] DECIMAL NOT NULL
)
INSERT INTO #Table1 ([Number])
VALUES('1'),
('1'),
('1'),
('1')
SELECT * FROM #Table1
CREATE TABLE #Table2 (
Id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
Table1Id INT NOT NULL,
CONSTRAINT FK_Table2_Table1 FOREIGN KEY (Table1Id) REFERENCES #Table1 (Id)
)
INSERT INTO #Table2 ([Table1Id])
VALUES('1'),
('1'),
('3')
SELECT * FROM #Table2
SELECT SUM(T1.Number) AS SummedNumbersThatAreReferencedByTable2
FROM #Table1 AS T1
WHERE EXISTS(
SELECT TOP 1 *
FROM #Table2 AS T2
WHERE T2.Table1Id = T1.Id
)
ROLLBACK TRANSACTION
END TRY
BEGIN CATCH
PRINT 'Rolling back changes, there was an error!!'
ROLLBACK TRANSACTION
DECLARE #Msg NVARCHAR(MAX)
SELECT #Msg=ERROR_MESSAGE()
RAISERROR('Error Occured: %s', 20, 101,#msg) WITH LOG
END CATCH
If this is the answer then please mark it as so, cheers

How to get a column derived from joining on inserted value?

For the following example I set shipping method to 'UPS'
CREATE TABLE [dbo].[Customer] (CustomerID int primary key, ShipMethodRef INT)
INSERT INTO [dbo].[Customer] VALUES (5497, 20);
CREATE TABLE [dbo].ShipMethod(ShipMethodID int PRIMARY KEY, Name varchar(10));
INSERT INTO [dbo].ShipMethod VALUES (20, 'Fedex'), (21, 'UPS')
UPDATE [dbo].[Customer]
set ShipMethodRef = CASE WHEN EXISTS (SELECT ShipMethodID from [dbo].[ShipMethod]
WHERE [dbo].[ShipMethod].Name = 'UPS')
THEN (SELECT ShipMethodID from [dbo].[ShipMethod]
WHERE [dbo].[ShipMethod].Name = 'UPS')
ELSE curTable.ShipMethodRef END
OUTPUT ShipMethod.Name as ShipMethodName
FROM [dbo].[Customer] curTable
JOIN [dbo].ShipMethod ShipMethod ON curTable.ShipMethodRef = ShipMethod.ShipMethodID
WHERE CustomerID=5497;
The OUTPUT clause returns Fedex - How can I change it to reflect the post insert state that the customer's shipping method is 'UPS' (as their shipping method Id is now 21)?
I don't think this can be done with a single statement except in the way Martin showed in his comment, but you can get the output from inserted into a table variable or a temporary table and then select from that joined to the translation tables.
Here's how I would do that (note the update statement is simplified):
DECLARE #UpdatedIds AS TABLE (ShipMethodID int);
UPDATE [dbo].[Customer]
SET ShipMethodRef = COALESCE((
SELECT ShipMethodID
FROM [dbo].[ShipMethod]
WHERE [dbo].[ShipMethod].Name = 'UPS'
), ShipMethodRef)
OUTPUT inserted.ShipMethodRef INTO #UpdatedIds
FROM [dbo].[Customer]
WHERE CustomerID=5497;
SELECT SM.ShipMethodID, SM.Name
FROM [dbo].ShipMethod AS SM
JOIN #UpdatedIds AS Updated
ON SM.ShipMethodID = Updated.ShipMethodID

T-SQL insert and update foreign key without cursor

I have two tables in MS SQL:
CREATE TABLE Table1 (ID INT IDENTITY(1,1) NOT NULL, TEXTVal VARCHAR(100), Table2Id int)
insert into Table1 (TEXTVal) values('aaa');
insert into Table1 (TEXTVal) values('bbb'); insert into Table1 (TEXTVal) values('ccc');
CREATE TABLE Table2 (ID INT IDENTITY(1,1) NOT NULL, TEXTVal VARCHAR(100), Table2Id int)
Id are identity columns. I want to copy TEXTVal values from Table1 to Table2:
INSERT INTO Table2 (TEXTVal)
SELECT TEXTVal FROM Table1
where TEXTVal <> 'ccc'
and after that update column Table2Id in Table1 with appropriate values of Id from Table2. I can do this with cursor and SCOPE_IDENTITY().
I am just wondering, is there a way to do it without cursor in T-SQL?
As Jeroen stated in comments, you'll want to use OUTPUT. In the following example if you don't have an AdventureWorks database, just use a test database. You should be able to copy/paste this and just run it to see it in action!
USE AdventureWorks;
GO
----Creating the table which will store permanent table
CREATE TABLE TestTable (ID INT, TEXTVal VARCHAR(100))
----Creating temp table to store ovalues of OUTPUT clause
DECLARE #TmpTable TABLE (ID_New INT, TEXTVal_New VARCHAR(100),ID_Old INT, TEXTVal_Old VARCHAR(100))
----Insert values in real table
INSERT TestTable (ID, TEXTVal)
VALUES (1,'FirstVal')
INSERT TestTable (ID, TEXTVal)
VALUES (2,'SecondVal')
----Update the table and insert values in temp table using Output clause
UPDATE TestTable
SET TEXTVal = 'NewValue'
OUTPUT Inserted.ID, Inserted.TEXTVal, Deleted.ID, Deleted.TEXTVal INTO #TmpTable
WHERE ID IN (1,2)
----Check the values in the temp table and real table
----The values in both the tables will be same
SELECT * FROM #TmpTable
SELECT * FROM TestTable
----Clean up time
DROP TABLE TestTable
GO
ResultSet:
TmpTable:
ID_New TextVal_New ID_Old TextVal_Old
——————— ——————— ——————— ———————
1 NewValue 1 FirstVal
2 NewValue 2 SecondVal
Original Table:
ID TextVal
——————— ———————
1 NewValue
2 NewValue
As you can see it is possible to capture new values, and the values you are updating. In this example I'm just stuffing them into a table variable but you could do whatever you'd like with them. :)

Is there a way to retrieve inserted identity as well as some values from the query in an INSERT SELECT?

I have a situation in which I need to insert some values from a query into a table that has an identity PK. For some of the records, I need also to insert values in another table which has a 1-to-1 (partial) relationship:
CREATE TABLE A (
Id int identity primary key clustered,
Somevalue varchar(100),
SomeOtherValue int)
CREATE TABLE B (Id int primary key clustered,
SomeFlag bit)
DECLARE #inserted TABLE(NewId int, OldId)
INSERT INTO A (Somevalue)
OUTPUT Inserted.Id into #inserted(NewId)
SELECT SomeValue
FROM A
WHERE <certain condition>
INSERT INTO B (Id, SomeFlag)
SELECT
i.NewId, B.SomeFlag
FROM #inserted i
JOIN A ON <some condition>
JOIN B ON A.Id = B.Id
The problem is that the query from A in the first INSERT/SELECT returns records that can only be differentiated by the Id, which I cannot insert. Unfortunately I cannot change the structure of the A table, to insert the "previous" Id which would solve my problem.
Any idea that could lead to a solution?
With INSERT ... OUTPUT ... SELECT ... you can't output columns that are not in the target table. You can try MERGE instead:
MERGE INTO A as tgt
USING (SELECT Id, SomeValue FROM A WHERE <your conditions>) AS src
ON 0 = 1
WHEN NOT MATCHED THEN
INSERT (SomeValue)
VALUES (src.SomeValue)
OUTPUT (inserted.Id, src.Id) -- this is your new Id / old Id mapping
INTO #inserted
;
SCOPE_IDENTITY() returns the last identity value generated by the current session and current scope. You could stick that into a #table and use that to insert into B
SELECT SCOPE_IDENTITY() as newid into #c
Though, your INSERT INTO B join conditions implies to me that the value in B is already known ?

Get inserted table identity value and update another table

I have two tables with foreign key constraint on TableB on TablesAs KeyA column. I was doing manual inserts till now as they were only few rows to be added. Now i need to do a bulk insert, so my question if i insert multiple rows in TableA how can i get all those identity values and insert them into TableB along with other column values. Please see the script below.
INSERT INTO Tablea
([KeyA]
,[Value] )
SELECT 4 ,'StateA'
UNION ALL
SELECT 5 ,'StateB'
UNION ALL
SELECT 6 ,'StateC'
INSERT INTO Tableb
([KeyB]
,[fKeyA] //Get value from the inserted row from TableA
,[Desc])
SELECT 1 ,4,'Value1'
UNION ALL
SELECT 2 ,5,'Value2'
UNION ALL
SELECT 3 ,6, 'Value3'
You can use the OUTPUT clause of INSERT to do this. Here is an example:
CREATE TABLE #temp (id [int] IDENTITY (1, 1) PRIMARY KEY CLUSTERED, Val int)
CREATE TABLE #new (id [int], val int)
INSERT INTO #temp (val) OUTPUT inserted.id, inserted.val INTO #new VALUES (5), (6), (7)
SELECT id, val FROM #new
DROP TABLE #new
DROP TABLE #temp
The result set returned includes the inserted IDENTITY values.
Scope identity sometimes returns incorrect value. See the use of OUTPUT in the workarounds section.

Resources