I run these commands in a Windows Forms app written in C#:
NpgsqlCommand command = new NpgsqlCommand("DO $$ BEGIN IF EXISTS(select id from clients where id=#cnumberclient) THEN INSERT INTO reservations(roomnumber,clientnumber,datein, dateout) values(#rnumber, #cnumber, #din, #dout); ELSE raise notice 'HYI'; END IF; END $$;", conn.getConnection());
command.Parameters.Add("#cnumber", NpgsqlDbType.Integer).Value = ClientId;
command.Parameters.Add("#cnumberclient", NpgsqlDbType.Integer).Value = ClientId;
command.Parameters.Add("#rnumber", NpgsqlDbType.Integer).Value = RoomId;
command.Parameters.Add("#din", NpgsqlDbType.Date).Value = dateIn;
command.Parameters.Add("#dout", NpgsqlDbType.Date).Value = dateOut;
then I open the connection and execute the command:
conn.openConnection();
if (command.ExecuteNonQuery() == 1)
But I get an error.
column "cnumberclient" doesn't exist
Postgres doesn't seem to see the parameters
Can you help me?
Remove the # symbol from your parameters, that's not part of the parameter name, it's the symbol to identify them in the Sql query.
Related
I have a Postgres database with a stored procedure that returns JSON documents, based on the article here: http://www.sqlines.com/postgresql/npgsql_cs_result_sets
The procedure is represented like this:
-- Procedure that returns a single result set (cursor)
CREATE OR REPLACE FUNCTION get_data_test() RETURNS refcursor AS $$
DECLARE
ref refcursor; -- Declare a cursor variable
BEGIN
OPEN ref FOR -- Open a cursor
SELECT row_to_json(r) AS data
FROM
(
SELECT *
FROM data AS d
) r;
RETURN ref; -- Return the cursor to the caller
END;
$$ LANGUAGE plpgsql;
I am then running the following code from a .net console app:
// Making connection with Npgsql provider
using (NpgsqlConnection conn = new NpgsqlConnection(connstring))
{
conn.Open();
var trans = conn.BeginTransaction();
var cmd = new NpgsqlCommand("get_data_test", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Transaction = trans;
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
Trace.WriteLine(reader[0]);
}
}
The output is a single instance wit the name of the refcursor, rather than the actual data:
< unnamed portal 1 >
If I run the select query directly in the command text, the result set is returned as expected. I've also tried explicitly calling the proc via text using "SELECT get_data_test()" but this also fails with just the cursor name.
I don't believe I am missing a step and this refcursor should be returned unbundled. What am I doing wrong?
As it happens, the tutorial was wrong:
Nice GitHub bug report: https://github.com/npgsql/npgsql/issues/1777
Correct answer:
-- Procedure that returns a single result set (cursor)
CREATE OR REPLACE FUNCTION get_data_test() RETURNS TABLE (data JSON) AS $$
BEGIN
RETURN query
SELECT row_to_json(r) AS data
FROM
(
SELECT *
FROM data AS d
) r;
END;
$$ LANGUAGE plpgsql;
I simply want to execute the following Mysql statement
SET #a = 1;SELECT #a;
with MySql.Data.MySqlClient
[System.Reflection.Assembly]::LoadWithPartialName("MySql.Data")
$password = 'mypassword'
$sql = "SET #a = 1;SELECT #a;"
$server = 'localhost'
$port = 3306
$database = 'test'
$User = 'root'
$conn=new-object MySql.Data.MySqlClient.MySqlConnection
$connectionstring = "Server=$Server;Port=$port;Database=$DataBase;Uid=$User;Pwd=$Password;allow zero datetime=yes"
$conn.ConnectionString = $connectionstring
$conn.Open()
$cmd=new-object MySql.Data.MySqlClient.MySqlCommand($sql,$conn)
$ds=New-Object system.Data.DataSet
$da=New-Object MySql.Data.MySqlClient.MySqlDataAdapter($cmd)
$da.fill($ds)
$conn.close()
$ds.tables[0]
I get a fatal error.
When I replace $sql by either
$sql = "SELECT DATABASE();"
or
$sql = "SELECT 1;"
I get the expected result.
I found this question, but it doesn't solve my problem.
I'm trying to port SQLIse (a part of the SQLPSX project ) to the MySQL version MySQLIse.
I want to process any simple valid mysql statements.
EDIT:
I was trying to run parts of the sakila-schema.sql the mysql demo database install script which runs by something like
mysql> SOURCE
C:/temp/sakila-db/sakila-schema.sql;
I found the solution in this blog
I have to add
;Allow User Variables=True
to the connection string:
$connectionstring = "Server=$Server;Port=$port;Database=$DataBase;Uid=$User;Pwd=$Password;allow zero datetime=yes;Allow User Variables=True"
works. I tested it with version 6.3.6.0. of MySql.Data.
I have a form with inputs 'name' and 'phone'.
When they have values, everything works: a record is inserted into the database using a stored procedure, spFoo:
String sp = "spFoo 'test name','test phone'";
OdbcCommand command = new OdbcCommand(sp, connection);
command.CommandType = System.Data.CommandType.StoredProcedure;
connection.Open();
Response.Write(command.ExecuteNonQuery().ToString());
Works on application
Works on Mgmt Studio
But when they don't have values I get -1 as a response in the application:
String sp = "spFoo '',''";
Does not work in application (-1)
Works on Mgmt Studio
I want the user to be able to create a record without any input.
Why does this work in management studio, but not on the application?
Update: I added defaults to the params in the stored procedure, it didn't work; I gave empty strings "NULL" as values in the code, still no luck. Is this a server setting or something that won't allow empty variables?
You'll need to remove the SET NOCOUNT ON; from your stored procedure.
From the documentation:
Stops the message that shows the count of the number of rows affected by a Transact-SQL statement or stored procedure from being returned as part of the result set.
As others have pointed out you should parameterise your query too but the -1 is caused by NOCOUNT being ON.
Edit
I realise it's not what you're asking but to use a parameterised query with ODBC you need to use ?'s as ordinal place holders as per the documentation here. For example:
using (OdbcConnection connection = new OdbcConnection(connectionString))
{
string sp = "{call spFoo (?, ?)}";
using (OdbcCommand command = new OdbcCommand(sp, connection))
{
command.CommandType = System.Data.CommandType.StoredProcedure;
connection.Open();
//the order here is important, the names are not!
command.Parameters.Add("#name", OdbcType.VarChar).Value = "test name";
command.Parameters.Add("#phone", OdbcType.VarChar).Value = "test phone";
Console.WriteLine(command.ExecuteNonQuery().ToString());
}
}
When you are calling a stored procedure from code, you should use the Parameters property on the command. Try this:
String sp = "spFoo";
command.Parameters.Add("#name", "test name");
command.Parameters.Add("#phone", "test phone");
As JimmyV said, you should use the command.Parameters.Add method to setup your parameters, passing in null whenever a parameter value is not specified. To address your comment about the error 'procedure or function 'spFoo' expects parameter '#name', which was not supplied', you'll also need to modify your stored procedure to use default values when a param is not supplied (e.g. null):
CREATE PROCEDURE MyStoredProcedure
#foo int = null
AS
BEGIN
...
END
Sorry for not adding this a comment on the above post. Not enough reputation!
You shouldn't be calling a stored procedure the way that you currently are. You should be using parameters. Your code is susceptible to SQL injection.
Never string concat user inputted values.
What you should have, is a stored procedure setup similarly:
CREATE PROCEDURE spFoo
#name varchar(50) = 'Jim', -- default
#phone varchar(50) = null -- optional
AS
BEGIN
SET NOCOUNT ON;
-- INSERT STATEMENT
END
GO
And then supply the parameters in the code:
string name = this.nameTextBox.Text;
string phone = this.phoneTextBox.Text;
if (string.IsNullOrWhiteSpace(name))
name = null;
if (string.IsNullOrWhiteSpace(phone))
phone = null;
SqlConnection connection = new SqlConnection(#"<connection string>");
using (SqlCommand command = connection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
// leave this as the stored procedure name only
command.CommandText = "spFoo";
// if name is null, then Jim gets passed (see stored procedure definition)
// if phone is null, then null gets passed (see stored procedure definition)
command.Parameters.AddWithValue("#name", name);
command.Parameters.AddWithValue("#phone", phone);
try
{
connection.Open();
int result = command.ExecuteNonQuery();
Console.WriteLine(result);
}
finally
{
if (connection.State != ConnectionState.Closed)
connection.Close();
}
}
I'm not sure why you used the Odbc namespace objects since it sounds like you are using MS-SQL. You should be using objects from the System.Data.SqlClient namespace.
The answer to your actual question would most likely involve executing a script (not a stored procedure) similar to:
DECLARE #RC int
DECLARE #name varchar(50)
DECLARE #phone varchar(50)
-- TODO: Set parameter values here.
EXECUTE #RC = spFoo
#name,
#phone
GO
Which is not recommended.
I have a connection to a database with right to another. I want to call a procedure on the other database which has a user table data type parameter. But the user table data type isn't found whatever I try.
I tried using database name in front of [dbo].[myType] but it's not a valid syntax.
I tried creating the same type in the current database
I tried creating the same type in the model database
I tried appending "USE MyOtherDatabase" at the top of my SqlCommand.Text
Everything failed (I'm really abashed the "USE ..." approach failed).
How can I do that?
Sample of code:
// While connected to MyOtherDatabase
CREATE TYPE dbo.typeClubMembersVersion AS TABLE (
ID INT
, UNIQUE(ID)
, [version] INT
)
GO
CREATE PROCEDURE dbo.spCheckCMembersMods (
#pVersions AS dbo.typeClubMembersVersion READONLY
, #pWhoID AS BIGINT
)
AS
BEGIN
[...]
END
SqlCommand com = new SqlConnection(functions.ConnectionString).CreateCommand();
com.CommandText = #"
// While connected to CurrentDatabase
USE MyOtherDatabase
DECLARE #tbl AS dbo.typeClubMembersVersion
BEGIN TRANSACTION
UPDATE dbo.tClubMembers
SET
Title = #Title
OUTPUT inserted.ID, deleted.[version] INTO #tbl (ID, [version])
WHERE IdMember = #IdMember
EXEC dbo.spCheckCMembersMods #tbl, #whoID
COMMIT
";
com.Parameters.Add("#Title", SqlDbType.NVarChar, 20).Value = this.Title;
com.Parameters.Add("#IdMember", SqlDbType.BigInt).Value = this.Id;
com.Parameters.Add("#whoID", SqlDbType.BigInt).Value = (object)whoID ?? DBNull.Value;
com.Connection.Open();
try
{
com.ExecuteNonQuery();
}
catch (Exception exe)
{
throw exe;
}
finally
{
com.Connection.Close();
}
First, what you are calling "Schemas" are actually "Databases" in SQL Server. The "dbo." in your object names is a "Schema" in SQL Server. The "USE.." command only works on Databases.
Secondly, you cannot reference or use Types from another database, it has to be defined in the same database(s) that it is used in. Types can be in other SQL Server schemas, but not in other Databases, which is what you are actually trying to do here.
OK, as you noted, your Type is defined in [myOtherDatbase] so why doesn't it work? Probably because the USE.. and SQL command strings do not work the way you might think. Whenever you pass a string like this to SQL Server and try to execute it:
com.CommandText = #"
// While connected to CurrentDatabase
USE MyOtherDatabase
DECLARE #tbl AS dbo.typeClubMembersVersion
BEGIN TRANSACTION
UPDATE dbo.tClubMembers
SET
Title = #Title
OUTPUT inserted.ID, deleted.[version] INTO #tbl (ID, [version])
WHERE IdMember = #IdMember
EXEC dbo.spCheckCMembersMods #tbl, #whoID
COMMIT
";
SQL Server will first compile the entire string and then try to execute it. This means that all of the commands are compiled first before any of them are executed. And that means that your DECLARE #tbl and UPDATE.. commands are compiled before the USE command is executed. So when they are compiled you are still in the previous database where the Type has not been defined. This is what leads to your syntax errors (which are coming from the compiler, not from their execution).
There are three possible solutions:
Define the Type in currentDatabase also (I am pretty sure that this works, but not 100%).
Reconnect with a connection string that specifies "Initial Catalog=MyOtherDatabase".
Re-execute everything after your USE command with Dynamic SQL.
Of these I would recommend #2.
Silly me, I just realized that there is another option:
First execute just the USE command by itself,
then, execute the rest of the SQL commands on the same connection.
Of course this will leave you in [MyOtherDatabase], so you may want to end this by executing another USE command back to your original database.
It's been such a very long time since I had to use SqlConnection.ChangeDatabase I fergot about it. Until now I've always been able to use "fully named objects" to make my databases interract with each other.
Since I'm currently stuck I'll use it but I hope somebody tells me a way that don't force me to let go the current database connection.
SqlCommand com = new SqlConnection(functions.ConnectionString).CreateCommand();
com.CommandText = #"
DECLARE #tbl AS dbo.typeClubMembersVersion
BEGIN TRANSACTION
UPDATE dbo.tClubMembers
SET
Title = #Title
OUTPUT inserted.ID, deleted.[version] INTO #tbl (ID, [version])
WHERE IdMember = #IdMember
EXEC dbo.spCheckCMembersMods #tbl, #whoID
COMMIT
";
com.Parameters.Add("#Title", SqlDbType.NVarChar, 20).Value = this.Title;
com.Parameters.Add("#IdMember", SqlDbType.BigInt).Value = this.Id;
com.Parameters.Add("#whoID", SqlDbType.BigInt).Value = (object)whoID ?? DBNull.Value;
com.Connection.Open();
try
{
com.Connection.ChangeDatabase("MyOtherDatabase");
com.ExecuteNonQuery();
}
catch (Exception exe)
{
throw exe;
}
finally
{
com.Connection.Close();
}
I currently have the following funciton in an oracle database that returns a concatenated string seperated by pipes. It's a legacy application that is being updated to use .net 3.5. The exiisting application concatenates the returned result set into a VARCHAR2 data type. What I want to do is return the entire result set back to my .net client. The MS SQL equivalent of what I'm trying to accomplish is a simple "SELECT * FROM TBL WHERE id = #id" I'm not use to some of the concepts Oracle uses. I't seems like e blend of OOP and SQL querying. I've read multiple examples on this but can't seem to find exactly what I'm looking for. Can you please help?
CREATE OR REPLACE FUNCTION DOCSADMIN.GET_DOCS (
RECID IN NUMBER) -- RECORD ID
RETURN VARCHAR2 -- CONCATENATED STRING WITH PIPES
IS
RETVAL VARCHAR2(5000) :='';
DOCSTRING VARCHAR2(5000) :='';
DOCNAME VARCHAR2(5000) :='';
DOCNUMBER NUMBER;
STATUS VARCHAR2(5000) :='';
DOCTYPE VARCHAR2(5000) :='';
EDITDATE DATE :='';
/******************************************************************************
NAME: GET_DOCS
PURPOSE: Pulls associated docs from profile table
******************************************************************************/
CURSOR GETDOCINFO IS SELECT DOCNUMBER, DOCNAME, CUSTOM_STATUS, DOCUMENTTYPES.DESCRIPTION, LAST_EDIT_TIME
FROM PROFILE, DOCUMENTTYPES, FORMS WHERE NAD_APID = IN_APID AND PROFILE.FORM = FORMS.SYSTEM_ID AND
DOCUMENTTYPE = DOCUMENTTYPES.SYSTEM_ID AND FORM_NAME = 'DOCS_PROFILE' ORDER BY DOCNUMBER;
BEGIN
OPEN GETDOCINFO;
--GET THE FIRST RECORD
FETCH GETDOCINFO INTO DOCNUMBER, DOCNAME, STATUS, DOCTYPE, EDITDATE;
--LOOP THROUGH ALL ASSOCIATED DOCS AND GRAB INFO
WHILE GETDOCINFO%FOUND LOOP
BEGIN
DOCSTRING := DOCNUMBER || '|~|' || DOCNAME || '|~|' || STATUS || '|~|' || DOCTYPE || '|~|' || WS_EDITDATE;
RETVAL := RETVAL || DOCSTRING || '|^|';
GOTO STARTOVER;
END;
<<STARTOVER>>
FETCH GETDOCINFO INTO DOCNUMBER, DOCNAME, STATUS, DOCTYPE, EDITDATE;
END LOOP;
CLOSE GETDOCINFO;
RETURN RETVAL;
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
WHEN OTHERS THEN
-- Consider logging
the error and then re-raise
RAISE;
END GET_DOCS;
/
Well, you could convert the function in to a procedure and have an OUT parameter of the SYS_REFCURSOR type. With Oracle and .Net you can pass back a cursor and iterate through that as a reader.
Sample Oracle procedure:
CREATE OR REPLACE PROCEDURE TEST_SCHEMA.TEST_PROCEDURE (
out_DATA OUT SYS_REFCURSOR;
) AS
BEGIN
OPEN out_DATA FOR
SELECT col1,
col2
FROM TEST_SCHEMA.TEST_TABLE;
END test_procedure;
Sample .Net end:
using (OracleConnection connection = new OracleConnection("connstring"))
using (OracleCommand command = connection.CreateCommand()) {
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "TEST_SCHEMA.TEST_PROCEDURE";
command.Parameters.Add("out_DATA", OracleType.Cursor)
.Direction = ParameterDirection.Output;
connection.Open();
command.ExecuteNonQuery();
OracleDataReader reader =
command.Parameters["out_DATA"].Value as OracleDataReader;
if (reader != null) {
using (reader) {
while(reader.Read()) {
string col1 = reader["col1"] as string;
string col2 = reader["col2"] as string;
}
}
}
}
Be sure to close the cursor after you're done using it (accomplished above by the using (reader) statement).
So in your case, you could probably create a procedure that outputs the original cursor in your function, then just iterate over the cursor in .Net as listed above. Just a note, the column names from the Oracle side are important and will match what you're using in .Net.
What I have so far compiles fine.
CREATE OR REPLACE PROCEDURE DOCSADMIN.GET_DOCS_SP ( IN_APID IN NUMBER, out_DATA OUT SYS_REFCURSOR )
AS
BEGIN
OPEN out_DATA FOR
SELECT DOCNUMBER, DOCNAME, CUSTOM_STATUS, DOCUMENTTYPES.DESCRIPTION, LAST_EDIT_TIME
FROM PROFILE, DOCUMENTTYPES, FORMS WHERE APID = IN_APID AND PROFILE.FORM = FORMS.SYSTEM_ID AND
DOCUMENTTYPE = DOCUMENTTYPES.SYSTEM_ID AND FORM_NAME = 'PROFILE' ORDER BY DOCNUMBER;
END GET_DOCS_SP;
/
However, I've run into another situation and would appreciate your input. If I wanted to call the following from a sql database using OPENQUERY how would I do so? The legacy version that was returning the concatenated string looked like the following.
SELECT * FROM OPENQUERY (TESTSERVER, 'SELECT DOCSADMIN.GET_DOCS_SP (26) AS DOCINFO FROM DUAL')
Do I just remove the as DOCINFO FROM DUAL clause?
Thanks