Pass a table as parameter in openquery - sql

I've created a datatype and declared a table of this type which I intend to pass as a parameter to an OPENQUERY statement. OPENQUERY returns an error that the object has no columns.
Firstly, the table parameter is defined as follows:
CREATE TYPE LooseTimecardsTableType AS TABLE ([LABORKEY] [float] NULL)
GO
DECLARE #DataTable AS LooseTimecardsTableType
INSERT INTO #DataTable
SELECT DISTINCT WOBase.LABORKEY
FROM Lab_WO_DataWH AS WOBase
left outer JOIN Lab_hrs_DataWH LabHrsWH ON LabHrsWH.WORKORDERLABORKEY = WOBase.LABORKEY
WHERE LabHrsWH.WORKORDERLABORKEY IS NULL AND WOBase.LABORKEY IS NOT NULL AND WOBase.LABORPRICE <> 0
The data table returns a single column of LABORKEY's which I want use to restrict the number of records from the OPENQUERY. This in turn is defined as follows:
DECLARE #SQLString NVARCHAR(500), #TableVariable LooseTimecardsTableType
SET #SQLString = N'SELECT * FROM OPENQUERY(Remoteserver, ''SELECT DISTINCT
DA.USERNAME, DA.WORKORDERLABORKEY, LB.PERFORMEDBY
FROM
REMOTE.WORKORDERDETAILAUDITS DA
JOIN REMOTE.WORKORDERLABORBASE LB ON LB.LABORKEY = DA.WORKORDERLABORKEY
JOIN #TableVariable ON #TableVariable.LABORKEY = LB.LABORKEY
WHERE DA.WORKORDERAUDITCATEGORY = 0'')'
EXECUTE sp_executesql #SQLString, N'#TableVariable LooseTimecardsTableType READONLY', #DataTable
Please assist in getting this to work

Instead of using a table variable you could turn the select statement that populates the table into a view using the where clause and then join the view to the table on the remote server.

