In MYSQL, I could use variable to generate the row number like below:
SET #num=0;
SELECT #num:=#num+1 rownum,id FROM test;
How should I do this in MSSQL? I tried below code:
DECLARE #num int
SET #num=0;
SELECT #num=#num+1 ,id FROM test;
then I got the error message:
A SELECT statement that assigns a value to a variable must not be
combined with data-retrieval operations.
I know there're some built-in functions in mssql to generate the row number, but I'd like to know how to use variables in this case to solve the issue.
Appreciate if someone could help on this. :)
As the error message says, you can't do variable assignment in a select statement that also does data retrieval.
You can assign a variable using a select statement...
declare #myInt int;
select #myvariable = 1; -- works just like "set" in this case
And you can do data retrieval...
select someColumn from MyTable
But not both at the same time.
You can assign a value to a variable when selecting from a table, as long as you don't also select columns that don't get assigned to variables.
declare #myInt int;
select #myInt = myIntColumn from MyTable
If MyTable has a lot of rows in it, it's not clear which row will be used to populate the value of #myInt. SQL is "allowed" to pick any row it wants.
Of course, in your case that doesn't matter, since you're assigning a literal value to the variable. You can do this, where by "can", I mean the syntax won't be rejected:
declare #myInt int = 0;
select #myInt = #myInt + 1
from MyTable
But don't do this. It's an interesting quirk, which I am showing it to you with the good faith assumption that you won't use it. It is not supported and should not be relied upon.
Jeff Moden wrote an article about this where he goes over the dangers.
But don't do it even if you follow his rules.
If you want to create a rownum based on the variable value, you can do that by updating the value in the table like below:
drop table if exists #t
create table #t (id int, rownum int)
insert #t(id) select 100
insert #t(id) select 200
insert #t(id) select 300
insert #t(id) select 400
declare #i int
set #i = 0
update #t set rownum = #i, #i = #i + 1
select * from #t
order by rownum
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.
Suggest me the proper syntex for below raw condition.
ALTER PROCEDURE [dbo].[pro_name]
#number as int ,
Select * from table
if(#number=0)
begin
set #number=select max(number)from table
end
where table.number=#number
here i need to set #number with max value if input value passed is '0'. and also want to use th e same in wherer clause.
ALTER PROCEDURE [dbo].[pro_name]
#number as int=0 --I recommend a default value here too
as
set #number=case #number when 0 then (select max(number)from [table]) else #number end;
Select * from [table] t
where t.number=#number;
go
EDIT:
Since there is not point in having two exactly the same answers and I was beaten, here is an alternate solution:
alter procedure [dbo].[pro_name]
#number as int as
select top 1 * from [table]
where number = #number or #number = 0
order by [table].number desc
However this will only work if the number column is unique, which may or may not be the case in your case.
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
Is there a way to make a TSQL variable constant?
No, but you can create a function and hardcode it in there and use that.
Here is an example:
CREATE FUNCTION fnConstant()
RETURNS INT
AS
BEGIN
RETURN 2
END
GO
SELECT dbo.fnConstant()
One solution, offered by Jared Ko is to use pseudo-constants.
As explained in SQL Server: Variables, Parameters or Literals? Or… Constants?:
Pseudo-Constants are not variables or parameters. Instead, they're simply views with one row, and enough columns to support your constants. With these simple rules, the SQL Engine completely ignores the value of the view but still builds an execution plan based on its value. The execution plan doesn't even show a join to the view!
Create like this:
CREATE SCHEMA ShipMethod
GO
-- Each view can only have one row.
-- Create one column for each desired constant.
-- Each column is restricted to a single value.
CREATE VIEW ShipMethod.ShipMethodID AS
SELECT CAST(1 AS INT) AS [XRQ - TRUCK GROUND]
,CAST(2 AS INT) AS [ZY - EXPRESS]
,CAST(3 AS INT) AS [OVERSEAS - DELUXE]
,CAST(4 AS INT) AS [OVERNIGHT J-FAST]
,CAST(5 AS INT) AS [CARGO TRANSPORT 5]
Then use like this:
SELECT h.*
FROM Sales.SalesOrderHeader h
JOIN ShipMethod.ShipMethodID const
ON h.ShipMethodID = const.[OVERNIGHT J-FAST]
Or like this:
SELECT h.*
FROM Sales.SalesOrderHeader h
WHERE h.ShipMethodID = (SELECT TOP 1 [OVERNIGHT J-FAST] FROM ShipMethod.ShipMethodID)
My workaround to missing constans is to give hints about the value to the optimizer.
DECLARE #Constant INT = 123;
SELECT *
FROM [some_relation]
WHERE [some_attribute] = #Constant
OPTION( OPTIMIZE FOR (#Constant = 123))
This tells the query compiler to treat the variable as if it was a constant when creating the execution plan. The down side is that you have to define the value twice.
No, but good old naming conventions should be used.
declare #MY_VALUE as int
There is no built-in support for constants in T-SQL. You could use SQLMenace's approach to simulate it (though you can never be sure whether someone else has overwritten the function to return something else…), or possibly write a table containing constants, as suggested over here. Perhaps write a trigger that rolls back any changes to the ConstantValue column?
Prior to using a SQL function run the following script to see the differences in performance:
IF OBJECT_ID('fnFalse') IS NOT NULL
DROP FUNCTION fnFalse
GO
IF OBJECT_ID('fnTrue') IS NOT NULL
DROP FUNCTION fnTrue
GO
CREATE FUNCTION fnTrue() RETURNS INT WITH SCHEMABINDING
AS
BEGIN
RETURN 1
END
GO
CREATE FUNCTION fnFalse() RETURNS INT WITH SCHEMABINDING
AS
BEGIN
RETURN ~ dbo.fnTrue()
END
GO
DECLARE #TimeStart DATETIME = GETDATE()
DECLARE #Count INT = 100000
WHILE #Count > 0 BEGIN
SET #Count -= 1
DECLARE #Value BIT
SELECT #Value = dbo.fnTrue()
IF #Value = 1
SELECT #Value = dbo.fnFalse()
END
DECLARE #TimeEnd DATETIME = GETDATE()
PRINT CAST(DATEDIFF(ms, #TimeStart, #TimeEnd) AS VARCHAR) + ' elapsed, using function'
GO
DECLARE #TimeStart DATETIME = GETDATE()
DECLARE #Count INT = 100000
DECLARE #FALSE AS BIT = 0
DECLARE #TRUE AS BIT = ~ #FALSE
WHILE #Count > 0 BEGIN
SET #Count -= 1
DECLARE #Value BIT
SELECT #Value = #TRUE
IF #Value = 1
SELECT #Value = #FALSE
END
DECLARE #TimeEnd DATETIME = GETDATE()
PRINT CAST(DATEDIFF(ms, #TimeStart, #TimeEnd) AS VARCHAR) + ' elapsed, using local variable'
GO
DECLARE #TimeStart DATETIME = GETDATE()
DECLARE #Count INT = 100000
WHILE #Count > 0 BEGIN
SET #Count -= 1
DECLARE #Value BIT
SELECT #Value = 1
IF #Value = 1
SELECT #Value = 0
END
DECLARE #TimeEnd DATETIME = GETDATE()
PRINT CAST(DATEDIFF(ms, #TimeStart, #TimeEnd) AS VARCHAR) + ' elapsed, using hard coded values'
GO
If you are interested in getting optimal execution plan for a value in the variable you can use a dynamic sql code. It makes the variable constant.
DECLARE #var varchar(100) = 'some text'
DECLARE #sql varchar(MAX)
SET #sql = 'SELECT * FROM table WHERE col = '''+#var+''''
EXEC (#sql)
For enums or simple constants, a view with a single row has great performance and compile time checking / dependency tracking ( cause its a column name )
See Jared Ko's blog post https://blogs.msdn.microsoft.com/sql_server_appendix_z/2013/09/16/sql-server-variables-parameters-or-literals-or-constants/
create the view
CREATE VIEW ShipMethods AS
SELECT CAST(1 AS INT) AS [XRQ - TRUCK GROUND]
,CAST(2 AS INT) AS [ZY - EXPRESS]
,CAST(3 AS INT) AS [OVERSEAS - DELUXE]
, CAST(4 AS INT) AS [OVERNIGHT J-FAST]
,CAST(5 AS INT) AS [CARGO TRANSPORT 5]
use the view
SELECT h.*
FROM Sales.SalesOrderHeader
WHERE ShipMethodID = ( select [OVERNIGHT J-FAST] from ShipMethods )
Okay, lets see
Constants are immutable values which are known at compile time and do not change for the life of the program
that means you can never have a constant in SQL Server
declare #myvalue as int
set #myvalue = 5
set #myvalue = 10--oops we just changed it
the value just changed
Since there is no build in support for constants, my solution is very simple.
Since this is not supported:
Declare Constant #supplement int = 240
SELECT price + #supplement
FROM what_does_it_cost
I would simply convert it to
SELECT price + 240/*CONSTANT:supplement*/
FROM what_does_it_cost
Obviously, this relies on the whole thing (the value without trailing space and the comment) to be unique. Changing it is possible with a global search and replace.
There are no such thing as "creating a constant" in database literature. Constants exist as they are and often called values. One can declare a variable and assign a value (constant) to it. From a scholastic view:
DECLARE #two INT
SET #two = 2
Here #two is a variable and 2 is a value/constant.
SQLServer 2022 (currently only as Preview available) is now able to Inline the function proposed by SQLMenace, this should prevent the performance hit described by some comments.
CREATE FUNCTION fnConstant() RETURNS INT AS BEGIN RETURN 2 END GO
SELECT is_inlineable FROM sys.sql_modules WHERE [object_id]=OBJECT_ID('dbo.fnConstant');
is_inlineable
1
SELECT dbo.fnConstant()
ExecutionPlan
To test if it also uses the value coming from the Function, I added a second function returning value "1"
CREATE FUNCTION fnConstant1()
RETURNS INT
AS
BEGIN
RETURN 1
END
GO
Create Temp Table with about 500k rows with Value 1 and 4 rows with Value 2:
DROP TABLE IF EXISTS #temp ;
create table #temp (value_int INT)
DECLARE #counter INT;
SET #counter = 0
WHILE #counter <= 500000
BEGIN
INSERT INTO #temp VALUES (1);
SET #counter = #counter +1
END
SET #counter = 0
WHILE #counter <= 3
BEGIN
INSERT INTO #temp VALUES (2);
SET #counter = #counter +1
END
create index i_temp on #temp (value_int);
Using the describe plan we can see that the Optimizer expects 500k values for
select * from #temp where value_int = dbo.fnConstant1(); --Returns 500001 rows
Constant 1
and 4 rows for
select * from #temp where value_int = dbo.fnConstant(); --Returns 4rows
Constant 2
Robert's performance test is interesting. And even in late 2022, the scalar functions are much slower (by an order of magnitude) than variables or literals. A view (as suggested mbobka) is somewhere in-between when used for this same test.
That said, using a loop like that in SQL Server is not something I'd ever do, because I'd normally be operating on a whole set.
In SQL 2019, if you use schema-bound functions in a set operation, the difference is much less noticeable.
I created and populated a test table:
create table #testTable (id int identity(1, 1) primary key, value tinyint);
And changed the test so that instead of looping and changing a variable, it queries the test table and returns true or false depending on the value in the test table, e.g.:
insert #testTable(value)
select case when value > 127
then #FALSE
else #TRUE
end
from #testTable with(nolock)
I tested 5 scenarios:
hard-coded values
local variables
scalar functions
a view
a table-valued function
running the test 10 times, yielded the following results:
scenario
min
max
avg
scalar functions
233
259
240
hard-coded values
236
265
243
local variables
235
278
245
table-valued function
243
272
253
view
244
267
254
Suggesting to me, that for set-based work in (at least) 2019 and better, there's not much in it.
set nocount on;
go
-- create test data table
drop table if exists #testTable;
create table #testTable (id int identity(1, 1) primary key, value tinyint);
-- populate test data
insert #testTable (value)
select top (1000000) convert(binary (1), newid())
from sys.all_objects a
, sys.all_objects b
go
-- scalar function for True
drop function if exists fnTrue;
go
create function dbo.fnTrue() returns bit with schemabinding as
begin
return 1
end
go
-- scalar function for False
drop function if exists fnFalse;
go
create function dbo.fnFalse () returns bit with schemabinding as
begin
return 0
end
go
-- table-valued function for booleans
drop function if exists dbo.tvfBoolean;
go
create function tvfBoolean() returns table with schemabinding as
return
select convert(bit, 1) as true, convert(bit, 0) as false
go
-- view for booleans
drop view if exists dbo.viewBoolean;
go
create view dbo.viewBoolean with schemabinding as
select convert(bit, 1) as true, convert(bit, 0) as false
go
-- create table for results
drop table if exists #testResults
create table #testResults (id int identity(1,1), test int, elapsed bigint, message varchar(1000));
-- define tests
declare #tests table(testNumber int, description nvarchar(100), sql nvarchar(max))
insert #tests values
(1, N'hard-coded values', N'
declare #testTable table (id int, value bit);
insert #testTable(id, value)
select id, case when t.value > 127
then 0
else 1
end
from #testTable t')
, (2, N'local variables', N'
declare #FALSE as bit = 0
declare #TRUE as bit = 1
declare #testTable table (id int, value bit);
insert #testTable(id, value)
select id, case when t.value > 127
then #FALSE
else #TRUE
end
from #testTable t'),
(3, N'scalar functions', N'
declare #testTable table (id int, value bit);
insert #testTable(id, value)
select id, case when t.value > 127
then dbo.fnFalse()
else dbo.fnTrue()
end
from #testTable t'),
(4, N'view', N'
declare #testTable table (id int, value bit);
insert #testTable(id, value)
select id, case when value > 127
then b.false
else b.true
end
from #testTable t with(nolock), viewBoolean b'),
(5, N'table-valued function', N'
declare #testTable table (id int, value bit);
insert #testTable(id, value)
select id, case when value > 127
then b.false
else b.true
end
from #testTable with(nolock), dbo.tvfBoolean() b')
;
declare #testNumber int, #description varchar(100), #sql nvarchar(max)
declare #testRuns int = 10;
-- execute tests
while #testRuns > 0 begin
set #testRuns -= 1
declare testCursor cursor for select testNumber, description, sql from #tests;
open testCursor
fetch next from testCursor into #testNumber, #description, #sql
while ##FETCH_STATUS = 0 begin
declare #TimeStart datetime2(7) = sysdatetime();
execute sp_executesql #sql;
declare #TimeEnd datetime2(7) = sysdatetime()
insert #testResults(test, elapsed, message)
select #testNumber, datediff_big(ms, #TimeStart, #TimeEnd), #description
fetch next from testCursor into #testNumber, #description, #sql
end
close testCursor
deallocate testCursor
end
-- display results
select test, message, count(*) runs, min(elapsed) as min, max(elapsed) as max, avg(elapsed) as avg
from #testResults
group by test, message
order by avg(elapsed);
The best answer is from SQLMenace according to the requirement if that is to create a temporary constant for use within scripts, i.e. across multiple GO statements/batches.
Just create the procedure in the tempdb then you have no impact on the target database.
One practical example of this is a database create script which writes a control value at the end of the script containing the logical schema version. At the top of the file are some comments with change history etc... But in practice most developers will forget to scroll down and update the schema version at the bottom of the file.
Using the above code allows a visible schema version constant to be defined at the top before the database script (copied from the generate scripts feature of SSMS) creates the database but used at the end. This is right in the face of the developer next to the change history and other comments, so they are very likely to update it.
For example:
use tempdb
go
create function dbo.MySchemaVersion()
returns int
as
begin
return 123
end
go
use master
go
-- Big long database create script with multiple batches...
print 'Creating database schema version ' + CAST(tempdb.dbo.MySchemaVersion() as NVARCHAR) + '...'
go
-- ...
go
-- ...
go
use MyDatabase
go
-- Update schema version with constant at end (not normally possible as GO puts
-- local #variables out of scope)
insert MyConfigTable values ('SchemaVersion', tempdb.dbo.MySchemaVersion())
go
-- Clean-up
use tempdb
drop function MySchemaVersion
go