loop through rows and INSERT INTO SELECT from the same table - sql-server

I'm trying to create a stored procedure in SQL-Server2012, that will update some rows, then re-insert them again into the same table but with NULLs or different calculated values.
my table 'schedule':
CREATE TABLE [cil].[schedule](
[id] [int] IDENTITY(1,1) NOT NULL,
[taskFK] [int] NULL,
[scheduledDate] [smalldatetime] NULL,
[completionDate] [smalldatetime] NULL,
[rotaCycle] [smallint] NULL,
[result] [smallint] NULL,
CONSTRAINT [PK_schedule] PRIMARY KEY CLUSTERED
my table 'task':
CREATE TABLE [cil].[task](
[id] [int] IDENTITY(1,1) NOT NULL,
[standard] [varchar](250) NULL,
[equipFK] [int] NULL,
[areaFK] [smallint] NULL,
CONSTRAINT [PK_task] PRIMARY KEY CLUSTERED
existing stored procedure:
CREATE PROCEDURE [cil].[updateCompletionDate]
#equipID int,
#myCompletionDate datetime
AS
UPDATE cil.schedule SET completionDate=#myCompletionDate
WHERE schedule.id IN (
SELECT schedule.id
FROM cil.schedule
LEFT JOIN cil.task
ON cil.schedule.taskFK=cil.task.id
WHERE CAST(scheduledDate AS DATE)<=CAST(GetDate() AS DATE)
AND completionDate IS NULL
AND result IS NOT NULL
AND equipFK=#equipID);
GO
and now I want to add the RE-INSERT to the above SP:
INSERT INTO cil.schedule (taskFK, scheduledDate, rotaCycle)
SELECT taskFK, scheduledDate = DATEADD(dd, (SELECT rotaCycle FROM
cil.schedule WHERE id=??),scheduledDate), rotaCycle
FROM cil.schedule LEFT JOIN cil.task ON task.id=schedule.taskFK
WHERE completionDate=#myCompletionDate
AND task.equipFK=#equipID;
Dont know how to loop through all ID from 1st part of the SP?

you can do this by using output clause and delete table. i have created below pseudo code to give you some idea how this will work -
CREATE PROCEDURE [cil].[updateCompletionDate]
#equipID int,
#myCompletionDate datetime
AS
DECLARE #MyTable table(
id int ,
taskFK int
.... ); -- declare all required columns
UPDATE cil.schedule SET completionDate=#myCompletionDate
OUTPUT deleted.id,
deleted.taskFK -- list of required columns you want
INTO #MyTable
WHERE schedule.id IN (
SELECT schedule.id
FROM cil.schedule
LEFT JOIN cil.task
ON cil.schedule.taskFK=cil.task.id
WHERE CAST(scheduledDate AS DATE)<=CAST(GetDate() AS DATE)
AND completionDate IS NULL
AND result IS NOT NULL
AND equipFK=#equipID)
INSERT INTO cil.schedule (taskFK, scheduledDate, rotaCycle)
select [required coluns]
from #MyTableVa join cil.schedule -- all requied join conditions

You can use SQL Output clause to update your table and insert effected rows into the same table
Please check following simple sample script
/*
create table OutputClauseSample (
Id int identity(1,1),
Code varchar(10),
Val int
)
insert into OutputClauseSample select 'A',10
insert into OutputClauseSample select 'B',20
insert into OutputClauseSample select 'C',30
*/
update OutputClauseSample
set Val = 2 * Val
output (deleted.Code)
into OutputClauseSample (Code)
where id = 3
select * from OutputClauseSample

here is my procedure, cursor loops through and re-inserts modified correctly data:
CREATE PROCEDURE [cil].[addComplDateAnd_ReSchedule]
#equipID int,
#myCompletionDate datetime2(6)
AS
/* add completion date */
UPDATE cil.schedule SET completionDate=#myCompletionDate
WHERE schedule.id IN (
SELECT schedule.id
FROM cil.schedule
LEFT JOIN cil.task ON cil.schedule.taskFK=cil.task.id
WHERE CAST(scheduledDate AS DATE)<=CAST(GetDate() AS DATE)
AND completionDate IS NULL
AND result IS NOT NULL
AND equipFK=#equipID);
/* reschedule tasks */
DECLARE #nextTaskID AS INT;
DECLARE #nextScheduledDate AS DATETIME2(6);
DECLARE #nextRotaCycle AS INT;
DECLARE db_cursor CURSOR FOR
SELECT taskFK, scheduledDate, rotaCycle
FROM cil.schedule
LEFT JOIN cil.task ON cil.schedule.taskFK=cil.task.id
WHERE completionDate=#myCompletionDate
AND equipFK=#equipID;
OPEN db_cursor;
FETCH NEXT FROM db_cursor INTO #nextTaskID, #nextScheduledDate, #nextRotaCycle;
WHILE ##FETCH_STATUS = 0
BEGIN
--Do stuff with scalar values
INSERT INTO cil.schedule (taskFK, scheduledDate, rotaCycle)
VALUES(#nextTaskID
,DATEADD(dd, #nextRotaCycle, #nextScheduledDate)
,#nextRotaCycle)
FETCH NEXT FROM db_cursor INTO #nextTaskID, #nextScheduledDate,
#nextRotaCycle;
END;
CLOSE db_cursor;
DEALLOCATE db_cursor;
GO

Related

Update table with stored procedure without null

I tried to implement procedure which main goal is to store data into new separate table.First I create table which I want to populate with data
CREATE TABLE dbo.CustomerReportLogs (
ID int IDENTITY (1,1) NOT NULL,
CustomerFullName NVARCHAR(100) NULL,
LocationName NVARCHAR(100) NULL,
Amount decimal (18,2) NULL,
Currency NVARCHAR (20) NULL,
EmployeeId int NULL,
ValidFrom date NULL,
ValidTo date NULL,
CONSTRAINT PK_ID_CustomerReportLogs PRIMARY KEY CLUSTERED (
ID ASC
))
GO
So next step is how to populate this table with stored procedure. I order to do this I wrote this lines of code:
CREATE OR ALTER PROCEDURE dbo.procedure2 (#CustomerId int, #validFrom date, #ValidTo date,#EmployeeID int)
AS
BEGIN
SELECT CONCAT(c.FirstName, ' ', c.LastName) AS CustomerFullName, lo.Name as LocationName,acd.Amount as Amount,cu.Name as Currency,acc.EmployeeId,#validFrom as ValidFrom,#ValidTo as ValidTo
FROM dbo.Customer as c
INNER JOIN dbo.Account AS acc ON acc.CurrencyId=c.Id
INNER JOIN dbo.AccountDetails AS acd ON acd.AccountId=acc.Id
INNER JOIN dbo.Currency AS cu ON cu.id=acc.CurrencyId
INNER JOIN dbo.Location as lo ON lo.Id=acd.LocationId
INNER JOIN dbo.Employee AS emp ON emp.ID=acc.EmployeeId
WHERE acc.CustomerId=#CustomerId and acd.TransactionDate between #validFrom and #ValidTo and acc.EmployeeId=#EmployeeID
DECLARE #CustomerFullName NVARCHAR(100)
DECLARE #LocationName NVARCHAR(100)
DECLARE #Amount decimal (18,2)
DECLARE #Currency NVARCHAR (20)
INSERT INTO dbo.CustomerReportLogs(CustomerFullName,LocationName,Amount,Currency,EmployeeId,ValidFrom,ValidTo)
VALUES (#CustomerFullName,#LocationName,#Amount,#Currency,#EmployeeId,#ValidFrom,#ValidTo)
SELECT #CustomerFullName as CustomerFullName,#LocationName AS LocationName,#Amount AS Amount,#Currency as Currency,
#EmployeeId as EmployeeId,#ValidFrom as ValidFrom ,#ValidTo as ValidTo
END
GO
Above line code create stored procudure but now new problem arise namely when I try to execute first whit this command
EXEC dbo.procedure2 #CustomerId=8,#validFrom='2019.01.25', #ValidTo='2019.03.01', #EmployeeID=8
So this procedure can't update properly dbo.CustomerReportLogs so I got some results with null and some with values.Output you can see on pic below:
[![enter image description here][1]][1]
So can anybody help me how to update this table properly not with null
CREATE OR ALTER PROCEDURE dbo.procedure2 (
#CustomerId INT
,#validFrom DATE
,#ValidTo DATE
,#EmployeeID INT
)
AS
BEGIN
INSERT INTO dbo.CustomerReportLogs (
CustomerFullName
,LocationName
,Amount
,Currency
,EmployeeId
,ValidFrom
,ValidTo
)
OUTPUT INSERTED.[CustomerFullName],INSERTED.[LocationName],INSERTED.[Amount],INSERTED.[Currency],INSERTED.[EmployeeId],INSERTED.[ValidFrom] ,INSERTED.[ValidTo]
SELECT CONCAT (c.FirstName,' ',c.LastName) AS CustomerFullName
,lo.Name AS LocationName
,acd.Amount AS Amount
,cu.Name AS Currency
,acc.EmployeeId
,#validFrom AS ValidFrom
,#ValidTo AS ValidTo
FROM dbo.Customer AS c
INNER JOIN dbo.Account AS acc ON acc.CurrencyId = c.Id
INNER JOIN dbo.AccountDetails AS acd ON acd.AccountId = acc.Id
INNER JOIN dbo.Currency AS cu ON cu.id = acc.CurrencyId
INNER JOIN dbo.Location AS lo ON lo.Id = acd.LocationId
INNER JOIN dbo.Employee AS emp ON emp.ID = acc.EmployeeId
WHERE acc.CustomerId = #CustomerId
AND acd.TransactionDate BETWEEN #validFrom
AND #ValidTo
AND acc.EmployeeId = #EmployeeID
END

Are these SQL Server procedures concurrency safe?

I have the following table which contains units of work:
create table [dbo].[Queue]
(
[QueueId] bigint not null identity(1,1),
[UserId] bigint default null, -- irrelevant foreign key here
primary key clustered ([QueueId] asc)
);
go
A pack of workers keeps circling this table and grabs one or more units of work by changing the UserId field from null to a positive value. No two workers should be able to update the same QueueId at the same time and they should not wait (readpast should help with that).
The following method makes it easy to add work to the table:
/**
* Push some work units.
* (rewritten from basic while insert to #Larnu's Tally suggestion)
*/
create procedure [dbo].[spPushWork]
#Count int
as
begin
if #Count < 1 or #Count > 1000000 throw 50001, N'#Count must be 1-1M.', 1;
with [num] as
(
select [num] from (values (null),(null),(null),(null),(null),(null),(null),(null),(null),(null)) [num]([num])
), [tally] as
(
select top (#Count)
row_number() over (order by (select null)) as [ind]
from [num] [num1]
cross join [num] [num2]
cross join [num] [num3]
cross join [num] [num4]
cross join [num] [num5]
cross join [num] [num6]
)
merge into [dbo].[queue]
using (select [ind] from [tally]) [t]
on 1 = 0
when not matched then insert default values;
end
go
And now we have 2 methods to grab work.
Method #1 is thread safe (I hope) as it's a select-update combo:
/**
* This grabs work units in a single operation (Select + Update).
*/
create procedure [dbo].[spGrabSafe]
#UserId bigint
,#Count int = 1
as
begin
if #UserId < 1 throw 50001, N'#UserId must be 1+.', 1;
if #Count < 1 throw 50001, N'#Count must be 1+.', 2;
declare #Ids table ([Id] bigint not null);
-- fetch and claim via single query
with [cte] as
(
select top(#Count) [QueueId]
from [dbo].[Queue] with (readpast) -- skip locked
where [UserId] is null
order by [QueueId] asc
)
update [q]
set [UserId] = #UserId
output [inserted].[QueueId] into #Ids
from [dbo].[Queue] [q]
join [cte] on [cte].[QueueId] = [q].[QueueId];
select [Id] from #Ids;
end;
go
Method #2 does the same thing as method one in 2 operations by first locking the rows and then claiming them by changing the UserId. It also has a delay argument that allows us to make it run longer for testing:
/**
* This grabs work units in multiple operations (Select&lock + Update).
*/
create procedure [dbo].[spGrabUnsafe]
#UserId bigint
,#Count int = 1
,#Delay time = null
as
begin
if #UserId < 1 throw 50001, N'#UserId must be 1+.', 1;
if #Count < 1 throw 50001, N'#Count must be 1+.', 1;
declare #Ids table ([Id] bigint not null);
begin transaction
-- fetch the QueueId's
insert into #Ids
select top(#Count) [QueueId]
from [dbo].[Queue]
with (xlock, rowlock, readpast) -- lock rows + skip locked
where [UserId] is null
order by [QueueId] asc;
-- claim via UserId
update [q]
set [UserId] = #UserId
from [dbo].[Queue] [q]
join #Ids [ids] on [ids].[Id] = [q].[QueueId];
-- this allows to wait a bit to test concurrency
if #Delay is not null
begin
declare #Time varchar(12) = convert(varchar, #Delay, 114);
waitfor delay #Time;
end;
commit transaction;
select [Id] from #Ids;
end
go
Is method #2 safe in a concurrent environment? There is a gap between selecting and updating UserId. But the rows should be locked...

Only one row user defined table can be passed to stored procedure

SELECT
#MaxSeq = (CASE
WHEN (SELECT COUNT(*) FROM app_Attachments
WHERE app_Attachments.AppId = #appID) <= 0
THEN 1
ELSE (SELECT (MAX(seq)+1)
FROM app_Attachments
WHERE app_Attachments.AppId = #appID)
END)
DECLARE #Id INT;
SELECT #Id = (COALESCE((SELECT MAX(Id)+1 FROM app_Attachments), 1))
INSERT INTO app_Attachments (Id, AppID, AttName, AttContentType, AttData, AddedBy, AddedDate, Seq)
SELECT
#Id, AppId, ImgNames, ImgType, Bytes, #AddedBy, #AddedDate, #MaxSeq
FROM
#Attachments --user defined table
CREATE TYPE [dbo].[AppsAttachments] AS TABLE
(
[AppId] [INT] NULL,
[Bytes] [VARBINARY](MAX) NULL,
[ImgNames] [NVARCHAR](MAX) NULL,
[ImgType] [NVARCHAR](200) NULL
)
Insert into table using stored procedure that has user defined table as parameter only success if table passed has one row, it was working well when the primary key is set as identity, after I changed the primary key into manual integer to be entered the problem occurred
if Id is not identity, then you need to ensure the Id is not duplicates. Use Row_number() to generate a running sequence of number
insert into app_Attachments (Id,AppID,AttName,AttContentType,AttData,AddedBy,AddedDate,Seq)
select #Id + ROW_NUMBER() OVER (ORDER BY AppId) - 1,
AppId,
ImgNames,
ImgType,
Bytes,
#AddedBy,
#AddedDate,
#MaxSeq
from #Attachments --user defined table
if you also need to increase the Seq as well, add a row_number() to the query

How to get rid of timeout error when using mssql hierarchy check constrint

i am creating sql hirercky table
Here is my code;
The Constraint Function Code
alter Function Accounts.Types_Sub_Check_fn (#ID uniqueidentifier, #Sub Uniqueidentifier) returns int
begin
--declare #id uniqueidentifier = '8c7d4151-246c-476c-adf6-964ca9afdd3c' declare #sub uniqueidentifier = '47c2b6da-25fc-4921-adfa-b1f635bddde6'
declare #a int
declare #b int =(iif(#ID=#SUB,2,0))
;with cte(id, lvl) as
(
select f.sub,
1
from Accounts.Types as f
where f.id = #id
union all
select f.sub,
lvl + 1
from Accounts.Types as f
inner join cte as c
on f.id = c.id
)
select #a = (select count (*)
from cte
where id =#sub) + #b
option (maxrecursion 0)
return #a
end
go
The Table code
create Table Accounts.Types
(
ID uniqueidentifier not null CONSTRAINT DF_Accounts_Types_ID DEFAULT newid() CONSTRAINT PK_Accounts_Types_ID PRIMARY KEY NONCLUSTERED (ID) ,
Name varchar(200) not null CONSTRAINT UQ_Accounts_Types_NAME UNIQUE (NAME),
Sub uniqueidentifier CONSTRAINT FK_Accounts_Types_Sub Foreign key references Accounts.Types ,
Ctype uniqueidentifier CONSTRAINT FK_Accounts_Types_Ctype Foreign key references Accounts.Types ,
insert_time datetime not null CONSTRAINT DF_Accounts_Types_Insert_Time DEFAULT getdate() ,
insert_user uniqueidentifier CONSTRAINT DF_Accounts_Types_Insert_User DEFAULT'9EC66F53-9233-4A6C-8933-F8417D2BB5A9' ,
ts timestamp,
INDEX IX_Accounts_Types_NAME#ASC CLUSTERED (Name ASC),
Constraint Check_Accounts_Types_Sub check (Accounts.Types_Sub_Check_fn(ID,Sub)<=1)
)
go
This function will give 2 as result if trying to insert itseft as parent (in sub column)
it will give 1 if its already a child, which trying to insert as its parent
The Check constraint is created to check if the the parent (sub column) for any id should not be its child or grand child,
and itself cannot be its parent
When i try to insert a data which does not match the check constraint, it stuck, and give a timeout error,
eg:
insert into Accounts.Types (ID, Name, Sub)
values ('607936b9-6f95-4989-8ebe-87a08807f43e','LLL','607936b9-6f95-4989-8ebe-87a08807f43e')
this will give timeout
can anyone help me out, i need to get rid of time out error; get the constraint error only
Easy question - when will your recursion end when your ID and Sub are the same values and you don't limit maxrecursion or lvl? Never. It'll never end.
values ('607936b9-6f95-4989-8ebe-87a08807f43e','LLL','607936b9-6f95-4989-8ebe-87a08807f43e')
You have to remove rows where ID = Sub or add maxrecursion or add level limit or normalize your table.
alter Function Accounts.Types_Sub_Check_fn (#ID uniqueidentifier, #Sub Uniqueidentifier) returns int
begin
--declare #id uniqueidentifier = '00279c6b-df00-4144-810d-571fdb1c5109' declare #sub uniqueidentifier = 'bc887e7b-36d2-4ece-8ec1-720dc81a9de4'
declare #a int = 0
declare #b int =(iif(#ID=#SUB,2,0))
if #ID <> #sub
begin
;with cte(id, lvl) as
(
select f.Sub ,
1
from Accounts.Types as f
where f.id = #sub
union all
select iif(f.Sub = #sub, Null, f.sub),
lvl + 1
from Accounts.Types as f
inner join cte as c
on f.id = c.id
)
select #a = (select count (*)
from cte
where id =#id)
option (maxrecursion 0);
end
-- select #a + #b
return #a + #b
end
go

max of date in a query in sql server

I need help with writing part of query. Here's what I have
create table first_table(empid [varchar] (10) primary key not null,
DateInserted Datetime)
insert into first_table('1001','2012-02-13');
insert into first_table('1002','2013-02-13');
insert into first_table('1003','2014-02-11');
insert into first_table('1004','2012-02-13');
insert into first_table('2001','2012-02-12');
insert into first_table('2002','2014-02-13');
insert into first_table('5001','2014-02-13');
create table second_table(empid [varchar] (10) not null, CompanyID [varchar] (10) not null)
insert into second_table('1001','1');
insert into second_table('1002','1');
insert into second_table('1003','1');
insert into second_table('1004','1');
insert into second_table('2001','2');
insert into second_table('2002','2');
insert into second_table('5001','5');
create table valid_companies(CompanyID [varchar] (10) not null)
insert into valid_companies('1');
insert into valid_companies('2');
I want to select records from first_table that are valid_companies and with max date.
query should print
1003,1,2014-02-11
2002,2,2014-02-13
I am able to get
select DateInserted,ni.empID
,CompanyID
from
[dbo].[second_table] vw
inner join [dbo].[first_table] ni on ni.EmpID=vw.EmpID
where TagValue in(
SELECT DISTINCT [CompanyID]
FROM [dbo].[Valid_Companies]
)
. How to include max(DateInserted) in this query
Thx
R
try this
select e.empid, ec.CompanyID,
Max(e.DateInserted) LatestDate
from second_table ec
join first_table e
on e.empid = ec.empid
join valid_companies v
on v.CompanyID = ec.CompanyID
group by e.empid, ec.CompanyID

Resources