Apply OFFSET and FETCH NEXT to an existing stored procedure - sql-server

I have an existing stored procedure and I would like to apply the concept of paging by using OFFSET and FETCH NEXT.
However since this stored procedure already exists, and is called from other sources, I don't want to break it. I have added 2 optional parameters called #PageNumber INT = NULL and #NumberOfRecordsToRetrieve INT = NULL. I would like to know how to incorporate the logic into my query inside the stored procedure when these values are not supplied.
CREATE PROCEDURE TEST_PROCEDURE
#Id INT,
#PageNumber INT = NULL,
#NumberOfRecordsToRetrieve INT = null
AS
BEGIN
SET NOCOUNT ON;
DECLARE #offsetRows INT, #nextRows INT, #maxCount INT
IF(#PageNumber IS NOT NULL AND #NumberOfRecordsToRetrieve IS NOT NULL)
BEGIN
SET #offsetRows = (#PageNumber - 1) * #NumberOfRecordsToRetrieve;
SET #nextRows = #NumberOfRecordsToRetrieve;
END
ELSE
BEGIN
SET #offsetRows = 0;
SET #nextRows = 0;
END
SELECT
Id,
Column2,
Column3
FROM
ABCCustomers
WHERE
Id = #Id
UNION
SELECT
Id,
Column2,
Column3
FROM
DEFCustomers
WHERE
Id = #Id
ORDER BY
Column2
-- this section needs to be applied only if the parameters have values
OFFSET #offsetRows ROWS
FETCH NEXT #nextRows ROWS ONLY
END
The offset and fetch next needs to be applied only when there is a value or else I will get an error. How do I solve this?

Simple, but perfectly acceptable:
IF #PageNumber IS NULL
{Do query without OFFSET..FETCH}
ELSE
{Do query with OFFSET..FETCH}

If you do not want to double your SQL statment: COALESCE is a alternative.
select id,
name
from user
order by id
offset COALESCE(#offset,0) rows
fetch next COALESCE(#pageSize,2147483647) rows only
Like this answer:
Using OFFSET-FETCH, how to default number of rows to "all rows"?

Related

How to get and use the value returned by a stored procedure to a INSERT INTO... SELECT... statement

I am just new in SQL language and still studying it. I'm having hard time looking for answer on how can I use the stored procedure and insert value into a table.
I have this stored procedure:
CREATE PROCEDURE TestID
AS
SET NOCOUNT ON;
BEGIN
DECLARE #NewID VARCHAR(30),
#GenID INT,
#BrgyCode VARCHAR(5) = '23548'
SET #GenID = (SELECT TOP (1) NextID
FROM dbo.RandomIDs
WHERE IsUsed = 0
ORDER BY RowNumber)
SET #NewID = #BrgyCode + '-' + CAST(#GenID AS VARCHAR (30))
UPDATE dbo.RandomIDs
SET dbo.RandomIDs.IsUsed = 1
WHERE dbo.RandomIDs.NextID = #GenID
SELECT #NewID
END;
and what I'm trying to do is this:
INSERT INTO dbo.Residents([ResidentID], NewResidentID, [ResLogdate],
...
SELECT
[ResidentID],
EXEC TestID ,
[ResLogdate],
....
FROM
source.dbo.Resident;
There is a table dbo.RandomIDs containing random 6 digit non repeating numbers where I'm pulling out the value via the stored procedure and updating the IsUsed column of the table to 1. I'm transferring data from one database to another database and doing some processing on the data while transferring. Part of the processing is generating a new ID with the required format.
But I can't get it to work Sad I've been searching the net for hours now but I'm not getting the information that I need and that the reason for my writing. I hope someone could help me with this.
Thanks,
Darren
your question is little bit confusing, because you have not explained what you want to do. As i got your question, you want to fetch random id from randomids table and after performed some processing on nextid you want to insert it into resident table [newresidentid] and end of the procedure you fetch data from resident table. if i get anything wrong feel free to ask me.
your procedure solution is following.
CREATE PROCEDURE [TestId]
AS
SET NOCOUNT ON;
BEGIN
DECLARE #NEWID NVARCHAR(30)
DECLARE #GENID BIGINT
DECLARE #BRGYCODE VARCHAR(5) = '23548'
DECLARE #COUNT INTEGER
DECLARE #ERR NVARCHAR(20) = 'NO IDS IN RANDOM ID'
SET #COUNT = (SELECT COUNT(NEXTID) FROM RandomIds WHERE [IsUsed] = 0)
SET #GENID = (SELECT TOP(1) [NEXTID] FROM RandomIds WHERE [IsUsed] = 0 ORDER BY [ID] ASC)
--SELECT #GENID AS ID
IF #COUNT = 0
BEGIN
SELECT #ERR AS ERROR
END
ELSE
BEGIN
SET #NEWID = #BRGYCODE + '-' + CAST(#GENID AS varchar(30))
UPDATE RandomIds SET [IsUsed] = 1 WHERE [NextId] = #GENID
INSERT INTO Residents ([NewResidentId] , [ResLogDate] ) VALUES (#NEWID , GETDATE())
SELECT * FROM Residents
END
END
this procedure will fetch data from your randomids table and perform some processing on nextid than after it directs insert it into resident table and if you want to insert some data through user you can use parameter after declaring procedure name
E.G
CREATE PROCEDURE [TESTID]
#PARAM1 DATATYPE,
#PARAM2 DATATYPE
AS
BEGIN
END
I'm not convinced that your requirement is a good one but here is a way to do it.
Bear in mind that concurrent sessions will not be able to read your update until it is committed so you have to kind of "lock" the update so you will get a block until you're going to commit or rollback. This is rubbish for concurrency, but that's a side effect of this requirement.
declare #cap table ( capturedValue int);
declare #GENID int;
update top (1) RandomIds set IsUsed=1
output inserted.NextID into #cap
where IsUsed=0;
set #GENID =(select max( capturedValue) from #cap )
A better way would be to use an IDENTITY or SEQUENCE to solve your problem. This would leave gaps but help concurrency.

T-SQL different result between code in stored and same code in query pane

I'm working on a procedure that should return a o or a 1, depending on result from parameter calculation (parameters used to interrogate 2 tables in a database).
When I excute that code in a query pane, it gives me the results i'm expecting.
code looks like:
SELECT TOP 1 state, updDate INTO #history
FROM [xxx].[dbo].[ImportHystory] WHERE (db = 'EB') ORDER BY addDate DESC;
IF (SELECT state FROM #history) = 'O'
BEGIN
SELECT TOP 1 * INTO #process_status
FROM yyy.dbo.process_status WHERE KeyName = 'eb-importer';
IF(SELECT s.EndDate FROM #process_status s) IS NOT NULL
IF (SELECT s.EndDate FROM #process_status s) > (SELECT h.updDate FROM #history h)
BEGIN
IF (SELECT MessageLog from #process_status) IS NOT NULL SELECT 1;
ELSE SELECT 0;
END
ELSE
SELECT 1;
ELSE
SELECT 1;
END
ELSE
SELECT 0
I'm in the situation where EndDate from #process_status is null, so the execution returns 1.
Once i put the SAME code in a SP, and pass 'EB' and 'eb-importer' as parameters, it returns 0.
And I exec the procedure with the data from the table right in front of me, so i know for sure that result is wrong.
Inside the procedure:
ALTER PROCEDURE [dbo].[can_start_import] (#keyName varchar, #db varchar, #result bit output)
DECLARE #result bit;
and replace every
SELECT {0|1}
with
SELECT #result = {0|1}
Executed from the Query pane:
DECLARE #result bit;
EXEC [dbo].[can_start_import] #KeyName = 'eb-importer', #db = 'EB', #result = #result OUTPUT
SELECT #result AS N'#result'
Why does this happen?
You are doing a top(1) query without an order by. That means SQL Server can pick any row from table1 that matches the where clause.
If you want to guarantee that the result is the same every time you execute that code you need an order by statement that unambiguously orders the rows.
So, apparently 2 things needed to be done:
set the length of the varchar parameter with a higher length,
filter with ' like ' instead of ' = ' for god knows what reason
Now it work as i expected to do, but i still don't get the different results between the query pane and the procedure if i use the equal...

Run A Loop in SQL Server

I want to run a stored procedure on each ID return by a SELECT query. Is there a simple way to do something like:
FOREACH (SELECT ID FROM myTABLE WHERE myName='bob') AS id
BEGIN
EXEC #return_value = [dbo].[spMYPROC]
#PARAM1 = id
#PARAM2 = 0
END
Since I just happened to answer a very similar question yesterday, I have this code handy. As others have stated, it may not be the best approach, but still it's nice to learn how to use a while loop anyway.
Assuming a table named "Customer"
declare #Id int
select #Id = MIN(Id)
from Customer c
while(select COUNT(1)
from Customer c
where c.Id >= #Id) > 0
begin
--run your sproc right here
select #Id = MIN(Id)
from Customer c
where c.Id > #Id
end
DECLARE #ID INT, #return_value INT
DECLARE c CURSOR FOR
SELECT
ID
FROM myTABLE
WHERE myName = 'bob'
OPEN c; FETCH NEXT FROM c INTO #ID
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC #return_value = [dbo].[spMYPROC]
#PARAM1 = #ID,
#PARAM2 = 0
FETCH NEXT FROM c INTO #ID
END
CLOSE c; DEALLOCATE c;
You have two option here
Option 1 Using Split Function
Pass a comma deliminated list of IDs and use a Split function Inside your Procedure to make split these values and do whatever you want to do with it.
To
Make it work you will need two thing
1) Create a Function which
accepts a Comma Deliminated string and split them.
2) Modify you
Store Procedure and add this function in there in a way that passed
parameter is passed to the function inside that store procedure and
that function split the values before passing it onto your store
Procedure .
Create this function 1st
Function Definition
CREATE FUNCTION [dbo].[FnSplit]
(
#List nvarchar(2000),
#SplitOn nvarchar(5)
)
RETURNS #RtnValue table (Id int identity(1,1), Value nvarchar(100))
AS
BEGIN
WHILE(Charindex(#SplitOn,#List)>0)
BEGIN
INSERT INTO #RtnValue (value)
SELECT VALUE = ltrim(rtrim(Substring(#List,1,Charindex(#SplitOn,#List)-1)))
SET #List = SUBSTRING(#List,Charindex(#SplitOn,#List)+len(#SplitOn),len(#List))
END
INSERT INTO #RtnValue (Value)
SELECT VALUE = ltrim(rtrim(#List))
RETURN
END
Modify you strored Procedure something like this
Stored Procedure
ALTER Procedure [dbo].[spMYPROC] (#Param1 VARCHAR(1000)= NULL)
AS
BEGIN
SELECT * FROM TableName
where ColumnNAME IN (SELECT Value FROM dbo.FnSplit(#Param1,','))
END
GO
Option 2 Table Type Parameter
Create a Table Type and alter your proc to accept a Table Type Parameter and do whatever you want to do with them values inside your proc.
TABLE TYPE
CREATE TYPE dbo.TYPENAME AS TABLE
(
Value int
)
GO
Stored Procedure to Accept That Type Param
ALTER PROCEDURE [dbo].[spMYPROC]
#TableParam TYPENAME READONLY
AS
BEGIN
SET NOCOUNT ON;
--Temp table to store passed Id values
declare #tmp_values table (value INT );
--Insert passed values to a table variable inside the proc
INSERT INTO #tmp_values (value)
SELECT Value FROM #TableParam
/* Do your stuff here whatever you want to do with Ids */
END
EXECUTE PROC
Declare a variable of that type and populate it with your values.
DECLARE #Table TYPENAME --<-- Variable of this TYPE
INSERT INTO #Table --<-- Populating the variable
SELECT ID FROM myTABLE WHERE myName='bob'
EXECUTE [dbo].[spMYPROC] #Table --<-- Stored Procedure Executed

Efficiently return a value from a stored procedure

I have query which returns single value (i.e) count. I'm exceuting it using the stored procedure in the following way and using execute reader with dataset to get single value
CREATE PROCEDURE GetCnt
#EmpNo char(4)
AS
BEGIN
SET NOCOUNT ON;
Declare #Cnt int
SELECT #Cnt = count(*)
FROM employees
WHERE EMPLNO = #EmpNo
AND test = 'p'
BEGIN
SELECT #Cnt
END
END
is this effcient way
or Do I need to use the execute.scalar() and return value directly from the query instead of assigning to #cnt
can any one advise me
All ExecuteScalar does is get the first field from the first record.
Can't you just SELECT the count directly?
BEGIN
SET NOCOUNT ON
SELECT Count(*) FROM employees WHERE EMPLNO = #EmpNo AND test='p'
END
You do not need to create the variable. You just need the following:
CREATE PROCEDURE GetCnt
#EmpNo char(4)
AS
BEGIN
SET NOCOUNT ON;
SELECT count(1)
FROM employees
WHERE EMPLNO = #EmpNo
AND test = 'p'
END
Since this is only one value being returned from the stored procedure, you will likely want to use ExecuteScalar()

Can I send array of parameter to store procedure?

I have User table, it has UserId uniqueidentifier, Name varchar and IsActive bit.
I want to create store procedure to set IsActive to false for many user, for example, if I want to deactive 2 users, I want to send Guid of those users to store procedure (prefer as array). I want to know how can I do it?
P.S. I'm working on Microsoft SQL Azure
Along the same lines than Elian, take a look at XML parameters. Generally speaking you should have a cleaner/safer implementation using xml than parsing a list of strings. Click here for a code example
Here is a solution I used a while ago and that was working fine.
Send the list of guid you want to deactive merged into a comma separated string to the sp.
Then in the sp, you first convert this string into a table thanks to a table-valued function.
Here is a sample with bigint, but you can easily modify it so that it works with guid
Step 1 : the table-valued function
CREATE FUNCTION [dbo].[BigIntListToTable] (
#list VARCHAR(max)
)
RETURNS
#tbl TABLE
(
nval BIGINT NOT NULL
) AS
BEGIN
DECLARE #nPos INT
DECLARE #nNextPos INT
DECLARE #nLen INT
SELECT #nPos = 0, #nNextPos = 1
WHILE #nNextPos > 0
BEGIN
SELECT #nNextPos = CHARINDEX(',', #list, #nPos + 1)
SELECT #nLen = CASE WHEN #nNextPos > 0
THEN #nNextPos
ELSE LEN(#list) + 1
END - #nPos - 1
INSERT #tbl (nval)
VALUES (CONVERT(BIGINT, SUBSTRING(#list, #nPos + 1, #nLen)))
SELECT #nPos = #nNextPos
END
RETURN
END
Step 2 : the stored proc
CREATE PROCEDURE [dbo].[spMySP]
#IdList VARCHAR(max)
AS
BEGIN
SET NOCOUNT ON;
SET ROWCOUNT 0
UPDATE dbo.YourTable
SET isActive = 0
FROM dbo.YourTable
INNER JOIN dbo.BigIntListToTable(#IdList) l
ON dbo.YourTable.id = l.nval
END

Resources