Replace procedural approach with a set based approach - sql

I have a stored procedure that is called from an SSIS package several times with a different value as an argument. The stored procedure contains an open query to an SSRS cube, and returns a list of people. The resultset is stored in a temporary table.
CREATE TABLE #tmp (Person varchar(50), Cat1 bit, Cat2 bit, Cat3 bit, Cat4 bit, Cat5 bit)
INSERT INTO #tmp EXEC sys.sp_executesql #query
SET #sqlCommand = 'SELECT Person FROM #tmp WHERE ' + #Category + '= 1'
EXEC (#sqlCommand);
For each person in the resultset, there should be an insert to another table with some information. The information is the name of the person, some other static content, and a value which I can get from running another stored procedure and count the rows returned.
An example of an insert could be
Name Static info ##rowcount
+-------------++--------------++--------------+
|Homer Simpson||Something here||ValueFromCount|
+-------------++--------------++--------------+
As of now, I have a prewritten stored procedure that inserts a row to a the desired table, but it has a parameter which is the person name. I get the ##rowcount value by executing the other SP from the current SP (a bit nested but works fine).
I could easily use an SQL WHILE loop or a CURSOR for each person in the resultset, and call the insert SP, but I can't help to think that there must be a more efficient way of doing this!
The flow right now is
SSIS package executes the stored procedure that returns a list of persons
For each person in the list, execute the SP_Insert_To_Table procedure.
2.1. From SP_Insert_To_Table procedure, execute SP_Get_All_Actions and select ##rowcount as the dynamic value to be inserted.
I have the idea that I might be able to join the result from the #tmp table with the count of actions the person has done, so that I would get a new resultset that could be inserted all in one, but I can't figure it out since the count is specific for each person in the list.
Is there a way to to this in a set based fashion instead of using a procedural approach?

With no details to work with this is a bit challenging but it certainly sounds like removing the loop should be pretty straight forward. All I can do is rough pseudocode.
Insert into SomeTable (ColumnsListedHere)
from SomeOtherTable
join Something on some conditions
where SomeCriteria = GetListOfPerson

Related

Get a specific column from a returned result of SP?

I have a stored procedure that returns a result, let's say it return rows of products. But each product status is not in our hand(can't get it). Our DBA just gave us another stored procedure to get the status of a product. We need to get individual product status by calling their SP. Let's say we have Product table,
CREATE TABLE PRODUCTS
(
ID INT,
Name NVARCHAR(100)
)
CREATE PROCEDURE GetProducts
AS
BEGIN
INSERT INTO #TEMPTABLE
SELECT * FROM PRODUCTS; -- Yes Too Much simplified
-- Create cursor and set additional status in #TEMPTABLE
END;
EXEC GetStatus #ProductId; -- SP That need to get status
The problem is that GetStatus is only way to get the status and this sp sometimes return 2 columns, sometimes 4 and sometimes 8. The return columns will always include Status column for sure.
If columns names is fixed then there is no problem. Is there is a way to create dynamic table at the time of executing SP.
Tried this but not working,
WITH DynamicTable AS
(
EXEC GetStatus
)
The answer to your question is no. There is no good way to get the value of a specific column returned by a stored procedure that can return a dynamic set of columns.
I say no "good" way, because of course there's a WAY. You can write an INSERT EXEC statement for every possible set of columns that the procedure can return and wrap each one in a TRY..CATCH block. If the first one errors, try the next one. As soon as you hit one that doesn't error, get the Status from it, and skip the rest.
That's the answer to your question. The solution to your problem, however, is to replace the GetStatus stored procedure with a Table-valued function. Then you can select from it, join to it, etc. I think the function would have to always return a consistent number of columns, but that would be better anyway, and the columns that aren't needed in a specific case could just be left empty or NULL.

Is there a way to pass temporary tables across the stored procedures

