Is multi-row uniqueness possible in SQL Server? - sql-server

I have a table where I try to define a covariate gouping like so
ID Rank Covariate
1 1 Age
1 2 Gender
1 3 YearOfBirth
2 1 Gender
The ID captures which covariates belong together in the same group. So covariate group 1 (ID = 1) is composed of age, gender and year of birth, whereas group 2 is Gender only.
Now, inserting a new covariate group consisting of, say gender only, should be illegal as this group already exists, however inserting a new group consisting of Age and Gender should be allowed (it is a subset of group 1 but not an exact match).
Also the rank matters so
ID Rank Covariate
2 Age
1 Gender
3 YearOfBirth
Should not be considered equal to group 1.
Is there a way to enforce this in sql-server?
Ideally the ID column would autoincrement on a legal insert (but thats a different issue).

I don’t know of any means to enforce the Covariant group uniqueness criteria via standard uniqueness constraints or check constraints or any other elegant solution for that matter. However, you can enforce your constraints by only allowing access to the table via a stored procedure or alternatively a view with a “INSTEAD OF INSERT” trigger defined.
Method 1 - Stored Procedure
The following example shows the stored procedure method. First we create a table value type so that we can pass our covariant group as a read-only parameter to our stored procedure.
CREATE TYPE CovariateGroupEntry AS TABLE
(
[Rank] INT NOT NULL
,[Covariate] NVARCHAR(50)
PRIMARY KEY([Rank], [Covariate])
)
Next we create our base table that will contain our covariant groups:
CREATE TABLE CovariateGroups
(
[ID] INT NOT NULL
,[Rank] INT NOT NULL
,[Covariate] NVARCHAR(50)
PRIMARY KEY([ID], [Rank], [Covariate])
)
Next step we create a dummy table that will be used to auto generate our ID:
CREATE TABLE CovariateGroupIDs
(
[GroupID] INT PRIMARY KEY IDENTITY
,[CreatedDateTime] DATETIME NOT NULL
)
Final step we create our procedure:
CREATE PROCEDURE CovariateGroup_Add
(
#covariateGroupEntry dbo.CovariateGroupEntry READONLY
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #groupID INT;
DECLARE #groupSize INT;
DECLARE #groupMatchCount INT;
DECLARE #minRank INT;
DECLARE #maxRankDelta INT;
DECLARE #minRankDelta INT;
-- Get the size of the new group which user will attempt to add.
SELECT #groupSize = COUNT([Rank])
FROM #covariateGroupEntry
-- Validate that the new group rank starts at 1 and increments by 1 step value only.
SELECT #minRank = ISNULL(MIN([Rank]), 0)
,#maxRankDelta = ISNULL(MAX(Delta), 0)
,#minRankDelta = ISNULL(MIN(Delta), 0)
FROM (
SELECT [Rank]
,[Rank] - (LAG([Rank], 1, 0) OVER (ORDER BY [Rank])) AS Delta
FROM #covariateGroupEntry
) RankValidation
IF ( (#minRank > 1) OR (#maxRankDelta > 1) OR (#minRankDelta < 1) )
BEGIN
-- Raise an error if our input data sets rank column does not start at 1 or does not increment by 1 as expected.
RAISERROR (N'Attempting to add covariant group with invalid rank order.', -- Message text.
15, -- Severity,
1 -- State
); -- Second argument.
END
ELSE
BEGIN
-- Generate a new group ID
INSERT INTO [dbo].[CovariateGroupIDs]
(
[CreatedDateTime]
)
SELECT GETDATE() AS [CreatedDateTime]
SET #groupID = SCOPE_IDENTITY();
WITH CTE_GroupsCompareSize
AS
(
-- Compare the size of the new group with all of the existing groups. If the size is different we can
-- safely assume that the group is either a sub set or super set of the compared group. These groups
-- can be excluded from further consideration.
SELECT [CovariateGroups].[ID]
,[CovariateGroups].[Rank]
,[CovariateGroups].[Covariate]
,COUNT([CovariateGroups].[Rank]) OVER (PARTITION BY [CovariateGroups].[ID]) GroupSize
,#groupSize AS NewGroupSize
FROM [CovariateGroups]
)
,CTE_GroupsCompareRank
AS
(
-- For groups of the same size left outer join the new group on the original groups on both rank and covariant entry.
-- If the MIN() OVER window function return a value of 0 then there is at least on entry in the compared groups that does
-- not match and is therefore deemed different.
SELECT [OrginalGroup].[ID]
,[OrginalGroup].[Rank]
,[OrginalGroup].[Covariate]
,MIN(
CASE
WHEN [NewGroup].[Covariate] IS NULL THEN 0
ELSE 1
END
) OVER (PARTITION BY [OrginalGroup].[ID]) AS EntireGroupRankMatch
FROM CTE_GroupsCompareSize [OrginalGroup]
LEFT OUTER JOIN #covariateGroupEntry [NewGroup] ON ([OrginalGroup].[Rank] = [NewGroup].[Rank] AND [OrginalGroup].[Covariate] = [NewGroup].[Covariate])
WHERE GroupSize = NewGroupSize
)
SELECT #groupMatchCount = COUNT(EntireGroupRankMatch)
FROM CTE_GroupsCompareRank
WHERE EntireGroupRankMatch = 1
IF ISNULL(#groupMatchCount, 0) = 0
BEGIN
INSERT INTO [CovariateGroups]
(
[ID]
,[Rank]
,[Covariate]
)
SELECT #groupID AS [ID]
,[Rank]
,[Covariate]
FROM #covariateGroupEntry
END
ELSE
BEGIN
-- Raise an error if our uniqueness constraints are not met.
RAISERROR (N'Uniqueness contain violation, the covariant set is not unique with table "CovariateGroups".', -- Message text.
15, -- Severity,
1 -- State
); -- Second argument.
END
END
END
Method 2 - View with trigger
The second method involves using a views and creating an instead of insert trigger on the view.
First we create the view as follow:
CREATE VIEW CovariateGroupsView
AS
SELECT [ID]
,[Rank]
,[Covariate]
FROM CovariateGroups
Then we create the trigger:
ALTER TRIGGER CovariateGroupsViewInsteadOfInsert on CovariateGroupsView
INSTEAD OF INSERT
AS
BEGIN
DECLARE #groupID INT;
DECLARE #groupSize INT;
DECLARE #groupMatchCount INT;
DECLARE #minRank INT;
DECLARE #maxRankDelta INT;
DECLARE #minRankDelta INT;
-- Get the size of the new group which user will attempt to add.
SELECT #groupSize = COUNT([Rank])
FROM inserted
-- Validate that the new group rank starts at 1 and increments by 1 step value only.
SELECT #minRank = ISNULL(MIN([Rank]), 0)
,#maxRankDelta = ISNULL(MAX(Delta), 0)
,#minRankDelta = ISNULL(MIN(Delta), 0)
FROM (
SELECT [Rank]
,[Rank] - (LAG([Rank], 1, 0) OVER (ORDER BY [Rank])) AS Delta
FROM inserted
) RankValidation
IF ( (#minRank > 1) OR (#maxRankDelta > 1) OR (#minRankDelta < 1) )
BEGIN
RAISERROR (N'Attempting to add covariant group with invalid rank order.', -- Message text.
15, -- Severity,
1 -- State
); -- Second argument.
END
ELSE
BEGIN
-- Generate a new group ID
INSERT INTO [dbo].[CovariateGroupIDs]
(
[CreatedDateTime]
)
SELECT GETDATE() AS [CreatedDateTime]
SET #groupID = SCOPE_IDENTITY();
WITH CTE_GroupsCompareSize
AS
(
-- Compare the size of the new group with all of the existing groups. If the size is different we can
-- safely assume that the group is either a sub set or super set of the compared group. These groups
-- can be excluded from further consideration.
SELECT [CovariateGroups].[ID]
,[CovariateGroups].[Rank]
,[CovariateGroups].[Covariate]
,COUNT([CovariateGroups].[Rank]) OVER (PARTITION BY [CovariateGroups].[ID]) GroupSize
,#groupSize AS NewGroupSize
FROM [CovariateGroups]
)
,CTE_GroupsCompareRank
AS
(
-- For groups of the same size left outer join the new group on the original groups on both rank and covariant entry.
-- If the MIN() OVER window function return a value of 0 then there is at least on entry in the compared groups that does
-- not match and is therefore deemed different.
SELECT [OrginalGroup].[ID]
,[OrginalGroup].[Rank]
,[OrginalGroup].[Covariate]
,MIN(
CASE
WHEN [NewGroup].[Covariate] IS NULL THEN 0
ELSE 1
END
) OVER (PARTITION BY [OrginalGroup].[ID]) AS EntireGroupRankMatch
FROM CTE_GroupsCompareSize [OrginalGroup]
LEFT OUTER JOIN inserted [NewGroup] ON ([OrginalGroup].[Rank] = [NewGroup].[Rank] AND [OrginalGroup].[Covariate] = [NewGroup].[Covariate])
WHERE GroupSize = NewGroupSize
)
SELECT #groupMatchCount = COUNT(EntireGroupRankMatch)
FROM CTE_GroupsCompareRank
WHERE EntireGroupRankMatch = 1
IF ISNULL(#groupMatchCount, 0) = 0
BEGIN
INSERT INTO [CovariateGroups]
(
[ID]
,[Rank]
,[Covariate]
)
SELECT #groupID AS [ID]
,[Rank]
,[Covariate]
FROM inserted
END
ELSE
BEGIN
RAISERROR (N'Uniqueness contain violation, the covariant set is not unique with table "CovariateGroups".', -- Message text.
15, -- Severity,
1 -- State
); -- Second argument.
END
END
END;
The following example show how the stored procedure should be executed:
DECLARE #covariateGroupEntry AS dbo.CovariateGroupEntry
-- INSERT GROUP 1 -------------------
INSERT INTO #covariateGroupEntry
(
[Rank]
,[Covariate]
)
SELECT 1 ,'Age' UNION ALL
SELECT 2 ,'Gender' UNION ALL
SELECT 3 ,'YearOfBirth'
EXEC CovariateGroup_Add #covariateGroupEntry
Following example shows how to insert a group using the view:
DECLARE #covariateGroupEntry AS dbo.CovariateGroupEntry
-- INSERT GROUP 1 -------------------
INSERT INTO #covariateGroupEntry
(
[Rank]
,[Covariate]
)
SELECT 1 ,'Age' UNION ALL
SELECT 2 ,'Gender' UNION ALL
SELECT 3 ,'YearOfBirth'
INSERT INTO [dbo].[CovariateGroupsView]
(
[Rank]
,[Covariate]
)
SELECT [Rank]
,[Covariate]
FROM #covariateGroupEntry
DELETE #covariateGroupEntry -- Delete our memory table if we intend to use it again.
In general I would avoid using the view method since it will be susceptible to more edge cases than the stored procedure and can have some unexpected behaviors. Example the following call:
INSERT INTO [dbo].[CovariateGroupsView]
(
[Rank]
,[Covariate]
)
SELECT 1 ,'Age' UNION ALL
SELECT 2 ,'Gender' UNION ALL
SELECT 3 ,'YearOfBirth'
Will not work as expected since the trigger on the view will treat every row as a separate data set / group. As a result the validation checks will fail.

It's obvious that there is no way to produce an enforceable unique constraint that repeats across multiple rows, because if it repeats then it is not unique.
There are, however, many clever ways to create a simple check that ensures that a grouping of your Covariate values will not be inserted in multiple times.
In terms of simplicity the below SQL will produce two columns: An ID, and the ordered occurance of the covariate values:
CREATE TABLE #tmp_Covariate (ID INT, RANK INT, Covariate VARCHAR(24))
INSERT INTO #tmp_Covariate (ID, RANK, Covariate)
VALUES (1,1,'Age')
,(1,2,'Gender')
,(1,3,'YearOfBirth')
,(2,1,'Gender')
SELECT DISTINCT ID
,STUFF((SELECT N', ' + CAST(C2.[Covariate] AS VARCHAR(256))
FROM #tmp_Covariate C2
WHERE C1.ID = C2.ID
ORDER
BY C2.ID,C2.RANK
FOR XML PATH ('')),1,2,'') AS GroupCovariate
FROM #tmp_Covariate C1
The results of the SELECT are as follows:
ID GroupCovariate
1 Age, Gender, YearOfBirth
2 Gender
If a third group is added to the table, where the covariate values are:
ID Rank Covariate
2 Age
1 Gender
3 YearOfBirth
Then the ordered occurance of the Covariates do not match the GroupCovariate column returned above.
If I were solving this, I'd create a function which accepts a table valued parameter. Feed your inputs that need to be checked against the table into the table exactly as they would appear if committed successfully.
DECLARE #TVP TABLE (Rank INT, Covariate VARCHAR(24))
INSERT INTO #TVP(Rank, Covariate) VALUES (1,'Age'),(2,'Gender'),(3,'YearOfBirth')
SELECT COUNT(CheckTable.GroupCovariate) AS Exist
FROM (SELECT STUFF((SELECT N', ' + CAST(C2.[Covariate] AS VARCHAR(256))
FROM #TVP C2
ORDER
BY C2.RANK
FOR XML PATH ('')),1,2,'') AS GroupCovariate
) AS InputTable
JOIN (SELECT DISTINCT ID
,STUFF((SELECT N', ' + CAST(C2.[Covariate] AS VARCHAR(256))
FROM #tmp_Covariate C2
WHERE C1.ID = C2.ID
ORDER
BY C2.ID,C2.RANK
FOR XML PATH ('')),1,2,'') AS GroupCovariate
FROM #tmp_Covariate C1) AS CheckTable
ON CheckTable.GroupCovariate = InputTable.GroupCovariate
Because the supplied group of covariates already exists in the table, the output will be 1 (can be returned as bool for true, or 0 for false if no group does not exist).
Exist
1
If I supply "FavoriteColor" as part of my covariants:
DECLARE #TVP TABLE (Rank INT, Covariate VARCHAR(24))
INSERT INTO #TVP(Rank, Covariate) VALUES (1,'FavoriteColor'),(2,'Gender'),(3,'YearOfBirth')
SELECT COUNT(CheckTable.GroupCovariate) AS Exist
FROM (SELECT STUFF((SELECT N', ' + CAST(C2.[Covariate] AS VARCHAR(256))
FROM #TVP C2
ORDER
BY C2.RANK
FOR XML PATH ('')),1,2,'') AS GroupCovariate
) AS InputTable
JOIN (SELECT DISTINCT ID
,STUFF((SELECT N', ' + CAST(C2.[Covariate] AS VARCHAR(256))
FROM #tmp_Covariate C2
WHERE C1.ID = C2.ID
ORDER
BY C2.ID,C2.RANK
FOR XML PATH ('')),1,2,'') AS GroupCovariate
FROM #tmp_Covariate C1) AS CheckTable
ON CheckTable.GroupCovariate = InputTable.GroupCovariate
my result is 0:
Exist
0

Related

Having trouble with MS Sql Server OPENJSON feature

I created a sql test script below for this question.
IF OBJECT_ID('tempdb..#temp') IS NOT NULL
DROP TABLE #temp;
CREATE TABLE #temp
(
Id INT NOT NULL PRIMARY KEY
, Attributes NVARCHAR(MAX) NULL
);
INSERT INTO #temp (Id, Attributes)
VALUES (1, '[{"Name":"Step","Value":"A"},{"Name":"State","Value":"Active"}]');
INSERT INTO #temp (Id, Attributes)
VALUES (2, '[{"Name":"Step","Value":"B"},{"Name":"State","Value":"Inactive"}]');
INSERT INTO #temp (Id, Attributes)
VALUES (3, '[{"Name":"State","Value":"Active"}]');
INSERT INTO #temp (Id, Attributes)
VALUES (4, '[{"Name":"Step","Value":"D"}]');
SELECT
t.Id
, t.Attributes
, stepname.Value AS [Step]
, statename.Value AS [State]
FROM #temp t
CROSS APPLY
OPENJSON(t.Attributes)
WITH
(
Name NVARCHAR(MAX) '$.Name'
, Value NVARCHAR(MAX) '$.Value'
) AS stepname
CROSS APPLY
OPENJSON(t.Attributes)
WITH
(
Name NVARCHAR(MAX) '$.Name'
, Value NVARCHAR(MAX) '$.Value'
) AS statename
WHERE 1 = 1
--AND (stepname.Name = statename.Name) -- A
--AND -- B
--( -- B
-- stepname.Name IS NULL -- B
-- OR stepname.Name = 'Step' -- B
--) -- B
--AND -- B
--( -- B
-- statename.Name IS NULL -- B
-- OR statename.Name = 'State' -- B
--); -- B
Running this script as is gives the following output:
Id
Attributes
Step
State
1
[{"Name":"Step","Value":"A"},{"Name":"State","Value":"Active"}]
A
A
1
[{"Name":"Step","Value":"A"},{"Name":"State","Value":"Active"}]
A
Active
1
[{"Name":"Step","Value":"A"},{"Name":"State","Value":"Active"}]
Active
A
1
[{"Name":"Step","Value":"A"},{"Name":"State","Value":"Active"}]
Active
Active
2
[{"Name":"Step","Value":"B"},{"Name":"State","Value":"Inactive"}]
B
B
2
[{"Name":"Step","Value":"B"},{"Name":"State","Value":"Inactive"}]
B
Inactive
2
[{"Name":"Step","Value":"B"},{"Name":"State","Value":"Inactive"}]
Inactive
B
2
[{"Name":"Step","Value":"B"},{"Name":"State","Value":"Inactive"}]
Inactive
Inactive
3
[{"Name":"State","Value":"Active"}]
Active
Active
4
[{"Name":"Step","Value":"D"}]
D
D
What I would like to see in the output though is only 4 rows, one per data row.
Id
Attributes
Step
State
1
[{"Name":"Step","Value":"A"},{"Name":"State","Value":"Active"}]
A
Active
2
[{"Name":"Step","Value":"B"},{"Name":"State","Value":"Inactive"}]
B
Inactive
3
[{"Name":"State","Value":"Active"}]
Active
4
[{"Name":"Step","Value":"D"}]
D
I have left some commented code in my example to see the types of things I've tried but all to no avail. Uncommenting the where statement with a '-- A' gets me closer, but not completely. I thought for sure that uncommenting the statements in the where clause with a '-- B' on the end would give me what I want, but it doesn't. Any ideas on how to do this?
I originally started out with only 1 OPENJSON block but had no success so I thought that perhaps having 2 OPENJSON blocks, one for the Step and one for the State would help, but still not able to get data rows 3 and 4 included since each of those rows is missing one of the 2 JSON values.
Thanks very much for any help!
Conditional aggregation could be used:
SELECT
t.Id
, t.Attributes
, [Step] = MAX(CASE WHEN stepname.Name = 'Step' THEN stepname.Value END)
, [State] = MAX(CASE WHEN statename.Name = 'State' THEN statename.Value END)
FROM #temp t
CROSS APPLY OPENJSON(t.Attributes) WITH (
Name NVARCHAR(MAX) '$.Name'
, Value NVARCHAR(MAX) '$.Value'
) AS stepname
CROSS APPLY OPENJSON(t.Attributes) WITH (
Name NVARCHAR(MAX) '$.Name'
, Value NVARCHAR(MAX) '$.Value'
) AS statename
GROUP BY t.Id, t.Attributes
ORDER BY t.Id;
db<>fiddle demo

How to fiind out the missing records (ID) from an indexed [order] table in sql

I have a table [Order] that has records with sequential ID (in odd number only, i.e. 1,3,5,7...989, 991, 993, 995, 997, 999), it is seen that a few records were accidentally deleted and should be inserted back, first thing is to find out what records are missing in the current table, there are hundreds of records in this table
Don't know how to write the query, can anyone kindly help, please?
I am thinking if I have to write a stored procedure or function but would be better if I can avoid them for environment reasons.
Below peuso code is what I am thinking:
set #MaxValue = Max(numberfield)
set #TestValue = 1
open cursor on recordset ordered by numberfield
foreach numberfield
while (numberfield != #testvalue) and (#testvalue < #MaxValue) then
Insert #testvalue into #temp table
set #testvalue = #textvalue + 2
Next
Next
UPDATE:
Expected result:
Order ID = 7 should be picked up as the only missing record.
Update 2:
If I use
WHERE
o.id IS NULL;
It returns nothing:
Since I didn't get a response from you, in the comments, I've altered the script for you to fill in accordingly:
declare #id int
declare #maxid int
set #id = 1
select #maxid = max([Your ID Column Name]) from [Your Table Name]
declare #IDseq table (id int)
while #id < #maxid --whatever you max is
begin
insert into #IDseq values(#id)
set #id = #id + 1
end
select
s.id
from #IDseq s
left join [Your Table Name] t on s.id = t.[Your ID Column Name]
where t.[Your ID Column Name] is null
Where you see [Your ID Column Name], replace everything with your column name and the same goes for [Your Table Name].
I'm sure this will give you the results you seek.
We can try joining to a number table, which contains all the odd numbers which you might expect to appear in your own table.
DECLARE #start int = 1
DECLARE #end int = 1000
WITH cte AS (
SELECT #start num
UNION ALL
SELECT num + 2 FROM cte WHERE num < #end
)
SELECT num
FROM cte t
LEFT JOIN [Order] o
ON t.num = o.numberfield
WHERE
o.numberfield IS NULL;

TSQL Where clause based on temp table data

I have a straight forward SQL query that I am working with and trying to figure out the best way to approach the where clause.
Essentially, there are two temp tables created and if there is data in the XML string passed to the stored procedure, those tables are populated.
My where clause needs to check these temp tables for data, and if there is no data, it ignores them like they are not there and fetches all data.
-- Create temp tables to hold our XML filter criteria
DECLARE #users AS TABLE (QID VARCHAR(10))
DECLARE #dls AS TABLE (dlName VARCHAR(50))
-- Insert our XML filters
IF #xml.exist('/root/data/users') > 0
BEGIN
INSERT INTO #users( QID )
SELECT ParamValues.x1.value('QID[1]', 'varchar(10)')
FROM #xml.nodes('/root/data/users/user') AS ParamValues(x1)
END
IF #xml.exist('/root/data/dls') > 0
BEGIN
INSERT INTO #dls( dlName )
SELECT ParamValues.x1.value('dlName[1]', 'varchar(50)')
FROM #xml.nodes('/root/data/dld/dl') AS ParamValues(x1)
END
-- Fetch our document details based on the XML provided
SELECT d.documentID ,
d.sopID ,
d.documentName ,
d.folderLocation ,
d.userGroup ,
d.notes
FROM dbo.Documents AS d
LEFT JOIN dbo.DocumentContacts AS dc
ON dc.documentID = d.documentID
LEFT JOIN dbo.DocumentContactsDLs AS dl
ON dl.documentID = d.documentID
-- How can I make these two logic checks work only if there is data, otherwise, include everything.
WHERE dc.QID IN (SELECT QID FROM #users)
AND dl.DL IN (SELECT dlName FROM #dls)
FOR XML PATH ('data'), ELEMENTS, TYPE, ROOT('root');
In the query above, I am trying to used the data in the temp tables only if there is data in them, otherwise, it needs to act like that where statement isn't there for that specific value and include records regardless.
Example: If only #users had data, it would ignore AND dl.DL IN (SELECT dlName FROM #dls) and get everything, regardless of what was in the DL column on those joined records.
Use NOT EXISTS to check the existence of any record in variable table. Here is one way
WHERE ( dc.QID IN (SELECT QID FROM #users)
OR NOT EXISTS (SELECT 1 FROM #users) )
AND ( dl.DL IN (SELECT dlName FROM #dls)
OR NOT EXISTS (SELECT 1 FROM #dls) )
Try this. But please note that I did not get a chance to test it properly and I believe that you want to check the values in #users first and if there is no record existing in that table, then you want to check with the entries in #dls. Also if there are no entries in both of these tables, then you want to skip both the tables.
DECLARE #fl bit = 0
SELECT #fl = CASE WHEN EXISTS (SELECT 1 FROM #users) THEN
1
WHEN EXISTS (SELECT 1 FROM #dls) THEN
2
ELSE
0
END
WHERE ( (dc.QID IN (SELECT QID FROM #users) AND #fl = 1)
OR
(dl.DL IN (SELECT dlName FROM #dls) AND #fl = 2)
OR (1=1 AND #fl = 0)
)

Create a comma separated string with numbers 1 to x, where x is read from the record

I have a table document with a field steps. This is an integer field and can contain a number between 1 and 1000.
Now a new field is added (followedsteps) which must contain the numbers from 1 to [the number from field steps], comma separated.
So when the field steps contains the number 5, I want this string 1,2,3,4,5 to be set in the new column followedsteps.
The field steps is not null-able, lowest value is 1.
Is there an (easy) way to do this?
It's a one time migration.
As you are going to perform this only one time, it will be better to generate first the sequences:
IF OBJECT_ID('tempdb..#DataSource') IS NOT NULL
BEGIN;
DROP TABLE #DataSource;
END;
CREATE TABLE #DataSource
(
[ID] INT
,[Sequence] VARCHAR(MAX)
);
DECLARE #MaximumID INT = 1000; -- in your case: SELECT MAX(steps) FROM document
WITH DataSource AS
(
SELECT 1 AS num
UNION ALL
SELECT num+1
FROM DataSource
WHERE num+1<=#MaximumID
)
INSERT INTO #DataSource
SELECT A.[num]
,DS.[Sequence]
FROM DataSource A
CROSS APPLY
(
SELECT STUFF
(
(
SELECT ',' + CAST(B.[num] AS VARCHAR(12))
FROM DataSource B
WHERE A.[num] >= B.[num]
ORDER BY B.[num]
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)')
,1
,1
,''
)
) DS ([Sequence])
option (maxrecursion 32767)
The code above creates a temporary table with data you need to perform the update:
Then in transaction, perform the update by [ID]:
BEGIN TRAN
UPDATE document
SET followedsteps = [Sequence]
FROM document A
INNER JOIN ##DataSource B
ON A.[steps] = b.[id]
COMMIT TRAN

How to UPDATE TOP(n) with ORDER BY giving a predictable result?

I'm trying to read the top 100 items of a database table that is being used like a queue. As I do this I'm trying to mark the items as done like this:
UPDATE TOP(#qty)
QueueTable WITH (READPAST)
SET
IsDone = 1
OUTPUT
inserted.Id,
inserted.Etc
FROM
QueueTable
WHERE
IsDone = 0
ORDER BY
CreatedDate ASC;
The only problem is, according to UPDATE (Transact-SQL) on MSDN, the ORDER BY is not valid in an UPDATE and:
The rows referenced in the TOP expression used with INSERT, UPDATE, or
DELETE are not arranged in any order.
How can I achieve what I need which is to update the items at the top of the queue while also selecting them?
SQL Server allows you to update a derived table, CTE or view:
UPDATE x
SET
IsDone = 1
OUTPUT
inserted.Id,
inserted.Etc
FROM (
select TOP (N) *
FROM
QueueTable
WHERE
IsDone = 0
ORDER BY
CreatedDate ASC;
) x
No need to compute a set of IDs first. This is faster and usually has more desirable locking behavior.
Tested in SSMS, it works fine. You may need to do some modification accordingly.
--create table structure
create table #temp1 (
id int identity(1,1),
value int
)
go
--insert sample data
insert #temp1 values (1)
go 20
--below is solution
declare #qty int = 10
declare #cmd nvarchar(2000) =
N'update #temp1
set value= 100
output inserted.value
where id in
(
select top '+ cast(#qty as nvarchar(5)) +' id from #temp1
order by id
)';
execute sp_executesql #cmd
You can use ranking function (for example row_number).
update top (100) q
set IsDone = 1
output
inserted.Id,
inserted.Etc
from (
select *, row_number() over(order by CreatedDate asc, (select 0)) rn
from QueueTable) q
where rn <= 100

Resources