SQL Server : sum by cursor - sql

This SQL Server 2012 function, and I want it to return all values by the cursor, but this SUM only last record from base.
When I want to SUM by:
SET #AllSalary = #salarya + #AllSalary
it shows NULL.
I don't know what is the problem, it could be syntax error, fact is it doesn't display the desired result.
(param #montha INT, will by used later)
CREATE FUNCTION allCasR
(
#montha INT
)
RETURNS INT
AS
BEGIN
-- Declare the return variable here
DECLARE #AllSalary INT;
DECLARE #salarya FLOAT;
DECLARE #tymC FLOAT;
-- Add the T-SQL statements to compute the return value here
DECLARE kursor_pensja CURSOR FOR
SELECT contracts.salary
FROM dbo.contracts ;
OPEN kursor_pensja;
FETCH NEXT FROM kursor_pensja INTO #salarya
WHILE ##FETCH_STATUS=0
BEGIN
SET #AllSalary =+ #salarya
FETCH NEXT FROM kursor_pensja INTO #salarya
END
CLOSE kursor_pensja
DEALLOCATE kursor_pensja
RETURN #AllSalary;
END

WHY on earth would you want to use a cursor for this??
Just use a SUM and since you're seeing NULL values, use ISNULL to convert NULL values into 0 (zero):
CREATE FUNCTION allCasR (#montha INT)
RETURNS INT
AS
BEGIN
DECLARE #AllSalary INT;
SELECT #AllSalary = SUM(ISNULL(salary, 0))
FROM dbo.Contracts
RETURN #AllSalary;
END
Update: if you must use a cursor as an exercise, then you need to make sure
that you properly initialize your value for #AllSalary to 0 (otherwise it's NULL from the beginning and will never get any other value!)
that you take care of the NULL values in the database table while iterating over the rows that would cause your entire SUM to be NULL in the end (either by excluding those values from the cursor with a WHERE salary IS NOT NULL or by applying a ISNULL(....) to the value being summed up):
Code should be:
CREATE FUNCTION allCasR (#montha INT)
RETURNS INT
AS
BEGIN
-- Declare the return variable here
DECLARE #AllSalary INT;
DECLARE #salarya FLOAT;
DECLARE #tymC FLOAT;
-- Add the T-SQL statements to compute the return value here
DECLARE kursor_pensja CURSOR FOR
SELECT contracts.salary
FROM dbo.contracts ;
-- you need to INITIALIZE this value to 0 !!!!!
SET #AllSalary = 0;
OPEN kursor_pensja;
FETCH NEXT FROM kursor_pensja INTO #salarya
WHILE ##FETCH_STATUS=0
BEGIN
-- you need to make sure to use ISNULL(.., 0) to avoid a NULL value in the SUM
SET #AllSalary += ISNULL(#salarya, 0);
FETCH NEXT FROM kursor_pensja INTO #salarya
END
CLOSE kursor_pensja
DEALLOCATE kursor_pensja
RETURN #AllSalary;
END

SET #AllSalary =+ #salarya
Your + sign is after the =, that's why it doesn't work as expected.
It should be:
SET #AllSalary += #salarya;
EDIT:
"That return NULL"
If it returns NULL that means some of your values are NULL.
Use SELECT ISNULL(contracts.salary, 0) rather than SELECT contracts.salary.

Related

In SQL Server can I implement the avg function manually? problem passing the name of the column to use

In SQL Server I want to create a function similar to the avg where I receive the column name as a parameter and query a table to get the values for the specified column.
Maybe this is not possible as I saw that I cannot use exec for the common solution outside functions that is to concatenate the query string and execute it.
create function myAvg(#columnName varchar(255))
returns float
as
begin
declare #queryStr varchar(255) = '(select AVG(cast(' + #columnName + ' as float)) from functionTest)';
return execute( #queryStr);
end
I get this error:
RETURN statements in scalar valued functions must include an argument
Another solution with cursors:
create function myAvg (#columnName varchar(255))
returns float
as
begin
declare #MyCursor as cursor;
declare #value float;
declare #total float = 0.0;
declare #nElems int = 0;
set #MyCursor = cursor for
select #columnName from functionTest;
open #MyCursor;
fetch next from #MyCursor into #value;
while ##FETCH_STATUS = 0
begin
set #total = #total + #value;
set #nElems = #nElems + 1;
fetch next from #MyCursor into #value;
end
close #MyCursor;
deallocate #MyCursor;
return #total /#nElems
end
Calling the cursor solution throws this error:
Error converting data type varchar to float
I guess functions can't be that versatile with dynamic column parameterization.
Is there an alternative?
Edited: I know I can do this in a stored procedure but then I can't call it from a query.
Thanks
You cannot do what you want to do.
First, you cannot replace an identifier -- such as a column name -- with a parameter. That is simply not how parameters work. They only replace constant values.
Second, a function cannot execute dynamic SQL. You can get around the first problem using dynamic SQL, but that is not allowed in a function.
So, you cannot have a function that takes a column names as an argument and returns the average.
Instead, you can just use a window function in the outer query:
select . . . ,
avg(column_name) over ()
from . . .;
Note: There actually is a way using a very complicated method of re-invoking a query in SQL Server, but it is not worth it.
you cannot use execute in functions. for this purpose is better to use CLR functions.
https://learn.microsoft.com/en-us/sql/relational-databases/user-defined-functions/create-clr-functions?view=sql-server-2017

Why is my T-SQL cursor executing twice?

SQL-Server Code:
Declare #offers VARCHAR(100)
Declare #offers_seq VARCHAR(100)
Declare Result Cursor
For Select Top 1 Offers,Offers_seq From [REZJQWB01]..ActiveBooking_OffersDetails_Seq
Open Result
While ##fetch_status=0
Begin
Fetch Next From Result Into #offers, #offers_seq
Declare #value VARCHAR(100) = #offers
While len(#value) >= 1
Begin
Set #value = substring(#value,charindex(';',#value)+1,len(#value))
Print #value
End
End
Close Result
Deallocate Result
What I'm trying to accomplish here is to split a set of delimited values present in one cell and then creating a cursor for the complete column. The first time I run this code it gives the below output:
2;6;7;8;9;12;13;14;17;19;21;
6;7;8;9;12;13;14;17;19;21;
7;8;9;12;13;14;17;19;21;
8;9;12;13;14;17;19;21;
9;12;13;14;17;19;21;
12;13;14;17;19;21;
13;14;17;19;21;
14;17;19;21;
17;19;21;
19;21;
21;
2;6;7;8;9;12;13;14;17;19;21;
6;7;8;9;12;13;14;17;19;21;
7;8;9;12;13;14;17;19;21;
8;9;12;13;14;17;19;21;
9;12;13;14;17;19;21;
12;13;14;17;19;21;
13;14;17;19;21;
14;17;19;21;
17;19;21;
19;21;
21;
Ideally It should Print only once but I'm not sure why the loop runs twice. The second time I run this, it gives the output as : 'Command(s) completed successfully'.
Kindly help.
Thanks.
The reason it is running twice is that you aren't doing the fetch until after you've already checked the ##fetch_status.
The steps look like this:
Check ##fetch_status, which is zero, since nothing has been fetched.
Fetch a result
Run your substring code
Check ##fetch_status again, which is still zero, because a record was fetched in the previous step
Fetch another result, which fails but the cursor is still pointing at the same row as before
Run your substring code again, same result
Check ##fetch_status again, which now returns -1 because the previous fetch failed.
For the same reason as you get two results from running it once, you get nothing the second time, because ##fetch_status is -1 from the previous execution. To fix both issues, you need to fetch before checking the status. Usually you'll see one of the following methods employed (psuedocode left as an exercise for you to implement). Typically I use the first option, but some find the second is easier to read:
-- (declare and open cursor)
while 1=1 begin
fetch next from cursor
if ##fetch_status <> 0 break;
-- (do stuff)
end
or
-- (declare and open cursor)
fetch next from cursor
while ##fetch_status = 0 begin
-- (do stuff)
fetch next from cursor
end
Correct Code:
Declare #offers VARCHAR(100)
Declare #offers_seq VARCHAR(100)
Declare Result Cursor
For Select Top 1 Offers,Offers_seq From [REZJQWB01]..ActiveBooking_OffersDetails_Seq
Open Result
While 1=1
Begin
Fetch Next From Result Into #offers, #offers_seq
If ##fetch_status <> 0 Break;
Declare #value VARCHAR(100) = #offers
While len(#value) > 1
Begin
Set #value = substring(#value,charindex(';',#value,2)+1,len(#value))
Set #value = ';'+#value
If len(#value) <= 1 Break;
Print #value
End
End
Close Result
Deallocate Result
Result:
;6;7;8;9;12;13;14;17;19;21;
;7;8;9;12;13;14;17;19;21;
;8;9;12;13;14;17;19;21;
;9;12;13;14;17;19;21;
;12;13;14;17;19;21;
;13;14;17;19;21;
;14;17;19;21;
;17;19;21;
;19;21;
;21;

Storing multiple rows while running a cursor in sql server

I have written a cursor which picks up users from Users temporary table. It then finds out the latest transaction (based on many factors) and gives output messages and other results.
I am not able to think of an idea to store these values for every user in some collection and then return in to front end. Here is my code.
declare #cur1_patientid uniqueidentifier
declare #cur1_dueid uniqueidentifier
declare #cur1_dueamount varchar(10)
declare #cur1_patientname varchar(50)
declare #cur1_paymentday varchar(2)
declare #cur1_monthlyamount varchar(10)
--transaction table variables start
declare #trans_id uniqueidentifier
declare #trans_date datetime
declare #trans_amount float
declare #trans_type varchar(45)
declare #trans_status varchar(45)
declare #trans_patVisitid uniqueidentifier
--transaction table variables end
--declare other variables that will be used - start
declare #output_ARB_Date datetime
declare #output_check_flag bit
declare #output_msg varchar(MAX)
declare #output_difference numeric(3)
--declare other variables that will be used - end
set #output_check_flag=0
set #output_msg=''
set #output_difference=0
declare arb_cur cursor FAST_FORWARD for
select PatientId,dueid,dueamount,patientname,paymentday,monthlyamount from #ARB_Report1 where patientId
in ('D3658295-51B5-41DF-A1A4-E34B90EBD0F7','27F37F32-4984-47CC-B470-D086192D68FB')
open arb_cur
FETCH NEXT FROM arb_cur INTO #cur1_patientid,#cur1_dueid,#cur1_dueamount,#cur1_patientname,#cur1_paymentday,#cur1_monthlyamount
WHILE ##FETCH_STATUS = 0
BEGIN
--SELECT #cur1_patientid,#cur1_dueid --shows patientid,dueid of cursor1
DECLARE trans_cur CURSOR FOR
SELECT id,date,amount,type,status,patientVisitId FROM Transactions WHERE patientId=#cur1_patientid order by date desc
open trans_cur
--fetch transaction table values in local variables
FETCH NEXT FROM trans_cur into #trans_id,#trans_date,#trans_amount,#trans_type,#trans_status,#trans_patVisitid
--reset the variables-start
set #output_check_flag=0
set #output_msg=''
set #output_difference=0
set #output_ARB_Date=null
--reset the variables-end
WHILE (##FETCH_STATUS = 0)
begin
if(#trans_type='Reoccuring' and #trans_amount=#cur1_monthlyamount and #trans_patVisitid is null)
begin
if(#trans_status='Failed')
begin
--// some code
end
else if(#trans_status='Success')
begin
--// some code
end
select #cur1_patientid,#cur1_monthlyamount,#trans_amount,#output_ARB_Date,#output_msg,#output_difference,#cur1_dueamount
------ SOME INSERT STATEMENT HERE TO STORE THESE VALUES INTO A COLLECTION FOR EVERY PATIENT------
break
end
FETCH NEXT FROM trans_cur into #trans_id,#trans_date,#trans_amount,#trans_type,#trans_status,#trans_patVisitid
end
CLOSE trans_cur
DEALLOCATE trans_cur
FETCH NEXT FROM arb_cur INTO #cur1_patientid,#cur1_dueid,#cur1_dueamount,#cur1_patientname,#cur1_paymentday,#cur1_monthlyamount
END
CLOSE arb_cur
DEALLOCATE arb_cur
GO

In T-SQL / SQL Server 2000, referencing a particular row of a result set

I want to reference the nth row of the #temptable (at the second SQL comment is below). What expression will allow me to do so?
DECLARE #counter INT
SET #counter = 0
WHILE (#counter<count(#temptable))
--#temptable has one column and 0 or more rows
BEGIN
DECLARE #variab INT
EXEC #variab = get_next_ticket 3906, 'n', 1
INSERT INTO Student_Course_List
SELECT #student_id,
-- nth result set row in #temptable, where n is #count+1
#variab
SET #counter = #counter +1
END
Cursor (will this work?):
for record in (select id from #temptable) loop
--For statements, use record.id
end loop;
Normally in a relational database like SQL Server, you prefer to do set operations. So it would be best to simply have INSERT INTO tbl SOMECOMPLEXQUERY even with very complex queries. This is far preferable to row processing. In a complex system, using a cursor should be relatively rare.
In your case, it would appear that the get_next_ticket procedure performs some significant logic which is not able to be done in a set-oriented fashion. If you cannot perform it's function in an alternative set-oriented way, then you would use a CURSOR.
You would declare a CURSOR on your set SELECT whatever FROM #temptable, OPEN it, FETCH from the cursor into variables for each column and then use them in the insert.
Instead of using a while loop (with a counter like you are doing) to iterate the table you should use a cursor
Syntax would be:
DECLARE #id int
DECLARE c cursor for select id from #temptable
begin
open c
fetch next from c into #id
WHILE (##FETCH_STATUS = 0)
BEGIN
--Do stuff here
fetch next from c into #id
END
close c
deallocate c
end

IQ SQL Error: Correlation name 'updates_cur' not found

Hi i have a procedure with a cursor. Basically in the cursor i am getting a record and inserting it into DBA.header_balancing with certain values that was received in the cursor.
I receive this error "Error: Correlation name 'updates_cur' not found"
CREATE PROCEDURE sp_iq_bw_balancing
AS
BEGIN
DECLARE #date_Var date
SET #date_Var = CONVERT(CHAR(10),datepart(yy,getdate())||'-'||datepart(mm,getdate())||'-'||datepart(dd,getdate()))
declare updates_cur cursor
for select region
from DBA.TEST_IMPORT_CSV
OPEN updates_cur
BEGIN
/*Header */
INSERT INTO DBA.header_balancing(region,store_no,start_date,tran_id,start_hour,start_minute,start_second,employee,freq_shopper,lane_no,tran_no,end_date,end_hour,end_minute,end_second,total_items,total_amount,total_tenders,load_date)
VALUES (updates_cur.region, updates_cur.store_no, updates_cur.tran_date,'9999999999','23','59','59','999999999','N','999','999999',updates_cur.tran_date,'23','59','59','1',updates_cur.iq_variance_sales,'1',date_Var)
END
CLOSE updates_cur
DEALLOCATE CURSOR updates_cur
END
go
Execute sp_iq_bw_balancing
If this is a Sybase IQ cursor then I think you need to do this, 1) change the OPEN to OPEN WITH HOLD. Then FETCH the value of "region,store_no,tran_date,iq_variance_sales INTO #region,#store_no,#tran_date,#iq_variance_sales" variables and then insert the variable values.
Also your original code above tries to insert four columns (region, store_no,tran_date,iq_variance_sales) from the cursor but the cursor SELECT only includes the first column (region)
Something like....on Sybase IQ
CREATE PROCEDURE sp_iq_bw_balancing
AS
BEGIN
DECLARE #region VARCHAR
DECLARE #store_no VARCHAR
DECLARE #tran_date DATE
DECLARE #iq_variance_sales VARCHAR
DECLARE #date_Var date
SET #date_Var = CONVERT(CHAR(10),datepart(yy,getdate())||'-'||datepart(mm,getdate())||'-'||datepart(dd,getdate()))
declare updates_cur cursor
for select region,store_no,tran_date,iq_variance_sales
from DBA.TEST_IMPORT_CSV
OPEN updates_cur WITH HOLD
FETCH updates_cur INTO #region,#store_no,#tran_date,#iq_variance_sales
WHILE (##sqlstatus = 0)
BEGIN
/*Header */
INSERT INTO DBA.header_balancing(region,store_no,start_date,tran_id,start_hour,start_minute,start_second ,employee,freq_shopper,lane_no,tran_no,end_date,end_hour,end_minute,end_second,total_items,total_amount,total_tenders,load_date)
VALUES (#region, #store_no, #tran_date,'9999999999','23','59','59','999999999','N','999','999999',#tran_date,'23','59','59','1',#iq_variance_sales,'1',date_Var)
FETCH NEXT updates_cur INTO #region,#store_no,#tran_date,#iq_variance_sales
END
CLOSE updates_cur
DEALLOCATE CURSOR updates_cur
END