I have a simple while loop and in the loop I am declaring a DECLARE #TABLE with one column and its data type is id. While the loop loops the records I am just inserting the values into the table variable. Whenever the loop is looping it the Declare #TABLE should get recreated and the old values should not exist. But its not happening. Below is the code
Declare #V int
Set #V = 1
While (#V <= 3)
begin
DECLARE #Changes table
(
Id int
)
Insert into #Changes
Values (#V)
select * from #Changes
SET #V=#V+1
END
In normal we should get the output as
1
2
3
But the output i am getting is
1
1
2
1
2
3
Which is wrong.
Is this the normal behavior or a bug in SQL
That's normal. T-SQL is a very odd language. Variable declarations affect when a variable can be referenced, but they don't actually participate in control flow.
Consider:
IF 1 = 0
BEGIN
DECLARE #a int
END
SET #a = 1
PRINT #a
Actually prints 1. It doesn't complain about an undeclared variable.
As Allan notes in the comments, if you want #Changes to be empty at the start of each loop, make it so:
Declare #V int
DECLARE #Changes table
(
Id int
)
Set #V = 1
While (#V <= 3)
begin
delete from #Changes
Insert into #Changes
Values (#V)
select * from #Changes
SET #V=#V+1
END
Related
I have this code:
DECLARE #TotalPayment DECIMAL(18,4)
DECLARE #GetTotalPaymentAmount AS TABLE
(
Amount DECIMAL(18,4),
CurrencyId CHAR(3)
)
INSERT INTO #GetTotalPaymentAmount
SELECT SUM(Amount), CurrencyId
FROM [dbo].[fn_DepositWithdrawReport]()
WHERE OperationTypeId = 2
GROUP BY CurrencyId
SET #TotalPayment = (SELECT Amount FROM #GetTotalPaymentAmount)
I am getting this error
Subquery returned more than 1 value.
So yes, I know that the issue in SET logic because #GetTotalPayment returning more than one row. If I am using for example TOP 1, it is working great, but I need all values of that table. How could I get all values and assign them to local variables from that table?
I am getting table like this
A 'C
---'---
10 'USD
20 'EURO
'
and I need to retrieve all of these values.
Please note that I do not know how many rows will be returned from temp table and saying just declare second variable won't work. The whole point of this would be eventually pass that variables to function as input parameter.
Here, I modified your code slightly. Should work :)
DECLARE #TotalPayment DECIMAL(18,4)
DECLARE #GetTotalPaymentAmount AS TABLE
(
Id int identity(1,1),--added Id column
Amount DECIMAL(18,4),
CurrencyId CHAR(3)
)
INSERT INTO #GetTotalPaymentAmount
SELECT SUM(Amount),CurrencyId
FROM [dbo].[fn_DepositWithdrawReport]()
WHERE OperationTypeId = 2
GROUP BY CurrencyId
declare #i int, #cnt int
set #i = 1
select #cnt = COUNT(*) from #GetTotalPaymentAmount
while #i <= #cnt
begin
select #TotalPayment = Amount from #GetTotalPaymentAmount where Id = #i
--do stuff with retrieved value
#i += 1
end
#So_Op
If you want the data to use in a SCALAR function then just do this
SELECT
G.Amount
,dbo.FN_ScalarFunction(G.Amount)
FROM
#GetTotalPaymentAmount G
If it's a TABLE Function then this works
SELECT
G.Amount
,F.ReturnValue
FROM
#GetTotalPaymentAmount G
CROSS APPLY
dbo.FN_TableFunction(G.Amount) F
I'm having a some troubles with a complicated query, that required me to insert something in a table, but if I found that two columns are the same I should stop the transaction with a trigger. I make some code to do that, but I'm not sure 100% of it even when it works fine now.
alter trigger TR1
on Passer instead of insert
as
begin
declare #A int
declare #B int
declare #C int
set #A = (select code_ligne from inserted)
set #B = (select ordre_passage from inserted)
set #C = (select code_ville from inserted)
select * from passer
where code_ligne = #A
and ordre_passage = #B
if(##rowcount = 0 )
begin
insert into Passer values(#A,#C,#B)
print 'okay'
print ##rowcount
end
end
When you have scalar variables like this in a trigger you are going to run into problems. If you have two rows inserted at once you will only get 1 row inserted into Passer. You don't need these variables at all. Just switch this around to be a single set based insert statement. Something along these lines.
alter trigger TR1 on Passer instead of insert as
begin
insert into Passer
(
code_ligne
, ordre_passage
, code_ville
)
select i.code_ligne
, i.ordre_passage
, i.code_ville
from inserted i
join Passer p on p.code_ligne = i.code_ligne
and p.ordre_passage = i.ordre_passage
if(##rowcount = 0 ) begin
print 'okay'
print ##rowcount
end
end
I'm trying to create a function that return a table variable.So firstly i get data from Table1 and put it in another table variable. Here i want check if this variable isempty the function return the parameter result else return the result of the table variable
The function script is bellow :
USE[DATABase1]
GO
IF OBJECT_ID (N'CodeFunc', N'TF') IS NOT NULL DROP FUNCTION dbo.CodeFunc;
GO
CREATE FUNCTION CodeFunc ( #Code nvarchar(4) , #Table nvarchar(40) = '' )
RETURNS #VirtualDAT TABLE
(
RowID INT IDENTITY ( 1 , 1 ),
Code nvarchar(400)
)
AS
BEGIN
DECLARE #CodeM nvarchar(400)
DECLARE #imax INT SET #imax = ##ROWCOUNT
DECLARE #i INT SET #i = 1
DECLARE #SelectDAT TABLE
(
RowID INT IDENTITY ( 1 , 1 ),
Code nvarchar(400)
)
INSERT #SelectDAT
SELECT Code FROM table1
WHERE table1.id = 41
IF(EXISTS (SELECT 1 FROM #SelectDAT))
BEGIN
WHILE (#i <= #imax)
BEGIN
SELECT #CodeM = Code FROM #SelectDAT WHERE RowID = #i
INSERT INTO #VirtualDAT(Code) VALUES (#CodeM)
SET #i = #i + 1
END
END
ELSE
INSERT INTO #VirtualDAT(Code) VALUES (#Code)
RETURN
END
So this script works without put it inside function.
And i test this function like this :SELECT * FROM dbo.CodeFunc( 'toto',Default ) the result is :
IF(EXISTS (SELECT 1 FROM #SelectDAT)) no record returned
esle the result is ok
As VR46 says. The ##ROWCOUNT will be set to 0 because there is no query before it. Any code executing in a function happens as a seperate set of queries. It was probably returning a value outside the function because you had previously used the query window for another unrelated query
You could re-factor this function quite dramatically. Look below, ##ROWCOUNT will work here as it is just after the insert query and will definitely have a value based on the insert.
I have not been able to test this, but I think something like this should do the same job.
USE[DATABase1]
GO
IF OBJECT_ID (N'CodeFunc', N'TF') IS NOT NULL DROP FUNCTION dbo.CodeFunc;
GO
CREATE FUNCTION CodeFunc ( #Code nvarchar(4) , #Table nvarchar(40) = '' )
RETURNS #VirtualDAT TABLE
(
RowID INT IDENTITY ( 1 , 1 ),
Code nvarchar(400)
)
AS
BEGIN
insert into #VirtualDAT
Select Code from table1 where table1.id = 41
if ##ROWCOUNT = 0
begin
INSERT INTO #VirtualDAT(Code) VALUES (#Code)
end
RETURN
END
Since you are assigning #imax with ##ROWCOUNT right after declaration of variable will be initialized with zero.
From MSDN ##ROWCOUNT
Returns the number of rows affected by the last statement.
If am not wrong you need to assign value to #imax after the insert into..select query.
INSERT #SelectDAT
SELECT Code FROM table1
WHERE table1.id = 41
SET #imax= ##ROWCOUNT
You can do the same in SET BASED APPROACH without using while loop.
CREATE FUNCTION Codefunc (#Code NVARCHAR(4),
#Table NVARCHAR(40) = '')
returns #VirtualDAT TABLE (
rowid INT IDENTITY ( 1, 1 ),
code NVARCHAR(400))
AS
BEGIN
IF EXISTS (SELECT code
FROM table1
WHERE table1.id = 41)
BEGIN
INSERT INTO #VirtualDAT
(code)
SELECT code
FROM table1
WHERE table1.id = 41
END
ELSE
INSERT INTO #VirtualDAT
(code)
VALUES (#Code)
RETURN
END
My question here is how do I use a variable to be a column name in a select statement. I have created the variable #B to be a column name that is BGNDATE1 through BGNDATE12. Rather than have 12 select statements I created a while loop. The column name is basically BGNDATE + the incremented integer.
The error I am getting is:
Conversion failed when converting the varchar value 'BGNDATE1' to data type int.
USE X --this is the database
DECLARE #DATES TABLE (ROWID INT, FISCDATES INT)
DECLARE #FY INT = 2012
DECLARE #I INT
DECLARE #IV VARCHAR(2)
DECLARE #B VARCHAR(9)
SELECT #FY AS FY
SET #I = 1
WHILE #I <= 12
BEGIN
SET #IV = #I
SET #B = 'BGNDATE' + #IV
INSERT INTO #DATES (ROWID)
SELECT #I
MERGE INTO #DATES AS T
USING (
--This is where the error is with regards to the variable #B
SELECT #B AS FISCDATES FROM DBO.Y -- Y is the table in the database
WHERE FSCYEAR = #FY) AS S
ON T.ROWID = #I
WHEN MATCHED
THEN UPDATE
SET T.FISCDATES = S.FISCDATES;
SET #I = #I + 1
END
SELECT * FROM #DATES
You can't use a variable as a column name (unless you create the entire query dynamically), but you can use a variable to select from different columns:
...
SELECT
CASE #IV
WHEN 1 THEN BGNDATE1
WHEN 2 THEN BGNDATE2
WHEN 3 THEN BGNDATE3
WHEN 4 THEN BGNDATE4
WHEN 5 THEN BGNDATE5
WHEN 6 THEN BGNDATE6
WHEN 7 THEN BGNDATE7
WHEN 8 THEN BGNDATE8
WHEN 9 THEN BGNDATE9
WHEN 10 THEN BGNDATE10
WHEN 11 THEN BGNDATE11
WHEN 12 THEN BGNDATE12
END AS FISCDATES FROM DBO.Y
...
When you select #B that won't work because #B is not a column name, it is a variable.
It would be best to denormalize the table so instead of having 12 columns named BGNDATE 1 through 12 you had another table to join to.
If you can't do that, do it with dynamic sql:
exec('MERGE INTO #DATES AS T
USING (
SELECT ' + #B + ' AS FISCDATES FROM DBO.Y
WHERE FSCYEAR = #FY) AS S
ON T.ROWID = #I
WHEN MATCHED
THEN UPDATE
SET T.FISCDATES = S.FISCDATES;')
I need to use a select expression in a while loop and I use the below sample code:
declare #i integer
set #i=1
while (#i<10)
begin
select #i as m;
set #i=#i+1
END
this code returns 10 separate table! I want it to return all select results in one table... is that possible? if yes... how?
You can use a temp table or table variable for this.
Here's how to do it using a temp table.
CREATE TABLE #t (m INT)
DECLARE #i INT
SET #i=1
WHILE (#i<10)
BEGIN
INSERT INTO #t SELECT #i
SET #i=#i+1
END
SELECT m FROM #t
Very similar with a table variable
DECLARE #t TABLE (m INT)
DECLARE #i INT
SET #i=1
WHILE (#i<10)
BEGIN
INSERT INTO #t SELECT #i
SET #i=#i+1
END
SELECT m FROM #t
It is not possible. Each SELECT statement generates its own result set. You can use temp table to add results of each iteration and then get all in one table. To generate sequence of integers you can use this (for SQL SERVER 2005 + )
;WITH CTE
AS
(
SELECT 1 N
UNION ALL
SELECT N + 1 FROM CTE
WHERE N<10
)
SELECT N FROM CTE
squillman got it...with #t (create table # - table persisted at top-level scope while session is open - if you have several batch statements, you can reference this table in any after declaration until you drop the table)
Cris also got it with #test (declare # table - variable - only persisted in the current scope - single execution batch block...note that there are several performance issues that can be introduced if you use this)
the last type of temp table you can use is a global temp table (create table ## - lasts as long as the session that created it stays open or until it is dropped)
with #t, you may want to add this to the beginning of your script if you don't close the session:
IF OBJECT_ID('tempdb..#t') IS NOT NULL
DROP TABLE #t
Enjoy the temp tables!
declare #i integer
DECLARE #test TABLE(
m /*your data type*/
)
set #i=1
while (#i<10)
begin
insert into #test select #i;
set #i=#i+1
END
select * from #test