Converting a working script into Stored procedure - sql-server

Posted this question yesterday and got a solution as well. The solution script works fine as-is but when I converted it into a stored procedure it gives wrong results. Not able to identify where exactly I am messing up with the code.
Table Schema:
CREATE TABLE [dbo].[VMaster](
[VID] [int] IDENTITY(1,1) PRIMARY KEY NOT NULL,
[VName] [varchar](30) NOT NULL
)
GO
CREATE TABLE [dbo].[TblMaster](
[SID] [int] IDENTITY(1,1) NOT NULL Primary Key,
[VID] [int] NOT NULL,
[CreatedDate] [datetime] default (getdate()) NOT NULL,
[CharToAdd] [varchar](10) NOT NULL,
[Start] [int] NOT NULL,
[End] [int] NOT NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[TblDetails](
[DetailsID] [int] IDENTITY(1,1) NOT NULL Primary Key,
[SID] [int] NOT NULL,
[Sno] [int] NOT NULL,
[ConcatenatedText] [varchar](20) NOT NULL,
[isIssued] [bit] default (0) NOT NULL,
[isUsed] [bit] default (0) NOT NULL
)
GO
ALTER TABLE [dbo].[TblMaster] WITH CHECK ADD CONSTRAINT [fk_SI_id] FOREIGN KEY([VID])
REFERENCES [dbo].[VMaster] ([VID])
GO
ALTER TABLE [dbo].[TblMaster] CHECK CONSTRAINT [fk_SI_id]
GO
Working solution:
CREATE FUNCTION [dbo].[udf-Create-Range-Number] (#R1 money,#R2 money,#Incr money)
-- Syntax Select * from [dbo].[udf-Create-Range-Number](0,100,2)
Returns
#ReturnVal Table (RetVal money)
As
Begin
With NumbTable as (
Select NumbFrom = #R1
union all
Select nf.NumbFrom + #Incr
From NumbTable nf
Where nf.NumbFrom < #R2
)
Insert into #ReturnVal(RetVal)
Select NumbFrom from NumbTable Option (maxrecursion 32767)
Return
End
Declare #Table table (SID int,VID int,CreateDate DateTime,CharToAdd varchar(25),Start int, [End] Int)
Insert Into #Table values
(1,1,'2016-06-30 19:56:14.560','ABC',1,5),
(2,1,'2016-06-30 19:56:14.560','XYZ',10,20),
(3,2,'2016-06-30 19:56:14.560','P1',10,15)
Declare #Min int,#Max int
Select #Min=min(Start),#Max=max([End]) From #Table
Select B.SID
,Sno = A.RetVal
,ConcetratedText = concat(B.CharToAdd,A.RetVal)
From (Select RetVal=Cast(RetVal as int) from [dbo].[udf-Create-Range-Number](#Min,#Max,1)) A
Join #Table B on A.RetVal Between B.Start and B.[End]
Order By B.Sid,A.RetVal
Stored procedure (This generates more records than the working solution!!)
CREATE PROCEDURE [dbo].[Add_Details]
(
#VID INT,
#CreatedDate DATETIME,
#CharToAdd VARCHAR(10),
#Start INT,
#End INT
)
AS
SET NOCOUNT ON
BEGIN
DECLARE #SID INT
INSERT INTO [dbo].[TblMaster] (VID, CreatedDate, CharToAdd, Start, [End])
VALUES (#VID, #CreatedDate, #CharToAdd, #Start, #End)
SET #SID = SCOPE_IDENTITY()
DECLARE #Min INT, #Max INT
SELECT #Min = #Start, #Max = #End
INSERT INTO [dbo].[TblDetails] (SID, Sno, [ConcatenatedText])
SELECT #SID
,Sno = A.RetVal
,ConcatenatedText = CONCAT(B.CharToAdd,A.RetVal)
FROM (SELECT RetVal = CAST(RetVal AS INT) FROM [dbo].[udf-Create-Range-Number](#Min,#Max,1)) A
JOIN dbo.TblMaster B ON A.RetVal BETWEEN B.Start AND B.[End]
ORDER BY B.SID,A.RetVal
END
GO
Declare #tmp datetime
Set #tmp = getdate()
EXEC [dbo].[Add_Details]
#VID = 1,
#CreatedDate = #tmp,
#CharToAdd = 'ABC',
#Start = 1,
#End = 5
EXEC [dbo].[Add_Details]
#VID = 1,
#CreatedDate = #tmp,
#CharToAdd = 'XYZ',
#Start = 10,
#End = 20
EXEC [dbo].[Add_Details]
#VID = 2,
#CreatedDate = #tmp,
#CharToAdd = 'P1',
#Start = 10,
#End = 15
Output of working script:
Output of the stored procedure:

You need to filter by VID on the second insert. It's picking up rows from previous executions. Since it only picks up other rows where the ranges are overlapping it doesn't always do it. Run the procedure a few more times and you'll see the duplication amplified a lot more. The reason it didn't do this in the original code was because you were using a temp table that was recreated each time you ran it.
INSERT INTO [dbo].[TblDetails] (SID, Sno, [ConcatenatedText])
SELECT #SID
,Sno = A.RetVal
,ConcatenatedText = CONCAT(B.CharToAdd,A.RetVal)
FROM (
SELECT RetVal = CAST(RetVal AS INT)
FROM [dbo].[udf-Create-Range-Number](#Min,#Max,1)) A
JOIN dbo.TblMaster B ON A.RetVal BETWEEN B.Start AND B.[End]
WHERE B.VID = #VID -- <<<---------
)
On a side note I would highly recommend changing that function to type int rather than money.

Related

How to insert data in table using Insert Query from values provided in parameters

I have this table structure and I want to write Insert Query that'll insert data into the table from the values provided in parameters
CREATE TABLE [dbo].[EMPLOYEE](
[ID] [int] NULL,
[EMPLOYEE_NAME] [varchar](50) NULL,
[DEPARTMENT_ID] [int] NULL,
) ON [PRIMARY]
DECLARE #ID VARCHAR(20) = '1, 2';
DECLARE #Name VARCHAR(50) = 'Asim Asghar, Ahmad'
DECLARE #DeptID VARCHAR(20) = '5, 12';
INSERT INTO EMPLOYEE VALUES (#ID, #Name, #DeptID)
Based on the data provided above it should add 4 rows with following data
1 Asim Asghar 5
2 Ahmad 5
1 Asim Asghar 12
2 Ahmad 12
Hope someone can help
You can not pass multiple values together through a variable at a time. The script should be as below considering one person at a time-
CREATE TABLE [dbo].[EMPLOYEE](
[ID] [int] NULL,
[EMPLOYEE_NAME] [varchar](50) NULL,
[DEPARTMENT_ID] [int] NULL,
) ON [PRIMARY]
DECLARE #ID INT = 1;
DECLARE #Name VARCHAR(50) = 'Asim Asghar';
DECLARE #DeptID INT = 5;
INSERT INTO EMPLOYEE(ID,EMPLOYEE_NAME,DEPARTMENT_ID) VALUES (#ID, #Name, #DeptID)
Then you can change the values for next person and execute the INSERT script again. And from the second execution, you have to skip the Table creation script other wise it will through error.
The question's query is trying to insert a single row, using strings values for the ID and DeptID fields. This will fail with a runtime error.
One can use the table value constructor syntax to insert multiple rows in a single INSERT statement :
INSERT INTO EMPLOYEE
VALUES
(1, 'Asim Asghar', 5),
(2, 'Ahmad', 5),
(1, 'Asim Asghar', 12),
(2, 'Ahmad', 12)
The values can come from parameters or variables.
Using duplicate IDs and names in an Employee table hints at a problem. Looks like the intent is to store employees and their department assignments. Otherwise why insert 4 rows instead of 8 with all possible combinations?
Employee should be changed to this :
CREATE TABLE [dbo].[EMPLOYEE]
(
[ID] [int] primary key not null,
[EMPLOYEE_NAME] [varchar](50)
)
And another table, EmployeeAssignment should be added
CREATE TABLE [dbo].[EMPLOYEE_ASSIGNMENT]
(
Employee_ID int not null FOREIGN KEY REFERENCES EMPLOYEE(ID),
[DEPARTMENT_ID] [int] not NULL,
PRIMARY KEY (Employee_ID,Department_ID)
)
The data can be inserted with two INSERT statements :
INSERT INTO EMPLOYEE
VALUES
(1, 'Asim Asghar'),
(2, 'Ahmad'),
INSERT INTO EMPLOYEE_ASSIGNMENT
VALUES
(1, 5),
(2, 5),
(1, 12),
(2, 12)
Try this, You need this function for splitting by char using dynamic delimiter.
CREATE FUNCTION UDF_SPLIT_BY_CHAR(#STRING VARCHAR(8000), #DELIMITER CHAR(1))
RETURNS #TEMPTABLE TABLE (S_DATA VARCHAR(8000))
AS
BEGIN
DECLARE #IDX INT=1,#SLICE VARCHAR(8000)
IF LEN(#STRING)<1 OR #STRING IS NULL RETURN
WHILE #IDX<> 0
BEGIN
SET #IDX = CHARINDEX(#DELIMITER,#STRING)
IF #IDX!=0
SET #SLICE = LEFT(#STRING,#IDX - 1)
ELSE
SET #SLICE = #STRING
IF(LEN(#SLICE)>0)
INSERT INTO #TEMPTABLE(S_DATA) VALUES(#SLICE)
SET #STRING = RIGHT(#STRING,LEN(#STRING) - #IDX)
IF LEN(#STRING) = 0 BREAK
END
RETURN
END
Declare #EMPLOYEE TABLE
(
[ID] [int] NULL,
[EMPLOYEE_NAME] [varchar](50) NULL,
[DEPARTMENT_ID] [int] NULL
)
DECLARE #ID VARCHAR(20) = '1, 2'
,#Name VARCHAR(50) = 'Asim Asghar, Ahmad'
,#DeptID VARCHAR(20) = '5, 12';
insert into #EMPLOYEE
(
[ID],[EMPLOYEE_NAME],[DEPARTMENT_ID]
)
Select a.S_DATA,b.S_DATA,c.S_DATA
from dbo.UDF_SPLIT_BY_CHAR(#id,',') a
left join dbo.UDF_SPLIT_BY_CHAR(#Name,',') b on 1=1
left join dbo.UDF_SPLIT_BY_CHAR(#DeptID,',') c on 1=1

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.

Appointment Slots not working

I have three tables.
Table 1.(Booking)
CREATE TABLE [dbo].[Booking](
[Booking_Serno] [int] IDENTITY(1,1) NOT NULL,
[Dt] [datetime] NULL,
[start] [nvarchar](50) NULL,
[todate] [nvarchar](50) NULL,
[Service_Id] [int] NULL
) ON [PRIMARY]
INSERT [dbo].[Booking] ([Booking_Serno], [Dt], [start], [todate], [Service_Id]) VALUES (1, CAST(0x0000A6DA00000000 AS DateTime), N'9:30 AM', N'10:00 AM', 1)
GO
Table 2.(Service)
CREATE TABLE [dbo].[Service](
[Service_Serno] [int] IDENTITY(1,1) NOT NULL,
[Service_Duration] [int] NULL
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[Service] ON
INSERT [dbo].[Service] ([Service_Serno], [Service_Duration]) VALUES (1, 30)
Table 3 (Rules)
CREATE TABLE [dbo].[Rules](
[Rule_Serno] [int] IDENTITY(1,1) NOT NULL,
[Start_Dt] [varchar](50) NULL,
[End_Dt] [varchar](50) NULL,
[from_dt] [varchar](50) NULL,
[to_dt] [varchar](50) NULL,
[Service_Id] [int] NULL
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
SET IDENTITY_INSERT [dbo].[Rules] ON
INSERT [dbo].[Rules] ([Rule_Serno], [Start_Dt], [End_Dt], [from_dt], [to_dt], [Service_Id]) VALUES (1, N'2016-07-02', N'2016-07-13', N'07:00', N'17:00', 1)
I am running a stored procedure. It gets me the desired result but then I am trying to book a time by changing the interval, the slots shows empty even if there is a slot booked. Ex. If i am setting a slot for 60 minutes and book a slot from 7:00-8:00 it shows booked(xxx) but when i change the interval to 30 the 7:00-8:00 becomes available. It should actually display 7:00-7:30 and 7:00-8:00 unavailable.
the Stored Procedure is
Dt:-12/12/2016 ; ServiceId:-1
CREATE PROCEDURE [dbo].[RealGetFollowUp] #Dt varchar(50), #ServiceId int
AS
--declare #starttime datetime = '2015-10-28 12:00', #endtime datetime = '2015-10-28 14:00'
DECLARE #starttime varchar(50),
#endtime varchar(50),
#interval int
SELECT
#starttime = Rules.from_dt,
#endtime = Rules.to_dt,
#interval = Service.Service_Duration
FROM Service
INNER JOIN Rules
ON Service.Service_Serno = Rules.Service_Id
WHERE Service.Service_Serno = #ServiceId
--SELECT * INTO #tmp FROM d;
DECLARE #slots int
SELECT
#slots = DATEDIFF(MINUTE, #starttime, #endtime) / #interval
SELECT TOP (#slots)
N = IDENTITY(int, 1, 1) INTO #Numbers
FROM master.dbo.syscolumns a
CROSS JOIN master.dbo.syscolumns b;
SELECT
DATEADD(MINUTE, ((n - 1) * #interval), #starttime) AS start,
DATEADD(MINUTE, (n * #interval), #starttime) AS todate INTO #slots
FROM #numbers
SELECT
#Dt AS 'Date',
LEFT(CONVERT(varchar, s.start, 108), 10) AS Start,
LEFT(CONVERT(varchar, s.todate, 108), 10) AS 'End',
CASE
WHEN b.start IS NULL THEN '-'
ELSE 'xx'
END AS Status
FROM [#slots] AS s
LEFT JOIN Booking AS b
ON s.start = b.start
AND s.todate = b.todate
AND b.Dt = #Dt
DROP TABLE #numbers, #slots
GO
I need to check if there is a slot booked in the Booking table and even if i change the interval in the service table, the slot booked in the booking table should be shown as booked.
Change the output SELECT in the sproc to...
SELECT
#Dt AS 'Date',
LEFT(CONVERT(varchar, s.start, 108), 10) AS Start,
LEFT(CONVERT(varchar, s.todate, 108), 10) AS 'End',
CASE
WHEN b.start IS NULL THEN '-'
ELSE 'xx'
END AS Status
FROM [#slots] AS s
LEFT JOIN Booking AS b
ON (
--Range is bigger than the meeting
(s.start <= b.start
AND s.todate >= b.todate)
OR
--Range is smaller than the meeting
(s.start Between b.start and b.toDate
AND s.todate Between b.start and b.toDate)
)
AND b.Dt = #Dt

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.

Persisted computed column with subquery

I have something like this
create function Answers_Index(#id int, #questionID int)
returns int
as begin
return (select count([ID]) from [Answers] where [ID] < #id and [ID_Question] = #questionID)
end
go
create table Answers
(
[ID] int not null identity(1, 1),
[ID_Question] int not null,
[Text] nvarchar(100) not null,
[Index] as [dbo].[Answers_Index]([ID], [ID_Question]),
)
go
insert into Answers ([ID_Question], [Text]) values
(1, '1: first'),
(2, '2: first'),
(1, '1: second'),
(2, '2: second'),
(2, '2: third')
select * from [Answers]
Which works great, however it tends to slow down queries quite a bit. How can I make column Index persisted? I have tried following:
create table Answers
(
[ID] int not null identity(1, 1),
[ID_Question] int not null,
[Text] nvarchar(100) not null,
)
go
create function Answers_Index(#id int, #questionID int)
returns int
with schemabinding
as begin
return (select count([ID]) from [dbo].[Answers] where [ID] < #id and [ID_Question] = #questionID)
end
go
alter table Answers add [Index] as [dbo].[Answers_Index]([ID], [ID_Question]) persisted
go
insert into Answers ([ID_Question], [Text]) values
(1, '1: first'),
(2, '2: first'),
(1, '1: second'),
(2, '2: second'),
(2, '2: third')
select * from [Answers]
But that throws following error: Computed column 'Index' in table 'Answers' cannot be persisted because the column does user or system data access. Or should I just forget about it and use [Index] int not null default(0) and fill it in on insert trigger?
edit: thank you, final solution:
create trigger [TRG_Answers_Insert]
on [Answers]
for insert, update
as
update [Answers] set [Index] = (select count([ID]) from [Answers] where [ID] < a.[ID] and [ID_Question] = a.[ID_Question])
from [Answers] a
inner join [inserted] i on a.ID = i.ID
go
You could change the column to be a normal column and then update its value when you INSERT/UPDATE that row using a trigger.
create table Answers
(
[ID] int not null identity(1, 1),
[ID_Question] int not null,
[Text] nvarchar(100) not null,
[Index] Int null
)
CREATE TRIGGER trgAnswersIU
ON Answers
FOR INSERT,UPDATE
AS
DECLARE #id int
DECLARE #questionID int
SELECT #id = inserted.ID, #questionID = inserted.ID_question
UPDATE Answer a
SET Index = (select count([ID]) from [Answers] where [ID] < #id and [ID_Question] = #questionID)
WHERE a.ID = #id AND a.ID_question = #questionID
GO
NB* This is not fully correct as it wont work correctly on UPDATE as we wont have the "inserted" table to reference to get the ID and questionid. There is a way around this but i cant remember it right now :(
Checkout this for more info
Computed columns only store the formula of the calculation to perform. That is why it will be slower when querying the computed column from the table. If you want to persist the values to an actual table column, then you are correct about using a trigger.

Resources