Call stored procedure with table-valued parameter from java - sql

In my application I want to execute query like SELECT * FROM tbl WHERE col IN (#list) where,#list can have variable no of values. I am using MS SQL server database. When I google this problem then I found this link
http://www.sommarskog.se/arrays-in-sql-2008.html
This link says to use table-valued parameter. So I created user-defined data type using Microsoft SQL Server Management Studio.
CREATE TYPE integer_list_tbltype AS TABLE (n int NOT NULL PRIMARY KEY)
Then I wrote stored procedure
CREATE PROCEDURE get_product_names #prodids integer_list_tbltype READONLY AS
SELECT p.ProductID, p.ProductName
FROM Northwind.dbo.Products p
WHERE p.ProductID IN (SELECT n FROM #prodids)
and then using management studio only I executed this procedure
DECLARE #mylist integer_list_tbltype
INSERT #mylist(n) VALUES(9),(12),(27),(37)
EXEC get_product_names #mylist
and it is giving me correct output. But I am wondering how to call this stored procedure from java source code. I know how to call simple stored procedure with constant number of argument
CallableStatement proc_stmt = null;
proc_stmt = con.prepareCall("{call test(?)}");
proc_stmt.setString(1,someValue);
but how to call stored procedure in table-value parameter case?

This is documented here in the JDBC driver manual. In your case, you'd have to do this:
try (SQLServerCallableStatement stmt =
(SQLServerCallableStatement) con.prepareCall("{call test(?)}")) {
SQLServerDataTable table = new SQLServerDataTable();
sourceDataTable.addColumnMetadata("n", java.sql.Types.INTEGER);
sourceDataTable.addRow(9);
sourceDataTable.addRow(12);
sourceDataTable.addRow(27);
sourceDataTable.addRow(37);
stmt.setStructured(1, "dbo.integer_list_tbltype", table);
}
I've also recently documented this in an article.

Looks like this is a planned addition to JDBC but has not been implemented yet:
http://blogs.msdn.com/b/jdbcteam/archive/2012/04/03/how-would-you-use-table-valued-parameters-tvp.aspx
Pass the parameter as a delimited string ("9,12,27,37") and then create a table-valued function in SQL Server called "fnSplit" or whatever that will return the integer values in a table (just search for "sql server split function," there are millions of them).

The typical answers (comma delimited or XML) all have problems with SQL Injection. I needed an answer that allows me to use a PreparedStatement. So I came up with this:
StringBuilder query = new StringBuilder();
query.append(
"DECLARE #mylist integer_list_tbltype;" +
"INSERT #mylist(n) VALUES(?)");
for (int i = 0; i < values.size() - 1; ++i) {
query.append(",(?) ");
}
query.append("; EXEC get_product_names #mylist ");
PreparedStatement preparedStmt = conn.prepareStatement(query.toString());
for (int i = 0; i < values.size(); ++i) {
preparedStmt.setObject(i + 1, itemLookupValues.get(i));
}

Now it's been added to JDBC Driver 6.0.
It CTP2 yet.
"This new driver now supports Table-Valued Parameters and Azure Active Directory. In addition to these new features, we have added additionally functionality for Always Encrypted. The driver also supports Internationalized Domain Names and Parameterized."
https://blogs.msdn.microsoft.com/jdbcteam/2016/04/04/get-the-new-microsoft-jdbc-driver-6-0-preview/
Download link:https://www.microsoft.com/en-us/download/details.aspx?displaylang=en&id=11774
Here is the documentation how to use it
https://msdn.microsoft.com/en-us/library/mt651781(v=sql.110).aspx

After searching for a while I found answer of this problem.Specially when you use IN clause and no of operands are variable then you can use comma-delimited values as an input in IN clause.
Here's the example how the stored procedure that will retrieve all lawyers of the given lawyer type in the provided ZIP code will look like using dynamic SQL.
CREATE PROCEDURE [dbo].[GetLawyers] ( #ZIP CHAR(5), #LawyerTypeIDs VARCHAR(100) )
AS
DECLARE #SQL VARCHAR(2000)
SET #SQL = 'SELECT * FROM [dbo].[Lawyers]
WHERE [ZIP] = ' + #ZIP + ' AND
[LawyerTypeID] IN (' + #LawyerTypeIDs + ')'
EXECUTE (#SQL)
GO
To execute the stored procedure passing the ZIP code entered by the user and the selected lawyer types in a comma separated value:
EXECUTE [dbo].[GetLawyers] '12345', '1,4'
So conclusion is you do not need to use TVP. Whichever language[Java, PHP] you use just pass parameters as comma-delimited string to stored procedure and it will work perfect.
So in JAVA you can call above stored procedure:-
proc_stmt = con.prepareCall("{call GetLawyers(?,?)}");
proc_stmt.setString(1,"12345");
proc_stmt.setString(2,"'1,4'");

Related

Entity Framework generated representation of a stored procedure signature is an Int instead of a record set

My stored procedure implementation is simple:
create Procedure [dbo].[GetNegotiationInfo] (
#negotiationId uniqueidentifier
) As
select NegotiationId, SellerId from NegotiationMaster where negotiationId = #negotiationId
Then I expected to be to write the following code after updating my model.
using (var db = new NegotiationContext())
{
var query = db.GetNegotiationInfo(NegotiationId).FirstOrDefault();
}
But I am getting the error "int does not contain a definition for FirstOrDefault()". I know that Entity Framework can have trouble generating complex stored procedures with temp tables etc but obviously my stored procedure doesn't get any simpler than this. Please note my stored procedure is this basic for my StackOverflow question and is not the actual stored procedure I will be using.
Stored procedures always return an integer. This is the status of the stored procedure call and will be set to NULL if not specified in the code. The general approach is that 0 indicates success and any other value indicates an error. Here is some documentation on the subject.
If you want to return a value from a stored procedure, use output parameters.
Alternatively, you might want a user defined function which returns a value to the caller. This can be a scalar or a table.
EDIT:
I would suggest that you look into user defined functions.
create function [dbo].[GetNegotiationInfo] (
#negotiationId uniqueidentifier
)
returns table
As
return(select NegotiationId, SellerId
from NegotiationMaster
where negotiationId = #negotiationId
);
In SQL, you would call this as:
select NegotiationId, SellerId
from dbo.GetNegotiationInfo(NegotiationId);
EDIT (in response to Aaron's comment):
This discussion is about SQL-only stored procedures. Entity Framework wraps stuff around stored procedures. The documentation mentioned in the comment below strongly suggests that EF should be returning the data from the last select in the stored procedure, but that you should be using ExecuteFunction -- confusingly even though this is a stored procedure. (Search for "Importing Stored Procedures that Return Types Other than Entities" in the document.)
Use SET NOCOUNT ON at the top of your procedure, the integer being returned is almost certainly the row count.
Like so:
ALTER Procedure [dbo].[GetNegotiationInfo] (
#negotiationId uniqueidentifier
) As
SET NOCOUNT ON;
select n.*,
s.firstname + ' ' + s.lastname sellername,
b.firstname + ' ' + b.lastname buyername,
substring(s.firstname, 1, 1) + substring(s.lastname, 1, 1) as sellerinit,
substring(b.firstname, 1, 1) + substring(b.lastname, 1, 1) as buyerinit,
s.email selleremail, b.email buyeremail,
a.ADDRESS1
from negotiationMaster n
inner join usermaster s on s.userid = n.sellerid
inner join usermaster b on b.userid = n.buyerid
inner join PropertyNegotiation p on p.NegotiationId = n.NegotiationId
inner join MyOtherDb..PROPERTIES a on a.property_id = p.PropertyId
where n.negotiationId = #negotiationId
return;
Its all about editing the "Function Import" in the Visual Studio Model Browser and creating a complex type for the return record set. Note, temp Tables in your stored procedure will be difficult for Entity Framework to inspect. see MSDN

Can we use a host-variable as table name in SELECT query?

I'm experimenting with Pro*C code.
I have 3 tables emp, mgr, all; All 3 tables contain emp_id and emp_name. I tried the below code it is giving me error. Please let me know if it is possible?
const char table_name[3]={'emp','mgr','all'}
int counter = 0;
while(counter < 3)
{
. . .
EXEC SQL SELECT emp_name INTO :ename
From :table_name[counter++]
where emp_id=:emp_id;
}
Can we use variables for SELECT and FROM ?
This is called Dynamic SQL:
Use this info:
Dynamic SQL
While embedded SQL is fine for fixed applications, sometimes it is important for a program to dynamically create entire SQL statements.
With dynamic SQL, a statement stored in a string variable can be issued.
PREPARE turns a character string into a SQL statement, and EXECUTE executes that statement. Consider the following example.
char *s = "INSERT INTO emp VALUES(1234, 'jon', 3)";
EXEC SQL PREPARE q FROM :s;
EXEC SQL EXECUTE q;
Alternatively, PREPARE and EXECUTE may be combined into one statement:
char *s = "INSERT INTO emp VALUES(1234, 'jon', 3)";
EXEC SQL EXECUTE IMMEDIATE :s;
Source: http://infolab.stanford.edu/~ullman/fcdb/oracle/or-proc.html
No, we can't use a host-variable to supply a table-name to a static SQL statement in Pro*C.
To quote Oracle Pro*C Programmer's Guide for Oracle 11.2g:
You cannot use input host variables to supply SQL keywords or the names of database objects. [..] If you need to change database object names at runtime, use dynamic SQL. See also Chapter 13, "Oracle Dynamic SQL".
In Pro*C Oracle provides different methods to execute SQL statements. The main distinction is between static and dynamic methods. And for dynamic there are several sub-methods that allow different degrees of freedom.
A static SQL statement is not 100% static - you can use input/output host-variables in where clause expressions (as operands), to supply values in insert statements, as select targets, etc. But not to specify table names. This is 'too dynamic' for static embedded SQL.
Note that you can still use host-variables in your dynamically prepared SQL statements. This is recommended to avoid SQL injection issues and increase performance (when a statement is executed several times).
Example (uses Oracle Dynamic SQL method 3):
const char *table_name[3] = {"emp", "mgr", "all"};
unsigned table_name_size = 0;
unsigned i = 0;
for (i = 0; i<table_name_size; ++i) {
char stmt[128] = {0};
snprintf(stmt, 128,
"SELECT emp_name "
" FROM %s "
" WHERE "
" emp_id = :emp_id",
table_name[i]);
EXEC SQL PREPARE emp_stmt FROM :stmt;
// check sqlca.sqlcode ...
EXEC SQL DECLARE emp_cursor CURSOR FOR emp_stmt;
EXEC SQL OPEN emp_cursor USING :emp_id;
// check sqlca.sqlcode ...
EXEC SQL FETCH emp_cursor INTO :ename;
// check sqlca.sqlcode ...
EXEC SQL CLOSE emp_cursor;
// check sqlca.sqlcode ...
// ...
}

Use Stored procedure like a function

I need to deal with the table name as a variable.Then I must using dynamic sql and therefore I must using Stored procedure.
But the problem that how can I use the stored procedure like a custom sql function.
e.g: select col1,(Exec sp1 param1,'tbName') from table1
Finally,I changed my design and and use dynamic SQL in one upper level.
This will be posible in sql server denali that introduces the new keywords "WITH RESULTSET".
The alternative on current sql versions is passing a temp-table to the stored procedure
Stored procedures can return scalar values through output parameters. Here's an example (from here).
Create the stored procedure like this:
CREATE PROCEDURE _4P_test
#intInput INT,
#intOutput INT OUTPUT
AS
SET #intOutput = #intInput + 1
Call it like this:
DECLARE #intResult INT
EXEC _4P_test 3, #intResult OUT
SELECT #intResult
However you should try to design your system so that you don't have to use dynamic SQL in the way you described.

Why can't use INSERT EXEC statement within a stored procedure called by another stored procedure?

First I try to explain the circumstances.
I store the the filter expression in one column separated by line breaks. The base idea was this:
SELECT
'SELECT ''' + REPLACE(topic_filter,CHAR(10),''' UNION ALL SELECT ''') + ''''
FROM dbo.topic_filter T
WHERE
T.id = #id
FOR XML PATH('')
After this I simply execute this string to put the datas into a temp table.
My problem starts here.
The snippet is in a stored procedure and used by multiple stored procedures to generate the base source to fill.
Approach 1:
Call this sp from another SP to fill a temp table.
Result 1:
An INSERT EXEC statement cannot be nested.
(If I call simply with exec dbo... style the code is working. I only get the error if I try to call within a stored procedure)
Approach 2:
I put the code above into a table values function.
Result 2:
Invalid use of a side-effecting operator 'INSERT EXEC' within a function.
(The function itself don't compiled)
Thanks,
Péter
In the meantime I managed to solve the problem (with help :) ). The solution is simple:
exec('insert into t2 ' + #str)
Where #str contains a select statement.
I don't know why but this way there is no error. The method I call the stored procedure:
SET #exec = 'exec dbo.trFilterTopic ''' + #id+ ''',null,null,1'
INSERT INTO #filtered
exec (#exec)
I hope I spare some time to other folks with this solution.
Bye,
Péter
It is an SQL Server restriction. You cannot have a nested insert exec (I'm not sure why).
If you go:
insert into t(value)
exec dbo.proc
, and inside dbo.proc you have
insert into t2(value2)
exec(#str)
, then it will not run.
Consider different ways of passing tables around, such as temporary tables or table-valued parameters.
Functions on SQL Server have limitations,
they arenot procedures, you can't use dynamic SQL like 'EXECUTE STRING', 'INSERT EXEC'...

SELECT FROM stored procedure?

If I have a stored proc in SQL Server 2008, I know I can run it from management studio like so:
exec rpt_myproc #include_all = 1, #start_date = '1/1/2010'
But I'm using an ad-hoc query tool that wasn't returning any results. So I asked it to give me the SQL it was running and it returns this:
SELECT DISTINCT TOP 100000
[dbo].[rpt_myproc].[company_name] AS 'company name',
[dbo].[rpt_myproc].[order_number] AS 'order number]
FROM [dbo].[rpt_myproc]
WHERE
([dbo].[rpt_myproc].[PARAM_start_date] IN ('1/1/2010'))
AND ([dbo].[rpt_myproc].[PARAM_include_all] IN ('1'))
I'm not familiar with that syntax. Is that even possible? The ad-hoc tool isn't failing, but it may be swallowing that error. Then again, maybe it's just giving me a shorthand which it will use translate to the proper syntax later. But if so, why would it give it to me in this form?
I can't seem to get that SQL to execute in Management Studio, so I was wondering if something like that were possible?
I understand that this is more than 3 years old, but in case anybody else is looking for an answer to this question. I had to deal with this reporting platform, Izenda, and have found that stored procedures are treated differently than the output from the "sql" icon. Here is what happens when you select sp as data source
A dynamic sql is build
It creates a two temporary tables with all of the columns that your sp is returning
The first temp table is populated with the result from your stored procedure
The second temp table is populated with the result plus the value of your input parameter.
A statement is created that queries these two temporary tables
Please note that if you don't feed it a parameter it will execute with a default value of empty string '' which will most likely return no data.
In my opinion, horrible idea to handle stored procs which is a good reason why we are planning to drop them for some other reporting solution.
You can insert the first result set of a stored procedure into a temporary table:
SELECT *
INTO #YourProc
FROM OPENROWSET('SQLNCLI',
'server=SERVERNAME\INSTANCENAME;trusted_connection=yes',
'set fmtonly off; exec rpt_myproc')
There's like 3 ways to do this, see this blog post. If you know the output beforehand, you can do it without the remote query.
What tool are you using? You should be able to specify the query type (i.e. SQL, or stored proc, etc)
Haven't used that tool before but a quick google came up with this example (not sure if it will help you)
Using a stored procedure in 5.x
This example uses a stored procedure to populate a table before report design or execution. As shown in the comments, the table StoredProcResults must already exist. Every time a report is created or viewed this stored procedure will update the results of the StoredProcResults table. For 6.x follow these instructions but treat the SP as a regular datasource.
// Customize a report on the fly prior to execution on a per user basis
public override void PreExecuteReportSet(Izenda.AdHoc.ReportSet reportSet){
/*this sample uses the adventure works database Here is the definition of the table and stored procedure created for this report.
CREATE TABLE [dbo].[StoredProcResults](
[ProductID] [int] NOT NULL,
[OrderQuantity] [int] NOT NULL,
[Total] [int] NOT NULL,
[DueDate] [smalldatetime] NOT NULL
) ON [PRIMARY]
CREATE PROCEDURE DoCustomAction (
#date1 as smalldatetime,
#date2 as smalldatetime
) AS
BEGIN
insert into StoredProcResults
select ProductID,OrderQty,LineTotal,ModifiedDate
from Sales.SalesOrderDetail
where ModifiedDate >= #date1 and ModifiedDate <= #date2
END
*/
string currentReportName = HttpContext.Current.Request.QueryString["rn"];
if (currentReportName == "StoredProcExample") {
SqlConnection myConnection = new SqlConnection(Izenda.AdHoc.AdHocSettings.SqlServerConnectionString);
SqlCommand myCommand = new SqlCommand("DoCustomAction", myConnection);
// Mark the Command as a SPROC
myCommand.CommandType = System.Data.CommandType.StoredProcedure;
// Add Parameters to SPROC
SqlParameter parameterdate1 = new SqlParameter("#date1", System.Data.SqlDbType.SmallDateTime);
parameterdate1.Value = "1/1/2003";
myCommand.Parameters.Add(parameterdate1);
SqlParameter parameterdate2 = new SqlParameter("#date2", System.Data.SqlDbType.SmallDateTime);
parameterdate2.Value = "12/31/2003";
myCommand.Parameters.Add(parameterdate2);
try{
myConnection.Open();
myCommand.ExecuteNonQuery();
}
finally{
myConnection.Close();
}
}
}
Are you sure it is a sproc? I've never heard or seen a usage of doing a direct select from a sproc.
What I have seen that works and functions exactly as your code seems to be working is table-valued functions, which are functions, that can take parameters and return a "SELECT FROMable" table just like this (in essence giving you a 'parameterized' view).