SQL Prevent multiple inserts at the same time for non-duplicates - sql

I am trying to prevent multiple inserts at the exact same time so I can prevent duplicate inserts. I have two tables:
Table B, this table has 4 columns id, timeToken, tokenOrder and taken.
Table A which I will be inserting into and that has id, createDate and timeToken.
What I am trying to do is prevent the timeToken in Table A not to have duplicate values in the case where multiple inserts are happening at the exact same time. I have the following code:
DECLARE #ReturnValue nvarchar
SELECT Top 1 #ReturnValue=timeToken FROM TableB WHERE taken = 0 Order By tokenOrder
Update TableB SET taken = 1 WHERE timeToken = #ReturnValue
INSERT INTO TableA Values(#ReturnValue, GETDATE())
Now that I think about it, is it possible to have my timeToken table in TableA auto increment with the timeToken from TableB?
Table B sample data:
id timeToken tokenOrder taken
1 1:00am 1 0
2 2:00am 2 0
3 3:00am 3 1
4 4:00am 4 0
5 5:00am 5 0
This is what I am expecting Table A to look like after 4 calls all at the exact same time that would cause duplicates (id starting at 5 - this could be because I have deleted old records).
Table A sample data:
id createDate timeToken
5 2014-11-22 12:45:34.243 1:00am
6 2014-11-22 12:45:34.243 2:00am
7 2014-11-22 12:45:34.243 4:00am
8 2014-11-22 12:45:34.243 5:00am

Try to rewrite like this, this should ensure that you do not get the row with taken=0 in TableB updated twice.
BEGIN TRANSACTION
DECLARE #taken table(
id int NOT NULL,
timeToken nvarchar(max));
Update TOP (1) TableB
SET taken = 1
OUTPUT UPDATED.id, UPDATED.timeToken
INTO #taken
WHERE timeToken =
(SELECT Top 1 timeToken FROM TableB WHERE taken = 0 Order By tokenOrder)
INSERT INTO TableA
SELECT id, GETDATE(), timeToken
FROM #taken
COMMIT TRANSACTION
See SQL Server isolation levels - read commited. READ COMMITTED is the default isolation level for the Microsoft SQL Server Database Engine.
In the example I copy id from TableB to TableA, but it is not probably required.

