SQL Server : print out cursor values - sql

I want to be able to print out or output the values from a cursor for my stored procedure's. I'm having a hard time trying to figure out how to do this. I need to output my values onto a sheet like a report about which customers have paid and which haven't. A comparison would be great.
Here are my stored procedures for the customers who haven't paid:
CREATE PROC [dbo].[AdminReport1]
AS
BEGIN
SELECT
booking.bookingID, booking.totalCost,
booking.bookingDate, booking.paymentConfirmation,
customers.customersID,
customers.firstname, customers.surname,
customers.contactNum
FROM
booking
INNER JOIN
customers ON booking.customerID = customers.customersID
WHERE
paymentConfirmation = 'False'
ORDER BY
bookingDate ASC
END
GO
Here are my stored procedures for the customers who HAVE paid:
CREATE PROC [dbo].[AdminReport2]
AS
BEGIN
SELECT
booking.bookingID, booking.totalCost,
booking.bookingDate, booking.paymentConfirmation,
customers.customersID,
customers.firstname, customers.surname,
customers.contactNum
FROM
booking
INNER JOIN
customers ON booking.customerID = customers.customersID
WHERE
paymentConfirmation = 'TRUE'
ORDER BY
bookingDate ASC
So I need a cursor to print out my values. If anyone could help it would be much appreciated. An example with how to do this in code would be appreciated.

While I'm not convinced this is the most efficient way to do it (it would be better for SQL Server to feed the data to something designed to make display data; SSRS or even to Excel), here is what I would do as a start:
--Create somewhere to put the proc data and fill it. This will give us something tangible to query against.
CREATE TABLE #NotPaid
(
bookingID INT ,
totalCost DECIMAL(7, 2) ,
bookingDate DATE ,
paymentConfirmation VARCHAR(50) ,
customersID INT ,
firstname VARCHAR(50) ,
surname VARCHAR(50) ,
contactNum VARCHAR(50)
)
CREATE TABLE #HasPaid
(
bookingID INT ,
totalCost DECIMAL(7, 2) ,
bookingDate DATE ,
paymentConfirmation VARCHAR(50) ,
customersID INT ,
firstname VARCHAR(50) ,
surname VARCHAR(50) ,
contactNum VARCHAR(50)
)
INSERT #NotPaid
EXEC AdminReport1
INSERT #HasPaid
EXEC AdminReport2
--Variables for use in our cursor. I'm only loading one table up as a demonstration.
DECLARE #bookingID INT ,
#totalCost DECIMAL(7, 2) ,
#bookingDate DATE ,
#paymentConfirmation VARCHAR(50) ,
#customersID INT ,
#firstname VARCHAR(50) ,
#surname VARCHAR(50) ,
#contactNum VARCHAR(50)
DECLARE #Library CURSOR
SET
#Library = CURSOR FOR
SELECT bookingID ,
totalCost ,
bookingDate ,
paymentConfirmation ,
customersID ,
firstname,
surname ,
contactNum FROM #HasPaid
--Run the cursor and print out the variables as we loop again. Any formatting will need to be done in here. Again, I'm not sure this is the best way to achieve what you are trying to achieve.
OPEN #Library
FETCH NEXT FROM #getProductID INTO #bookingID, #totalCost, #bookingDate,
#paymentConfirmation, #customersID, #firstname, #surname, #contactNum
PRINT 'I''m a header line'
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #firstname + ' Hasnt paid! His booking date is ' + STR(#bookingDate)
+ '. This is the middle part'
FETCH NEXT FROM #Library INTO #bookingID, #totalCost, #bookingDate,
#paymentConfirmation, #customersID, #firstname, #surname, #contactNum
END
PRINT 'I''m at the bottom'
CLOSE #Library
DEALLOCATE #Library
GO
This should give you a fairly good starting place.
In terms of general efficiency, it would be better to just have the one proc and you pass in the information you want out, so, pass in the fact you just want the Paids, or just want the NotPaids, or everyone. And just to be a bore about it, SQL isn't really the place to be doing this type of formatting. There are much better tools out there for this!

I would suggest staying away from cursors unless there is a specific valid reason for using them. You could simplify this slightly by having a view which has the columns you are looking for
CREATE VIEW [dbo].[AdminReport]
AS
BEGIN
SELECT
b.bookingID,
b.totalCost,
b.bookingDate,
b.paymentConfirmation,
c.customersID,
customers.firstname,
c.surname,
c.contactNum,
paymentConfirmation
FROM
booking b
INNER JOIN customers c
ON b.customerID= c.customersID
--Where
--paymentConfirmation = 'False'
ORDER BY
bookingDate ASC
END
GO
And then you can just select the records paid records as
Select * from AdminReport where paymentConfirmation = 'TRUE'
and the unpaid ones as
Select * from AdminReport where paymentConfirmation = 'FALSE'

Related

How can I solve my performance issue in my stored procedure?

