Is there a way I can determine in .NET, for any arbitrary SQL Server result set, if a given column in the result can contain nulls?
For example, if I have the statements
Select NullableColumn From MyTable
and
Select IsNull(NullableColumn, '5') as NotNullColumn From MyTable
and I get a datareader like this:
var cmd = new SqlCommand(statement, connection);
var rdr = cmd.ExecuteReader();
can I have a function like this?
bool ColumnMayHaveNullData(SqlDataReader rdr, int ordinal)
{
//????
}
I want it to return true for the first statement, and false for the second statement.
rdr.GetSchemaTable() doesn't work for this because it returns whether the underlying column can be null, which is not what I want. There are functions on datareader that return the underlying sql type of the field, but none seem to tell me if it can be null..
Unfortunately You can't because SQL server has no way of determining whether a field is nullable or not. You can do arbitral transformations on fields in result set (operators, function calls etc.) and those transformations do not have metadata about them whether they can or can't return null. So You have to figure that out manually or use views with schemabinding...
I'm a bit confused:
"doesn't work for this because it returns whether the underlying column can be null, which is not what I want. "
"but none seem to tell me if it can be null.."
You can query the underlying table to see if a column is null-able (assuming that is what you want).
SELECT IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'MyTable' AND COLUMN_NAME = 'MyColumn'
Related
I have the following code:
/*
// it works
cmd_oper = "UPDATE [Model Elements] SET [Record Status] = \"Disabled\" WHERE [Index] = #db_idx";
/*/
// it doesn't work
cmd_oper = "UPDATE [Model Elements] SET [Record Status] = #stat WHERE [Index] = #db_idx";
//*/
using( OleDbCommand cmd = new OleDbCommand( cmd_oper, svr_conn ) )
{
cmd.Parameters.Add( "#db_idx", OleDbType.Integer ).Value = 2;
//cmd.Parameters.Add( "#stat", OleDbType.VarChar ).Value = "Disabled";
cmd.Parameters.AddWithValue( "#stat", "Disabled" );
cmd.ExecuteNonQuery();
}
With the second variant of cmd_oper (the one not commented in the beginning of the code) I get "Data type mismatch in criteria expression". The other one works. The type of Record Status column is set in database as Short Text. I know there are many posts related to this error on StackOverflow but I couldn't find an exact fit. Thanks.
The fine manual, https://msdn.microsoft.com/en-us/library/system.data.oledb.oledbcommand.parameters(v=vs.110).aspx has the following to say about parameters
The OLE DB .NET Provider does not support named parameters for passing
parameters to an SQL statement or a stored procedure called by an
OleDbCommand when CommandType is set to Text. In this case, the
question mark (?) placeholder must be used. For example:
SELECT * FROM Customers WHERE CustomerID = ?
Therefore, the order in which OleDbParameter objects are added to the
OleDbParameterCollection must directly correspond to the position of
the question mark placeholder for the parameter in the command text.
Ergo, I think you need your code to be like:
cmd_oper = "UPDATE [Model Elements] SET [Record Status] = ? WHERE [Index] = ?";
using( OleDbCommand cmd = new OleDbCommand( cmd_oper, svr_conn ) )
{
cmd.Parameters.AddWithValue( "anything-name-doesnt-matter", "Disabled" );
cmd.Parameters.Add( "its-position-that-matters", OleDbType.Integer ).Value = 2;
cmd.ExecuteNonQuery();
}
For what it's worth, you should probably name your parameters sensibly (I named them silly above to demonstrate that the name is irrelevant) because the idea is that once prepared, you can execute a statement many times just changing the parameters:
cmd.Parameters["its-position-that-matters"].Value = 3;
cmd.ExecuteNonQuery();
cmd.Parameters["its-position-that-matters"].Value = 4;
cmd.ExecuteNonQuery();
cmd.Parameters["its-position-that-matters"].Value = 5;
cmd.ExecuteNonQuery();
cmd.Parameters["its-position-that-matters"].Value = 6;
cmd.ExecuteNonQuery();
Here i've run the update for index values 3, 4, 5 and 6 too, just by changing the parameter value and re-running. It would hence have been better of me to choose a sensible name for the "index" parameter, to make the code more readable
Steve, in his comment, has noted he believes that you can put named parameters in the query, but the names are ignored (they're essentially treated as ? marks anyway) so you'll still need to add the parameter values in the same order as the placeholders appear. If you have repeated a placeholder in a query, you'll have to repeat-add it to the parameters collection. I've no comment on the accuracy of steve's assertion; I've always used ?
Ultimately, this is all good evidence that really you should get into learning to use a data access library like Entity Framework, and stop writing SQL strings in your button click event handlers - it's not a good way to code. If you'd used EF from the outset, you'd never even have hit this problem. Good on you for using parameterised queries though. Now go check out EF and leave this '90s donkey-way of doing data access behind :)
I'm writing the following SQL query using the TableAdapter Query Configuration Wizard in Visual Studio.
SELECT COUNT(*) AS census
FROM Inventory INNER JOIN Taxonomy ON Inventory.GlobalID = Taxonomy.GlobalID
WHERE (Inventory.Institution = #institution) AND (Inventory.Year = #year) AND
(Inventory.Nending > 0)
I'm trying to add the following criteria to the WHERE clause:
(Taxonomy.Class = ISNULL(#class, Taxonomy.Class))
so that either
1) only rows that match the #class input parameter are returned or
2) all rows are returned regardless of their TaxonomyGlobal.Class value.
When I add this statement to the query my C# code that calls the query throws a System.ArgumentNullException error and states the #class value cannot be null.
Any help on how to add this criterion to the WHERE clause would be appreciated.
C# code:
namespace CollectionMetrics
{
class DatabaseQueries
{
QueryDataSetTableAdapters.InventoryTableAdapter queryAdapter =
new QueryDataSetTableAdapters.InventoryTableAdapter();
public void CensusQuery(string institution, short year, string xclass)
{
int census = 0;
string localClass = xclass;
if (xclass == "All Classes") localClass = null;
census = (int)queryAdapter.CensusBySpecies(localClass, institution, year);
censusOutput.Add(census);
}
}
}
SQL:
(#class IS NULL OR Taxonomy.Class = #class)
Since you are using TableAdapter, you will need to edit the field to allow nulls:
https://msdn.microsoft.com/en-us/library/ms233762.aspx
Setting the AllowDbNull Property
To enable a query to accept null values In the Dataset Designer,
select the TableAdapter query that needs to accept null parameter
values. Select Parameters in the Properties window and click the
ellipsis (…) button to open the Parameters Collection Editor. Select
the parameter that allows null values and set the AllowDbNull property
to true.
If you are using SqlParameters:
C#
var param = new SqlParameter("#class", (object) classVariable ?? DBNull.Value);
Replace classVariable with the name of the variable you are using in your code to set the value for the #class SqlParameter. The cast to object is required because the variable does not have the same type as DBNull.
I once tried doing what you're trying to do, thinking this was a nifty way to ignore parameters that weren't passed from the front end (and therefore were NULL).
But then I learned that using ISNULL() in the WHERE clause like this prevents indexes from being used, making your query much SLOWER than if you used:
WHERE (Taxonomy.Class = #Class OR #Class IS NULL)
Unintuitive, I admit; the way you're trying looks like it would be cleaner and therefore faster, but for SQL performance, the most important thing is using available indexes, and so it turns out the A OR B approach is actually faster than the ISNULL() approach you want to use.
As to why you're getting an error, it's got to be something the wizard is enforcing. If you tried your query purely in SQL (using SSMS), it would allow it. UNLESS your query is actually in a stored procedure and #Class is a required parameter.
As the code Shows below, I want to insert a row into a database table (oracle 11) and return a String-Value of the inserted row.
using (OracleCommand cmd = con.CreateCommand()) {
cmd.CommandText = "insert into foo values('foo','bar') returning idString into :lastIdParam";
cmd.Parameters.Add(new OracleParameter("lastIdParam", OracleDbType.Varchar2), ParameterDirection.ReturnValue);
cmd.ExecuteNonQuery(); // returning 1 (insert row successfully)
var result = cmd.Parameters["lastIdParam"].Value.ToString(); // == String.Empty
}
Debugging shows that lastIdParam.Value's value = Empty.String:
My Problem is, that I'm not getting the return string into my return-parameter but it will work when returning an integer value (like sequence no of inserted id). Cast Problem? ...?
The idString is filled if running the Statement directly (or if I just do something like returning 'ABC' into :myOutputParameter
Any ideas how to retrieve a string after inserting row?
Have you tried setting a size for the parameter? The default size is 0.
new OracleParameter("lastIdParam", OracleDbType.Varchar2, 128)
The idString is an expression which has no value in your context, unless it is a column name in your table. Therefore, it is epected to be empty. You may change your query like the example below and see what happens.
cmd.CommandText = "insert into foo values('foo','bar') returning hereYouHaveToUseAColumnFromTheFooTable into :lastIdParam";
Let me explain a little.
Image you have the flowing MySQL table:
CREATE TABLE test (
value DATETIME
);
Currently I am doing something like this:
command.CommandText = "UPDATE test SET value = ?value";
command.Parameters.AddWithValue("?value", DateTime.Now);
But the clients date/time settings are unreliable so I would like the time to be determined at the server. Something like:
command.CommandText = "UPDATE test SET value = NOW()";
The trick is that I want to pass the "NOW()" function as a parameter to the original query. Please understand that this small example is OVERLY simplified. In reality the table has 48 columns, 7 of which are datetime, marking the date and/or time the user made a particular operation to the object the database row denotes. When the user performs a new operation to the object I read the entire row and insert it again, with a different ID of course, (Something like a poor man's revision history) so I like to keep the date/time of the old operation and insert it in the new row, but at the same time set the current date/time (as seen by the database) for the new operation.
For simplicity lets go back to the previous example.
As far as I can find I have two choices.
bool switch;
....
command.CommandText = "UPDATE test SET value = " + switch ? "NOW()" : "?value";
command.Parameters.AddWithValue("?value", value);
OR
bool switch;
....
if (switch) {
command.CommandText = "SELECT NOW()";
value = Convert.ToDateTime(command.ExecuteScalar());
}
command.CommandText = "UPDATE test SET value = ?value";
command.Parameters.AddWithValue("?value", value);
I don't like neater of them. Wouldn't it be neat if I could do something similar to:
command.CommandText = "UPDATE test SET value = ?value";
if (switch) {
command.Parameters.AddWithValue("?value", "NOW()");
} else {
command.Parameters.AddWithValue("?value", value);
}
That way I would still execute the same one query.
Waiting for your opinions on this. I will be very grateful for any help you can provide.
You could use a stored procedure with an optional parameter to achieve this. If the parameter is NULL, you use NOW() instead. Although I'm not a big fan of stored procs (they are evil ! :-).
You could also use IFNULL(). Here is how it is done in T-SQL (function name is ISNULL) :
UPDATE test SET value = ISNULL(#value, GetDate() )
Situation: c#, sql 2000
I have a table, lets call it 'mytable' with 30 million rows.
The primary key is made up of fields A and B:
A char(16)
B smallint(2)
When i do a search like this, it runs really slowly (eg it does a full tablescan)
string a="a";
int b=1;
string sql = "select * from table(nolock) where a=#a and b=#b";
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.AddWithValue("#a", a);
cmd.Parameters.AddWithValue("#b", b);
using (SqlDataReader rdr = cmd.ExecuteReader()) {...}
}
Change it to this however, and it runs really quick (eg it hits the index):
string where =
String.Format("a='{0}' and b={1}", a, b);
string sql = "select * from table(nolock) where " + where;
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
using (SqlDataReader rdr = cmd.ExecuteReader()) {...}
}
What on earth is going on? Seems strange to me.
Do data types of parameter and column match? They don't it appears so datatype precedence applies
The column is smallint, but you send int. The column will be converted to int because it has a higher precedence. So it won't use an index.
Does it make any difference if you declare the b variable to be a short instead of int?
Does it make any difference if you explicitly specify the types of the parameters?
Does it make any difference if you use "where a=#a and b=#b" instead of the comma form?
I agree this does sound odd, and I wouldn't really expect any of these changes to help, but it's probably worth a try.
You may tell SQL Server which index to use for a query. Use the WITH (INDEX = INDEX_ID) option where INDEX_ID is the ID of the index.
Get index ID's with:
SELECT i.indid, i.name FROM sysindexes i
INNER JOIN sysobjects o ON o.ID = i.id
WHERE o.Name = 'table'
So try then:
SELECT * FROM table(NOLOCK) WITH (INDEX = 1) WHERE a=#a and b=#b
As #gbn said, setting the data type should make it easy for you.
string where =
String.Format("a='{0}' and b={1}", a, b);
In the example above, you are telling SQL to treat parameter a as char.
Whereas, in other example it will be treated as a varchar.
Use SQL profiler to see what is the SQL that gets executed in both the cases. That should clear it for you.
In the first case you are adding SqlParameter classes to the command. When the command is executed it is most likely generating DECLARE statements with the wrong data type. (You can verify this with a SQL trace.) If this is the case, the optimizer cannot select the correct index and falls back to a table scan.
If you use a stored proc instead, you would be forcing the parameters into the data types you declare. However, you can still do this from code if you specify the SqlDbType on the parameters.