SQL Update Parent Child relation on Trigger - sql-server

I would like to link rows in a self joined table with a trigger.
After an insert in a root table, I would like to create 3 "levels" in the child table.
And each of the level being a hierarchical data (or self joined) such as:
LVL1
LVL2
LVL3
Database is SQLSERVER.
I know there is a ton of material about self-joined and hierarchical SQL data, but ... I don't know I've not found what I expected. I've spent too many hours trying solutions, and searching online.
A link to SQLFiddle.
Here is the basic schema for example:
CREATE TABLE [dbo].[Root] (
[RootID] [int] PRIMARY KEY IDENTITY(1,1) NOT NULL,
[Name] [varchar](50)
)
GO
CREATE TABLE [dbo].[Child] (
[ChildID] [int] PRIMARY KEY IDENTITY(1,1) NOT NULL,
[RootID] [int],
[Name] [varchar](50),
[ParentID] [int]
)
GO
ALTER TABLE [dbo].[Child] WITH CHECK ADD CONSTRAINT [Child_RootID_FK] FOREIGN KEY([RootID])
REFERENCES [dbo].[Root] ([RootID]) ON DELETE SET NULL
GO
ALTER TABLE [dbo].[Child] WITH CHECK ADD CONSTRAINT [Child_ParentID_FK] FOREIGN KEY([ParentID])
REFERENCES [dbo].[Child] ([ChildID])
GO
CREATE TRIGGER [dbo].[Root_TR]
ON [dbo].[Root]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[Child] ([RootID], [Name])
SELECT
I.[RootID],
CONCAT_WS('_', (SELECT [Name] FROM [dbo].[Root] R WHERE R.[RootID] = I.RootID), LVL.n )
FROM INSERTED I
CROSS JOIN (VALUES (1), (2), (3)) AS LVL(n)
END
GO
INSERT INTO [dbo].[Root] ([Name]) VALUES (
'Foo'
)
SELECT * FROM [Root]
SELECT * FROM [Child]
I have the current result:
ChildID
RootID
Name
ParentID
1
1
Foo_1
NULL
2
1
Foo_2
NULL
3
1
Foo_3
NULL
The expected result would be:
ChildID
RootID
Name
ParentID
1
1
Foo_1
NULL
2
1
Foo_2
1
3
1
Foo_3
2
I'm not sure how to achieve this.
I've found a close answer (here) involving usage of SEQUENCE
A solution may be something like:
DROP TRIGGER [dbo].[Root_TR]
GO
CREATE SEQUENCE [dbo].[Sequence] START WITH 1 INCREMENT BY 1
GO
CREATE TRIGGER [dbo].[Root_TR]
ON [dbo].[Root]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON
ALTER SEQUENCE [dbo].[Sequence] RESTART
DECLARE #map TABLE ([ID] [int], [Seq] [int])
INSERT INTO [dbo].[Child] ([RootID], [Name])
OUTPUT [inserted].ChildID, NEXT VALUE FOR [dbo].[Sequence] INTO #map
SELECT
I.[RootID],
CONCAT_WS('_', (SELECT [Name] FROM [dbo].[Root] R WHERE R.[RootID] = I.RootID), LVL.n )
FROM INSERTED I
CROSS JOIN (VALUES (1), (2), (3)) AS LVL(n)
UPDATE C
SET C.[ParentID] = CASE
WHEN M.Seq = 1
THEN NULL
ELSE
(SELECT [Id] FROM #map WHERE [Seq] = [Seq] - 1)
END
FROM [dbo].Child C
INNER JOIN #map M ON C.ChildID = M.ID
END
GO
Unfortunately usage of NEXT VALUE FOR is not allowed in a OUTPUT clause.
Error 11720 NEXT VALUE FOR function is not allowed in the TOP, OVER, OUTPUT, ON, WHERE, GROUP BY, HAVING, or ORDER BY clauses.
I cannot relie on [Name] column to perform the UPDATE SET [ParentID] = ... FROM ... JOIN ...
There is a lot of answers regarding SQL self joined table but I can't really find the answer and my knowledge regarding SQL is limited.
I've attempted something like this
CREATE TRIGGER [dbo].[Root_TR]
ON [dbo].[Root]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON
ALTER SEQUENCE [dbo].[Sequence] RESTART
DECLARE #map TABLE ([ChildID] [int], [Seq] [int])
DECLARE #i int = NEXT VALUE FOR [Sequence]
INSERT INTO [dbo].[Child] ([RootID], [Name])
OUTPUT [inserted].ChildID, #i AS [Seq] INTO #map
SELECT
I.[RootID],
CONCAT_WS('_', (SELECT [Name] FROM [dbo].[Root] R WHERE R.[RootID] = I.RootID), LVL.n )
FROM INSERTED I
CROSS JOIN (VALUES (1), (2), (3)) AS LVL(n)
DECLARE #xml xml = (SELECT * FROM #map FOR XML AUTO)
PRINT CONVERT(nvarchar(max), #xml)
UPDATE C
SET C.[ParentID] = CASE
WHEN M.Seq = 1
THEN NULL
ELSE
(SELECT [ChildID] FROM #map WHERE [Seq] = [Seq] - 1)
END
FROM [dbo].Child C
INNER JOIN #map M ON C.ChildID = M.ChildID
END
GO
Once again unfortunately the temporary TABLE #map is not filled correctly.
The #i is called once and do not increment for each output row.
ChildID
Seq
10
1
11
1
12
1
Another try was to use a DEFAULT. But I've an error when creating the trigger: "Column name or number of supplied values does not match table definition.". Probably because the OUTPUT clause see more columns available in #map TABLE than the number of columns in the OUTPUT.
CREATE TRIGGER [dbo].[Root_TR]
ON [dbo].[Root]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON
ALTER SEQUENCE [dbo].[Sequence] RESTART
DECLARE #map TABLE (
[Seq] [int] PRIMARY KEY NOT NULL DEFAULT (NEXT VALUE FOR [Sequence]),
[ChildID] [int]
)
INSERT INTO [dbo].[Child] ([RootID], [Name])
OUTPUT [inserted].ChildID INTO #map
SELECT
I.[RootID],
CONCAT_WS('_', (SELECT [Name] FROM [dbo].[Root] R WHERE R.[RootID] = I.RootID), LVL.n )
FROM INSERTED I
CROSS JOIN (VALUES (1), (2), (3)) AS LVL(n)
DECLARE #xml xml = (SELECT * FROM #map FOR XML AUTO)
PRINT CONVERT(nvarchar(max), #xml)
UPDATE C
SET C.[ParentID] = CASE
WHEN M.Seq = 1
THEN NULL
ELSE
(SELECT [ChildID] FROM #map WHERE [Seq] = [Seq] - 1)
END
FROM [dbo].Child C
INNER JOIN #map M ON C.ChildID = M.ChildID
END
GO
And the last try was to use a #table (I do not distinguish clearly between #x TABLE, TABLE #x, TABLE x). But I want to scope the temporary table to the trigger, and not make the table available globally.
So, this time the TRIGGER is created. But when fired, the error message is Invalid object name 'Sequence'. I don't know why the [Sequence] object is not available in this #table.
CREATE TRIGGER [dbo].[Root_TR]
ON [dbo].[Root]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON
ALTER SEQUENCE [dbo].[Sequence] RESTART
CREATE TABLE #map (
[Seq] [int] PRIMARY KEY NOT NULL DEFAULT (NEXT VALUE FOR [Sequence]),
[ChildID] [int] NOT NULL
)
INSERT INTO [dbo].[Child] ([RootID], [Name])
OUTPUT [inserted].ChildID INTO #map
SELECT
I.[RootID],
CONCAT_WS('_', (SELECT [Name] FROM [dbo].[Root] R WHERE R.[RootID] = I.RootID), LVL.n )
FROM INSERTED I
CROSS JOIN (VALUES (1), (2), (3)) AS LVL(n)
DECLARE #xml xml = (SELECT * FROM #map FOR XML AUTO)
PRINT CONVERT(nvarchar(max), #xml)
UPDATE C
SET C.[ParentID] = CASE
WHEN M.Seq = 1
THEN NULL
ELSE
(SELECT [ChildID] FROM #map WHERE [Seq] = [Seq] - 1)
END
FROM [dbo].Child C
INNER JOIN #map M ON C.ChildID = M.ChildID
END
GO
So finally I don't have yet any solution to properly link my different levels with their parents.
Any help would be highly appreciated.

Here the complete trigger.
CREATE TRIGGER [dbo].[Root_TR]
ON [dbo].[Root]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON
CREATE TABLE #map (
[Seq] [int] PRIMARY KEY NOT NULL IDENTITY(1,1)
[ChildID] [int] NOT NULL
)
INSERT INTO [dbo].[Child] ([RootID], [Name])
OUTPUT INSERTED.ChildID INTO #map
SELECT
I.[RootID],
CONCAT_WS('_', (SELECT [Name] FROM [dbo].[Root] R WHERE R.[RootID] = I.RootID), LVL.n )
FROM INSERTED I
CROSS JOIN (VALUES (1), (2), (3)) AS LVL(n)
UPDATE C
SET C.[ParentID] = CASE
WHEN M.[Seq] = 1
THEN NULL
ELSE
(SELECT [ChildID] FROM #map WHERE [Seq] = M.[Seq] - 1)
END
FROM [dbo].Child C
INNER JOIN #map M ON C.ChildID = M.ChildID
END
GO

Related

After Trigger for Update and Insert failing

I am writing a trigger for keeping audit record for one table for Insert and Update records.
CREATE TABLE [dbo].[AppLog](
[TableName] [varchar](32) NOT NULL,
[ColumnName] [varchar](32) NOT NULL,
[RecordId] [varchar](20) NOT NULL,
[OldValue] [varchar](2000) NULL,
[NewValue] [varchar](2000) NULL,
[UpdatedBy] [varchar](200) NULL,
[UpdatedOn] [datetime] NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Persons](
[Personid] [int] IDENTITY(1,1) NOT NULL,
[LastName] [varchar](255) NOT NULL,
[FirstName] [varchar](255) NULL,
[Age] [int] NULL
) ON [PRIMARY]
GO
CREATE TRIGGER AuditRecord ON dbo.Persons
AFTER UPDATE, INSERT
AS
INSERT INTO AppLog
(TableName ,ColumnName ,RecordId ,OldValue ,NewValue ,UpdatedBy ,UpdatedOn )
SELECT 'Persons', 'LastName', COALESCE(i.Personid,NULL),
d.LastName, i.LastName, CURRENT_USER, GETDATE()
FROM Persons pv
LEFT JOIN INSERTED i ON pv.Personid = i.Personid
LEFT JOIN DELETED d ON pv.Personid = d.Personid;
GO
INSERT INTO Persons (FirstName,LastName,age)
VALUES ('Satish','Parida',40);
INSERT INTO Persons (FirstName,LastName,age)
VALUES ('SKP','Tada',90);
The last insert is failing as it is trying to insert null to recordid column in applog table, could someone explain or fix the issue.
The statement that is failing is the one below:
INSERT INTO Persons (FirstName,LastName,age)
VALUES ('SKP','Tada',90);
This is because in your Trigger you are using Persons are your "base" table and the performing a LEFT JOIN to both inserted and deleted. As result when you try to perform the above INSERT, values from the previous INSERT are used as well in the trigger's dataset. The person 'Parida' doesn't appear in the table inserted for your second INSERT, and so COALESCE(i.Personid,NULL) returns NULL; as I mentioned in my comment, there' no point using COALESCE to return NULL, as if an expression's value evaluates to NULL it will return NULL. As RecordID (which is what COALESCE(i.Personid,NULL) is being inserted into) can't have the value NULL the INSERT fails and the whole transaction is rolled back.
I suspect that what you want for your trigger is the below:
CREATE TRIGGER AuditRecord ON dbo.Persons
AFTER UPDATE, INSERT
AS BEGIN
INSERT INTO AppLog (TableName,
ColumnName,
RecordId,
OldValue,
NewValue,
UpdatedBy,
UpdatedOn)
SELECT 'Persons',
'LastName',
i.Personid,
d.LastName,
i.LastName,
CURRENT_USER,
GETDATE()
FROM inserted AS i
LEFT JOIN deleted AS d ON i.Personid = d.Personid;
END;
inserted will always have at least 1 row for an UPDATE or an INSERT. inserted would not for a DELETE, but your trigger won't fire on that DML event so using inserted as the "base" table seems the correct choice.
Following code should work
CREATE TABLE [dbo].[AppLog](
[TableName] [varchar](32) NOT NULL,
[ColumnName] [varchar](32) NOT NULL,
[RecordId] [varchar](20) NOT NULL,
[OldValue] [varchar](2000) NULL,
[NewValue] [varchar](2000) NULL,
[UpdatedBy] [varchar](200) NULL,
[UpdatedOn] [datetime] NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Persons](
[Personid] [int] IDENTITY(1,1) NOT NULL,
[LastName] [varchar](255) NOT NULL,
[FirstName] [varchar](255) NULL,
[Age] [int] NULL
) ON [PRIMARY]
GO
CREATE TRIGGER AuditRecord ON dbo.Persons
AFTER UPDATE, INSERT
AS
if exists(SELECT * from inserted) and exists (SELECT * from deleted)
BEGIN
INSERT INTO AppLog
(TableName ,ColumnName ,RecordId ,OldValue ,NewValue ,UpdatedBy ,UpdatedOn )
SELECT 'Persons', 'LastName', COALESCE(i.Personid,NULL),
d.LastName, i.LastName, CURRENT_USER, GETDATE()
FROM Persons pv
INNER JOIN INSERTED i ON pv.Personid = i.Personid
INNER JOIN DELETED d ON pv.Personid = d.Personid;
END
If exists (Select * from inserted) and not exists(Select * from deleted)
begin
INSERT INTO AppLog
(TableName ,ColumnName ,RecordId ,OldValue ,NewValue ,UpdatedBy ,UpdatedOn )
SELECT
'Persons', 'LastName',i.Personid,NULL,i.LastName,CURRENT_USER,GETDATE()
FROM
inserted AS i
END
GO
INSERT INTO Persons (FirstName,LastName,age)
VALUES ('Satish','Parida',40);
INSERT INTO Persons (FirstName,LastName,age)
VALUES ('SKP','Tada',90);
INSERT INTO Persons (FirstName,LastName,age)
VALUES ('abc','def',90);
INSERT INTO Persons (FirstName,LastName,age)
VALUES ('gg','hh',90);
UPDATE dbo.Persons SET LastName='Paridachanged' WHERE Personid=1
SELECT * FROM Persons
SELECT * FROM AppLog
USE KnockKnockDev;
GO
IF OBJECT_ID('dbo.AuditRecord', 'TR') IS NOT NULL
DROP TRIGGER dbo.AuditRecord;
GO
CREATE TRIGGER AuditRecord ON dbo.Persons
AFTER UPDATE, INSERT, DELETE
AS
DECLARE #Action as char(1)
DECLARE #Count as int
DECLARE #TableName as char(32)
DECLARE #ColumnName as char (32)
SET #TableName = 'Persons'
SET #ColumnName = 'LastName'
SET #Action = 'I' -- Set Action to 'I'nsert by default.
SELECT #Count = COUNT(*) FROM DELETED
IF #Count > 0
BEGIN
SELECT #Count = COUNT(*) FROM INSERTED
IF #Count > 0
SET #Action = 'U' -- Set Action to 'U'pdated.
ELSE
SET #Action = 'D' -- Set Action to 'D'eleted.
END
IF #Action = 'I'
BEGIN
INSERT INTO AppLog
(TableName ,ColumnName ,RecordId ,OldValue ,NewValue ,Action ,UpdatedBy ,UpdatedOn )
SELECT #TableName, #ColumnName, Personid,
NULL, LastName, #Action, CURRENT_USER, GETDATE()
FROM INSERTED;
END
ELSE IF #Action = 'D'
BEGIN
INSERT INTO AppLog
(TableName ,ColumnName ,RecordId ,OldValue ,NewValue ,Action ,UpdatedBy ,UpdatedOn )
SELECT #TableName, #ColumnName, Personid,
LastName, NULL, #Action, CURRENT_USER, GETDATE()
FROM DELETED;
END
ELSE
BEGIN
INSERT INTO AppLog
(TableName ,ColumnName ,RecordId ,OldValue ,NewValue ,Action ,UpdatedBy ,UpdatedOn )
SELECT #TableName, #ColumnName, i.Personid,
d.LastName, i.LastName, #Action, CURRENT_USER, GETDATE()
FROM Persons pv
INNER JOIN INSERTED i ON pv.Personid = i.Personid
INNER JOIN DELETED d ON pv.Personid = d.Personid;
END
GO

MSSQL Trigger Update on column

I have 2 tables and I want table 1 to have a trigger that insert or update in table 2, but I'm not sure how to do that.
Table 1:
CREATE TABLE [dbo].[DevicePorts](
[ID] [int] IDENTITY(1,1) NOT NULL,
[IsInUse] [bit] NOT NULL,
)
Table 2:
CREATE TABLE [dbo].[DevicePortActivities](
[ID] [uniqueidentifier] NOT NULL,
[StartTime] [datetimeoffset](7) NOT NULL,
[EndTime] [datetimeoffset](7) NULL,
[FK_DevicePortID] [int] NOT NULL FOREIGN KEY REFERENCES DevicePorts(ID),
)
Start of my trigger:
CREATE TRIGGER PortInUse
ON DevicePorts
AFTER UPDATE
AS BEGIN
SET NOCOUNT ON;
IF UPDATE (IsInUse)
BEGIN
IF IsInUse = 1
THEN
INSERT INTO [dbo].[DevicePortActivities]
(
[ID]
,[StartTime]
,[EndTime]
,[FK_DevicePortID]
)
VALUES
(
NEWID(),
SYSDATETIMEOFFSET(),
null,
<DevicePortID>
)
ELSE
UPDATE [dbo].[DevicePortActivities]
SET EndTime = SYSDATETIMEOFFSET()
WHERE FK_DevicePortID = <DevicePortID> AND EndTime is null
END
END
END
GO
What I'm trying to do is when 'IsInUse' is modified it should insert a row into 'DevicePortActivities' or update.
Conditions are, if 'IsInUse' is true then it should insert a record, if it's false it should update the last record where 'EndTime' is null.
You need to treat inserted as a table. I'd suggest looking at MERGE for this (because different rows may have had different changes applied by a single UPDATE).
Something like:
CREATE TRIGGER PortInUse
ON DevicePorts
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
MERGE INTO [dbo].[DevicePortActivities] t
USING (select i.ID,i.IsInUse as NewUse,d.IsInUse as OldUse
from inserted i inner join deleted d on i.ID = d.ID) s
ON
t.FK_DevicePortID = s.ID
WHEN MATCHED AND t.EndTime is null AND NewUse = 0 and OldUse = 1
THEN UPDATE SET EndTime = SYSDATETIMEOFFSET()
WHEN NOT MATCHED AND NewUse = 1 and OldUse = 0
THEN INSERT ([ID]
,[StartTime]
,[EndTime]
,[FK_DevicePortID])
VALUES (NEWID(),
SYSDATETIMEOFFSET(),
null,
s.ID);
END
I found a solution for my question
CREATE TRIGGER [dbo].[PortInUse]
ON [dbo].[DevicePorts]
AFTER UPDATE
AS BEGIN
SET NOCOUNT ON;
IF UPDATE (IsInUse)
BEGIN
DECLARE #IsInUse bit;
DECLARE #PortID int;
SELECT #IsInUse = i.IsInUse FROM inserted i;
SELECT #PortID = i.ID FROM inserted i;
IF (#IsInUse = 1)
INSERT INTO [dbo].[DevicePortActivities]
(
[ID]
,[StartTime]
,[EndTime]
,[FK_DevicePortID]
)
VALUES
(
NEWID(),
SYSDATETIMEOFFSET(),
null,
#PortID
);
ELSE
UPDATE [dbo].[DevicePortActivities]
SET EndTime = SYSDATETIMEOFFSET()
WHERE FK_DevicePortID = #PortID AND EndTime is null;
END
END
I'm not sure if there is a better way to do this, but it's working.

Statement contains an OUTPUT clause without INTO clause error using trigger in SQL Server 2012?

I have a trigger for auditing record insert,update and delete. My code is
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[mytable_insertafter_audit]
ON [dbo].[mytable]
AFTER INSERT, DELETE, UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Action as char(1);
SET #Action = (CASE WHEN EXISTS(SELECT * FROM INSERTED)
AND EXISTS(SELECT * FROM DELETED)
THEN 'U' -- Set Action to Updated.
WHEN EXISTS(SELECT * FROM INSERTED)
THEN 'I' -- Set Action to Insert.
WHEN EXISTS(SELECT * FROM DELETED)
THEN 'D' -- Set Action to Deleted.
ELSE NULL -- Skip. It may have been a "failed delete".
END)
IF (#Action = 'I')
BEGIN
INSERT INTO audit_trg
SELECT
lr.sanction_status, i.sanction_status,
GETDATE(), lr.id, 'mytable', #Action
FROM
mytable lr
INNER JOIN
INSERTED i ON i.id = lr.id
END
ELSE IF (#Action = 'U')
BEGIN
INSERT INTO audit_trg
SELECT
i.sanction_status, lr.sanction_status,
GETDATE(), lr.id, 'mytable', #Action
FROM
mytable lr
INNER JOIN
DELETED i ON i.id = lr.id
END
ELSE
BEGIN
INSERT INTO audit_trg
SELECT
lr.sanction_status, i.sanction_status,
GETDATE(), lr.id, 'mytable', #Action
FROM
mytable lr
INNER JOIN
DELETED i ON i.id = lr.id
END
END
and audit_trg table
CREATE TABLE [dbo].[audit_trg]
(
[id] [int] IDENTITY(1,1) NOT NULL,
[old_status] [varchar](50) NULL,
[new_status] [varchar](50) NULL,
[u_datetime] [datetime] NULL,
[ref_id] [int] NULL,
[table_name] [varchar](50) NULL,
[actions] [varchar](50) NULL,
CONSTRAINT [PK_audit_trg]
PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]
but I get error after some inserted that is
The target table 'dbo.mytable' of the DML statement cannot have
any enabled triggers if the statement contains an OUTPUT clause
without INTO clause.

UPDATE doesn't allow to INSERT null in FK field

I have a SQL Server database and in a table there's a lookup column which is a "nullable FK" linked to a master table.
There's a data import process in which we fetch data from table01_staging into table01. This is the UPDATE statement which is failing (if CarrierID is null) -
DECLARE #code as nvarchar(10); SET #code = 'xyz';
UPDATE table01
SET CarrierID = t2.CarrierID
FROM table01 AS t1
INNER JOIN table01_staging AS t2 ON t1.Code = #code;
WHERE t1.Code = #code;
It goes fine if the CarrierID is not null. In fact I can successfully execute:
UPDATE table01
SET CarrierID = null
WHERE t1.Code = 'xyz';
So setting null is not the problem but it doesn't work when its updated from the staging table which has a null value. How can I make it right?
Error : The UPDATE statement conflicted with the FOREIGN KEY constraint FK_table01_MasterCarrier. The conflict occurred in database table01, table dbo.MasterCarrier, column ID.
> EDIT 02: DONE!. Updated the first SQL to show my variable usage - which I believe was the culprit. Correcting the JOIN operation as follows with a COLLATion conversion error fix makes it work -
DECLARE #code as nvarchar(10); SET #code = 'xyz';
UPDATE table01
SET CarrierID = t2.CarrierID
FROM table01 AS t1
INNER JOIN table01_staging AS t2 ON t1.Code = t2.code COLLATE SQL_Latin1_General_CP1_CI_AS
WHERE t1.Code = #code;
Thank you all, esp. Zhang. I deserve a -1 for not being able to understand the JOIN clause properly :-)
Does table01_staging have the same Foreign Key Constraint on CarrierID?
If not, check whether there are some CarrierID not existing in MasterCarrier table.
select * from table01_staging t
where not exists (select 1 from MasterCarrier m where m.ID = t.CarrierID)
!!UPDATE!!
I created the sample table and data. It worked without any issue.
CREATE TABLE [dbo].MasterCarrier(
[id] [int] IDENTITY NOT NULL,
[NAME] [varchar](10) NULL,
CONSTRAINT [PK_mastertable] PRIMARY KEY CLUSTERED
(
[id] ASC
)
)
GO
CREATE TABLE [dbo].[table01](
[id] [int] NOT NULL,
[CarrierID] [int] NULL,
[Code] [varchar](10) NULL
)
GO
ALTER TABLE [dbo].table01 WITH CHECK ADD CONSTRAINT [FK_table01_MasterCarrier] FOREIGN KEY([CarrierID])
REFERENCES [dbo].[MasterCarrier] ([id])
GO
ALTER TABLE [dbo].table01 CHECK CONSTRAINT [FK_table01_MasterCarrier]
GO
CREATE TABLE [dbo].[table01_staging](
[id] [int] NOT NULL,
[CarrierID] [int] NULL,
[Code] [varchar](10) NULL
)
GO
--Insert sample data
INSERT INTO MasterCarrier (NAME) VALUES ('carrier');
INSERT INTO table01 (id, CarrierID, Code) VALUES (1, 1, 'abc'), (2, NULL, 'abc'),(3, 1, 'ddd');
INSERT INTO table01_staging (id, CarrierID, Code) VALUES (1, 1, 'abc'), (2, NULL, 'abc'),(3, 1, 'ddd');
UPDATE table01
SET CarrierID=t2.CarrierID
FROM table01 AS t1
INNER JOIN table01_staging AS t2 ON t1.ID = t2.ID
WHERE t1.Code='abc'
It gets (2 rows affected) message.

Find many to many supersets in SQL Server

I have three tables: File, Word and WordInstance:
CREATE TABLE [dbo].[File](
[FileId] [int] IDENTITY(1,1) NOT NULL,
[FileName] [nchar](10) NOT NULL)
CREATE TABLE [dbo].[Word](
[WordId] [int] IDENTITY(1,1) NOT NULL,
[Word] [nchar](10) NOT NULL,
[LatestFileId] [int] NOT NULL)
CREATE TABLE [dbo].[WordInstance](
[WordInstanceId] [int] IDENTITY(1,1) NOT NULL,
[WordId] [int] NOT NULL,
[FileId] [int] NOT NULL)
Note that I have omitted the foreign keys to make this concise. Given a FileId I want to return a true/false value which tells me whether there are other files that have the same words as the specified FileId.
Started with this, I know it is not working but provided as is:
CREATE FUNCTION [dbo].[DoesFileContainLastWord]
(
#fileId INT
)
RETURNS BIT
AS
BEGIN
DECLARE #count INT
DECLARE #ret BIT
SELECT #count = COUNT([tW].[WordId])
FROM [Word] AS tW
INNER JOIN [WordInstance] AS tWI
ON [tW].[WordId] = [tWI].[WordId]
INNER JOIN [File] AS tF
ON [tF].[FileId] = [tW].[LatestFileId]
WHERE [tF].[FileId] = #fileId
IF #count > 0
BEGIN
SET #ret = 0
END
ELSE
SET #ret = 1
RETURN #ret
END;
Tested on SQL Server 2005:
declare #file table (fileid int)
declare #instance table (fileid int,wordid int)
insert into #file (fileid)
select 1 union all select 2
insert into #instance (fileid,wordid)
select 1,1
union all select 1,2
union all select 1,3
union all select 2,1
union all select 2,2
declare #fileid int
set #fileid=2
;with fvalues as
(
select distinct wordid from #instance where fileid=#fileid
)
select case when exists
(
select *
from fvalues v
inner join #instance i on v.wordid = i.wordid
and i.fileid<>#fileid
group by i.fileid
having count(distinct i.wordid) >= (select count(*) from fvalues)
)
then cast(1 as bit)
else cast(0 as bit) end
Returns 0 for #fileid=1 and 1 for #fileid=2, as file 1's word set is a proper superset of file 2's.

Resources