using script in a variable - sql-server

Can you declare a variable, set it as a script definition and then execute it repeatedly throughout your script? I understand how to set a variable to the result of a script, but I want to re-use the definition itself. This is because I want to occasionally get the count from a script and sometimes the top result throughout the rest of my script and I want to make it so the script is easily customized by only needing to change the script once at the beginning.
An example:
declare #RepeatScript nvarchar(200)
declare #count int
declare #topresult int
set #RepeatScript = ' from Table1 where something = 1 and something else > getdate()-5'
set #count = select count(ID) & #RepeatScript
set #topresult = select top 1 (ID) & #RepeatScript
This very simple case would be simple to fix, but if I wanted to reference the same set of information multiple times without having to create and drop a temp_table over and over, this would be very helpful. I do this kind of thing in MS Access all the time, but I can't seem to figure out how to do it in SSMS.

You don't need to repeatedly run these queries. You don't even need to run more than 1 query to capture this information. This will capture both pieces of data in a single query. You can then reference that information anywhere else within the current batch. This meets your criteria of simply changing the script at the beginning.
declare #count int
, #topresult int
select #count = count(ID)
, #topresult = MAX(ID) --MAX would the same thing as top 1 order by ID desc
from Table1
where something = 1

declare #RepeatScript nvarchar(200)
declare #count varchar(200)
declare #topresult varchar(200)
set #RepeatScript = ' from Table1 where something = 1 and something else > getdate()-5'
set #count = 'select count(ID) '+#RepeatScript+''
set #topresult = 'select top 1 (ID)'+#RepeatScript+''
print (#count)
print (#topresult)
Something like that? but instead of using print you would be using exec to run the select statement. Does that help?

Related

Inserting data from one table to another using a loop

I am trying to insert data from one table into another (from table [PPRS] to table [Verify]) where the Caption in PPRS is the same as in table [Master]. Someone suggested i use a loop to insert the data instead of hard coding it, however I am confused as to how to go about it.
Here's my code so far:
Declare #counter int
declare #total int
set #counter = 0
SELECT #total = Count(*) FROM PPRS
while #counter <= #total
begin
set #counter += 1
insert into [Verify]
select [Task_ID],
[Project_StartDate] ,
[PPR_Caption],
[Date]
FROM PPRS
where [PPR_Caption] in (SELECT [Caption] from Master)
end
No data is being inserted (0 rows affected)
The sample data I'm trying to insert:
17286 01/03/2018 MP - Youth Environmental Services (12/15) 15/10/2018
I suggest that this is along the lines of what you want to do:
INSERT INTO [Verify] (some_col) -- you never told us the name of this column
SELECT TOP 10 [PPR_Caption]
FROM PPRS
WHERE [PPR_Caption] IN (SELECT [Caption] FROM MasterRecords)
ORDER BY some_column;
That is, you want to insert ten records into the Verify table, determined by some order in the source PPRS table.
Try to declare specific columns to which you want to make insert. Please provide definition of table PPRS and Verify for more precise help from community.
Anyway idea please find bellow:
Declare #counter int
set #counter = 0
while #counter <= 10
begin
set #counter += 1
insert into [Verify] (NameofColumn1_Table_Verify, NameofColumn2_Table_Verify,...)
select PPRS.NameofColumn1_Table_PPRS,
PPRS.NameofColumn2_Table_PPRS,
...
FROM PPRS
where PPRS.[PPR_Caption] in (SELECT DISTINCT [Caption] from MasterRecords)
end
Anyway I think the loop is unnecesary. One batch should make it. Please write what you want to achieve at the end.

how to use declare variable in select query in stored procedure using sql server

Hello I want to concate two things one is string and other is int variable. Now, these thing I want to store in one variable and use that variable in select query as a into type to create a temptable in stored procedure using sql server.
Here is my query
USE [FlightExamSoftware]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- For Storing Question in Temp table
-- EXEC [GetQuestionListPerSubjectRatioWise] 1,11
ALTER PROCEDURE [dbo].[GetQuestionListPerSubjectRatioWise]
#SubjectID INT,
#NumberOfQue INT,
#UserID int
AS
BEGIN
DECLARE #strQuery VARCHAR(MAX);
DECLARE #PerChapQue INT;
DECLARE #tempTable VARCHAR(MAX) = 'tempTestUser' + #UserID;
SELECT #PerChapQue = COUNT(appQueID)/#NumberOfQue FROM tblQuestion WHERE appQueSubID=#SubjectID
SELECT COUNT(appQueID)/#PerChapQue ChapwiseQue
,CASE WHEN COUNT(appQueID)>=#PerChapQue THEN COUNT(appQueID)/#PerChapQue ELSE 1 END ChapWiseQuePlusOne
,appQueChapID into #tempTable
FROM tblQuestion
WHERE appQueSubID=#SubjectID
GROUP BY appQueChapID
END
Now, I am talking about these line
DECLARE #tempTable VARCHAR(MAX) = 'tempTestUser' + #UserID;
In these line two things are concate one is string and other is int. And store in varchar variable.
And use in following select query i.e.
SELECT COUNT(appQueID)/#PerChapQue ChapwiseQue
,CASE WHEN COUNT(appQueID)>=#PerChapQue THEN COUNT(appQueID)/#PerChapQue ELSE 1 END ChapWiseQuePlusOne
,appQueChapID into #tempTable
FROM tblQuestion
WHERE appQueSubID=#SubjectID
GROUP BY appQueChapID
END
Now, in these query I want to create a temptable named #tempTable.
But, in these line it showing error i.e. Incorrect syntax near '#tempTable'.
Confuse that where is the syntax is wrong.
Thank You.
There are a number of things wrong with your code.
When concatenating an int to a string, you must first cast the int to varchar. Otherwise, SQL Server will try to implicitly convert the string to int, that will result with an error.
So this: DECLARE #tempTable VARCHAR(MAX) = 'tempTestUser' + #UserID; should become this:
DECLARE #tempTable VARCHAR(MAX) = 'tempTestUser' + CAST(#UserID AS VARCHAR(11)); (you need 11 chars to be able to fit the minimum value of int: -2,147,483,648)
You can't use select...into with a table variable.
You can only use it for actual tables (temporary or regular).
your #tempTable isn't even a table variable (not that it will help with a select...into).
Even if you would use select...into the correct way, unless you are going to use a global temporary table (and that doesn't come without it's risks), Unless your stored procedure uses this temporary table later on, it will be useless, since temporary tables are bound to scope.
Taking all of that into consideration I'm not sure what output you are actually looking for. If you could edit your question to include the desired output of your stored procedure as well as some sample data as DDL+DML, it would be easier to help you write better code.
Hope this Dynamic Query helps you:
Try like this:
USE [FlightExamSoftware]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- For Storing Question in Temp table
-- EXEC [GetQuestionListPerSubjectRatioWise] 1,11
ALTER PROCEDURE [dbo].[GetQuestionListPerSubjectRatioWise]
#SubjectID INT,
#NumberOfQue INT,
#UserID int
AS
BEGIN
DECLARE #strQuery VARCHAR(MAX);
DECLARE #PerChapQue INT;
DECLARE #tempTable VARCHAR(MAX) = 'tempTestUser' + CAST(#UserID AS VARCHAR);
SELECT #PerChapQue = COUNT(appQueID)/#NumberOfQue FROM tblQuestion WHERE appQueSubID=#SubjectID
SET #strQuery='
SELECT COUNT(appQueID)/'+CAST(#PerChapQue AS VARCHAR)+' ChapwiseQue
,CASE WHEN COUNT(appQueID)>='+CAST(#PerChapQue AS VARCHAR)+' THEN COUNT(appQueID)/'+CAST(#PerChapQue AS VARCHAR)+' ELSE 1 END ChapWiseQuePlusOne
,appQueChapID
INTO '+#tempTable+'
FROM tblQuestion
WHERE appQueSubID='+CAST(#SubjectID AS VARCHAR)+'
GROUP BY appQueChapID
/*.................................
And you have to use the temp table inside the String only
.................................*/
'
EXEC (#strQuery)
END

Selecting the values from a table in sql using a loop

I have a table with a certain amount of ids and I want to use these seperate ids to retrieve data from another table.
set #CurrentRow = 0
set #RowsToProcess = (SELECT COUNT(*) FROM #QuestionsPrimaryTable)
WHILE(#CurrentRow < #RowsToProcess)
BEGIN
DECLARE #id int
DECLARE #value varchar(200)
SET #CurrentRow = #CurrentRow + 1
SELECT #id = Q.QuestionsId FROM #QuestionsPrimaryTable Q
SET #value = (SELECT Q.QuestionPrimaryDescription FROM QuestionPrimary Q WHERE Q.QuestionPrimaryID = #id)
PRINT #value
END
the seperate id values I am trying to retrieve is 5, 7, 9
as it is at the moment I only retrieve value of 9
How can I retrieve the separate id values?
I'm not sure if what you're after actually requires a loop. Cursors are very rarely required in SQL, so I'd always look to achieve the result without one if possible.
Are you looking for something like this, where you can JOIN QuestionPrimary and #QuestionsPrimaryTable, and filter results where the ID in 5 or 7 or 9?
SELECT qp.QuestionPrimaryID, qp.QuestionPrimaryDescription
FROM QuestionPrimary qp
INNER JOIN #QuestionsPrimaryTable qpt
ON qp.QuestionPrimaryID = qpt.[JOIN_COLUMN]
WHERE qpt.QuestionPrimaryID IN(5,7,9)
If you absolutely need to loop through this data, you will need to add something to your script that will move to the next record in #QuestionsPrimaryTable. The way it is currently written it sets #Id to the same value during every iteration.
Depending on how you plan to use #QuestionsPrimaryTable, you could simply add a delete into the loop to remove the last record you selected.
set #CurrentRow = 0
set #RowsToProcess = (SELECT COUNT(*) FROM #QuestionsPrimaryTable)
WHILE(#CurrentRow < #RowsToProcess)
BEGIN
DECLARE #id int
DECLARE #value varchar(200)
SET #CurrentRow = #CurrentRow + 1
SELECT #id = MAX(Q.QuestionsId) FROM #QuestionsPrimaryTable Q
SET #value = (SELECT Q.QuestionPrimaryDescription FROM QuestionPrimary Q WHERE Q.QuestionPrimaryID = #id)
PRINT #value
DELETE #QuestionsPrimaryTable
WHERE QuestionsId = #id
END
That being said, there is likely a much better way to accomplish this. If you can elaborate on your question, we can probably provide a better solution for you.
Loops should be avoided wherever a set based approach is feasible. Having said that, in case you definitely want a loop, then this should fix your issue:
SET #CurrentRow = #CurrentRow + 1 -- first value is 1
SELECT #id = Q.QuestionsId
FROM (
SELECT QuestionsId,
ROW_NUMBER() OVER (ORDER BY QuestionsId) AS rn
FROM #QuestionsPrimaryTable) Q
WHERE Q.rn = #CurrentRow
In your code the same QuestionsId value is being fetched for each loop iteration. Using ROW_NUMBER() you can access the #CurrentRow record.

Is it possible to modify the value of an input parameter from within the same function to which it was passed?

Currently I am having to assign the value of the parameter to a local variable. I am obviously able to modify that.
Just wanted to trim down the code a bit and lessen the confusion when it comes to parameters and local variables that start with the same values.
i.e.
#parameterAddress vs. #variableAddress
Being able to work with #parameterAddress alone would be much simpler.
You asked about a function, rather than a stored procedure, so the answer should be yes. However you are right to be cautious.
A simple test shows that the variable being passed to the function doesn't get changed. This indicates that the parameter is being passed by value (ie a copy is passed on the stack), and not by reference.
CREATE FUNCTION dbo.MyFunc
(
#input int
)
RETURNS int
BEGIN
SET #input = #input + 3;
RETURN #input;
END;
DECLARE #i INT;
SET #i = 2;
SELECT dbo.MyFunc(#i);
SELECT #i as '#i';
The results are 5 and 2. So #i doesn't get changed, even though #input has been changed inside the function.
If you are speaking of a stored procedure......then here is a response.
(I'm not sure by "function" you mean that in a general sense...or specifically a "user defined function".)
HOWEVER:
Creating a local variable and setting it to a the value of an input-parameter is a WELL KNOWN SQL-PARAMETER-SNIFFING workaround.
http://blogs.msdn.com/b/turgays/archive/2013/09/10/parameter-sniffing-problem-and-workarounds.aspx
look for "Use Local Variables" in the above link.
So your efforts to make the code "trimmer" may have unintended consequences.
Just run this part over and over and see what happens:
Declare #OrderID int
select #OrderID = -99999
EXEC dbo.uspOrderGetByOverrideSurrogateKey #OrderID
Full code:
Use Northwind
GO
/*
Declare #OrderID int
select #OrderID = -99999
EXEC dbo.uspOrderGetByOverrideSurrogateKey #OrderID
*/
IF EXISTS
(
SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_TYPE = N'PROCEDURE' and ROUTINE_SCHEMA = N'dbo' and ROUTINE_NAME = N'uspOrderGetByOverrideSurrogateKey'
)
BEGIN
DROP PROCEDURE [dbo].[uspOrderGetByOverrideSurrogateKey]
END
GO
CREATE Procedure dbo.uspOrderGetByOverrideSurrogateKey (
#OrderID int
)
AS
BEGIN
SET NOCOUNT ON
Select #OrderID = (select top 1 OrderID from dbo.Orders order by newid() )
SELECT o.OrderID,o.CustomerID,o.EmployeeID,o.OrderDate,o.RequiredDate,o.ShippedDate,o.ShipVia ,o.Freight,o.ShipName,o.ShipAddress,o.OrderID,o.CustomerID,o.EmployeeID,o.OrderDate
FROM
dbo.Orders o
WHERE
o.OrderID = #OrderID
SET NOCOUNT OFF
END
GO

What are the different ways to replace a cursor?

I'd like to know your experience(s) with replacing SQL Server cursors in existing code, or how you took a problem that a procedural guy would use a cursor to solve, and did it set-based.
What was the problem the cursor was used to solve? How did you replace the cursor?
try to never loop, work on sets of data.
you can insert, update, delete multiple rows at one time. here in an example insert of multiple rows:
INSERT INTO YourTable
(col1, col2, col3, col4)
SELECT
cola, colb+Colz, colc, #X
FROM ....
LEFT OUTER JOIN ...
WHERE...
When looking at a loop see what it done inside it. If it is just inserts/deletes/updates, re-write to use single commands. If there are IFs, see if those can be CASE statements or WHERE conditions on inserts/deletes/updates. If so, remove the loop and use set commands.
I've taken loops and replaced them with the set based commands and reduced the execution time from minutes to a few seconds. I have taken procedures with many nested loops and procedure calls and kept the loops (was impossible to only use inserts/deletes/updates), but I removed the cursor, and have seen less locking/blocking and massive performance boosts as well. Here are two looping methods that are better than cursor loops...
if you have to loop, over a set do something like this:
--this looks up each row for every iteration
DECLARE #msg VARCHAR(250)
DECLARE #hostname sysname
--first select of currsor free loop
SELECT #hostname= min(RTRIM(hostname))
FROM master.dbo.sysprocesses (NOLOCK)
WHERE hostname <> ''
WHILE #hostname is not null
BEGIN
set #msg='exec master.dbo.xp_cmdshell "net send '
+ RTRIM(#hostname) + ' '
+ 'testing "'
print #msg
--EXEC (#msg)
--next select of cursor free loop
SELECT #hostname= min(RTRIM(hostname))
FROM master.dbo.sysprocesses (NOLOCK)
WHERE hostname <> ''
and hostname > #hostname
END
if you have a reasonable set of items (not 100,000) to loop over you can do this:
--this will capture each Key to loop over
DECLARE #msg VARCHAR(250)
DECLARE #From int
DECLARE #To int
CREATE TABLE #Rows
(
RowID int not null primary key identity(1,1)
,hostname varchar(100)
)
INSERT INTO #Rows
SELECT DISTINCT hostname
FROM master.dbo.sysprocesses (NOLOCK)
WHERE hostname <> ''
SELECT #From=0,#To=##ROWCOUNT
WHILE #From<#To
BEGIN
SET #From=#From+1
SELECT #msg='exec master.dbo.xp_cmdshell "net send '
+ RTRIM(hostname) + ' '
+ 'testing "'
FROM #Rows WHERE RowID=#From
print #msg
--EXEC (#msg)
END
I've replaced some cursors with WHILE loops.
DECLARE #SomeTable TABLE
(
ID int IDENTITY (1, 1) PRIMARY KEY NOT NULL,
SomeNumber int,
SomeText varchar
)
DECLARE #theCount int
DECLARE #theMax int
DECLARE #theNumber int
DECLARE #theText varchar
INSERT INTO #SomeTable (SomeNumber, SomeText)
SELECT Number, Text
FROM PrimaryTable
SET #theCount = 1
SELECT #theMax = COUNT(ID) FROM #SomeTable
WHILE (#theCount <= #theMax)
BEGIN
SET #theNumber = 0
SET #theText = ''
SELECT #theNumber = IsNull(Number, 0), #theText = IsNull(Text, 'nothing')
FROM #SomeTable
WHERE ID = #theCount
-- Do something.
PRINT 'This is ' + #theText + ' from record ' + CAST(#theNumber AS varchar) + '.'
SET #theCount = #theCount + 1
END
PRINT 'Done'
Well, often an app dev used to procedural programming will - out of habit - try to do everything procedurally, even in SQL.
Most often, a SELECT with the right paramters might do - or maybe you're dealing with an UPDATE statement.
The point really is: you need to begin to think in set operations and tell your RDBMS what you want done - not how to do it step by step.
It's hard to give a single, "right" answer to this..... you'd almost have to show it with a concrete example.
Marc
I wrote some code that calculated running totals for financial data related to a given year. In each quarter, I had to add the value for the current quarter to the running total while handling NULLs appropriately so that the running total for the previous quarter carried over when the value for the current quarter was NULL.
Originally, I did this using a cursor and from a functional standpoint this met the business requirement. From a technical standpoint, it turned out to be a show-stopper because as the amount of data increased the code took exponentially longer. The solution was to replace the cursor with a correlated sub-query which met the functional requirements and eliminated any performance issues.
Hope this helps,
Bill
While (see what I did there) the question is asked and answered, I wanted to add my response just in case it would help anyone else. Like many others, I'm converting over to while statements, but I wanted to leverage the existing primary key in my data set rather rather than counting up to the size of the result set. I came up with this pattern, which has been working well. I'm open to other others and improvements. Thanks!
declare #PrimaryKey int
/* get first record for looping */
select #PrimaryKey = min(PrimaryKey) from Table
/* it will be null when all records have iterated */
while (#PrimaryKey is not null)
begin
/* do stuff */
/* iterate to next key */
select #PrimaryKey = min(PrimaryKey) from Table where PrimaryKey > #PrimaryKey
end

Resources