I think you can solve this problem in two steps:
Step1: Buffer all requests as soon as they arrive.
Step2: Periodically assign free tokens to the buffered requests.
Preparation
A sequence object will help resolve any order ambiguity:
CREATE SEQUENCE dbo.Taken_Seq
START WITH 1
INCREMENT BY 1 ;
GO
An auxiliary table will play the role of the buffer:
CREATE TABLE buffer (
requester uniqueidentifier, createdate datetime, seq_value bigint, id int);
I will also use a GUID to refer to the different processes asking for a token (requesters):
ALTER TABLE TableA add Requester uniqueidentifier;
Solution outline
As soon as a request comes (identified by a GUID) buffer it consuming the next sequence value, like this (here I use newid() to get a GUID, your application should have already assigned one to your request):
declare #seq bigint;
SELECT #seq = NEXT VALUE FOR dbo.Taken_Seq;
insert buffer values (newid(), getdate(), #seq, null);
Suppose now that three such requests arrive simultaneously, as in:
declare #seq bigint;
SELECT #seq = NEXT VALUE FOR dbo.Taken_Seq;
insert buffer values (newid(), getdate(), #seq, null);
SELECT #seq = NEXT VALUE FOR dbo.Taken_Seq;
insert buffer values (newid(), getdate(), #seq, null);
SELECT #seq = NEXT VALUE FOR dbo.Taken_Seq;
insert buffer values (newid(), getdate(), #seq, null);
The contents of the buffer table will then look like this:
requester createdate seq_value id
------------------------------------ ----------------------- -------------------- -----------
109B560C-155C-40BD-A13A-59D21EBEB1F8 2017-04-05 11:17:35.127 31 NULL
FAC00C2E-14AA-4502-AB5C-DDD756914653 2017-04-05 11:17:35.127 32 NULL
E95889C3-E291-4A1C-A7E8-0B8CC53D4D7B 2017-04-05 11:17:35.127 33 NULL
Next we can match each buffered request to a token. This will be done by assinging an id value to each request in our buffered table:
; with a as
(select rn =row_number() over (order by seq_value), *
from buffer
where id is null),
b as
(
select rn=row_number() over (order by tokenOrder), *
from TableB
where taken = 0
)
update buffer set buffer.id = b.id
from buffer
join a on buffer.requester = a.requester
join b on a.rn = b.rn
This is now how our buffer table looks like:
requester createdate seq_value id
------------------------------------ ----------------------- -------------------- -----------
109B560C-155C-40BD-A13A-59D21EBEB1F8 2017-04-05 11:17:35.127 31 1
FAC00C2E-14AA-4502-AB5C-DDD756914653 2017-04-05 11:17:35.127 32 2
E95889C3-E291-4A1C-A7E8-0B8CC53D4D7B 2017-04-05 11:17:35.127 33 3
Join the buffer table with TableB to find the tokens:
select buffer.requester, tableB.* from buffer join tableB on buffer.id= tableB.id
Mark the tokens as taken:
update TableB set taken = 1 from buffer where buffer.id = TableB.id
Finally, insert into TableA:
insert TableA (requester, createdate, timeToken)
select buffer.requester, buffer.createdate, TableB.timeToken
from buffer join TableB on buffer.id = TableB.id
Note:
Obviously some of these steps must be contained within a single transaction

Related

ignoring last value if its the same as current value

I have an table where i would like to query the following:
The data comes in batches . This data is combined with an id.
This ID only gets send ones when the new batch comes in. After that the ID only changes when there is a new batch . In the mean time the value stays null
What i need to do is if new data comes in and it has the same id as the previous batch i have to continue the insert with null in the id field instead of pushing a new row with the same id value.
Beneath is a simplistic view of the table
ID Values
1 10
null 20
null 20
null 20
null 20
2 20
null 20
null 20
null 20
null 20
1 20
null 20
If you could help me point in a directions that would help me a lot.
Maybe to clearify the id value is a set of tags. So there are some definied tags(100 or more) and when a new batch comes the batch gets a tag with it. And if that tag is the same as the previous the null has to continue instead of inserting the same tag
You'll need to add an identity field (or a timestamp) in order to be able to query the latest ID.
ALTER TABLE MyTable ADD MyIdent INT IDENTITY(1, 1) NOT NULL
Then on your insert (if your Id value is NULL) you can call
INSERT INTO MyTable (Id, Values)
SELECT TOP 1 Id, #ValuesVariable
FROM MyTable
WHERE Id IS NOT NULL
ORDER BY MyIdent DESC
This below Sp may helps to inert data try this
IF OBJECT_ID('tempdb..#Temp')IS NOT NULL
DROP TABLE #Temp
CREATE TABLE #Temp (ID INT,[Values] INT)
CREATE PROCEDURE usp_Insert
(
#Id INT,
#Values INT
)
AS
BEGIN
IF NOT EXISTS (SELECT 1 FROM #Temp WHERE ID = #ID)
BEGIN
INSERT INTO #Temp(ID,[Values])
SELECT #Id,#Values
END
ELSE
INSERT INTO #Temp(ID,[Values])
SELECT NULL,#Values
END
EXEC usp_Insert 2,12
SELECT * FROM #Temp

How do flag the unselected records in a top selection

I have a requirement I need to choose only a selected number of records from a group. However, I also need to flag the not choosen records in the even that they will need to be referred to at a later date.
I have over 80K records in Segment 1. The requirement is to select 50000 records
I've tried this:
UPDATE mytable
SET [SuppressionReason] = 'REC LIMIT REACHED - S1'
WHERE
[ID] NOT IN
(
SELECT TOP 50000 [ID] FROM mytable
WHERE segment = '1'
);
However, this results in 0 records getting labeled in the SuppressionReason field as 'REC LIMIT REACHED - S1'. What am I missing or doing wrong?
Based on testing with the following code, are you absolutely certain that you have more than 50,000 records?
DROP TABLE IF EXISTS #TEMP
CREATE TABLE #TEMP
(
ID INT IDENTITY(1,1),
FIRSTNAME VARCHAR(10),
LASTNAME VARCHAR(10),
SEGMENT INT,
SUPPRESSION VARCHAR(10)
)
INSERT INTO #TEMP
(FIRSTNAME, LASTNAME, SEGMENT)
VALUES
('JOHN', 'KRAMER',1),
('MATT','GEORGE',1),
('PHILIP','MCCAIN',1),
('ANDREW','THOMAS',1)
UPDATE #TEMP
SET SUPPRESSION = 'YEP'
WHERE ID NOT IN
(SELECT TOP(2) ID FROM #TEMP WHERE SEGMENT = 1)
SELECT * FROM #TEMP
This produces the following output, which I suspect is exactly what you are expecting to get.
1 JOHN KRAMER 1 NULL
2 MATT GEORGE 1 NULL
3 PHILIP MCCAIN 1 YEP
4 ANDREW THOMAS 1 YEP

Know identity before insert

I want to copy rows from the table within the table itself. But before inserting I need to modify a varchar column appending the value of identity column to it.
My table structure is:
secID docID secName secType secBor
1 5 sec-1 G 9
2 5 sec-2 H 12
3 5 sec-3 G 12
4 7 sec-4 G 12
5 7 sec-5 H 9
If I want to copy data of say docID 5, currently this runs through a loop one row at a time.
I can write my query as
insert into tableA (docID, secName, secType, secBor)
select 8, secName, secType, secBor from tableA where docID = 5
But how can I set value of secName before hand so that it becomes sec-<value of secID column>?
Don't try to guess the value of identity column. In your case you could simply create a computed column secName AS CONCAT('sec-', secID). There is no further need to update that column.
DB Fiddle
It is also possible to create an AFTER INSERT trigger to update the column.
Since SQL Server does not have GENERATED ALWAYS AS ('Sec - ' + id) the only simple option I see is to use a trigger.
Adding to my comment something like:
insert into tableA (docID, secName, secType, secBor)
select
ROW_NUMBER() OVER (ORDER BY DocID),
'Sec -'+ ROW_NUMBER() OVER (ORDER BY DocID),
secType, secBor
from tableA
where docID = 5
In SQL Server 2012 and later, you can achieve this by using the new sequence object.
CREATE SEQUENCE TableAIdentitySeqeunce
START WITH 1
INCREMENT BY 1 ;
GO
create table TableA
(
secId int default (NEXT VALUE FOR TableAIdentitySeqeunce) not null primary key,
varcharCol nvarchar(50)
)
declare #nextId int;
select #nextId = NEXT VALUE FOR TableAIdentitySeqeunce
insert TableA (secId, varcharCol)
values (#nextId, N'Data #' + cast(#nextId as nvarchar(50)))

Inserting data into multiple tables at same time

This is what I am trying to do:
Let's say I have two tables dbo.source & dbo.destination
I want to copy all records from source to destination IF a certain natural key (unique non clustered) does not exist in the destination. If the insert is successful, then output some values to a temporary buffer table.
Next I want to list all the records from the source which DID have a match in the destination, and copy these as well to the buffer table.
Is there anyway I can achieve this so that the buffer table does not hold redundant data ?
This is my current logic:
Step1: Get records from the source table where the natural key does not match the destination and insert into destination
Insert these into buffer table with flag
MERGE INTO dbo.Destination dest USING dbo.Source AS src
ON dest.Name = src.Name --Natural Key
WHEN NOT MATCHED THEN
INSERT (xxx) VALUES (xxx)
OUTPUT src.ID, Inserted.ID, 'flagA'
INTO dbo.Buffer;
Step2:
Get records from the source table where the natural key matched the destination
Insert these into buffer with a flag
Insert INTO dbo.Buffer
Select src.ID, src.Name, 'flagB'
FROM dbo.Source src
inner join dbo.Destination dest
on src.Name = dest.Name
With this logic, I am getting redundant rows into my buffer, which do not exactly track the inserts as intended. Can anyone critique my sql based on what I am trying to do.
You can try it some like it, the dislike of this technique is that you always update one field. Maybe, you need to adapt my example to your needs.
DECLARE #Source table (id int identity , myValue varchar(5))
DECLARE #Destination table (id int identity , myValue varchar(5))
DECLARE #Buffer table (sourceId int , InsertId varchar(5),flag varchar(5))
insert #Source (myValue) values ( 'a') ,( 'e'),( 'i'),( 'o'),( 'u')
insert #Destination (myValue) values ('a') ,('b'),('c')
;merge #Destination t
using #Source S
on
t.myValue = s.myValue
when not matched then insert (myValue) values (s.myValue)
when matched then update set myValue = t.myValue
output s.id, inserted.id, case $action when 'INSERT' then 'flagA' else 'flagB' end into #Buffer;
select * from #Destination
select * from #Buffer
Result
Destination table
id myValue
----------- -------
1 a
2 b
3 c
4 e
5 i
6 o
7 u
Buffer table
sourceId InsertId flag
----------- -------- -----
2 4 flagA
3 5 flagA
4 6 flagA
5 7 flagA
1 1 flagB
use this output
output s.id, case $action when 'INSERT' then Cast(inserted.id as varchar(5)) else inserted.myValue end , case $action when 'INSERT' then 'flagA' else 'flagB' end into #Buffer;
for
sourceId InsertId flag
----------- -------- -----
2 4 flagA
3 5 flagA
4 6 flagA
5 7 flagA
1 a flagB
When you run your second query, it also matches the just inserted rows. You should do something like this:
Insert INTO dbo.Buffer
Select src.ID, src.name, 'flagB'
FROM dbo.Source src
inner join dbo.Destination dest on src.Name = dest.Name
where not exists (
select * from dbo.Buffer b where b.xxx = 'flagA' and b.yyy = src.name
)
Or just use when matched by target, as LONG suggested.

SQL trying to update record with unique value from declared table

I am having a major problem, basically I have two tables, TableA and TableB
TableA I will be inserting into then updating
TableB is a table with dateTokens, timeTokens, tokenOrder and taken
I have declared a table called #taken which copies TableB and puts its values in it.
What I am trying to do is the following:
Declare #taken Table
Insert a record into TableA
Get the ID from the record that was just inserted into TableA via SCOPE_IDENTITY()
Insert the data from TableB into #taken where taken from TableB is 0 (some records in TableB taken will be marked as 1, so I only want the records that are not taken) Also in this step, I redo the tokenOrder via Row_Number() Over (Order By tokenOrder) as tokenOrder.
Select from the #taken table where the new tokenOrder is equal to the ID from the new inserted row in TableA
Update TableA with timeToken and dateToken Where id is equal to the ID from the new inserted row in TableA
Update TableB timeToken and dateToken as taken where tokenOrder is equal to the ID from the new inserted row in TableA
My problem is that this code is in a stored procedure and I put the call to the stored procedure in a loop, looping through 200 times and when I run it, I get the exact number of rows I am expecting, however the results are not right.
My Table B Data looks like this
dateToken timeToken tokenOrder taken
Monday 1:00pm 1 0
Monday 1:10pm 2 0
Monday 1:20pm 3 0
and so on all the way till midnight increasing by 10 minutes. (there is like a 3 hour break somewhere in there)
the results I get when I run my stored procedure are
dateToken timeToken
Monday 1:10pm
Monday 1:30pm
Monday 1:50pm
Monday 2:10pm
Monday 2:30pm
So it appears to be skipping every other timeToken and I have no idea why
Here is my code:
#dateToken nvarchar(MAX) OUTPUT,
#timeToken nvarchar(MAX) OUTPUT,
Declare #TableA_PK BIGINT
DECLARE #taken table(
id int NOT NULL,
dateToken nvarchar(max),
timeToken nvarchar(max),
tokenOrder int,
taken bit);
INSERT INTO TableA (dateToken, timeToken) VALUES (‘’, ‘’)
SET #TableA_PK=SCOPE_IDENTITY()
INSERT INTO #taken SELECT id, dateToken, timeToken, Row_Number() Over (Order By tokenOrder) As tokenOrder, taken FROM TableB WHERE taken = 0
SELECT #dateToken = dateToken, #timeToken = timeToken FROM #taken WHERE tokenOrder = #TableA_PK
UPDATE TableA SET dateToken = #dateToken, timeToken = #timeToken WHERE id = #TableA_PK
UPDATE TableB SET taken = 1 WHERE tokenOrder = #TableA_PK
Any help would be much appreciated. PLEASE HELP, i've been struggling with this for daysssss
One other thing, when I goto look at the data in TableB after I run this code, all the rows are marked as taken, which is expected.
After inserting "TableA" identity value of it (#TableA_PK=SCOPE_IDENTITY()) increases by one.
But you are calculating tokenOrder value with "Row_Number() Over (Order By tokenOrder)" and only for "taken=0" rows.
Every time this starts from "1".
That's why this code skips previously rows every run.
For example;
In the first turn, you are getting the correct result.
But in the second turn, you only take "token = 2" rows and skip "token = 1".