The table variable cannot be passed as a parameter to OPENQUERY. As my table had only a single column I was able to convert it to a string and pass the string to OPENQUERY as a parameter. I did this within a procedure that accepts a table variable. After parsing the table to a string, the procedure also runs the OPENQUERY.
CREATE PROCEDURE PRM_LIST ( #TableVariable LooseTimecardsTableType READONLY)
AS
DECLARE
#LBKY_NVAR NVARCHAR(2500),
#POINTER INT,
#SQLString NVARCHAR(max)
SELECT #POINTER = MIN(LABORKEY) FROM #TableVariable
WHILE #POINTER IS NOT NULL
BEGIN
SET #LBKY_NVAR = IIF(#LBKY_NVAR IS NULL,'('+''''+ CONVERT(VARCHAR,#POINTER) + '''',
#LBKY_NVAR + ',' + ''''+ CONVERT(VARCHAR,#POINTER) + '''')
SELECT #POINTER = MIN(LABORKEY) FROM #TableVariable WHERE LABORKEY > #POINTER
END
SET #LBKY_NVAR = #LBKY_NVAR + ')'
SET #LBKY_NVAR = REPLACE(#LBKY_NVAR,'''','''''')
SET #SQLString =
N'SELECT * FROM OPENQUERY(REMOTE, ''SELECT DISTINCT
DA.USERNAME, DA.WORKORDERLABORKEY, LB.PERFORMEDBY
FROM
REMOTE.WORKORDERDETAILAUDITS DA
JOIN REMOTE.WORKORDERLABORBASE LB ON LB.LABORKEY = DA.WORKORDERLABORKEY
WHERE DA.WORKORDERAUDITCATEGORY = 0
AND LB.LABORKEY IN '+CAST(#LBKY_NVAR AS nvarchar(2500))+ N'
'')'
EXEC (#SQLString)
GO
The procedure call is:
EXECUTE PRM_LIST #DATATABLE

Related

How to send column name as param in stored proc and function in MSSQL

I'm trying to create a stored procedure in mssql (sql server) which takes the params of table name and column name. The stored proc should update given table and column name with base 64 value. I was able to create the function to convert string to base64 in sql server, which is a follows,
ALTER FUNCTION [dbo].[uFnStrToB64]
(
#InputString VARCHAR(MAX)
)
RETURNS VARCHAR(MAX)
AS
BEGIN
RETURN (
SELECT
CAST(N'' AS XML).value(
'xs:base64Binary(xs:hexBinary(sql:column("bin")))'
, 'VARCHAR(MAX)'
)
FROM (
SELECT CAST(#InputString AS VARBINARY(MAX)) AS bin
) AS RetVal
)
END;
Now, I'm calling this function in following stored procedure, as follows
ALTER PROCEDURE [dbo].[UpdateTableColumnWithB64]
#tbl sysname,
#Name sysname
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL NVARCHAR(MAX);
SET #SQL = N' UPDATE ' + QUOTENAME(#tbl)
+ N' SET ' + #Name + ' = ' + dbo.uFnStrToB64(#Name)
EXECUTE sp_executesql #SQL
END
But instead of updating the column value with base 64 of the column value, it is replacing it with base 64 of column name. But when I run following update command, it works flawlessly,
UPDATE mytable SET mycolumn=dbo.uFnStrToB64(mycolumn)
I'm calling stored proc as follows,
DECLARE #return_value int
EXEC #return_value = [dbo].[UpdateTableColumnWithB64]
#tbl = mytable,
#Name = mycolumn
SELECT 'Return Value' = #return_value
GO
Adding create and insert table commands, if someone wants to run it and try it,
CREATE TABLE mytable(
mycolumn varchar(max) NULL
);
INSERT INTO mytable(mycolumn) VALUES ('test'), ('test2'), ('test3');
Can somebody help me understand, when I move the update statement to stored procedure why the same update statement takes mycolumn as string instead of getting value from column? I'm open to change function and stored proc Or open for ways to do base 64 conversion in stored proc without usage of function.
Thanks to #dan, following is the solution
SET #SQL = N' UPDATE ' + QUOTENAME(#tbl)
+ N' SET ' + QUOTENAME(#Name)
+ N' = dbo.uFnStrToB64(' + QUOTENAME(#Name)
+ N');';

Stored Procedure That Return Different Table According To Parameter

I want to write a stored procedure that takes #FirmId as a parameter and I will use the related table according to this parameter.
What I want to obtain (but I don't want to use) is something like that:
CREATE PROCEDURE spFirmDetailGetByFirmId
#FirmId AS INT
AS
BEGIN
IF #FirmId = 1
SELECT * FROM Firm1
ELSE IF #FirmId = 2
SELECT * FROM Firm2
.
.
.
.
ELSE IF #FirmId = 1000
SELECT * FROM Firm1000
END
And also I don't want to create query string and then EXEC it, something like that in the fallowing code block. Because the real query is too complex and it will be very hard to manage if I use this option.
CREATE PROCEDURE spFirmDetailGetByFirmId
#FirmId AS INT
AS
BEGIN
DECLARE #Query AS NVARCHAR(MAX) = 'SELECT * FROM Firm'
SET #Query = #Query + CAST(#FirmId AS NVARCHAR(10))
EXEC(#Query)
END
Is there any other option?
Thanks.
I take your Yes the tables are identical and will be kept identical to suggest two approaches:
DECLARE #Firm VARCHAR(10)='Firm3';
SELECT * FROM Firm1 WHERE #Firm='Firm1'
UNION ALL
SELECT * FROM Firm2 WHERE #Firm='Firm2'
UNION ALL
SELECT * FROM Firm3 WHERE #Firm='Firm3'
[...]
UNION ALL
SELECT * FROM Firm1000 WHERE #Firm='Firm1000'
The second is:
DECLARE #query NVARCHAR(MAX)='SELECT * FROM ####';
SET #query=REPLACE(#query,'####',#Firm);
EXEC (#query)
The second could be used with a VIEW (in place of the #query), where you could read the VIEW's definition into the variable and create an ALTER VIEW-statement dynamically... Your procedure would call the same VIEW (but this would crash with parallel calls!)
This code can by use in a stored procedure to automatic create the view, every time you need to add columns
declare #tableId int
declare #columns varchar(max)
declare #tablesCount int
declare #tableName varchar(255)
declare #query varchar(255)
declare #id int
declare #result nvarchar(max)
set #columns = ''
set #tableName = 'Firm'
set #id = 1
set #result = ''
--Base table
select #tableId = object_id from sys.tables where name =#tableName
--Count how many table with the 'same name'
select #tablesCount= count(*) from sys.tables where name like #tableName+'%'
--Build Columns to add in the view
select #columns =#columns+name+', 'from Sys.columns where object_id = #tableId
--Drop View
set #result = 'Drop view vw_'+#tableName
exec sp_executesql #result
set #result=''
while(#id<=#tablesCount)
Begin
declare #idVarchar varchar(10)
set #idVarchar = cast(#id as varchar(10))
set #result =#result+'Select '+#columns+#idVarchar+' as FirmId from '+#tableName+#idVarchar
+'
Union all
'
set #id =#id+1
End
set #result = substring(#result, 1, len(#result)-12)
set #result='Create view vw_'+#tableName+' as
'+#result
exec sp_executesql #result
There is a another choice to this, you can also use sp_helpText to get the current definition of the view and append only add new table identifier

Passing two values to a stored procedure?

I've written a stored procedure which is called on a link which provides a date value every time and #cg is NULL that time to filter the result on a particular date.
DECLARE #return_value int
EXEC #return_value = [dbo].[Get_Mydata]
#cg = NULL,
#tosearch = '15-05-2014'
SELECT 'Return Value' = #return_value
GO
And after first execution of the stored procedure, it gives some results and using same stored procedure.
I need to filter result by passing below parameter so this time #cg is NOT NULL.
DECLARE #return_value int
EXEC #return_value = [dbo].[Get_Mydata]
#cg = 'CUSTOMER NAME',
#tosearch = 'manish'
SELECT 'Return Value' = #return_value
GO
I'm not able to figure how should I create a dynamic where clause and add it to existing query as well as how to pass value to same parameter which already been passed as date.
More like first getting results for a particular date and then applying like filter on that result. I cannot pass different parameter that's Front end developers requirement.
This is my stored procedure and table data here. http://sqlfiddle.com/#!3/bb917
create proc Get_Mydata
(
#cg varchar(50),
#tosearch varchar(50)
)
as
begin
set nocount on
declare #sqlquery nvarchar(max)
set #sqlquery = N'select q_no, trandate, cust_name from testsp where CONVERT(Date, trandate, 103) = CONVERT(Date, ''' + #tosearch + ''' ,103)';
create table #temp1
(
q_no int,
trandate datetime,
cust_name varchar(50)
)
insert into #temp1(q_no, trandate, cust_name)
exec (#sqlquery)
select * from #temp1 as T;
set nocount off
end
What I have understood is that you want stored procedure to filter results on Date column when you pass null to #cg param and you want to filter results on Cust_name when you pass string 'Cust_Name' to your #Cg Param.
It should be fairly simple, But in any case you do not need a temp table to get the results back its just an over kill of a fairly simple query.
I would do something like this....
Pass the column name to #ColumnName Parameter, and your value to #tosearch parameter. It will build the query depending on what values you pass.
Make sure when you pass a value(Column Name) to #ColumnName.
create proc Get_Mydata
(
#ColumnName varchar(50),
#tosearch varchar(50)
)
as
begin
set nocount on;
declare #sqlquery nvarchar(max);
set #sqlquery = N' select q_no, trandate, cust_name '
+ N' from testsp '
+ N' where ' + QUOTENAME(#ColumnName) + N' = '
+ CASE
WHEN #ColumnName = 'trandate'
THEN N' CAST(#tosearch AS DATE)'
WHEN #ColumnName = 'cust_name'
THEN N' #tosearch'
ELSE N'' END
EXECUTE sp_executesql #sqlquery
,N'#tosearch varchar(50)'
,#tosearch
set nocount off;
end

adding a table as parameter in a query string in a stored procedure

I have the following query :
ALTER procedure [dbo].[jk_insertAllLocation]
#locationTbl as locationTable readonly,
#TableName varchar(100)
as
declare #tbl as locationTable,#sql varchar(max)
begin
set #sql = 'insert into ' + #TableName +'(location_id,name,address,latitude,longitude,distance,state,sub_cat,id_cat,icon_link,checkinsCount,IDSearch)
select * from ' + #locationTbl
exec sp_executesql #sql
end
I need to pass a table and a table name as parameter and I need to insert in the table name (#TableName) passed as parameter all the data in the table (#locationTbl) passed as parameter
but I know that I cannot concatenate the table (#locationTbl) in the query ...
so how can I fix this?
You can use temp tables (Temporary tables section on link):
ALTER procedure [dbo].[jk_insertAllLocation]
#locationTbl as locationTable readonly,
#TableName varchar(100)
as
begin
declare #tbl as locationTable,#sql varchar(max)
if object_id('#_tmp_location_table') is not null drop table #_tmp_location_table
select * into #_tmp_location_table from #locationTbl
set #sql = 'insert into ' + #TableName + '(location_id,name,address,latitude,longitude,distance,state,sub_cat,id_cat,icon_link,checkinsCount,IDSearch) select * from #_tmp_location_table'
exec sp_executesql #sql
end

How to use table variable in a dynamic sql statement?

In my stored procedure I declared two table variables on top of my procedure. Now I am trying to use that table variable within a dynamic sql statement but I get this error at the time of execution of that procedure. I am using Sql Server 2008.
This is how my query looks like,
set #col_name = 'Assoc_Item_'
+ Convert(nvarchar(2), #curr_row1);
set #sqlstat = 'update #RelPro set '
+ #col_name
+ ' = (Select relsku From #TSku Where tid = '
+ Convert(nvarchar(2), #curr_row1) + ') Where RowID = '
+ Convert(nvarchar(2), #curr_row);
Exec(#sqlstat);
And I get the following errors,
Must declare the table variable "#RelPro".
Must declare the table variable "#TSku".
I have tried to take the table outside of the string block of dynamic query but to no avail.
On SQL Server 2008+ it is possible to use Table Valued Parameters to pass in a table variable to a dynamic SQL statement as long as you don't need to update the values in the table itself.
So from the code you posted you could use this approach for #TSku but not for #RelPro
Example syntax below.
CREATE TYPE MyTable AS TABLE
(
Foo int,
Bar int
);
GO
DECLARE #T AS MyTable;
INSERT INTO #T VALUES (1,2), (2,3)
SELECT *,
sys.fn_PhysLocFormatter(%%physloc%%) AS [physloc]
FROM #T
EXEC sp_executesql
N'SELECT *,
sys.fn_PhysLocFormatter(%%physloc%%) AS [physloc]
FROM #T',
N'#T MyTable READONLY',
#T=#T
The physloc column is included just to demonstrate that the table variable referenced in the child scope is definitely the same one as the outer scope rather than a copy.
Your EXEC executes in a different context, therefore it is not aware of any variables that have been declared in your original context. You should be able to use a temp table instead of a table variable as shown in the simple demo below.
create table #t (id int)
declare #value nchar(1)
set #value = N'1'
declare #sql nvarchar(max)
set #sql = N'insert into #t (id) values (' + #value + N')'
exec (#sql)
select * from #t
drop table #t
You don't have to use dynamic SQL
update
R
set
Assoc_Item_1 = CASE WHEN #curr_row = 1 THEN foo.relsku ELSE Assoc_Item_1 END,
Assoc_Item_2 = CASE WHEN #curr_row = 2 THEN foo.relsku ELSE Assoc_Item_2 END,
Assoc_Item_3 = CASE WHEN #curr_row = 3 THEN foo.relsku ELSE Assoc_Item_3 END,
Assoc_Item_4 = CASE WHEN #curr_row = 4 THEN foo.relsku ELSE Assoc_Item_4 END,
Assoc_Item_5 = CASE WHEN #curr_row = 5 THEN foo.relsku ELSE Assoc_Item_5 END,
...
from
(Select relsku From #TSku Where tid = #curr_row1) foo
CROSS JOIN
#RelPro R
Where
R.RowID = #curr_row;
You can't do this because the table variables are out of scope.
You would have to declare the table variable inside the dynamic SQL statement or create temporary tables.
I would suggest you read this excellent article on dynamic SQL.
http://www.sommarskog.se/dynamic_sql.html
Well, I figured out the way and thought to share with the people out there who might run into the same problem.
Let me start with the problem I had been facing,
I had been trying to execute a Dynamic Sql Statement that used two temporary tables I declared at the top of my stored procedure, but because that dynamic sql statment created a new scope, I couldn't use the temporary tables.
Solution:
I simply changed them to Global Temporary Variables and they worked.
Find my stored procedure underneath.
CREATE PROCEDURE RAFCustom_Room_GetRelatedProducts
-- Add the parameters for the stored procedure here
#PRODUCT_SKU nvarchar(15) = Null
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
IF OBJECT_ID('tempdb..##RelPro', 'U') IS NOT NULL
BEGIN
DROP TABLE ##RelPro
END
Create Table ##RelPro
(
RowID int identity(1,1),
ID int,
Item_Name nvarchar(max),
SKU nvarchar(max),
Vendor nvarchar(max),
Product_Img_180 nvarchar(max),
rpGroup int,
Assoc_Item_1 nvarchar(max),
Assoc_Item_2 nvarchar(max),
Assoc_Item_3 nvarchar(max),
Assoc_Item_4 nvarchar(max),
Assoc_Item_5 nvarchar(max),
Assoc_Item_6 nvarchar(max),
Assoc_Item_7 nvarchar(max),
Assoc_Item_8 nvarchar(max),
Assoc_Item_9 nvarchar(max),
Assoc_Item_10 nvarchar(max)
);
Begin
Insert ##RelPro(ID, Item_Name, SKU, Vendor, Product_Img_180, rpGroup)
Select distinct zp.ProductID, zp.Name, zp.SKU,
(Select m.Name From ZNodeManufacturer m(nolock) Where m.ManufacturerID = zp.ManufacturerID),
'http://s0001.server.com/is/sw11/DG/' +
(Select m.Custom1 From ZNodeManufacturer m(nolock) Where m.ManufacturerID = zp.ManufacturerID) +
'_' + zp.SKU + '_3?$SC_3243$', ep.RoomID
From Product zp(nolock) Inner Join RF_ExtendedProduct ep(nolock) On ep.ProductID = zp.ProductID
Where zp.ActiveInd = 1 And SUBSTRING(zp.SKU, 1, 2) <> 'GC' AND zp.Name <> 'PLATINUM' AND zp.SKU = (Case When #PRODUCT_SKU Is Not Null Then #PRODUCT_SKU Else zp.SKU End)
End
declare #curr_row int = 0,
#tot_rows int= 0,
#sku nvarchar(15) = null;
IF OBJECT_ID('tempdb..##TSku', 'U') IS NOT NULL
BEGIN
DROP TABLE ##TSku
END
Create Table ##TSku (tid int identity(1,1), relsku nvarchar(15));
Select #curr_row = (Select MIN(RowId) From ##RelPro);
Select #tot_rows = (Select MAX(RowId) From ##RelPro);
while #curr_row <= #tot_rows
Begin
select #sku = SKU from ##RelPro where RowID = #curr_row;
truncate table ##TSku;
Insert ##TSku(relsku)
Select distinct top(10) tzp.SKU From Product tzp(nolock) INNER JOIN
[INTRANET].raf_FocusAssociatedItem assoc(nolock) ON assoc.associatedItemID = tzp.SKU
Where (assoc.isActive=1) And (tzp.ActiveInd = 1) AND (assoc.productID = #sku)
declare #curr_row1 int = (Select Min(tid) From ##TSku),
#tot_rows1 int = (Select Max(tid) From ##TSku);
If(#tot_rows1 <> 0)
Begin
While #curr_row1 <= #tot_rows1
Begin
declare #col_name nvarchar(15) = null,
#sqlstat nvarchar(500) = null;
set #col_name = 'Assoc_Item_' + Convert(nvarchar(2), #curr_row1);
set #sqlstat = 'update ##RelPro set ' + #col_name + ' = (Select relsku From ##TSku Where tid = ' + Convert(nvarchar(2), #curr_row1) + ') Where RowID = ' + Convert(nvarchar(2), #curr_row);
Exec(#sqlstat);
set #curr_row1 = #curr_row1 + 1;
End
End
set #curr_row = #curr_row + 1;
End
Select * From ##RelPro;
END
GO
I don't think that is possible (though refer to the update below); as far as I know a table variable only exists within the scope that declared it. You can, however, use a temp table (use the create table syntax and prefix your table name with the # symbol), and that will be accessible within both the scope that creates it and the scope of your dynamic statement.
UPDATE: Refer to Martin Smith's answer for how to use a table-valued parameter to pass a table variable in to a dynamic SQL statement. Also note the limitation mentioned: table-valued parameters are read-only.
Here is an example of using a dynamic T-SQL query and then extracting the results should you have more than one column of returned values (notice the dynamic table name):
DECLARE
#strSQLMain nvarchar(1000),
#recAPD_number_key char(10),
#Census_sub_code varchar(1),
#recAPD_field_name char(100),
#recAPD_table_name char(100),
#NUMBER_KEY varchar(10),
if object_id('[Permits].[dbo].[myTempAPD_Txt]') is not null
DROP TABLE [Permits].[dbo].[myTempAPD_Txt]
CREATE TABLE [Permits].[dbo].[myTempAPD_Txt]
(
[MyCol1] char(10) NULL,
[MyCol2] char(1) NULL,
)
-- an example of what #strSQLMain is : #strSQLMain = SELECT #recAPD_number_key = [NUMBER_KEY], #Census_sub_code=TEXT_029 FROM APD_TXT0 WHERE Number_Key = '01-7212'
SET #strSQLMain = ('INSERT INTO myTempAPD_Txt SELECT [NUMBER_KEY], '+ rtrim(#recAPD_field_name) +' FROM '+ rtrim(#recAPD_table_name) + ' WHERE Number_Key = '''+ rtrim(#Number_Key) +'''')
EXEC (#strSQLMain)
SELECT #recAPD_number_key = MyCol1, #Census_sub_code = MyCol2 from [Permits].[dbo].[myTempAPD_Txt]
DROP TABLE [Permits].[dbo].[myTempAPD_Txt]
Using Temp table solves the problem but I ran into issues using Exec so I went with the following solution of using sp_executesql:
Create TABLE #tempJoin ( Old_ID int, New_ID int);
declare #table_name varchar(128);
declare #strSQL nvarchar(3072);
set #table_name = 'Object';
--build sql sting to execute
set #strSQL='INSERT INTO '+#table_name+' SELECT '+#columns+' FROM #tempJoin CJ
Inner Join '+#table_name+' sourceTbl On CJ.Old_ID = sourceTbl.Object_ID'
**exec sp_executesql #strSQL;**