I have a View View_Booking in sql server 2014:
bkID bkSlot bkStatus
---- ------ --------
2 Lunch 1
4 Lunch 1
6 Dinner 0
7 Lunch 1
While in c# I have used a gridview and casted bkStatus into string like:
<asp:Label ID="lblStatus" Text='<%# (Eval("bkStatus")+"" == "1") ? "Booked" : "Pending" %>'
... ></asp:Label>
bkID bkSlot bkStatus
---- ------ --------
2 Lunch Booked
4 Lunch Booked
6 Dinner Pending
7 Lunch Booked
Now I'm searching into View using this query:
SELECT * FROM View_Booking
WHERE CAST(bkID AS NVARCHAR(MAX)) LIKE '%" + keyword + "%'
OR bkSlot LIKE '%"+keyword+"%'
OR bkStatus LIKE << ? >>
But don't know how to search for bkStatus which is passed as string from c# while it's a int in sql?
Some recommendations
The query you have provided need to be optimized:
First, using CAST(bkID AS NVARCHAR(MAX)) will affect the performance of the query, because it will not use any index, also casting to NVARCHAR(MAX) will decrease the performance.
bkStatus is a numeric column so you have to use = operator and compare with numeric values (0 or 1 or ...), also the text values provided are defined in the asp tag not in the database, so they are used in the application level not the data level.
if you are using CAST(bkID AS NVARCHAR(MAX)) to search for the bkid column that contains a specific digit (ex: search for 1 -> result 1,10,11,...), then try Casting to a specific size (ex: CAST(bkID as NVARCHAR(10))
It is recommended to use parameterized queries for a better performance and to prevent Sql injection attacks. look at #un-lucky answer
You can use a dictionary Object to store the ID values related to the keywords
Example
Note: The use of CAST and Like will not used any index, this example is based on your requirements (i tried to combine the recommendations i provided with others recommendations)
var dicStatus = new Dictionary<int, string> {
{ 0, "Pending" },
{ 1, "Booked" },
{ 2, "Cancelled" }
// ...
};
string querySql = " SELECT * FROM View_Booking" +
" WHERE CAST(bkID AS NVARCHAR(10)) LIKE #bkID" +
" OR bkSlot LIKE #bkSlot" +
" OR bkStatus = #status";
using (SqlConnection dbConn = new SqlConnection(connectionString))
{
dbConn.Open();
using (SqlCommand sqlCommand = new SqlCommand(querySql, dbConn))
{
sqlCommand.Parameters.Add("#bkID", SqlDbType.VarChar).value ="%" + keyword + "%";
sqlCommand.Parameters.Add("#bkSlot", SqlDbType.VarChar).value ="%" + keyword + "%";
sqlCommand.Parameters.Add("#status", SqlDbType.Int).value = dicStatus.FirstOrDefault(x => x.Value == keyword).Key;
sqlCommand.ExecuteNonQuery();
}
}
Also if BkID is an integer column it is better to use
sqlCommand.Parameters.Add("#bkID", SqlDbType.Int).value = (Int)keyword ;
References & Helpful Links
Like operator for integer
Performance hit using CAST in T-SQL
How much do CAST statements affect performance?
SQL Server: Index columns used in like?
C# Dictionary get item by index
Getting query to work with parameter and "like"
So you need a search box in which user can search by using bkID,bkSlot or bkStatus, If the search text is Booked or Pending we have to add the filter for bkStatus which will be an integer field in the database. right? Few more thing that I have to mention here is the usage of using as well as the parameterization for queries for a smarter and safer way of execution. So I would like to suggest to build and execute the query like the following:
int statusCode = -1;
if(keyword.ToLower() == "booked")
statusCode = 1;
else if(keyword.ToLower() == "pending")
statusCode = 0;
string querySql = " SELECT * FROM View_Booking" +
" WHERE CAST(bkID AS NVARCHAR(MAX)) LIKE #bkID" +
" OR bkSlot LIKE #bkSlot" +
" OR bkStatus = #status";
using (SqlConnection dbConn = new SqlConnection("connectionString here"))
{
dbConn.Open();
using (SqlCommand sqlCommand = new SqlCommand(querySql, dbConn))
{
sqlCommand.Parameters.Add("#bkID", SqlDbType.VarChar).value ="%" + keyword + "%";
sqlCommand.Parameters.Add("#bkSlot", SqlDbType.VarChar).value ="%" + keyword + "%";
sqlCommand.Parameters.Add("#status", SqlDbType.int).value = statusCode;
sqlCommand.ExecuteNonQuery();
}
}
Please note the following:
If you want to include the bkStatus filter for book, Pend etc.. then you have to change the condition accordingly by using .Contains() or .StartsWith() instead for .ToLower()
statusCode is initialized with -1 to avoid bkStatus based filter for all other values
You can use declare function to create a temporary table that has a list of bkStatus.
It will be easier for you to create a query by using bkstatus as a foreign key. After that, you don't have to use cast or like function anymore. It will be a little bit inefficient.
You can try this code below :
declare #bkstatus table (number int primary key , bkstatus varchar(10) )
insert into #bkstatus (number , bkstatus)
values ( 0 , 'Pending'), (1 , 'Booked')
and then using this query :
SELECT * FROM View_Booking v
INNER JOIN #bkstatus b on v.bkstatus = b.number
WHERE b.bkstatus = #keyword
Just another option using CHOOSE() to decode the bkStatus and TRY_CONVERT() to test bkID.
Example
Declare #KeyWord varchar(50) = 'Pending';
Select *
From View_Booking
Where bkID = try_convert(int,#KeyWord)
or bkSlot like '%'+#KeyWord+'%'
or choose(bkStatus+1,'Pending','Booked')=#KeyWord
Returns
bkID bkSlot bkStatus
6 Dinner 0
If keyword will be status name and not status id, I would create BookingStatus table, have bkStatus and bkStatusTitle columns there and join it to the View_Booking. You could easily do LIKE on bkStatusTitle then.
SELECT * FROM View_Booking
WHERE CAST(bkID AS NVARCHAR(16)) LIKE '%' + #keyword + '%'
OR bkSlot LIKE '%' + #keyword + '%'
OR bkStatusTitle LIKE '%' + #keyword + '%'
If keyword will be a string representation of bkStatus, I would just see if the values are same.
As a side note, it's a bad idea to build your SQL queries concatenating user input into it like '%' + keyword + '%'. This is open to SQL injection attacks. It's best to use SQL parameters to pass user input to SQL queries. Using '%' + #keyword + '%' in the SQL bit and in C# something like example below would be much safer.
sqlCommand.Parameters.Add("#keyword", SqlDbType.VarChar, 1000);
sqlCommand.Parameters["#keyword"].Value = searchText;
Parameterized queries also give you a benefit of same query text for multiple requests, which in turn allows SQL Server to cache SQL execution plans and reuse them, giving slightly better performance.
Your bkStatus is integer. In the view you translate the integer value into a user meaningful string. Up to that point it's all ok. Now, for the user to search all you need to do is reverse your translations from strings to integer and search for integers.
Keeping things simple
searchStatusKey = yourVariableHodingTheString == "Booked" ? 1 : 0;
However, to avoid tedious bugs and painless upgrades of code in multiple places (say because they decided to add another status there) I would recommend a translation table here. Something like HashMap (associative array).
var statusMap = new Dictionary<int, string> {
{ 0, "Pending" },
{ 1, "Booked" },
/* can always add more pairs in the future as needed */
};
Now, assuming your parameter is in a variable called searchStatus
searchStatusKey = statusMap.FirstOrDefault(x => x.Value == searchStatus).Key;
Now Provide as bkStatus parameter in the where part the searchStatusKey value and you're done.
select * from View_Booking where bkStatus = << ? >>
I will just focus on this part of your question (Is it the question itself?):
But don't know how to search for bkStatus which is passed as string from c# while it's a int in sql?
One way of dealing with that in SQL is with the help of the CASE clause. In your specific case you could (doesn`t mean should) do something like:
SELECT * FROM View_Booking
WHERE CAST(bkID AS NVARCHAR(MAX)) LIKE '%" + keyword + "%'
OR bkSlot LIKE '%"+keyword+"%'
OR bkStatus = CASE '%"+keyword+"%' WHEN 'Booked' THEN CAST(1 AS INT) WHEN 'Pending' THEN CAST(0 AS INT) WHEN ... THEN ... ELSE ... END'
But I suggest the use of parameters as indicated in #un-lucky's answer. There's a whole lot more we could discuss in terms of best practices here, so I suggest you to take a look at the following articles:
Lookup tables: You stated that bkStatus is of type INT soy I assume you could have more options than Booked or Pending, for example: Reserved or Cancelled. In that case your actual code may become increasingly untidy with every option you add.
Best practices for using ADO.NET: You did not specify how do you access the database from your front end. Even though this article has been around for years, most of its content is still current. I assume this may be helpful.
Building Better Entity Framework: In case you are using Entity Framework to access your database.
Hope it helps.
Make an enum for BookingStatus and make a function that accepts string and returns the enum value. See the below code.
public enum BookingStatus {
[Description("Pending")]
Pending = 0,
[Description("Booked")]
Booked = 1
}
Now the function is as below,
public static T GetValueFromDescription<T>(string p_description)
{
var type = typeof(T);
if (!type.IsEnum) throw new InvalidOperationException();
foreach (var field in type.GetFields())
{
var attribute = Attribute.GetCustomAttribute(field,
typeof(DescriptionAttribute)) as DescriptionAttribute;
if (attribute != null)
{
if (attribute.Description == p_description)
return (T)field.GetValue(null);
}
else
{
if (field.Name == p_description)
return (T)field.GetValue(null);
}
}
throw new ArgumentException("Not found.", "description");
// or return default(T);
}
Now in the parameter in sql query, call this function with parameter as "Booked" or "Pending" and it will return enum BookingStatus.Booked. You can easily extract int value from that.
(int)BookingStatus.Booked // will give 1
It looks as if you are trying to search freely amongst several columns. This is quite a common problem, and the real solution can be found at www.Sommarskog.se on dynamic search conditions.
Your solution looks as if it is vulnerable to SQL Injection. May I suggest that you implement something similar to the stored procedure search_orders_3?
Related
I have a list of items with different ids which represent a SQL table's PK values.
Is there any way to build an efficient and safe statement?
Since now I've always prepared a string representing the statement and build it as I traversed the list via a foreach loop.
Here's an example of what I'm doing:
string update = "UPDATE table SET column = 0 WHERE";
foreach (Line l in list)
{
update += " id = " + l.Id + " OR";
}
// To remove last OR
update.Remove(update.Length - 3);
MySqlHelper.ExecuteNonQuery("myConnectionString", update);
Which feels very unsafe and looks very ugly.
Is there a better way for this?
So yeah, in SQL you've got the 'IN' keyword which allows you to specify a set of values.
This should accomplish what you would like (syntax might be iffy, but the idea is there)
var ids = string.Join(',', list.Select(x => x.Id))
string update = $"UPDATE table SET column = 0 WHERE id IN ({ids})";
MySqlHelper.ExecuteNonQuery("myConnectionString", update);
However, the way you're performing your SQL can be considered dangerous (you should be fine as this just looks like ids from a DB, who knows, better to be safe than sorry). Here you're passing parameters straight into your query string, which is a potential risk to SQL injection which is very dangerous. There are ways around this, and using the inbuilt .NET 'SqlCommand' object
https://www.w3schools.com/sql/sql_injection.asp
https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcommand?view=dotnet-plat-ext-6.0
It would be more efficient to use IN operator:
string update = "UPDATE table SET column = 0 WHERE id IN (";
foreach (Line l in list)
{
update += l.Id + ",";
}
// To remove last comma
update.Remove(update.Length - 1);
// To insert closing bracket
update += ")";
If using .NET Core Framework, see the following library which creates parameters for a WHERE IN. The library is a port from VB.NET which I wrote in Framework 4.7 years ago. Clone the repository, get SqlCoreUtilityLibrary project for creating statements.
Setup.
public void UpdateExample()
{
var identifiers = new List<int>() { 1, 3,20, 2, 45 };
var (actual, exposed) = DataOperations.UpdateExample(
"UPDATE table SET column = 0 WHERE id IN", identifiers);
Console.WriteLine(actual);
Console.WriteLine(exposed);
}
Just enough code to create the parameterizing SQL statement. Note ActualCommandText method is included for development, not for production as it reveals actual values for parameters.
public static (string actual, string exposed) UpdateExample(string commandText, List<int> identifiers)
{
using var cn = new SqlConnection() { ConnectionString = GetSqlConnection() };
using var cmd = new SqlCommand() { Connection = cn };
cmd.CommandText = SqlWhereInParamBuilder.BuildInClause(commandText + " ({0})", "p", identifiers);
cmd.AddParamsToCommand("p", identifiers);
return (cmd.CommandText, cmd.ActualCommandText());
}
For a real app all code would be done in the method above rather than returning the two strings.
Results
UPDATE table SET column = 0 WHERE id IN (#p0,#p1,#p2,#p3,#p4)
UPDATE table SET column = 0 WHERE id IN (1,3,20,2,45)
Is it safe method for C# against sql injection?
string sqlDeclare = $"DECLARE #number nvarchar(MAX) SET #number = '%{sNumber}%' ";
string sqlFilter = "";
if (!string.IsNullOrEmpty(Number))
{
sqlFilter += $" and [table].[number] like #number ";
}
string sql = sqlDeclare + " SELECT [table].[*] ";
sql += " WHERE [table].[state] = 0 and [table].[column1] <> 3 AND [table].[id] > 0 ";
if (!string.IsNullOrEmpty(sqlFilter))
{
sql += sqlFilter;
}
sql += " order by datein";
P.S. I can't use Parametr.Add()
Is it safe method for C# against sql injection?
if sNumber actually was a numeric type, like int or decimal then I'd say "maybe" because it's pretty hard to inject SQL into a number, but at the end of the day, all you've done is change from concatenating a string value directly into an sql query, which is the classic fail:
"SELECT * FROM t WHERE x = " + str
into putting a string value into a db variable that you then use in an sql. It means the select query won't be the place where the injecting is done, but it just moves the injection point earlier:
Think what happens when sNumber is a value like '; DROP TABLE students;--
DECLARE #number nvarchar(MAX) SET #number = '%';
DROP TABLE Students;--%'
SELECT ...
If you truly are restricted to using this function (it needs throwing away, tbh) then you'll have to do your best at preventing injection by sanitizing the input. In this case you say you're expecting a document number like 2-12 so your C# should look like
if(!Regex.IsMatch(userInput, #"^\d+-\d+$"))
throw new ArgumentException("Bad input value, expecting document number like 2-12");
var dt = Execute4Table($"SELECT * FROM t WHERE x = '{input}');
In a general sense, it would be clunky, but you could perhaps do some wrapper function that takes N parameters and serializes them to JSON, then you pass them to sqlserver as a json string and get SQLS to deser them into N parameters that you use in the query. The two serializers should encode any user provided values in such a way that they are interpreted as data rather than code. You could also do a similar thing with base64 if your sqlserver is too old for json. To some extents this isn't much different to replacing all ' with '' but with a set/deser approach you're handing off responsibility for that encoding to a well tested library, which is probably the only thing you can do if you are rather hamstrung by this method you're forced to use
It would be safe if you type check the sNumber variable and you made sure it has no string dangerous data (your sNumber could have 0';TRUNCATE TABLE [table];-- and that would be SQL Injection). If you check if your snumber is integer, you would be safe on your string interpolation.
Otherwise it would not be safe. It is best to avoid string interpolation/string concatenation.
int checkedNumber;
if (Int32.TryParse(out checkedNumber, sNumber))
{
string sqlDeclare = $"DECLARE #number nvarchar(MAX) SET #number = '%{sNumber}%' ";
string sqlFilter = "";
if (!string.IsNullOrEmpty(Number))
{
sqlFilter += $" and [table].[number] like #number ";
}
string sql = sqlDeclare + " SELECT [table].[*] ";
sql += " WHERE [table].[state] = 0 and [table].[column1] <> 3 AND [table].[id] > 0 ";
if (!string.IsNullOrEmpty(sqlFilter))
{
sql += sqlFilter;
}
sql += " order by datein";
}
I am facing a problem , I have a query in SQL Server 2014, The query result should be based on a WHERE clause that takes a string from a C# CheckedListBox.
I have the string in this form (the following values are for example only) :-
cat,dog,bird,duck
And inside the database the records look like this:-
dog cat-dog cat-dog-duck bird-dog-duck etc...
I have tried this :-
DECLARE #animals nvarchar(max) = 'dog,bird,cat'
select x,y,z WHERE CHARINDEX(animals, replace(#animals,',',' ')) > 0
The result would show rows with only ONE SINGLE VALUE like dog cat bird But it wouldn't show rows with values like dog-cat dog-cat-bird etc! it just shows rows with one single word from #animals string.
How can I select all rows where column animals contains either a word or more from #animals string.
Thanks in advance...
You should create a temp table for store all searching value or you should create a temp table from the comma separated variable for the example visit Query for convert CSV values into temp table. Then use inner join for filter records from your table like below.
declare #temp table (animal varchar(50))
insert into #temp values ('cat')
insert into #temp values ('dog')
insert into #temp values ('bird')
select * from SomeTable a
inner join #temp t on a.Column like '%' + t.animal + '%'
Make stored procedure for that query and call it from C#.
And inside the database the records look like this:-
dog cat-dog cat-dog-duck bird-dog-duck etc...
There is the source of your problems. Before reading anything else I wrote in my answer, you should read Is storing a delimited list in a database column really that bad?, where you will see a lot of reasons why the answer to this question is Absolutely yes!.
Once you're done with that, the solution to the problem should be as obvious to you as it is to me - Fix the database structure - meaning remove that column storing delimited data and replace it with a table referenced by a many-to-many relationship to your existing table, that will hold the animals data for you.
The first part of the solution is using a table valued parameter instead of sending a delimited string to the database.
There are plenty of examples on how to do this on stackoverflow - like here and there.
Once you've done that, you can use a hack with like as a workaround, in case you can't change the database structure:
SELECT <ColumnsList>
FROM <TableName> As T
JOIN #TVP As TVP
ON '-' + T.Animals +'-' LIKE '%-' + TPV.Animal +'-%'
Note I've added the delimiter to both ends of both columns.
If you can change the structure you will have a query like this:
SELECT <ColumnsList>
FROM <TableName> As T
JOIN TableToAnimals AS TTA
ON T.Id = TTA.TableId
JOIN Aniamls AS A
ON TTA.AnimalId = A.Id
JOIN #TVP As TVP
ON A.Name = TVP.Animal
You should take a look at Table valued parameters, it will let you send in a table of values as a parameter from C# to SQL. You make a table with "animals" and then make a sub-select in your Stored Proc.
See example code from link below, shows how to pass the parameter:
// Assumes connection is an open SqlConnection object.
using (connection)
{
// Create a DataTable with the modified rows.
DataTable addedCategories = CategoriesDataTable.GetChanges(DataRowState.Added);
// Configure the SqlCommand and SqlParameter.
SqlCommand insertCommand = new SqlCommand("usp_InsertCategories", connection);
insertCommand.CommandType = CommandType.StoredProcedure;
SqlParameter tvpParam = insertCommand.Parameters.AddWithValue("#tvpNewCategories", addedCategories);
tvpParam.SqlDbType = SqlDbType.Structured;
// Execute the command.
insertCommand.ExecuteNonQuery();
}
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/table-valued-parameters
For anyone who has the same problem. I have found the solution for this on SQL Server. Use the Full Text Search and your problem is easily solved. It's awesome.
Check the following link :-
https://learn.microsoft.com/en-us/sql/relational-databases/search/get-started-with-full-text-search?view=sql-server-2017
use like this:
var query = string.Empty;
var index=0;
foreach(var animal in animals) {
if (query.Length>0) {
query +=" and ";
}
var paramName = "#animalName" + index++;
query +="(animals like " + paramName + " or animals like '%' + " + paramName + " or animals like " + paramName + " + '%' or animals like '%' + " + paramName + " + '%')";
SqlParameter thisParam = new SqlParameter(paramName, animal);
command.Parameters.Add(thisParam);
}
command.CommandText = "select * from tableName WHERE " + query;
I need to perform an update to a View that has multiple underlying tables using the ExecuteCommand method of a DataContext. I am using this method because of the known restriction of linqToSQL when performing this type of operation on Views having multiple underlying tables.
My existing SQL statement is similar to the following where I am setting newFieldID to a null value simply for this post to illustrate the issue. In the application, newFieldID is assigned a passed parameter and could actually be an integer value; but my question is specific to the case where the value being provided is a null type:
using (var _ctx = new MyDataContext())
{
int? newFieldID = null;
var updateCmd = "UPDATE [SomeTable] SET fieldID = " + newFieldID + "
WHERE keyID = " + someKeyID;
try
{
_ctx.ExecuteCommand(updateCmd);
_ctx.SubmitChanges();
}
catch (Exception exc)
{
// Handle the error...
}
}
This code will fail for the obvious reason that the updateCmd won't be completed in the case of a null value for the newFieldID. So how can I replace or translate the CLR null value with an SQL null to complete the statement?
I know I could move all of this to a Stored Procedure but I am looking for an answer to the existing scenario. I've tried experimenting with DBNull.Value but aside from the challenge of substituting it for the newFieldID to use in the statement, simply placing it into the string breaks the validity of the statement.
Also, enclosing it within a single quotes:
var updateCmd = "UPDATE [SomeTable] SET fieldID = '" + DBNull.Value + "'
WHERE keyID = " + someKeyID;
Will complete the statement but the value of the field is translated to an integer 0 instead of an SQL null.
So How does one go about converting a CLR null or nullable int to an SQL Null value given this situation?
Correct way to do it: use override of ExecuteCommand accepting not only command text, but also array of parameters and use parameterized query instead of command string concatenation:
var updateCmd = "UPDATE [SomeTable] SET fieldID = {0} WHERE keyID = {1}";
_ctx.ExecuteCommand(updateCmd, new [] {newFieldID, someKeyID});
It will not only prevent you from sql injection, but also it will do following for you (from MSDN description):
If any one of the parameters is null, it is converted to DBNull.Value.
Try checking newFieldID == null and change the statement accordingly.
Something like below or using separate if / else statement.
var updateCmd = "UPDATE [SomeTable] SET fieldID =" + (newFieldID == null ? "null" : Convert.ToString(newFieldID)) + " WHERE keyID = " + someKeyID;
Normally, when using Stored Procedures or Prepared Statements, you use Parameters to assign values. When you have a DbParameter, you can assign null or DBNull.Value to the Value-Property or your parameter.
If you want to have the null as text in the statement, simply use the SQL-keyword NULL
var updateCmd = "UPDATE [SomeTable] SET fieldID = NULL WHERE keyID = " + someKeyID;
As pointed out by Andy Korneyev and others, a parameterized array approach is the best and probably the more appropriate method when using Prepared statements. Since I am using LinqToSQL, the ExecuteCommand method with the second argument which takes an array of parameters would be advised but it has the following caveats to its usage.
A query parameter cannot be of type System.Nullable`1[System.Int32][] (The main issue I'm trying to resolve in this case)
All parameters must be of the same type
Shankar's answer works although it can quickly become very verbose as the number of parameters could potentially increase.
So my workaround for the problem involve somewhat of a hybrid between the use of parameters as recommended by Andy and Shankar's suggestion by creating a helper method to handle the null values which would take an SQL statement with parameter mappings and the actual parameters.
My helper method is:
private static string PrepareExecuteCommand (string sqlParameterizedCommand, object [] parameterArray)
{
int arrayIndex = 0;
foreach (var obj in parameterArray)
{
if (obj == null || Convert.IsDBNull(obj))
{
sqlParameterizedCommand = sqlParameterizedCommand.Replace("{" + arrayIndex + "}", "NULL");
}
else
sqlParameterizedCommand = sqlParameterizedCommand.Replace("{" + arrayIndex + "}", obj.ToString());
++arrayIndex;
}
return sqlParameterizedCommand;
}
So I can now execute my statement with parameters having potential null values like this:
int? newFieldID = null;
int someKeyID = 12;
var paramCmd = "UPDATE [SomeTable] SET fieldID = {0} WHERE keyID = {1}";
var newCmd = PrepareExecuteCommand(paramCmd, new object[] { newFieldID });
_ctx.ExecuteCommand(newCmd);
_ctx.SubmitChanges();
This alleviates the two previously referenced limitations of the ExecuteCommand with a parameter array. Null values get translated appropriately and the object array may varied .NET types.
I am marking Shankar's proposal as the answer since it sparked this idea but posting my solution because I think it adds a bit more flexibility.
I am managing a static class with all the SQL Scripts for DML and Selects.
However, there's a scenario that search can take more than 1 search criteria.
e.g.
Select items from table_items where capacity = 10 and type = 'metal'
When capacity or type is not mentioned, the query should return all the item,
Select items from table_items
There are couple combinations here.
capacity can be empty input
type can be empty input
both can be empty input
Does it mean there has to be 3 different SQL queries in my SQL Script static class? And validate each of above combinations over case/if-else? Doesn't make sense to me. Specially that the application is built using data access, entity model separately in a 3-tier architecture.
Perhaps I am totally forgetting the roots/basics. I do not want to write dynamic sql queries.
What could be a good work-around for this scenario?
EDIT: Original query
public static readonly string SqlGetItemsBy_ID_Capacity_Type = "SELECT TT.[ID], " +
"TT.[CAPACITY], " +
"TT.[TYPE], " +
//some code here
"FROM [ITEMS] AS TT " +
"WHERE //some code here +
"AND TT.[CAPACITY] = CASE WHEN 0 = #Capacity" + //+ numCapacitySearch.Value +
"THEN TT.[CAPACITY] ELSE #Capacity END " + //+ numCapacitySearch.Value +
"AND TT.[TYPE] = CASE WHEN ??? IS NULL THEN TT.[TYPE] ELSE #Type END" ;
Reference to ??? when comparing with NULL...
A case statement where the column is returned if the submitted value is null will give you all rows like this.
Select
items
from
table_items
where 1=1
and capacity = case when #capvalue is null then capacity else #capvalue end
and type = case when #typevalue is null then type else #typevalue end
Fiddle
Notice that if you set the values to null, all rows are returned. If you set a value to say 1, then only that row is returned.
You could do it like so in your query:
Select items
from table_items
where (capacity <> null or capacity = 10)
AND
(type <> null or type = 'metal')
Or you could do it progmatically with a few if elses
And if you are using an ORM like the entity framework. You have dynamic queries and can usually 'build' the query step by step.