Syntax Error, but Copy/Pasted Query Text Works in SSMS - c#

I am having an issue generating a SQL query using C#. To troubleshoot, I made the button that executes the query also display the query text in a textbox on the form. What's perplexing is that I get an error saying "Incorrect syntax near 'IF'" when the program tries to execute the query, but if I copy/paste the query from the textbox to SSMS it works fine.
The variable that stores the query looks like:
string myQuery = #"
SELECT DISTINCT filter.id_column INTO #temp1
FROM MasterDB.dbo.filter filter
LEFT JOIN ClientDB.dbo.codes codetable
ON filter.id_column=codetable.id_column
WHERE codetable.name IS NULL
DECLARE #code_id1 INT;
SET #code_id1 = (SELECT MAX(code_num) FROM ClientDB.dbo.codes)+1
EXEC('ALTER TABLE #temp1 ADD tempID INT IDENTITY(' + #code_id1 + ',1)')
GO
IF (SELECT COUNT(*) FROM #temp1)>0
BEGIN
DECLARE #code_id2 INT;
SET #code_id2 = (SELECT MAX(tempID) FROM #temp1)+1
UPDATE ClientDB.dbo.track
SET next=#code_id2 WHERE [trackname]='account'
END";
The C# code to populate the textbox with the query text and then run the query looks like:
using (SqlConnection myConnection = new SqlConnection(HostConnStr))
using (SqlCommand myCommand = myConnection.CreateCommand())
{
myCommand.CommandText = myQuery;
this.textBox1.Text = myCommand.CommandText;
myConnection.Open();
try { myCommand.ExecuteNonQuery(); }
catch (SqlException s) { MessageBox.Show(s.ToString()); }
myConnection.Close();
}
Does anyone know why the query text can be copied to SSMS and run fine, but throws a SQL exception when executed from C#? And how do I make the query run?
Critique on the query design will be appreciated, but I am more concerned with simply getting the query to execute since it does what I need it to do as-is.
EDIT: This may be a duplicate (I was thrown off by the error being near 'IF' when it appears that 'GO' is the problem, so my searches were in the wrong direction. However, I am still not sure that the answers provided in similar questions will work since I am under the impression that splitting the query into multiple commands will fail due to the later part of the query referencing a temporary table in the earlier part (will the temporary table not become unavailable after the first command is finished?).

It's the GO statement. You can replace it with ; in most instances.
In TSQL it's OK to have multiple statements separated by GO. In the ADO.NET version you can't do this.
The way to do this would be spilt the string on the GO and execute each independently. Such as this example,
string scriptText = #"...."
//split the script on "GO" commands
string[] splitter = new string[] { "\r\nGO\r\n" };
string[] commandTexts = scriptText.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
foreach (string commandText in commandTexts)
{
//execute commandText
}

Related

Return ID of newly inserted row on a PostgreSQL database using C# and Npgsql?

I'm building a WinForms project in C# using a PostgreSQL database and the Npgsql framework.
For inserting a record, I need to return the ID of the new record. This SO question says to add SELECT SCOPE_IDENTITY() to the query string passed to cmd. So my query string looks like this:
string insertString = "INSERT INTO sometable (company_name, category, old_value, old_desc, new_value, new_desc, reference1, reference2) VALUES (#comp, #cat, #oldValue, #oldDesc, #newValue, #newDesc, #ref1, #ref2); SELECT SCOPE_IDENTITY();";
and then get the ID with something like
int modified = cmd.ExecuteNonQuery();
But that's likely SQL Server-specific. If I use that method, I get an exception at the above line saying, "fuction scope_identity() does not exist".
I wasn't able to find anything that seemed to address this on the Npgsql documentation.
Per the linked SO question and Denis' suggestions I've tried adding both
RETURNING id;
and
CURRVAL(pg_get_serial_sequence('my_tbl_name','id_col_name'))
to the query string, replacing SELECT SCOPE_IDENTITY(); with those statements in the code above. In both cases they work as intended in DBeaver on an insert, but in my C# code in my WinForm project, modified was set to "1".
NOTE: I re-titled the question and added more information about what I've done.
Add "returning idcolumn" to the end of the sql query, then run the command with the ExecuteScalar() method instead of ExecuteNonQuery(). It should return with an int.
string insert = "insert into table1 (col1) values (something) returning idcol";
int id = cmd.ExecuteScalar();
All the comments above were almost nearly spot on and got me to a solution but didn't exactly wrap it in a bow -- so I thought i'd post my implementation that works (with silly fake example tables of course).
private int? InsertNameIntoNamesTable(string name)
{
int? id = null;
using (var dbcon = new NpgsqlConnection(_connectionString))
{
dbcon.Open();
StringBuilder sb = new StringBuilder();
var sql = $#"
insert into names_table
(name)
values
({name})
returning id;
";
sb.Append(sql);
using (var cmd = new NpgsqlCommand(sql, dbcon))
{
id = (int)cmd.ExecuteScalar();
}
dbcon.Close();
}
return id;
}

handling large amounts of data to include in an oracle select statement

Recent bug report states that a method being called is crashing the service causing it to restart. After troubleshooting, the cause was found to be an obnoxious Oracle SQL call with thousands of strings passed. There is a collection of strings being passed to a method from an external service which often is more than 10,000 records. The original code used a where clause on the passed collection using the LIKE keyword, which I think is really, really bad.
public IList<ContainerState> GetContainerStates(IList<string> containerNumbers)
{
string sql =
String.Format(#"Select CTNR_NO, CNTR_STATE FROM CONTAINERS WHERE CTRN_SEQ = 0 AND ({0})",
string.Join("OR", containerNumbers
.Select(item => string.Concat(" cntr_no LIKE '", item.SliceLeft(10), "%' ")))
);
return DataBase.SelectQuery(sql, MapRecordToContainerState, new { }).ToList();
}
Clarification of in house methods used which may be confusing:
DataBase.SelectQuery is an internal library method using generics which gets passed the sql string, a function to map the records to .NET objects, and the parameters being passed and returns an IEnumerable of Objects of type retuned by the Mapping function.
SliceLeft is an extension method from another internal helper library that just returns the first part of a string up to the number of characters specified by the parameter.
The reason that the LIKE statement was apparently used, is that the strings being passed and the strings in the database only are guaranteed to match the first 10 characters. Example ("XXXX000000-1" in the strings being passed should match a database record like "XXXX000000-8").
I believed that the IN clause using the SUBSTR would be more efficent than using multiple LIKE clauses and replaced the code with:
public IList<ContainerRecord> GetContainerStates(IList<string> containerNumbers)
{
string sql =
String.Format(#"Select CTNR_NO, CNTR_STATE FROM CONTAINERS WHERE CTRN_SEQ = 0 AND ({0})",
string.Format("SUBSTR(CNTR_NO, 1, 10) IN ({0}) ",
string.Join(",", containerNumbers.Select(item => string.Format("\'{0}\'", item.SliceLeft(10) ) ) )
)
);
return DataBase.SelectQuery(sql, MapRecordToContainerState, new { }).ToList();
}
This helped slightly, and there were fewer issues in my tests, but when there are huge amounts of records passed, there is still an exception thrown and core dumps occur, as the SQL is longer than the server can parse during these times. The DBA suggests saving all the strings being passed to a temporary table, and then joining against that temp table.
Given that advice, I changed the function to:
public IList<ContainerRecord> GetContainerStates(IList<string> containerNumbers)
{
string sql =
#"
CREATE TABLE T1(cntr_num VARCHAR2(10));
DECLARE GLOBAL TEMPORARY TABLE SESSION.T1 NOT LOGGED;
INSERT INTO SESSION.T1 VALUES (:containerNumbers);
SELECT
DISTINCT cntr_no,
'_IT' cntr_state
FROM
tb_master
WHERE
cntr_seq = 0
AND cntr_state IN ({0})
AND adjustment <> :adjustment
AND SUBSTR(CTNR_NO, 1, 10) IN (SELECT CNTR_NUM FROM SESSION.T1);
";
var parameters = new
{
#containerNumbers = containerNumbers.Select( item => item.SliceLeft(10)).ToList()
};
return DataBase.SelectQuery(sql, MapRecordToContainerState, parameters).ToList();
}
Now I'm getting a "ORA-00900: invalid SQL statement". This is really frustrating, how can I properly write a SQL Statement that will put this list of strings into a temporary table and then use it in a SELECT Statement to return the list I need?
There are couple possible places could cause this error, it seams that the "DECLARE GLOBAL TEMPORARY" is a JAVA API, I don't think .net has this function. Please try "Create global temporary table" instead. And, I don't know whether your internal API could handle multiple SQLs in one select sql. As far as I know, ODP.net Command class can only execute one sql per call. Moreover, "create table" is a DDL, it therefore has its own transaction. I can't see any reason we should put them in the same sql to execute. Following is a sample code for ODP.net,
using (OracleConnection conn = new OracleConnection(BD_CONN_STRING))
{
conn.Open();
using (OracleCommand cmd = new OracleCommand("create global temporary table t1(id number(9))", conn))
{
// actually this should execute once only
cmd.ExecuteNonQuery();
}
using (OracleCommand cmd = new OracleCommand("insert into t1 values (1)", conn)) {
cmd.ExecuteNonQuery();
}
// customer table is a permenant table
using (OracleCommand cmd = new OracleCommand("select c.id from customer c, t1 tmp1 where c.id=tmp1.id", conn)) {
cmd.ExecuteNonQuery();
}
}

Creating Registration form in c# with mysql

Hello so i m creating a registration form in C# with MySql so it connects to the database and everything but i get this error Napaka pri registraciji Unknown column " in 'field list' the translation of Napaka pri registraciji means Error at registering i just have it in my language. I get this error when i insert data in textboxes and press Register..
the code:
private void btn_Reg_Click(object sender, EventArgs e)
{
MySqlConnection dataConnection = new MySqlConnection();
dataConnection.ConnectionString = "datasource=localhost;port=3306;username=root;password=";
dataConnection.Open();
MySqlTransaction transakcija = dataConnection.BeginTransaction();
MySqlCommand dataCommand = new MySqlCommand();
dataCommand.Connection = dataConnection;
dataCommand.Transaction = transakcija;
try
{
dataCommand.CommandText = "Insert INTO lr.users (upIme,geslo) VALUES (`"+this.tB_upIme.Text+"`,`"+this.tB_geslo.Text+"`)";
dataCommand.CommandType = CommandType.Text;
dataCommand.ExecuteNonQuery();
transakcija.Commit();
MessageBox.Show("Registracija uspešna!");
}
catch (Exception eks)
{
transakcija.Rollback();
MessageBox.Show("Napaka pri registraciji\n" + eks.Message);
}
finally
{
dataCommand.Connection.Close();
}
}
There are two things I immediately see wrong here...
First, you're using back ticks to wrap your values. In MySQL Back ticks represent database objects, so the query is looking for objects named by those values instead of using the values themselves. So instead of this:
`"+this.tB_upIme.Text+"`
You'd want this:
'"+this.tB_upIme.Text+"'
Second, and vastly more importantly, your code is wide open to SQL injection attacks. You'll want to use query parameters, not direct string concatenation. While it may look like you're just putting values into the query string, you're actually taking user input and treating it as executable code in your query string, which means users can run any arbitrary code they want on your database.
First, add parameters to your query:
"Insert INTO lr.users (upIme,geslo) VALUES (#upIme, #geslo)"
(You'll notice this also makes the query a heck of a lot cleaner and easier to read.) Then add your parameters to the command:
dataCommand.Parameters.AddWithValue("#upIme", this.tB_upIme.Text);
dataCommand.Parameters.AddWithValue("#geslo", this.tB_geslo.Text);
Then when you execute that command it will treat the user-input values as values instead of as executable code.
Change to single quotes ' in the values.
dataCommand.CommandText =
"Insert INTO lr.users (upIme,geslo)
VALUES ('"+this.tB_upIme.Text+"','"+this.tB_geslo.Text+"');";

Update table according to row number in C# sql

I am writing a small program using an SQL database. The table name is StudentInfo.
I need to know the SQL code for the following
for (n=0; n<nRows; n++) {
string sql1="update StudentInfo set Position=" + n + " where <this has to be the row number>";
}
nRows is number of rows.
How can I get the row number for the above code?
best way to do this is to create a stored procedure in the database and use your code to pass the relevent information to the server
In order to accomplish this task you'll want to create a Stored Procedure or build a Query that actually accepts parameters. This will help you pass variables between, your method of concatenation will actually cause an error or become susceptible to SQL Injection attacks.
Non Parameter SQL Command:
using(SqlConnection sqlConnection = new SqlConnection("Database Connection String Here"))
{
string command =
"UPDATE Production.Product " +
"SET ListPrice = ListPrice * 2 " +
"WHERE ProductID IN " +
"(SELECT ProductID " +
"FROM Purchasing.ProductVendor" +
"WHERE BusinessEntityID = 1540);" +
using (SqlCommand sqlCommand = new SqlCommand(command, sqlConnection))
{
int execute = command.ExecuteNonQuery();
if( execute <= 0)
{
return false;
}
else
{
return true;
}
}
}
That method is essentially creation a connection, running our SQL Command, then we are using an integer to verify that it did indeed run our command successful. As you can see we simply using SQL to run our command.
The other important thing to note, you can't create a sub-query with an update; you have to create an update then run a select as the sub-query to hone in more specific data across so you can span across tables and so on.
The other alternative would be to use a parameter based query, where your passing variables between SQL and your Application.
I won't post code to that, because I believe you wrote the C# loop to demonstrate what you would like SQL to do for you. Which is only update particular rows; based on a specific criteria.
If you could post additional information I'd be more then happy to help you. But I'm just going to post what I believe you are trying to accomplish. Correct me if I'm wrong.

String list in SqlCommand through Parameters in C#

Working with a SqlCommand in C# I've created a query that contains a IN (list...) part in the where clause. Instead of looping through my string list generating the list I need for the query (dangerous if you think in sqlInjection). I thought that I could create a parameter like:
SELECT blahblahblah WHERE blahblahblah IN #LISTOFWORDS
Then in the code I try to add a parameter like this:
DataTable dt = new DataTable();
dt.Columns.Add("word", typeof(string));
foreach (String word in listOfWords)
{
dt.Rows.Add(word);
}
comm.Parameters.Add("LISTOFWORDS", System.Data.SqlDbType.Structured).Value = dt;
But this doesn't work.
Questions:
Am I trying something impossible?
Did I took the wrong approach?
Do I have mistakes in this approach?
Thanks for your time :)
What you are trying to do is possible but not using your current approach. This is a very common problem with all possible solutions prior to SQL Server 2008 having trade offs related to performance, security and memory usage.
This link shows some approaches for SQL Server 2000/2005
SQL Server 2008 supports passing a table value parameter.
I hope this helps.
You want to think about where that list comes from. Generally that information is in the database somewhere. For example, instead of this:
SELECT * FROM [Table] WHERE ID IN (1,2,3)
You could use a subquery like this:
SELECT * FROM [Table] WHERE ID IN ( SELECT TableID FROM [OtherTable] WHERE OtherTableID= #OtherTableID )
If I understand right, you're trying to pass a list as a SQL parameter.
Some folks have attempted this before with limited success:
Passing Arrays to Stored Procedures
Arrays and Lists in SQL 2005
Passing Array of Values to SQL Server without String Manipulation
Using MS SQL 2005's XML capabilities to pass a list of values to a command
Am I trying something impossible?
No, it isn't impossible.
Did I took the wrong approach?
Your approach is not working (at least in .net 2)
Do I have mistakes in this approach?
I would try "Joel Coehoorn" solution (2nd answers) if it is possible.
Otherwise, another option is to send a "string" parameter with all values delimited by an separator. Write a dynamic query (build it based on values from string) and execute it using "exec".
Another solution will be o build the query directly from code. Somthing like this:
StringBuilder sb = new StringBuilder();
for (int i=0; i< listOfWords.Count; i++)
{
sb.AppendFormat("p{0},",i);
comm.Parameters.AddWithValue("p"+i.ToString(), listOfWords[i]);
}
comm.CommandText = string.Format(""SELECT blahblahblah WHERE blahblahblah IN ({0})",
sb.ToString().TrimEnd(','));
The command should look like:
SELECT blah WHERE blah IN (p0,p1,p2,p3...)...p0='aaa',p1='bbb'
In MsSql2005, "IN" is working only with 256 values.
I would recommend setting the parameter as a comma delimited string of values and use a Split function in SQL to turn that into a single column table of values and then you can use the IN feature.
http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=50648 - Split Functions
If you want to pass the list as a string in a parameter, you could just build the query dynamically.
DECLARE #query varchar(500)
SET #query = 'SELECT blah blah WHERE blahblah in (' + #list + ')'
EXECUTE(#query)
I used to have the same problem, I think there is now way to do this directly over the ADO.NET API.
You might consider inserting the words into a temptable (plus a queryid or something) and then refering to that temptable from the query. Or dynamically creating the query string and avoid sql injection by other measures (e.g. regex checks).
This is an old question but I've come up with an elegant solution for this that I love to reuse and I think everyone else will find it useful.
First of all you need to create a FUNCTION in SqlServer that takes a delimited input and returns a table with the items split into records.
Here is the following code for this:
ALTER FUNCTION [dbo].[Split]
(
#RowData nvarchar(max),
#SplitOn nvarchar(5) = ','
)
RETURNS #RtnValue table
(
Id int identity(1,1),
Data nvarchar(100)
)
AS
BEGIN
Declare #Cnt int
Set #Cnt = 1
While (Charindex(#SplitOn,#RowData)>0)
Begin
Insert Into #RtnValue (data)
Select
Data = ltrim(rtrim(Substring(#RowData,1,Charindex(#SplitOn,#RowData)-1)))
Set #RowData = Substring(#RowData,Charindex(#SplitOn,#RowData)+1,len(#RowData))
Set #Cnt = #Cnt + 1
End
Insert Into #RtnValue (data)
Select Data = ltrim(rtrim(#RowData))
Return
END
You can now do something like this:
Select Id, Data from dbo.Split('123,234,345,456',',')
And fear not, this can't be susceptible to Sql injection attacks.
Next write a stored procedure that takes your comma delimited data and then you can write a sql statement that uses this Split function:
CREATE PROCEDURE [dbo].[findDuplicates]
#ids nvarchar(max)
as
begin
select ID
from SomeTable with (nolock)
where ID in (select Data from dbo.Split(#ids,','))
end
Now you can write a C# wrapper around it:
public void SomeFunction(List<int> ids)
{
var idsAsDelimitedString = string.Join(",", ids.Select(id => id.ToString()).ToArray());
// ... or however you make your connection
var con = GetConnection();
try
{
con.Open();
var cmd = new SqlCommand("findDuplicates", con);
cmd.Parameters.Add(new SqlParameter("#ids", idsAsDelimitedString));
var reader = cmd.ExecuteReader();
// .... do something here.
}
catch (Exception)
{
// catch an exception?
}
finally
{
con.Close();
}
}

Categories