I have 4 stored procedures. I need to take the result of the first stored procedure (2 temp tables) and pass it into the second stored procedure. These temp tables need to be used in the from clause in the second stored procedure.
Similarity the third and fourth stored procedures need results from the previous stored procedures.
is there a way to pass temporary tables across the stored procedures?
Regarding this comment, "it was 1 Sp but we broke it into 4 so its easier to alter if needed", I suggest that you break it up even more. In other words, implement encapsulation.
Have a separate stored procedure for each time you want to select data from the actual tables. Do not populate temp tables in these procedures, just return the data.
Then write a stored procedure that creates and populates temp tables from the procs mentioned above, and does the necessary processing.
Here is a simple example:
create procedure GetData1
select Field1, Field2
from blah, blah, blah
create procedure AssembleAllData
create table #temp1 (Field1, Field2)
insert into #temp1
exec GetData1
select Field1, Field2, etc
from #temp1 join anActualTable etc
drop table #temp1
In your current SP1, you can create temporary table pass the name to the second stored procedure like below
SP1 code
IF OBJECT_ID('tempdb.dbo.#TempTable1') IS NOT NULL
DROP TABLE #TempTable1
EXEC SP2 N'#TempTable1'
Inside the SP2 you can insert the values into #TempTable1 which will be available to the calling SP
SP2 code
CREATE procedure [dbo].[SP2]
#outTempTable NVARCHAR(128)
AS
IF #outTempTable IS NOT NULL AND LEN(#outTempTable) > 0
BEGIN
EXEC ( 'INSERT INTO ' + #outTempTable + ' SELECT * FROM TableA' )
END
Your question sounds more like an answer than a question. Just do as you described.
You don't need to pass the data in the temp tables from one procedure to the next. The data is just there. In one procedure you write to the temp table and in the next procedure you read from the temp table.
I would also not create temp tables dynamically, just create them and let them wait for data. This assumes that the temp table data is local to a session (in oracle this is the case and in a way the reason why temp tables exist).
Also I would opt against passing table names between procedures. There is almost always a better way and it is a no-no anyways. If you are under the impression that you need variable temp table names, then you really want to add another column to the temp tables (you may even call it "temp_table_name", though it almost certainly means something different). Then you can pass the "temp_table_name" around and the selects would need a where temp_table_name = ... and the inserts would have to populate this extra column.

SQL Server Stored Procedure Multiple Insert in a single table from Array

I am using a stored procedure to insert records into a table. And do this at least 12 times in a loop to insert multiple records which is very inefficient.
here is the procedure as CREATED
Create PROC [dbo].[SP_INSERT_G_SAMPLING]
#GameID INT,
#ScoreID INT
as
begin
INSERT INTO GAMESCORE (GAMEID, SCOREID) VALUES
(#GameID, #ScoreID)
end
I pass on the values ex(1,3) and loop with more values from the website.
I want to however pass on all the values at one time like (1,3),(4,5),(8,9)
and then alter the above procedure to receive and insert multiple rows.
ALTER PROC [dbo].[SP_INSERT_G_SAMPLING]
#totalinsert nvarchar(Max)
INSERT INTO GAMESCORE (GAMEID, SCOREID) VALUES
(#totalinsert)
with #totalinsert being like (1,3),(4,5),(8,9) pushed from the webpage.
any help is greatly appreciated
What you're going to have to do is write a table valued function which accepts the multi-value string and breaks it out into a table object. If you can change your source to use a record delimiter instead of having comma sets it would be slightly easier to process. An example of that would look like this.
The below is pure psuedo and has not been validated in any way, just meant to give you a rough idea of where to go.
ex: #TotalInsert = 1,2|4,5|8,9
DECLARE #Results TABLE
(
value1 INT,
value2 INT
)
DECLARE #setlist VARCHAR(max);
WHILE Len(#TotalInsert) > 0
BEGIN
SET #setlist = LEFT(#totalinsert, Charindex('|', #totalinsert))
INSERT INTO #results
SELECT LEFT(#setlist, Charindex(',', #setlist) - 1),
RIGHT(#setlist, Charindex(',', Reverse(#setlist)) + 1)
SET #totalinsert = RIGHT(#totalinsert, Len(#totalinsert) - Len(#setlist))
END
I'm assuming you're using .NET for your website since you're also using SQL Server.
Have a look at table valued parameters, this page also includes a nice example of how to use the table valued parameters in .NET.
Check here for a better example of making a stored procedure with a table valued parameter in T-SQL.
Here is the full discussion:
http://www.sommarskog.se/arrays-in-sql-2005.html#XMLlist%20of%20values
Personally, I sent xml to the stored procedure, I "shred it" into #variable or #temp tables, then I do my INSERT/UPDATE/MERGE/DELETE from there.
Here is a fuller discussion on xml-shredding.
http://pratchev.blogspot.com/2007/06/shredding-xml-in-sql-server-2005.html
My personal trick is to create a strong dataset, populate the strong dataset with rows, and use the ds.GetXml() to send the xml down to the TSQL. With a strong dataset, I get strong-typing when populating the values. But at the end of the day, dataset is just some super fancy xml.

Export Multiple Query Results to Single Excel File

I ran a sp in SSMS and it gathers information from 50+ databases with the exact same structure. I am pulling results such as CustomerName, NumberOfUsers and VersionofCode. When I execute the procedure, I get 50+ different result sets, all with the same columns selected. Instead of exporting these 50+ times and putting it together in a single excel sheet, I'd like to see if I can export all results to 1 excel file.
Is this possible? I would have to think there would be a way to do this as my column names match up for every database I am querying.
Any help is appreciated!
There are probably a number of ways to solve this problem. I would address the issue by attempting to merge the many result sets from your stored procedure calls into a single result set and then perform whatever output-export (to excel) that you wish to do.
Simplest method would be to use a temp table to accumulate the results from each stored proc call. You can use the "INSERT #temptable EXEC mystoredproc #param1" syntax to store the results of a stored proc.
Here's a little example I whipped up:
-- *** Create a sample stored proc that returns one result set ***
CREATE PROC spGetCompanyEmployees #pCompanyID AS INT
AS
BEGIN
SELECT Company.CompanyName
, Department.DepartmentName
, Employee.EmployeeName
FROM Company
LEFT JOIN Department ON Department.CompanyID = Company.CompanyID
LEFT JOIN Employee ON Employee.DepartmentID = Department.DepartmentID
WHERE Company.CompanyID = #pCompanyID
END
GO
-- *** Demonstrate how to call that stored proc multiple times,
-- *** accumulating the results in a temp table and selecting
-- *** the combined results at the end.
CREATE TABLE #ttbl
(
CompanyName NVARCHAR(60)
, DepartmentName NVARCHAR(60)
, EmployeeName NVARCHAR(60)
)
INSERT #ttbl
EXEC spGetCompanyEmployees 1
INSERT #ttbl
EXEC spGetCompanyEmployees 2
SELECT * FROM #ttbl
The resulting output from that final SELECT will be a combined, single result set from both stored procedure calls.
I hope this helps.
I combined the suggestion to use a temp table with an example from this blog post that uses sys.sp_msforeachdb to iterate through databases, resulting in the following:
Create TABLE #temp
(
DbName NVARCHAR(50),
TableName NVARCHAR(50)
)
INSERT #temp
EXEC
sys.sp_msforeachdb
'SELECT ''?'' DatabaseName, Name FROM [?].sys.Tables WHERE Name LIKE ''%sometablename%'''
Select * from #temp

sql lookup ID and insert if no result found

I have a select statement that needs to look up a customer ID from a customer name. If an ID does not exist for that name, a new record needs to be created in the customer table. this has to be done as part of a select statement (related to the app its being run from).
I tried looking at a UDF that returned either the existing ID or a new ID, before realizing that you can't modify tables from a function.
any idea how to accomplish this?
EDIT:
I think i need to clarify things a bit more. The select statement can and will change on a per-implementation basis. What I'm looking for is a generic way of looking up or creating the customer id (that table and the need to do the lookup does not change) as part of a larger select statement.
the app that is using the sql loads the select statement from a config file, and has 'SELECT' hard coded, so there's no chance of adding an exec before the select etc.
It looks like what I need is something like 'select a.1 (exec dotheLookup(name)) as customerID, a.2 FROM table, but I'm not sure how to go about that.
I suggest you to Create a stored procedure for this. Something like
Create procedure customer
--parameters
AS
Begin
IF exists(Select lookup(customerName) as customerID from table)
BEGIN
--Your select goes here
END
ELSE
BEGIN
--Insert into customer table and return scopeidentity
--Select goes here
END
END
Updated Answer:
You cannot perform data manipulation using select statement.
You could execute a stored procedure before you execute the SELECT statement, the run a function that returns ID from name:
exec CheckForCustomerByNameAndAddIDIfItDoesntExist(customerName)
declare iCustomerID int
select iCustomerID = GetCustomerIDFromName(customerName)
select a.1, a.2, iCustomerID as customerID from table
Something like that
Can you modify the database server? If so, add a linked server pointing to the local server.
EXEC master.dbo.sp_addlinkedserver #server = N'LinkedLocal', #srvproduct=N'', #provider=N'SQLNCLI', #datasrc=N'LocalServerName'
EXEC master.dbo.sp_addlinkedsrvlogin #rmtsrvname=N'LinkedLocal',#useself=N'True',#locallogin=NULL,#rmtuser=NULL,#rmtpassword=NULL
Then just run an OPENQUERY that invokes a stored procedure that does your work:
select * from OPENQUERY("LinkedLocal", 'exec Database.Schema.StoredProcedure ''Param1'', ''Param2'')