How can I get better performance with my sql query in a SP? It has a lot of memory usage.if you look at below my execution pan you will see that :
IF NOT EXISTS(SELECT * FROM Common.[CustomerxxxIds] WHERE xyzType = #xyzType AND CustomerId = #CustomerId)[/code]
has alot of memory usage. How can I reduce that?
ALTER PROCEDURE [Common].[SaveCustomerxxxIds]
(
#xyzType NVARCHAR(128),
#CustomerId INT,
#xxxId INT OUTPUT
)
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS(SELECT * FROM Common.[CustomerxxxIds] WHERE xxxType = #xxxType AND CustomerId = #CustomerId)
BEGIN
INSERT INTO Common.[CustomerxxxIds]
([xxxId]
,[CustomerId]
,[xxxType])
VALUES
(0
,#CustomerId
,#xxxType)
END
UPDATE Common.[CustomerxxxIds]
SET [xxxId] = ([xxxId]) + 1
WHERE [xxxType] = #xxxType
AND CustomerId = #CustomerId
SELECT #xxxId = xxxId
FROM Common.[CustomerxxxIds]
WHERE [xxxType] = #xxxType
AND CustomerId = #CustomerId
END
You can do things to avoid "re-read" of the table to get the output value.
After the INSERT
( INSERT INTO Common.[CustomerxxxIds])
Use SCOPE_IDxxx() to get the newly created surrogate key.
The above will only work for IDxxx columns. From your question, you may not actually have an IDxxx column.
See
https://learn.microsoft.com/en-us/sql/t-sql/functions/scope-idxxx-transact-sql?view=sql-server-2017
.........
with the UPDATE and/or INSERT, you could use OUTPUT functionality to get the value.
https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql?view=sql-server-2017
This AVOIDS the last select statement (the "re-read" as I am calling it) to get the desired output value.
Obviously completely removing a SELECT statement will improve performance.
..
Below is a simple but complete Northwind database example of using OUTPUT for INSERT and UPDATE
SELECT 'Before' as Looksie, [ShipperID]
,[CompanyName]
,[Phone]
FROM [Northwind].[dbo].[Shippers]
--
DECLARE #MyInsertAuditTable table( AuditShipperID INT,
AuditCompanyName nvarchar(40),
AuditPhone nvarchar(24));
INSERT [Northwind].[dbo].[Shippers] (CompanyName , Phone )
OUTPUT INSERTED.ShipperID, INSERTED.CompanyName, INSERTED.Phone
INTO #MyInsertAuditTable (AuditShipperID, AuditCompanyName , AuditPhone )
SELECT TOP 1
--(SELECT MAX(ShipperID) + 1 from dbo.Shippers )
'Shipper' + LEFT(CONVERT(VARCHAR(38), NEWID()), 12)
, '(555) 555-5555'
FROM sys.objects
--Display the result set of the table variable.
SELECT AuditShipperID, AuditCompanyName, AuditPhone FROM #MyInsertAuditTable;
DECLARE #MyUpdateAuditTable table( AuditShipperID INT,
AuditCompanyName nvarchar(40),
AuditOldPhone nvarchar(24),
AuditNewPhone nvarchar(24));
UPDATE [Northwind].[dbo].[Shippers]
SET Phone = '(777) 555-7777'
OUTPUT inserted.ShipperID, inserted.CompanyName ,
deleted.Phone,
inserted.Phone
INTO #MyUpdateAuditTable ( AuditShipperID, AuditCompanyName, AuditOldPhone , AuditNewPhone)
FROM [Northwind].[dbo].[Shippers] shippers
JOIN #MyInsertAuditTable insAudit on shippers.ShipperID = insAudit.AuditShipperID
SELECT * from #MyUpdateAuditTable
SELECT 'After' as Looksie, [ShipperID]
,[CompanyName]
,[Phone]
FROM [Northwind].[dbo].[Shippers]
--
Results
Looksie ShipperID CompanyName Phone
Before 1 Speedy Express (503) 555-9831
Before 2 United Package (503) 555-3199
Before 3 Federal Shipping (503) 555-9931
..
AuditShipperID AuditCompanyName AuditPhone
9 Shipper3C062D46-EEA (555) 555-5555
...
AuditShipperID AuditCompanyName AuditOldPhone AuditNewPhone
9 Shipper3C062D46-EEA (555) 555-5555 (777) 555-7777
..
Looksie ShipperID CompanyName Phone
After 1 Speedy Express (503) 555-9831
After 2 United Package (503) 555-3199
After 3 Federal Shipping (503) 555-9931
After 9 Shipper3C062D46-EEA (777) 555-7777
You can achieve this by changing SELECT * to SELECT 1. it might help
IF NOT EXISTS (SELECT 1 FROM Common.[CustomerxxxIds]
WHERE xyzType = #xyzType AND CustomerId = #CustomerId)
Try this
ALTER PROCEDURE [Common].[SaveCustomerxxxIds]
(
#xyz NVARCHAR(128),
#CustomerId INT,
#xxxId INT OUTPUT
)
AS
BEGIN
set #xxxId=null
--Get xxxId
SELECT #xxxId=[xxxId] FROM Common.[CustomerxxxIds] WHERE xyz = #xyz AND CustomerId = #CustomerId
--If #xxxId means no record we should insert
if (#xxxId is null)
begin
--When insert we always insert xxxId as 0 then update to one then we collect the value (one) from db and return it.
--Better set value directly as one insert it to DB and return it as one. Instead of insert, update, select
--just insert
set #xxxId = 1
INSERT INTO Common.[CustomerxxxIds]
([xxxId]
,[CustomerId]
,[xyz])
VALUES
(#xxxId
,#CustomerId
,#xyz)
end
else
begin
--If we have the value we add one to it update the record and return it.
--better than update table then select.
--We already have the value we add one to it then update table and return the value we have
set #xxxId+=1
UPDATE Common.[CustomerxxxIds] SET [xxxId] = #xxxId
WHERE [xyz] = #xyz AND CustomerId = #CustomerId
END
end

Using a temp table with a stored procedure to cycle through IDs [duplicate]

How can one call a stored procedure for each row in a table, where the columns of a row are input parameters to the sp without using a Cursor?
Generally speaking I always look for a set based approach (sometimes at the expense of changing the schema).
However, this snippet does have its place..
-- Declare & init (2008 syntax)
DECLARE #CustomerID INT = 0
-- Iterate over all customers
WHILE (1 = 1)
BEGIN
-- Get next customerId
SELECT TOP 1 #CustomerID = CustomerID
FROM Sales.Customer
WHERE CustomerID > #CustomerId
ORDER BY CustomerID
-- Exit loop if no more customers
IF ##ROWCOUNT = 0 BREAK;
-- call your sproc
EXEC dbo.YOURSPROC #CustomerId
END
You could do something like this: order your table by e.g. CustomerID (using the AdventureWorks Sales.Customer sample table), and iterate over those customers using a WHILE loop:
-- define the last customer ID handled
DECLARE #LastCustomerID INT
SET #LastCustomerID = 0
-- define the customer ID to be handled now
DECLARE #CustomerIDToHandle INT
-- select the next customer to handle
SELECT TOP 1 #CustomerIDToHandle = CustomerID
FROM Sales.Customer
WHERE CustomerID > #LastCustomerID
ORDER BY CustomerID
-- as long as we have customers......
WHILE #CustomerIDToHandle IS NOT NULL
BEGIN
-- call your sproc
-- set the last customer handled to the one we just handled
SET #LastCustomerID = #CustomerIDToHandle
SET #CustomerIDToHandle = NULL
-- select the next customer to handle
SELECT TOP 1 #CustomerIDToHandle = CustomerID
FROM Sales.Customer
WHERE CustomerID > #LastCustomerID
ORDER BY CustomerID
END
That should work with any table as long as you can define some kind of an ORDER BY on some column.
DECLARE #SQL varchar(max)=''
-- MyTable has fields fld1 & fld2
Select #SQL = #SQL + 'exec myproc ' + convert(varchar(10),fld1) + ','
+ convert(varchar(10),fld2) + ';'
From MyTable
EXEC (#SQL)
Ok, so I would never put such code into production, but it does satisfy your requirements.
I'd use the accepted answer, but another possibility is to use a table variable to hold a numbered set of values (in this case just the ID field of a table) and loop through those by Row Number with a JOIN to the table to retrieve whatever you need for the action within the loop.
DECLARE #RowCnt int; SET #RowCnt = 0 -- Loop Counter
-- Use a table variable to hold numbered rows containg MyTable's ID values
DECLARE #tblLoop TABLE (RowNum int IDENTITY (1, 1) Primary key NOT NULL,
ID INT )
INSERT INTO #tblLoop (ID) SELECT ID FROM MyTable
-- Vars to use within the loop
DECLARE #Code NVarChar(10); DECLARE #Name NVarChar(100);
WHILE #RowCnt < (SELECT COUNT(RowNum) FROM #tblLoop)
BEGIN
SET #RowCnt = #RowCnt + 1
-- Do what you want here with the data stored in tblLoop for the given RowNum
SELECT #Code=Code, #Name=LongName
FROM MyTable INNER JOIN #tblLoop tL on MyTable.ID=tL.ID
WHERE tl.RowNum=#RowCnt
PRINT Convert(NVarChar(10),#RowCnt) +' '+ #Code +' '+ #Name
END
Marc's answer is good (I'd comment on it if I could work out how to!)
Just thought I'd point out that it may be better to change the loop so the SELECT only exists once (in a real case where I needed to do this, the SELECT was quite complex, and writing it twice was a risky maintenance issue).
-- define the last customer ID handled
DECLARE #LastCustomerID INT
SET #LastCustomerID = 0
-- define the customer ID to be handled now
DECLARE #CustomerIDToHandle INT
SET #CustomerIDToHandle = 1
-- as long as we have customers......
WHILE #LastCustomerID <> #CustomerIDToHandle
BEGIN
SET #LastCustomerId = #CustomerIDToHandle
-- select the next customer to handle
SELECT TOP 1 #CustomerIDToHandle = CustomerID
FROM Sales.Customer
WHERE CustomerID > #LastCustomerId
ORDER BY CustomerID
IF #CustomerIDToHandle <> #LastCustomerID
BEGIN
-- call your sproc
END
END
If you can turn the stored procedure into a function that returns a table, then you can use cross-apply.
For example, say you have a table of customers, and you want to compute the sum of their orders, you would create a function that took a CustomerID and returned the sum.
And you could do this:
SELECT CustomerID, CustomerSum.Total
FROM Customers
CROSS APPLY ufn_ComputeCustomerTotal(Customers.CustomerID) AS CustomerSum
Where the function would look like:
CREATE FUNCTION ComputeCustomerTotal
(
#CustomerID INT
)
RETURNS TABLE
AS
RETURN
(
SELECT SUM(CustomerOrder.Amount) AS Total FROM CustomerOrder WHERE CustomerID = #CustomerID
)
Obviously, the example above could be done without a user defined function in a single query.
The drawback is that functions are very limited - many of the features of a stored procedure are not available in a user-defined function, and converting a stored procedure to a function does not always work.
For SQL Server 2005 onwards, you can do this with CROSS APPLY and a table-valued function.
Using CROSS APPLY in SQL Server 2005
Just for clarity, I'm referring to those cases where the stored procedure can be converted into a table valued function.
This is a variation on the answers already provided, but should be better performing because it doesn't require ORDER BY, COUNT or MIN/MAX. The only disadvantage with this approach is that you have to create a temp table to hold all the Ids (the assumption is that you have gaps in your list of CustomerIDs).
That said, I agree with #Mark Powell though that, generally speaking, a set based approach should still be better.
DECLARE #tmp table (Id INT IDENTITY(1,1) PRIMARY KEY NOT NULL, CustomerID INT NOT NULL)
DECLARE #CustomerId INT
DECLARE #Id INT = 0
INSERT INTO #tmp SELECT CustomerId FROM Sales.Customer
WHILE (1=1)
BEGIN
SELECT #CustomerId = CustomerId, #Id = Id
FROM #tmp
WHERE Id = #Id + 1
IF ##rowcount = 0 BREAK;
-- call your sproc
EXEC dbo.YOURSPROC #CustomerId;
END
This is a variation of n3rds solution above. No sorting by using ORDER BY is needed, as MIN() is used.
Remember that CustomerID (or whatever other numerical column you use for progress) must have a unique constraint. Furthermore, to make it as fast as possible CustomerID must be indexed on.
-- Declare & init
DECLARE #CustomerID INT = (SELECT MIN(CustomerID) FROM Sales.Customer); -- First ID
DECLARE #Data1 VARCHAR(200);
DECLARE #Data2 VARCHAR(200);
-- Iterate over all customers
WHILE #CustomerID IS NOT NULL
BEGIN
-- Get data based on ID
SELECT #Data1 = Data1, #Data2 = Data2
FROM Sales.Customer
WHERE [ID] = #CustomerID ;
-- call your sproc
EXEC dbo.YOURSPROC #Data1, #Data2
-- Get next customerId
SELECT #CustomerID = MIN(CustomerID)
FROM Sales.Customer
WHERE CustomerID > #CustomerId
END
I use this approach on some varchars I need to look over, by putting them in a temporary table first, to give them an ID.
If you don't what to use a cursor I think you'll have to do it externally (get the table, and then run for each statement and each time call the sp)
it Is the same as using a cursor, but only outside SQL.
Why won't you use a cursor ?
I usually do it this way when it's a quite a few rows:
Select all sproc parameters in a dataset with SQL Management Studio
Right-click -> Copy
Paste in to excel
Create single-row sql statements with a formula like '="EXEC schema.mysproc #param=" & A2' in a new excel column. (Where A2 is your excel column containing the parameter)
Copy the list of excel statements into a new query in SQL Management Studio and execute.
Done.
(On larger datasets i'd use one of the solutions mentioned above though).
DELIMITER //
CREATE PROCEDURE setFakeUsers (OUT output VARCHAR(100))
BEGIN
-- define the last customer ID handled
DECLARE LastGameID INT;
DECLARE CurrentGameID INT;
DECLARE userID INT;
SET #LastGameID = 0;
-- define the customer ID to be handled now
SET #userID = 0;
-- select the next game to handle
SELECT #CurrentGameID = id
FROM online_games
WHERE id > LastGameID
ORDER BY id LIMIT 0,1;
-- as long as we have customers......
WHILE (#CurrentGameID IS NOT NULL)
DO
-- call your sproc
-- set the last customer handled to the one we just handled
SET #LastGameID = #CurrentGameID;
SET #CurrentGameID = NULL;
-- select the random bot
SELECT #userID = userID
FROM users
WHERE FIND_IN_SET('bot',baseInfo)
ORDER BY RAND() LIMIT 0,1;
-- update the game
UPDATE online_games SET userID = #userID WHERE id = #CurrentGameID;
-- select the next game to handle
SELECT #CurrentGameID = id
FROM online_games
WHERE id > LastGameID
ORDER BY id LIMIT 0,1;
END WHILE;
SET output = "done";
END;//
CALL setFakeUsers(#status);
SELECT #status;
A better solution for this is to
Copy/past code of Stored Procedure
Join that code with the table for which you want to run it again (for each row)
This was you get a clean table-formatted output. While if you run SP for every row, you get a separate query result for each iteration which is ugly.
In case the order is important
--declare counter
DECLARE #CurrentRowNum BIGINT = 0;
--Iterate over all rows in [DataTable]
WHILE (1 = 1)
BEGIN
--Get next row by number of row
SELECT TOP 1 #CurrentRowNum = extendedData.RowNum
--here also you can store another values
--for following usage
--#MyVariable = extendedData.Value
FROM (
SELECT
data.*
,ROW_NUMBER() OVER(ORDER BY (SELECT 0)) RowNum
FROM [DataTable] data
) extendedData
WHERE extendedData.RowNum > #CurrentRowNum
ORDER BY extendedData.RowNum
--Exit loop if no more rows
IF ##ROWCOUNT = 0 BREAK;
--call your sproc
--EXEC dbo.YOURSPROC #MyVariable
END
I had some production code that could only handle 20 employees at a time, below is the framework for the code. I just copied the production code and removed stuff below.
ALTER procedure GetEmployees
#ClientId varchar(50)
as
begin
declare #EEList table (employeeId varchar(50));
declare #EE20 table (employeeId varchar(50));
insert into #EEList select employeeId from Employee where (ClientId = #ClientId);
-- Do 20 at a time
while (select count(*) from #EEList) > 0
BEGIN
insert into #EE20 select top 20 employeeId from #EEList;
-- Call sp here
delete #EEList where employeeId in (select employeeId from #EE20)
delete #EE20;
END;
RETURN
end
I had a situation where I needed to perform a series of operations on a result set (table). The operations are all set operations, so its not an issue, but...
I needed to do this in multiple places. So putting the relevant pieces in a table type, then populating a table variable w/ each result set allows me to call the sp and repeat the operations each time i need to .
While this does not address the exact question he asks, it does address how to perform an operation on all rows of a table without using a cursor.
#Johannes offers no insight into his motivation , so this may or may not help him.
my research led me to this well written article which served as a basis for my solution
https://codingsight.com/passing-data-table-as-parameter-to-stored-procedures/
Here is the setup
drop type if exists cpRootMapType
go
create type cpRootMapType as Table(
RootId1 int
, RootId2 int
)
go
drop procedure if exists spMapRoot2toRoot1
go
create procedure spMapRoot2toRoot1
(
#map cpRootMapType Readonly
)
as
update linkTable set root = root1
from linktable lt
join #map m on lt.root = root2
update comments set root = root1
from comments c
join #map m on c.root = root2
-- ever growing list of places this map would need to be applied....
-- now consolidated into one place
here is the implementation
... populate #matches
declare #map cpRootMapType
insert #map select rootid1, rootid2 from #matches
exec spMapRoot2toRoot1 #map
I like to do something similar to this (though it is still very similar to using a cursor)
[code]
-- Table variable to hold list of things that need looping
DECLARE #holdStuff TABLE (
id INT IDENTITY(1,1) ,
isIterated BIT DEFAULT 0 ,
someInt INT ,
someBool BIT ,
otherStuff VARCHAR(200)
)
-- Populate your #holdStuff with... stuff
INSERT INTO #holdStuff (
someInt ,
someBool ,
otherStuff
)
SELECT
1 , -- someInt - int
1 , -- someBool - bit
'I like turtles' -- otherStuff - varchar(200)
UNION ALL
SELECT
42 , -- someInt - int
0 , -- someBool - bit
'something profound' -- otherStuff - varchar(200)
-- Loop tracking variables
DECLARE #tableCount INT
SET #tableCount = (SELECT COUNT(1) FROM [#holdStuff])
DECLARE #loopCount INT
SET #loopCount = 1
-- While loop variables
DECLARE #id INT
DECLARE #someInt INT
DECLARE #someBool BIT
DECLARE #otherStuff VARCHAR(200)
-- Loop through item in #holdStuff
WHILE (#loopCount <= #tableCount)
BEGIN
-- Increment the loopCount variable
SET #loopCount = #loopCount + 1
-- Grab the top unprocessed record
SELECT TOP 1
#id = id ,
#someInt = someInt ,
#someBool = someBool ,
#otherStuff = otherStuff
FROM #holdStuff
WHERE isIterated = 0
-- Update the grabbed record to be iterated
UPDATE #holdAccounts
SET isIterated = 1
WHERE id = #id
-- Execute your stored procedure
EXEC someRandomSp #someInt, #someBool, #otherStuff
END
[/code]
Note that you don't need the identity or the isIterated column on your temp/variable table, i just prefer to do it this way so i don't have to delete the top record from the collection as i iterate through the loop.

SQL Query To Get Structured Result

Sir, below is my SQL query, followed with two images of Query Result & Required Output.
As you can see on Result image that I have got this output from executing the following query.
There is second image of Required Output. I would need to display the output in that format. As First Department name should be at top & below Project details correspond to that department. The same cycle should get repeated for each and every department.
How should I achieve this ?
SQL Code:
DECLARE #ColName varchar(20)=null,
#Query varchar(MAX)=null,
#DepartmentName varchar(50)=null,
#deptt_code varchar(4)=null,
#DistrictId varchar(4)='0001',
#Deptt_Id char(4)=null,
#stYear varchar(4)=null,
#cYear varchar(4)=null,
#yr varchar(9)='2017-2018',
#tno int
BEGIN
set #stYear = SUBSTRING(#yr,0,5);
set #cYear = SUBSTRING(#yr,6,4);
--CREATE DYNAMIC TABLE WITH COLs
DECLARE #DepartmentTable table
(
department_name varchar(50),
department_code varchar(4)
);
DECLARE #ProjectTable table
(
project_name varchar(130),
project_code varchar(15),
department_code varchar(4),
department_name varchar(50),
district_code varchar(4),
district_name varchar(30),
tehsil_code varchar(6),
tehsil_name varchar(30),
service_code varchar(3),
[service_name] varchar(30),
sector_code varchar(3),
sector_name varchar(30),
project_start_year varchar(8),
project_compl_year varchar(8),
financial_year varchar(9)
);
--Insert into Department Table
INSERT INTO #DepartmentTable (department_code, department_name)
select deptt_code,deptt_name+'('+ RTRIM(LTRIM(deptt_short))+')' as dept_name from m_Department
where deptt_code in (select distinct department_code from t_Project_Details where district_id=#DistrictId
and financial_year=#yr);
--Insert into Project Table With Corresponding Department Name & Code
insert into #ProjectTable (
department_code,
project_code,
project_name,
department_name,
district_code,
district_name,
tehsil_code,
tehsil_name,
service_code,
[service_name],
sector_code,
sector_name,
project_start_year,
project_compl_year,
financial_year
)
SELECT pd.department_code,
pd.project_Code,
pd.project_name,
d.deptt_name,
pd.district_id,
di.district_name,
pd.tehsil_id,
t.tehsil_name,
pd.service_code,
s.[service_name],
pd.sector_code,
se.sector_name,
CONVERT(varchar,YEAR(pd.project_initiation_fin_from_yr)) + ' ' + CONVERT(varchar(3),pd.project_initiation_fin_from_yr,100) as project_start_year,
CONVERT(varchar,YEAR(pd.project_initiation_fin_to_yr)) + ' ' + CONVERT(varchar(3),pd.project_initiation_fin_to_yr,100) as project_compl_year,
pd.financial_year
FROM t_Project_Details pd
INNER JOIN m_Department d
ON d.deptt_code=pd.department_code
INNER JOIN m_Service s
ON s.service_code = pd.service_code
INNER JOIN m_Sector se
ON se.sector_code=pd.sector_code
INNER JOIN m_District di
ON di.district_code=pd.district_id
INNER JOIN m_Tehsil t
ON t.tehsil_code = pd.tehsil_id
WHERE
district_id=#DistrictId and
financial_year=#yr;
--select all from Department Table;
select * from #ProjectTable;
END
Query Result Image: https://drive.google.com/open?id=0Bxn7UXgmstmRaS1qX21kbjlwZzg
Required Output Image: https://drive.google.com/open?id=0Bxn7UXgmstmRekJkUWhBcmNCbk0
Your required output is a presentation layer issue; SQL can return your results but cannot make one row contain one thing and following rows contain many different things in a different format.
A SQL resultset will contain the same number of columns in all rows each with the same datatype.
Use something like SSRS to acheive your desire output.

Inserting a temporary table with cursor search function to an existing SQL query

I am needing help with trying to join a there two queries.
The first query is my main query for the report i am trying to build.
PARAMETERS [DocumentNo] String "", [hideComments] boolean False;
Select cus.name, cus.addra, cus.addrb, cus.addrc, cus.addrd, cus.addre, cus.pcode as PostCode, br.name As BranchName, br.addra As BrAddrA, br.addrb As BrAddrB,
br.addrc As BrAddrC, br.addrd As BrAddrD, br.addre As BrAddrE, br.postcode As postcodebr, br.Phonenumber as brphonenumber, v.make as vmake, v.YoM as vyear, v.model as vmodel, v.carcolour as vcolor, v.pkey as license, v.state as vstate, V.mileage as mileageout
,h.datetime, h.document, h.acct, h.inits, h.payref, h.corder, h.goods, h.vat,
(Case IsNull(p.supppart,'') when '' then l.part else p.supppart end) part, l.bopdes,
l.qty, l.unit, c.a2 As 'WorkOrderComments'
from jheads h
inner join jlines l on l.document = h.document
inner join customer cus on cus.keycode = h.acct
left join plines p on l.porder = p.document + '/' + cast(p.seqno as varchar(3))
left join codes c on c.keycode= h.branch and c.prefix='WA' and c.subkey1=0
left join branches br on br.branch = h.branch
left join vehicles v on v.Custacnum = h.acct
where H.Document= [DocumentNo]
The information i am needing to add to this report query is; i need the due date for two types based in the "reminders" table. How this information works is that the type column is populated with one of two options 'EMSN or 'INSP. the "VRM" coloumn from this table related to the "pkey" from vehicles table which is all ready in the first query. The VRM is the license of related to an order, but a license can have multiple EMSN and INSP records, there records are sorted sequentically and i need the latest records to be merged into the report for each type ( so i need two record specifically drawn out)
The query i have used to gather this information and temporary store this is
use [Masterdatabase]
GO
DECLARE #source TABLE
(
CustAcNum VARCHAR(255) NOT NULL,
SeqNo INT NOT NULL,
Class VARCHAR(255),
[type] VARCHAR(255) NOT NULL,
Vrm VARCHAR(12),
Duedate DATETIME,
VehicleId UNIQUEIDENTIFIER
);
DECLARE #filtered TABLE
(
CustAcNum VARCHAR(255) NOT NULL,
SeqNo INT NOT NULL,
Class VARCHAR(255),
[type] VARCHAR(255) NOT NULL,
Vrm VARCHAR(12),
Duedate DATETIME,
VehicleId UNIQUEIDENTIFIER
);
INSERT INTO #source
SELECT v.CustAcNum, r.SeqNo, r.[Class], r.[type], r.Vrm, r.Duedate, r.VehicleId
FROM dbo.Vehicles AS v
INNER JOIN dbo.Reminders AS r ON r.VRM = v.Pkey
WHERE r.[Type] = 'EMSN'
GROUP BY v.CustAcNum, r.SeqNo, r.[Class], r.[type], r.Vrm, r.Duedate, r.VehicleId
UNION
SELECT v.CustAcNum, r.SeqNo, r.[Class], r.[type], r.Vrm, r.Duedate, r.VehicleId
FROM dbo.Vehicles AS v
INNER JOIN dbo.Reminders AS r ON r.VRM = v.Pkey
WHERE r.[Type] = 'INSP'
GROUP BY v.CustAcNum, r.SeqNo, r.[Class], r.[type], r.Vrm, r.Duedate, r.VehicleId
ORDER BY v.CustAcNum, r.Seqno DESC;
DECLARE
#custAcNum VARCHAR(255)
,#seqNo INT
,#class VARCHAR(255)
,#type VARCHAR(255)
,#vrm VARCHAR(12)
,#duedate DATETIME
,#vehicleId UNIQUEIDENTIFIER
DECLARE source_cursor CURSOR
FAST_FORWARD
FOR SELECT * FROM #source WHERE NULLIF(ltrim(rtrim(CustAcNum)), '') IS NOT NULL;
OPEN source_cursor
FETCH NEXT FROM source_cursor
INTO #custAcNum, #seqNo, #class, #type, #vrm, #duedate, #vehicleId;
WHILE ##FETCH_STATUS = 0
BEGiN
IF NOT EXISTS (SELECT 1 FROM #filtered WHERE CustAcNum = #custAcNum AND [type] = #type)
BEGIN
INSERT INTO #filtered
VALUES (#custAcNum, #seqNo, #class, #type, #vrm, #duedate, #vehicleId);
END
FETCH NEXT FROM source_cursor
INTO #custAcNum, #seqNo, #class, #type, #vrm, #duedate, #vehicleId;
END
CLOSE source_cursor;
DEALLOCATE source_cursor;
select * from #filtered;
I'm now needing to join these two together and be able to extract the due date out of them for both type's.
I'm stuck on this so any help i would appreciate it.

SQL Call Stored Procedure for each Row without using a cursor

How can one call a stored procedure for each row in a table, where the columns of a row are input parameters to the sp without using a Cursor?
Generally speaking I always look for a set based approach (sometimes at the expense of changing the schema).
However, this snippet does have its place..
-- Declare & init (2008 syntax)
DECLARE #CustomerID INT = 0
-- Iterate over all customers
WHILE (1 = 1)
BEGIN
-- Get next customerId
SELECT TOP 1 #CustomerID = CustomerID
FROM Sales.Customer
WHERE CustomerID > #CustomerId
ORDER BY CustomerID
-- Exit loop if no more customers
IF ##ROWCOUNT = 0 BREAK;
-- call your sproc
EXEC dbo.YOURSPROC #CustomerId
END
You could do something like this: order your table by e.g. CustomerID (using the AdventureWorks Sales.Customer sample table), and iterate over those customers using a WHILE loop:
-- define the last customer ID handled
DECLARE #LastCustomerID INT
SET #LastCustomerID = 0
-- define the customer ID to be handled now
DECLARE #CustomerIDToHandle INT
-- select the next customer to handle
SELECT TOP 1 #CustomerIDToHandle = CustomerID
FROM Sales.Customer
WHERE CustomerID > #LastCustomerID
ORDER BY CustomerID
-- as long as we have customers......
WHILE #CustomerIDToHandle IS NOT NULL
BEGIN
-- call your sproc
-- set the last customer handled to the one we just handled
SET #LastCustomerID = #CustomerIDToHandle
SET #CustomerIDToHandle = NULL
-- select the next customer to handle
SELECT TOP 1 #CustomerIDToHandle = CustomerID
FROM Sales.Customer
WHERE CustomerID > #LastCustomerID
ORDER BY CustomerID
END
That should work with any table as long as you can define some kind of an ORDER BY on some column.
DECLARE #SQL varchar(max)=''
-- MyTable has fields fld1 & fld2
Select #SQL = #SQL + 'exec myproc ' + convert(varchar(10),fld1) + ','
+ convert(varchar(10),fld2) + ';'
From MyTable
EXEC (#SQL)
Ok, so I would never put such code into production, but it does satisfy your requirements.
I'd use the accepted answer, but another possibility is to use a table variable to hold a numbered set of values (in this case just the ID field of a table) and loop through those by Row Number with a JOIN to the table to retrieve whatever you need for the action within the loop.
DECLARE #RowCnt int; SET #RowCnt = 0 -- Loop Counter
-- Use a table variable to hold numbered rows containg MyTable's ID values
DECLARE #tblLoop TABLE (RowNum int IDENTITY (1, 1) Primary key NOT NULL,
ID INT )
INSERT INTO #tblLoop (ID) SELECT ID FROM MyTable
-- Vars to use within the loop
DECLARE #Code NVarChar(10); DECLARE #Name NVarChar(100);
WHILE #RowCnt < (SELECT COUNT(RowNum) FROM #tblLoop)
BEGIN
SET #RowCnt = #RowCnt + 1
-- Do what you want here with the data stored in tblLoop for the given RowNum
SELECT #Code=Code, #Name=LongName
FROM MyTable INNER JOIN #tblLoop tL on MyTable.ID=tL.ID
WHERE tl.RowNum=#RowCnt
PRINT Convert(NVarChar(10),#RowCnt) +' '+ #Code +' '+ #Name
END
Marc's answer is good (I'd comment on it if I could work out how to!)
Just thought I'd point out that it may be better to change the loop so the SELECT only exists once (in a real case where I needed to do this, the SELECT was quite complex, and writing it twice was a risky maintenance issue).
-- define the last customer ID handled
DECLARE #LastCustomerID INT
SET #LastCustomerID = 0
-- define the customer ID to be handled now
DECLARE #CustomerIDToHandle INT
SET #CustomerIDToHandle = 1
-- as long as we have customers......
WHILE #LastCustomerID <> #CustomerIDToHandle
BEGIN
SET #LastCustomerId = #CustomerIDToHandle
-- select the next customer to handle
SELECT TOP 1 #CustomerIDToHandle = CustomerID
FROM Sales.Customer
WHERE CustomerID > #LastCustomerId
ORDER BY CustomerID
IF #CustomerIDToHandle <> #LastCustomerID
BEGIN
-- call your sproc
END
END
If you can turn the stored procedure into a function that returns a table, then you can use cross-apply.
For example, say you have a table of customers, and you want to compute the sum of their orders, you would create a function that took a CustomerID and returned the sum.
And you could do this:
SELECT CustomerID, CustomerSum.Total
FROM Customers
CROSS APPLY ufn_ComputeCustomerTotal(Customers.CustomerID) AS CustomerSum
Where the function would look like:
CREATE FUNCTION ComputeCustomerTotal
(
#CustomerID INT
)
RETURNS TABLE
AS
RETURN
(
SELECT SUM(CustomerOrder.Amount) AS Total FROM CustomerOrder WHERE CustomerID = #CustomerID
)
Obviously, the example above could be done without a user defined function in a single query.
The drawback is that functions are very limited - many of the features of a stored procedure are not available in a user-defined function, and converting a stored procedure to a function does not always work.
For SQL Server 2005 onwards, you can do this with CROSS APPLY and a table-valued function.
Using CROSS APPLY in SQL Server 2005
Just for clarity, I'm referring to those cases where the stored procedure can be converted into a table valued function.
This is a variation on the answers already provided, but should be better performing because it doesn't require ORDER BY, COUNT or MIN/MAX. The only disadvantage with this approach is that you have to create a temp table to hold all the Ids (the assumption is that you have gaps in your list of CustomerIDs).
That said, I agree with #Mark Powell though that, generally speaking, a set based approach should still be better.
DECLARE #tmp table (Id INT IDENTITY(1,1) PRIMARY KEY NOT NULL, CustomerID INT NOT NULL)
DECLARE #CustomerId INT
DECLARE #Id INT = 0
INSERT INTO #tmp SELECT CustomerId FROM Sales.Customer
WHILE (1=1)
BEGIN
SELECT #CustomerId = CustomerId, #Id = Id
FROM #tmp
WHERE Id = #Id + 1
IF ##rowcount = 0 BREAK;
-- call your sproc
EXEC dbo.YOURSPROC #CustomerId;
END
This is a variation of n3rds solution above. No sorting by using ORDER BY is needed, as MIN() is used.
Remember that CustomerID (or whatever other numerical column you use for progress) must have a unique constraint. Furthermore, to make it as fast as possible CustomerID must be indexed on.
-- Declare & init
DECLARE #CustomerID INT = (SELECT MIN(CustomerID) FROM Sales.Customer); -- First ID
DECLARE #Data1 VARCHAR(200);
DECLARE #Data2 VARCHAR(200);
-- Iterate over all customers
WHILE #CustomerID IS NOT NULL
BEGIN
-- Get data based on ID
SELECT #Data1 = Data1, #Data2 = Data2
FROM Sales.Customer
WHERE [ID] = #CustomerID ;
-- call your sproc
EXEC dbo.YOURSPROC #Data1, #Data2
-- Get next customerId
SELECT #CustomerID = MIN(CustomerID)
FROM Sales.Customer
WHERE CustomerID > #CustomerId
END
I use this approach on some varchars I need to look over, by putting them in a temporary table first, to give them an ID.
If you don't what to use a cursor I think you'll have to do it externally (get the table, and then run for each statement and each time call the sp)
it Is the same as using a cursor, but only outside SQL.
Why won't you use a cursor ?
I usually do it this way when it's a quite a few rows:
Select all sproc parameters in a dataset with SQL Management Studio
Right-click -> Copy
Paste in to excel
Create single-row sql statements with a formula like '="EXEC schema.mysproc #param=" & A2' in a new excel column. (Where A2 is your excel column containing the parameter)
Copy the list of excel statements into a new query in SQL Management Studio and execute.
Done.
(On larger datasets i'd use one of the solutions mentioned above though).
DELIMITER //
CREATE PROCEDURE setFakeUsers (OUT output VARCHAR(100))
BEGIN
-- define the last customer ID handled
DECLARE LastGameID INT;
DECLARE CurrentGameID INT;
DECLARE userID INT;
SET #LastGameID = 0;
-- define the customer ID to be handled now
SET #userID = 0;
-- select the next game to handle
SELECT #CurrentGameID = id
FROM online_games
WHERE id > LastGameID
ORDER BY id LIMIT 0,1;
-- as long as we have customers......
WHILE (#CurrentGameID IS NOT NULL)
DO
-- call your sproc
-- set the last customer handled to the one we just handled
SET #LastGameID = #CurrentGameID;
SET #CurrentGameID = NULL;
-- select the random bot
SELECT #userID = userID
FROM users
WHERE FIND_IN_SET('bot',baseInfo)
ORDER BY RAND() LIMIT 0,1;
-- update the game
UPDATE online_games SET userID = #userID WHERE id = #CurrentGameID;
-- select the next game to handle
SELECT #CurrentGameID = id
FROM online_games
WHERE id > LastGameID
ORDER BY id LIMIT 0,1;
END WHILE;
SET output = "done";
END;//
CALL setFakeUsers(#status);
SELECT #status;
A better solution for this is to
Copy/past code of Stored Procedure
Join that code with the table for which you want to run it again (for each row)
This was you get a clean table-formatted output. While if you run SP for every row, you get a separate query result for each iteration which is ugly.
In case the order is important
--declare counter
DECLARE #CurrentRowNum BIGINT = 0;
--Iterate over all rows in [DataTable]
WHILE (1 = 1)
BEGIN
--Get next row by number of row
SELECT TOP 1 #CurrentRowNum = extendedData.RowNum
--here also you can store another values
--for following usage
--#MyVariable = extendedData.Value
FROM (
SELECT
data.*
,ROW_NUMBER() OVER(ORDER BY (SELECT 0)) RowNum
FROM [DataTable] data
) extendedData
WHERE extendedData.RowNum > #CurrentRowNum
ORDER BY extendedData.RowNum
--Exit loop if no more rows
IF ##ROWCOUNT = 0 BREAK;
--call your sproc
--EXEC dbo.YOURSPROC #MyVariable
END
I had some production code that could only handle 20 employees at a time, below is the framework for the code. I just copied the production code and removed stuff below.
ALTER procedure GetEmployees
#ClientId varchar(50)
as
begin
declare #EEList table (employeeId varchar(50));
declare #EE20 table (employeeId varchar(50));
insert into #EEList select employeeId from Employee where (ClientId = #ClientId);
-- Do 20 at a time
while (select count(*) from #EEList) > 0
BEGIN
insert into #EE20 select top 20 employeeId from #EEList;
-- Call sp here
delete #EEList where employeeId in (select employeeId from #EE20)
delete #EE20;
END;
RETURN
end
I had a situation where I needed to perform a series of operations on a result set (table). The operations are all set operations, so its not an issue, but...
I needed to do this in multiple places. So putting the relevant pieces in a table type, then populating a table variable w/ each result set allows me to call the sp and repeat the operations each time i need to .
While this does not address the exact question he asks, it does address how to perform an operation on all rows of a table without using a cursor.
#Johannes offers no insight into his motivation , so this may or may not help him.
my research led me to this well written article which served as a basis for my solution
https://codingsight.com/passing-data-table-as-parameter-to-stored-procedures/
Here is the setup
drop type if exists cpRootMapType
go
create type cpRootMapType as Table(
RootId1 int
, RootId2 int
)
go
drop procedure if exists spMapRoot2toRoot1
go
create procedure spMapRoot2toRoot1
(
#map cpRootMapType Readonly
)
as
update linkTable set root = root1
from linktable lt
join #map m on lt.root = root2
update comments set root = root1
from comments c
join #map m on c.root = root2
-- ever growing list of places this map would need to be applied....
-- now consolidated into one place
here is the implementation
... populate #matches
declare #map cpRootMapType
insert #map select rootid1, rootid2 from #matches
exec spMapRoot2toRoot1 #map
I like to do something similar to this (though it is still very similar to using a cursor)
[code]
-- Table variable to hold list of things that need looping
DECLARE #holdStuff TABLE (
id INT IDENTITY(1,1) ,
isIterated BIT DEFAULT 0 ,
someInt INT ,
someBool BIT ,
otherStuff VARCHAR(200)
)
-- Populate your #holdStuff with... stuff
INSERT INTO #holdStuff (
someInt ,
someBool ,
otherStuff
)
SELECT
1 , -- someInt - int
1 , -- someBool - bit
'I like turtles' -- otherStuff - varchar(200)
UNION ALL
SELECT
42 , -- someInt - int
0 , -- someBool - bit
'something profound' -- otherStuff - varchar(200)
-- Loop tracking variables
DECLARE #tableCount INT
SET #tableCount = (SELECT COUNT(1) FROM [#holdStuff])
DECLARE #loopCount INT
SET #loopCount = 1
-- While loop variables
DECLARE #id INT
DECLARE #someInt INT
DECLARE #someBool BIT
DECLARE #otherStuff VARCHAR(200)
-- Loop through item in #holdStuff
WHILE (#loopCount <= #tableCount)
BEGIN
-- Increment the loopCount variable
SET #loopCount = #loopCount + 1
-- Grab the top unprocessed record
SELECT TOP 1
#id = id ,
#someInt = someInt ,
#someBool = someBool ,
#otherStuff = otherStuff
FROM #holdStuff
WHERE isIterated = 0
-- Update the grabbed record to be iterated
UPDATE #holdAccounts
SET isIterated = 1
WHERE id = #id
-- Execute your stored procedure
EXEC someRandomSp #someInt, #someBool, #otherStuff
END
[/code]
Note that you don't need the identity or the isIterated column on your temp/variable table, i just prefer to do it this way so i don't have to delete the top record from the collection as i iterate through the loop.