TSQL set the column value of a table variable - tsql

I am trying to set individual columns of a table variable iteratively as follows:
declare #reg_data table
(
I int NOT NULL PRIMARY KEY IDENTITY,
Y float
)
declare #counter int, #numRows int
SET #counter = 0
SET #numRows = (select MAX(val) + 10 from tableY)
WHILE #counter < numRows
BEGIN
SET #reg_data.Y = dbo.func1(#counter) --HOW DO I DO THIS!!!
#counter = #counter + 1
END
The above does not work because you cannot access table variables like an array. How can I obtain the following functionality?

You can't set values in records that doesn't exists, so you need an insert:
WHILE #counter < numRows
BEGIN
INSERT INTO #reg_data (Y) values (dbo.func1(#counter))
#counter = #counter + 1
END

Just for the completeness a one-statement example with a CTE and no looping:
DECLARE #reg_data TABLE (
I INT NOT NULL PRIMARY KEY IDENTITY,
Y FLOAT
);
WITH cteNum AS (
SELECT MAX(val) + 10 AS val
FROM #tableY
HAVING MAX(val) >= 0
UNION ALL
SELECT val-1
FROM cteNum
WHERE val > 0
)
INSERT #reg_data(Y)
SELECT dbo.func1(val)
FROM cteNum
OPTION (MAXRECURSION 0);

