My Code:
SqlCommand command = new SqlCommand("SELECT min(Score) FROM MenAthletics WHERE [(#sportevent)] < (#result);", connect);
command.Parameters.AddWithValue("#sportevent", sportEvent);
command.Parameters.AddWithValue("#result", result);
the #result works fine (just a double variable)
the #sportevent doesnt't work (error: invalid columnname) (sportEvent is a string)
how can I choose a column by giving in a string?
You can parameterize values in SQL statements, but you cannot parameterize column or table names. You need to change the column name in the SQL string itself, for example, with string.Format:
SqlCommand command = new SqlCommand(
string.Format("SELECT min(Score) FROM MenAthletics WHERE [{0}] < (#result);", sportEvent)
, connect
);
command.Parameters.AddWithValue("#result", result);
Make sure that the column name does not come from user's input, otherwise you would open up your code to SQL injection attacks. In case the column name does come from user's input, you can validate the string against a list of available table columns, which could be made statically or by examining the structure of your table at runtime.
You could dynamically build the SQL query, instead of passing the column name as a parameter.
You can't use a column name as a parameter; you should instead consider constructing your query this way:
SqlCommand command =
new SqlCommand(
String.Format(#"SELECT min(Score)
FROM MenAthletics WHERE [{0}] < #result;",
sportEvent),
connect);
command.Parameters.AddWithValue("#result", result);
This kind of sql is called "dynamic sql" and can be an effective way of constructing queries on the fly.
However, there are pitfalls. As well as validating the user input, also make sure that the user you are connecting to the database with only has enough permissions to carry out the actions you want to do.
Another approach, which is less elegant, but can be placed directly into a stored procedure, is to use a CASE statement;
For example:
SELECT min(Score)
FROM MenAthletics
WHERE
CASE
WHEN #sportEvent = 'SomeColumnName' THEN SomeColumnName
WHEN #sportEvent = 'SomeColumnName2' THEN SomeColumnName2
END < #result;
This gets very tedious to both create and maintain on large tables. The advantage is that the query is not dynamic.
This is because value in the sportEvent string which you are passing as a parameter is not matching with actual column existing in your database table.
Make sure that both of them matches and then only this error will go.
Otherwise dont pass table's column name as a parameter, directly write it in the query and let its column value be a parameter.
Hope it helps.
Related
I know that you can increment a value in SQL by doing the following:
"UPDATE Table SET value=value+1 where Id=#Id"
Now, I'm wondering if it's possible to do the equivalent with parameters?
I've tried
string sql = "UPDATE Table SET value=#value where Id=#Id"
var cmd = new SQLiteCommand(sql, conn);
cmd.Parameters.Add("#value", DBType.Int).Value = "value+1";
but it didn't work, unsurprisingly. Is there any way to accomplish this with Parameters? If not and I use the first query, is it safe regarding injections?
You can't pass an expression as a parameter, only a value. If you know that you want to increment but you don't know by how much then you can use this SQL:
UPDATE SomeTable SET SomeColumn = SomeColumn + #Increment WHERE Id = #Id
and then you can pass 1 to that parameter to increment by 1, etc. Unless you pass all the SQL code in as a string and tell the database to parse that and execute it as SQL, you have to include all the logic in the SQL code and the parameters can only be data values. It's just how C# works. You couldn't pass part of the logic into a method as a string and expect it to be executed.
Is it possible to add the alias of a column as an SqlParameter to an SQL command? If so, how is it specified?
As a simplified example, say I have an SQL command that is constructed like this:
SqlCommand GetCommand(string columnName)
{
string filter = String.Format("SELECT MyColumn1 AS '{0}' FROM MyTable", columnName);
return new SqlCommand(filter);
}
This command does nothing to prevent an SQL Injection attack, so I want to follow the standard procedure and parameterize the command.
I'm used to converting statements in the WHERE clause to use parameters. The statements look similar to the above, for example:
SqlCommand command("SELECT * FROM MyTable WHERE name = '{0}'", name);
When I convert this, it becomes:
SqlCommand command("SELECT * FROM MyTable WHERE name = #name");
command.Parameters.Add(new SqlParameter("name", SqlDbType.NVarChar) { Value = name });
That works well. Taking the same approach here with the GetCommand() method gives:
SqlCommand GetCommand(string columnName)
{
string filter = "SELECT MyColumn1 AS #columnName FROM MyTable";
SqlCommand command = new SqlCommand(filter);
command.Parameters.Add(new SqlParameter("columnName", SqlDbType.NVarChar)
{ Value = columnName });
return command;
}
But, when the command is executed, it results in:
An exception of type 'System.Data.SqlClient.SqlException' occurred in MyApplication.exe but was not handled in user code
Additional information: Incorrect syntax near '#columnName'.
I see there are plenty of questions on SO and the web in general about use of SqlParameter, but seemingly none that touch on their use with column aliases.
There's no indication in Microsoft's SqlParameter documention either. From this, I noticed that the SqlParameter.Direction property defaults to ParameterDirection.Input; the column alias is used in output, so I tried InputOutput and Output, but it made no difference to the results.
Short answer: you can't.
Column Aliases are not parameterizable. They are identifiers in the SQL language, not values - just like the column name itself, or the table name.
"Get me column X from table Y and name it Z in the result set." None of X, Y or Z are parameterizable.
Note that this is not a limitation of SqlParameter but of the SQL language as implemented by Sql Server.
Parameters are not designed for aliasing TSQL columns. If you need an alias, just give it one in the TSQL. Additionally, the In/Out aspect of the parameter is for cases where the query modifies the parameter during running. Such as an output parameter of a stored procedure.
In truth, what it appears you're trying to do is get a dataset where the returned column name is based upon an inputted value.
I would use a data adapter to fill a data table, and then just rename the column to the desired value.
dataTable.Columns["MyColumn1"].ColumnName = columnName;
Strange thing to do, but you can build some sql with the parameter and then exec it.
Declare #sql VarChar(255)
Set #sql = 'SELECT ClientNumber AS ' + #columnName + ' FROM Ib01'
Exec(#sql)
You can't parameterize schema names and aliases. If you must have dynamic aliases, you'll need to protect yourself from SQL injection in the application layer.
Consider creating a whitelist of valid aliases, and only select from the whitelist.
If the alias comes from user input, you'll have to validate/sanitize the input.
Edit: It's a bad practice to rely on the application layer to prevent SQL injection attacks. But sometimes business drivers or other reasons force this to happen. My suggestions here are ways to mitigate the risk of SQL injection if you are forced to do this.
I have the following code:
SqlDataAdapter adapter = new SqlDataAdapter("SELECT TOP 0 * FROM [tableName]", conn);
DataTable dt = new DataTable();
adapter.FillSchema(dt, SchemaType.Mapped);
I am using this to build a DataTable of the table schema.
Normally, when people are asking about SQL injection, they are talking about query params :), my question is about the table name:
SELECT TOP 0 *
FROM [tableName]
[tableName] is actually going to be dynamic / determined at runtime (this is for a framework btw)...
The tableName passed into the method is not trusted, so I want to make sure there isn't any funny business going on?
Do I have to manually scrub the table name (and I'm sure I'll miss something)? Or is there a built in method for that? Or somehow to prevent the SQL injection on the table name?
You could just go with the rules for valid table names
128 char - letters, numbers, and limited other special characters
In those rules I don't think you could do an injection
CreateTable see table_name
ValidIdentifier
You can use the following query to get a list of all tables in the database and use that as a white list:
SELECT TABLE_NAME FROM <DATABASE_NAME>.INFORMATION_SCHEMA.Tables
WHERE TABLE_TYPE='BASE TABLE'
Replace
<DATABASE_NAME>
with your database name.
If someone attempts to input a table name which is not in that list, then don't execute the query.
Update
As for the "multiple connection strings" case, you can also use a simple query to check the current database name:
SELECT db_name()
So you should be able to easily craft a method that simply gets a list of valid table names from the current database, no matter if the connection-string/db-name is dynamic or not. And in the grand scheme of things, I doubt that these two queries are going to affect the performance of your application, like you said, you could easily cache the table list, if necessary.
The tableName passed into the method is not trusted, so I want to make
sure there isn't any funny business going on?
One method is to first execute a parameterized query, passing the table name as a parameter (nvarchar max length 128) to the QUOTENAME function:
SELECT QUOTENAME(#TableName) AS ScrubbedTableName;
The returned value can then be used in your TableAdapter query without the risk of SQL injection. However, this will not prevent disclosure of meta data if a table other than one expected is specified.
You can keep sqlDataAdapter and use
Adapter.selectcommand.parameters.add(#"whatever", value)
Edit: Sorry! I missed the part about this being related to a table name, this code doesnt work. :( I apologize.
I am working on a C# windows form which is connected to MySQL and updates strings within the form. I have everything working properly except for a small issue.
Say you want to update the "notes" field to read "The dog's bone", that apostrophe is causing the SQL query to end and cause an error. How can I get around this please?
UPDATE `database`
SET `notes` = 'The dog's bone'
WHERE `Pet` = 'Dog';
Thanks!
You can escape ' character in MySQL with doubling it like ''.
Other than that, if you use parameterized queries, you will not need this at all. Just pass your The dog's bone string directly to your parameterized query and you will be fine.
Also I strongly suspect you try to use UPDATE instead of SELECT statement. In MySQL, SELECT syntax doesn't have any SET part.
And using a reserved keyword as a column name is a bad idea. As a best practice, change your database column name to non-reserved word.
using(var con = new MySqlConnection(conString))
using(var cmd = con.CreateCommand())
{
cmd.CommandText = #"UPDATE `database` SET notes = #notes
WHERE Pet = #pet";
cmd.Parameters.AddWithValue("#notes", "The dog's bone");
cmd.Parameters.AddWithValue("#pet", "Dog");
con.Open();
cmd.ExecuteNonQuery();
}
I used AddWithValue method as an example in my code since I didn't know your column types but you don't use it. This method may generate unexpected and surprising results sometimes. Use Add method overloads to specify your parameter type and it's size.
Escape it with another single quote ':
SELECT `database`
SET `notes` = 'The dog''s bone'
WHERE `Pet` = 'Dog';
I have an arraylist that holds a subset of names found in my database. I need to write a query to get a count of the people in the arraylist for certain sections i.e.
There is a field "City" in my database from the people in the arraylist of names I want to know how many of them live in Chicago, how many live in New York etc.
Can someone help me how I might set up an sql statement to handle this. I think somehow I have to pass the subset of names to sql somehow.
Here is a sample how I am writing my sql in my code
Public Shared Function GetCAData(ByVal employeeName As String) As DataTable
Dim strQuery As String = "SELECT EMPLID, EMPLNME, DISP_TYPE, BEGIN_DTE FROM Corr WHERE (EMPLNME = #name)"
Dim cmd As New SqlCommand(strQuery)
cmd.Parameters.Add("#name", SqlDbType.VarChar)
cmd.Parameters("#name").Value = employeeName
Dim dt As DataTable = GenericDataAccess.GetData(cmd)
Return dt
End Function
I need a way to create a function using your sql statement to return a datatable object of names and the parameters would be city, and the List of names.
The above example isnt the sql I am looking for its just a skeleton of what the function would look like that I want to create.
So then you would use the function by iterating through all the cities passing in the same set of names each time in the fron end.
Your Sql statement will need to look like:
select city, count(*)
from table
where city in ('Chicago', 'New York')
group by city
Where the list of cities is your arraylist. You could pas this into a stored procedure as a variable or you could build the Sql string dynamically within your code.
If you are using SQL Server 2008, you can create a stored proc with a table variable as the input parameter. Then inside the proc you can just join to the input parameter table to get what you want.
You could use table valued parameters, but that's SQL Server 2008 and up only.
If the number of names is limited and you need to support older versions of SQL Server, you can use multiple parameters. That way you're still safe from SQL injection. I'm not entirely sure on what query you want to do, so I'll give an example based on your code:
Public Shared Function GetCAData(ByVal employeeName() As String) As DataTable
Dim sql As StringBuilder = New StringBuilder()
sql.Append("SELECT EMPLID, EMPLNME, DISP_TYPE, BEGIN_DTE FROM Corr WHERE EMPLNME IN (")
Dim cmd As New SqlCommand()
For I As Integer = 0 To employeeName.Length - 1
sql.Append("#name").Append(I).Append(",")
cmd.Parameters.AddWithValue("#name" & I, employeeName(I))
Next
sql.Remove(sql.Length - 1, 1).Append(")")
cmd.CommandText = sql.ToString()
Return GenericDataAccess.GetData(cmd)
End Function
(I'm sorry if my VB looks a little odd, I never use it anymore)
It actually builds a SQL statement dynamically, but the "dynamic" part is just a bunch of generated parameter names, which you then set. The maximum number of allowed parameters is 2000-something.
The best approach would depend on how big the subset of names is.
The very simplest, and probably worst approach would be to just create some dynamic sql like this..
"select city,count(*) from table where user in ("name1","name2","name3") GROUP BY city"
Where the in section is generated from the arraylist, perhaps like the following
private string CreateSQL(List<string> names)
{
var sb = new StringBuilder();
sb.Append("select city,count(*) from table where user in ('");
foreach ( var name in name)
{
sb.Append("'");
sb.Append(name);
sb.Append("',");
}
sb.Remove(sb.Length-1,1); //remove trailing ,
sb.Append(") GROUP BY city");
return sb.ToString();
}
The where in clause has a 100 item limit I believe. If your subset is anything like that length, then you really need a better approach, like putting the users in another table and doing a join. In fact if the subset of users is taken from the same database by another query, then post that and we'll right a single query.
edit: I actually don't know how to do where user in type queries as parameterised commands, be wary of sql injection !
One way of doing this is to pass it in as an XML parameter. The XML would be something like
#Cities = '<xml><city>Chicago</city><city>New York</city></xml>'
And this can be selected against as a table. This would have a slightly different behaviour than Macros's in that it will return rows for cities where the population is zero.
SELECT
tempTable.item.value('.', 'varchar(50)') AS City,
COUNT(DISTINCT people) AS [Population]
FROM #Cities.nodes('//city') tempTable(item)
LEFT OUTER JOIN peopleTable
ON tempTable.item.value('.', 'varchar(50)') = peopleTable.City
The above is the first time I have attempted that approach though so I'd be glad to have it critiqued!
My habitual approach is to pass it in as a comma delimited list and use a split function to get it into table format that can be joined against. An example split function is here