Why use a cursor for this at all??
Why don't you just write some UPDATE statements:
WHILE #counter < numRows
BEGIN
UPDATE #reg_data
SET Y = dbo.func1(#counter)
WHERE I = #counter
SET #counter = #counter + 1
END
You need to somehow be able to read out the identifying values (the I) from the table variable, so you can use that in your UPDATE statement to apply the update to exactly one row (that's identified by that value of I)

Related

How to loop through 0-9 in a sql query?

This is redshift. I have an update command that looks like this:
UPDATE users
SET birthday = temp_users.birth_date
FROM public.users as gpu
INNER JOIN temp_schema.users_birth_dates_with_row_numbers AS temp_users ON gpu.id = temp_users.id
WHERE mod(temp_users.row_id, 10) = 0 -- this is what I want to change from 0 -> 9
;
For context: I want to run this time times with a different modulo number. How can I do this? Row id is basically created with the row_number function and I want to do this in batches so that it doesn't hold any locks for too long.
SQL WHILE loop syntax and example
The syntax of the WHILE loop in SQL looks like as follows:
WHILE condition BEGIN {...statements...} END
Example:
DECLARE #Counter INT
SET #Counter=0
WHILE ( #Counter <= 9)
BEGIN
PRINT 'The counter value is = ' + CONVERT(VARCHAR,#Counter)
SET #Counter = #Counter + 1
END

Paging data and get row count with out considering the page number in stored procedure

I am using SQL Server 2012. I need to do paging in my stored procedure. In client side (Web page), I need to have the result of the stored procedure for requested page and also I need to have the row count without considering the page number for setting number of page with that condition.
For example, I need to run this stored procedure for querying ten row of second page when my SubscribeId is 12345674.
Create Procedure TestSelectBill
(#PageNumber int = 1 ,
#RowCount int = 10 ,
#SubscribeId Int = 0)
As
Begin
Select *
From billing.BillMaster As BM
Where (Bm.SubscribeId = #SubscribeId)
Order by SubscribeId
Offset (#PageNumber - 1) * #RowCount Rows
Fetch Next #RowCount Rows Only;
End
I have to execute this stored procedure like this :
Execute TestSelectBill
#PageNumber = 2, #RowCount int = 10, #SubscribeId = 12345674
Imagine, that I have 105 rows in billing.BillMaster for this SubscribeId = 123456574. Now I need to show 10 row to my end user as result and I have to let him to select one page between 1 to 11.
That means I need to know how many row is exist for this condition SubscribeId = 123456574.
I can change my stored procedure like the below code to return row count:
Create Procedure TestSelectBill
(#PageNumber int = 1,
#RowCount int = 10,
#SubscribeId Int = 0)
As
Begin
DECLARE #ROW_COUNT INT = 0
-- Find Row Count for this condition
Select
#ROW_COUNT = COUNT(*)
From
billing.BillMaster As BM
Where
(Bm.SubscribeId = #SubscribeId)
-- Select Result
SELECT
Row_Count = #ROW_COUNT,
*
FROM
billing.BillMaster As BM
WHERE
(Bm.SubscribeId = #SubscribeId)
ORDER BY
SubscribeId
OFFSET ( #PageNumber - 1 ) * #RowCount ROWS
FETCH NEXT #RowCount ROWS ONLY;
End
But as you see I have to write my select two times and it is not good because modification and maintenance of this stored procedure will be very complicated.
Also, I can save my result into temp table and then use that like the below code:
CREATE Procedure TestSelectBill
(#PageNumber int = 1,
#RowCount int = 10,
#SubscribeId Int = 0)
As
Begin
DECLARE #ROW_COUNT INT = 0
-- Main Select
SELECT
*
FROM
billing.BillMaster As BM
INTO
#T
WHERE
(Bm.SubscribeId = #SubscribeId)
-- Find Row Count for this condituion
SELECT #ROW_COUNT = COUNT(*)
FROM #T
-- Select Result
SELECT
Row_Count = #ROW_COUNT,
*
FROM
#T
ORDER BY
SubscribeId
OFFSET (#PageNumber - 1) * #RowCount ROWS
FETCH NEXT #RowCount ROWS ONLY;
End
But as you can see in this way, I am using physical temp table that can be very slow when I have a lot of data in main select with out paging.
Can anyone tell me the best way to do that?
-- First solution use count with window function
CREATE Procedure TestSelectBill
(#PageNumber int = 1,
#RowCount int = 10,
#SubscribeId Int = 0)
As
Begin
SELECT
COUNT(*) OVER(ORDER BY (SELECT NULL)) AS row_count ,
*
FROM
billing.BillMaster As BM
WHERE
(Bm.SubscribeId = #SubscribeId)
ORDER BY
SubscribeId
OFFSET (#PageNumber - 1) * #RowCount ROWS
FETCH NEXT #RowCount ROWS ONLY;
End
GO
-- Second solution: use dynamic sql with multiple result
Create Procedure TestSelectBill
#PageNumber int = 1,
#RowCount int = 10,
#SubscribeId Int = 0
As
Begin
DECLARE #params NVARCHAR(max) = '#PageNumber int, #RowCount int, #SubscribeId int'
DECLARE #where NVARCHAR(max) = N' WHERE Bm.SubscribeId = #SubscribeId'
DECLARE #stmt NVARCHAR(max) = N'SELECT COUNT(*) as row_cnt FROM billing.BillMaster As BM '
DECLARE #stmt_rowcount NVARCHAR(max) = N'SELECT * FROM billing.BillMaster As BM '
DECLARE #order_by NVARCHAR(max) = ' ORDER BY SubscribeId
OFFSET (#PageNumber - 1) * #RowCount ROWS
FETCH NEXT #RowCount ROWS ONLY;'
SET #stmt += #where + #order_by
SET #stmt_rowcount += #where
-- First result set (rowcount)
EXEC [sys].[sp_executesql]
#stmt = #stmt_rowcount,
#params = #params,
#SubscribeId = #SubscribeId,
#PageNumber = #PageNumber,
#RowCount = #RowCount
-- Second result set (data)
IF ##ERROR = 0
BEGIN
EXEC [sys].[sp_executesql]
#stmt = #stmt,
#params = #params,
#SubscribeId = #SubscribeId,
#PageNumber = #PageNumber,
#RowCount = #RowCount
END
End
GO
You could create a temp table with an identity column that increments by 1 for each row inserted, then read the max value of that column to get the row count.

How does one use loops in TSQL?

In TSQL, I would like to change the following code from have to use hard coded dhomes to using a loop for optimization. My failed attempt at trying to add a loop is also included.
Declare #dhome Tinyint, #bp smallint, #lr smallint, #q smallint
// Set #dhome = 1
While(#dhome <= 3) // My attempt to add a loop
SELECT #lr = MAX(NQdDate), #q = NQd
FROM NQdHistory
WHERE dhomeId = #dhome
GROUP BY NQdDate, NQd
SELECT #bd = COUNT(*)
FROM bdhome
WHERE NQdDate= #lr AND dhomeID= #dhome
DELETE FROM ND1 WITH(XLOCK)
WHERE dhomeID= #dhome AND NQdDate= #lr
UPDATE NQdHistory
SET Nbd = #q - ##RowCount - #bp, NBd = #bp
WHERE NQdDate= #lr AND dhomeID= #dhome
Set #dhome = #dhome +1 //My attempt to end a loop
You're on the right track. You're missing your begin and end. Also, be sure to give #dhome a value. It looks like you started to and have it commented out on your third line:
Declare #dhome Tinyint, #bp smallint, #lr smallint, #q smallint
// Set #dhome = 1
While(#dhome <= 3) // My attempt to add a loop
begin
SELECT #lr = MAX(NQdDate), #q = NQd
FROM NQdHistory
WHERE dhomeId = #dhome
GROUP BY NQdDate, NQd
SELECT #bd = COUNT(*)
FROM bdhome
WHERE NQdDate= #lr AND dhomeID= #dhome
DELETE FROM ND1 WITH(XLOCK)
WHERE dhomeID= #dhome AND NQdDate= #lr
UPDATE NQdHistory
SET Nbd = #q - ##RowCount - #bp, NBd = #bp
WHERE NQdDate= #lr AND dhomeID= #dhome
Set #dhome = #dhome +1 //My attempt to end a loop
end
If you're familiar with C/C#/C++, think of T-SQL's Begin and End like curly braces { and }, if you're more familiar with VB Then and End If. Or more like pascals Begin and End. You get the idea :)
Missing a begin and end on your while.
WHILE (Transact-SQL)
Example 1
DECLARE #I INT,#COUNTVAR INT
SET #I = 1
DECLARE #Parent_Child TABLE(ID INT IDENTITY(1,1),ParentPositionID INT NULL,ChildPositionId Int)
INSERT INTO #Parent_Child(ParentPositionID,ChildPositionId)
SELECT DISTINCT PARENT_POSITION_ID,CHILD_POSITION_ID from tblPOSITION_HIERARCHY
--WHERE CHILD_POSITION_ID IN (--YOUR CONDITION IF ANY)
SELECT #COUNTVAR =COUNT(*) FROM #PTS_Parent_Child
DECLARE #int_SUPE_POSITION_ID INT, #int_CHILD_POSITION_ID INT
--loop through records here
WHILE #I <= #COUNTVAR
BEGIN
SELECT #int_SUPE_POSITION_ID=ParentPositionID,#int_CHILD_POSITION_ID=ChildPositionId FROM #Parent_Child WHERE ID=#I
--Whatever you want to do with records
SET #I=#I+1
END
Example 2
Just another approach if you are fine using temp tables.I have personally tested this and it will not cause any exception (even if temp table does not have any data.)
CREATE TABLE #TempTable
(
ROWID int identity(1,1) primary key,
HIERARCHY_ID_TO_UPDATE int,
)
--INSERT DATA INTO TEMP TABLE USING INSERT INTO CLAUSE OR FOR EAXMPLE BELOW
--INSERT INTO #TempTable VALUES(1)
--INSERT INTO #TempTable VALUES(2)
--INSERT INTO #TempTable VALUES(4)
--INSERT INTO #TempTable VALUES(6)
--INSERT INTO ##TempTable VALUES(8)
DECLARE #MAXID INT
SET #COUNTER =1
SELECT #MAXID=COUNT(*) FROM #TempTable
--PRINT #MAXID
WHILE (#MAXID > 0)
BEGIN
--DO THE PROCESSING HERE
SELECT #HIERARCHY_ID_TO_UPDATE =PT.HIERARCHY_ID_TO_UPDATE FROM #TempTable PT WHERE ROWID=#COUNTER
--PRINT '#MAXID VALUE '
--PRINT #MAXID
SET #MAXID=#MAXID-1
SET #COUNTER =#COUNTER + 1
End
If(OBJECT_ID('tempdb..#TempTable') IS NOT NULL)
BEGIN
DROP TABLE #TempTable
END

Sequential Reference Numbers

I have a table currently only two columns because I am learning, the first column is the Book_skey and this is generated automatically by the system using the IDENTITY function, however what I would like to do in the second coloumn is create a automated field called BookRef where the first row will be B00000001 the next row will be B00000002 etc.
USE Occupancy
DECLARE #BookNumber INT
SET #BookNumber = 1
WHILE #BookNumber <= 5000
BEGIN
INSERT INTO Book(BookNumber)
SELECT #BookNumber
SET #BookNumber = #BookNumber + 1
END
Can this be done?
Thanks
Wayne
You just need to use a computed column.
CREATE TABLE Book (
Book_skey int IDENTITY(1,1)
,BookRef as ('B' + RIGHT(CONVERT(CHAR(9), (100000000 + BookNumber)),8))
,BookNumber int
)
SET NOCOUNT ON
DECLARE #BookNumber INT
SET #BookNumber = 1
WHILE #BookNumber <= 5000
BEGIN
INSERT INTO Book(BookNumber)
SELECT #BookNumber
SET #BookNumber = #BookNumber + 1
END
SELECT * FROM Book

Assign a list of integers to an #var

I can:
declare #idOrder int
set #idOrder = 21319
I want:
declare #idOrder int
set #idOrder = (21319, 21320)
for use in a series of statements where the 'WHERE' clause uses the IN operator
delete Orders
where idOrder in #idOrder
instead of
delete Orders
where idOrder in (21319, 21320)
You can't do that as long as it's an int, as that's not a valid value for that datatype. A datatype that could take several integers is a table
declare #idOrder table (id int)
insert into #idOrder values(21319)
insert into #idOrder values(21320)
delete from Orders where idOrder in (select id from #idOrder)
In SQL Server you can also
CREATE FUNCTION [dbo].[fn_ado_param_int] (#ado nvarchar(4000))
RETURNS #VALUES TABLE (ado int)AS
BEGIN
declare #Delim char(1)
set #Delim = ','
DECLARE #chrind INT
DECLARE #Piece nvarchar(4000)
SELECT #chrind = 1
WHILE #chrind > 0
BEGIN
SELECT #chrind = CHARINDEX(#Delim,#ado)
IF #chrind > 0
SELECT #Piece = LEFT(#ado,#chrind - 1)
ELSE
SELECT #Piece = #ado
INSERT #VALUES(ado) VALUES(#Piece)
SELECT #ado = RIGHT(#ado,LEN(#ado) - #chrind)
IF LEN(#ado) = 0 BREAK
END
RETURN
END
declare #idOrder varchar(500);
set #inOrder = "21319,2138,2138";
delete from Orders where id in (select ado from dbo.fn_ado_param_int(#idOrder));