I have a query that I am writing in my C# code to retrieve results from database. Based on the user input, my query will change.
I have a user input say 'challenge'. And 'challenge' can have three values 0,1 and 2, based on what user selects in UI.
If user selects challenge 0, the query will be different.
If user selects challenge 1, the query will be different.
And if user selects challenge 2, the query will be union of query for challenge 0 and query for challenge 1.
Below is how my code for generating sql query looks at present:
string sql = @"SELECT
tbl.given_id, name, message, tbl.emplid, category, created_date
FROM
dbo.myTable tbl
WHERE
created_date >= @dtFrom
AND created_date < @dtTo
";
//Include only challenge 0
if(challenge == 0)
{
sql += " AND emplid IN (" + sb.ToString() + ")"; //sb.ToString() is list of emplids selected from UI.
}
//Include only challenge 1
if (challenge == 1)
{
sql += " AND given_Id IN (SELECT DISTINCT rec.given_Id from dbo.Recipient rec WHERE rec.given_Id = tbl.given_Id AND rec.emplid IN (" + sb.ToString() + ")) ";
}
//Include both challenge 0 and 1
if (challenge == 2)
{
sql = String.Format(@"
SELECT * FROM
(
(
SELECT
tbl.given_id, name, message, tbl.emplid, category, created_date
FROM
dbo.myTable tbl
WHERE
created_date >= @dtFrom
AND created_date < @dtTo
AND tbl.emplid IN ({0})
)
UNION
(
SELECT
tbl.given_id, name, message, tbl.emplid, category, created_date
FROM
dbo.myTable tbl
WHERE
created_date >= @dtFrom
AND created_date < @dtTo
AND given_Id IN (SELECT DISTINCT rec.given_Id from dbo.Recipient rec WHERE rec.given_Id = tbl.given_Id AND rec.emplid IN (" + sb.ToString() + "))
)
)
", sb.ToString());
}
So, if(challenge == 2), is generating the query by the union of query generated in challenge = 0 and challenge = 1.
The query code in challenge = 2, is just repetitive of both the above queries.
Is there any other way of writing such query, so that I can improve my query and increase the perfomance?
Thanks.
Edit - Writing just the queries generated to simplify my question:
Query 1 -
SELECT
tbl.given_id, name, message, tbl.emplid, category, created_date
FROM
dbo.myTable tbl
WHERE
created_date >= @dtFrom
AND created_date < @dtTo
AND tbl.emplid IN (@lst_emplids)
Query 2
SELECT
tbl.given_id, name, message, tbl.emplid, category, created_date
FROM
dbo.myTable tbl
WHERE
created_date >= @dtFrom
AND created_date < @dtTo
AND given_Id IN (SELECT DISTINCT rec.given_Id from dbo.Recipient rec WHERE rec.given_Id = tbl.given_Id AND rec.emplid IN (@lst_emplids)
Query 3
SELECT * FROM
(
(
SELECT
tbl.given_id, name, message, tbl.emplid, category, created_date
FROM
dbo.myTable tbl
WHERE
created_date >= @dtFrom
AND created_date < @dtTo
AND tbl.emplid IN (@lst_emplids)
)
UNION
(
SELECT
tbl.given_id, name, message, tbl.emplid, category, created_date
FROM
dbo.myTable tbl
WHERE
created_date >= @dtFrom
AND created_date < @dtTo
AND given_Id IN (SELECT DISTINCT rec.given_Id from dbo.Recipient rec WHERE rec.given_Id = tbl.given_Id AND rec.emplid IN (@lst_emplids)
)
)
There are a number of things you can do. First, it would be worthwhile to have SQL do the heavy lifting. You can pass the parameters to it and let it figure out how to arrange the query based on the arguments.
To take best advantage, you'd wrap your SQL up in a pre-defined procedure or function. In this case, I think I'd go with a function - but either is fine. My example is a function.
In either case, you can have SQL treat your list of employeeIDs as a table. To have that support, you'd create a table type in SQL:
create type dbo.IntIdsType as table ( Id int )
Table types are just declarations of structure - they're not table's per se. You can subsequently declare variables of that type.
Once you have a table type, your function would take all the arguments and figure out what to do:
Edit 2: I provided some sketchy conditional logic earlier - this has been modified in the function below:
create function dbo.GetMyTable
(
@from datetime,
@to datetime,
@challenge int,
@employeeIds dbo.IntIdsType readonly
)
returns table as return
select
tbl.given_id, name, message, tbl.emplid, category, created_date
from
dbo.myTable tbl
where
created_date >= @dtFrom
and
created_date < @dtTo
and
(
(
@challenge = 0
and
tbl.emplid in ( select Id from @employeeIds )
)
or
(
@challenge = 1
and
given_Id in
(
select
rec.given_Id
from
dbo.Recipient rec
where
rec.given_Id = tbl.given_Id
and
rec.emplid in ( select Id from @employeeIds )
)
)
or
(
@challenge = 2
and
(
(
@challenge = 0
and
tbl.emplid in ( select Id from @employeeIds )
)
or
(
given_Id in
(
select
rec.given_Id
from
dbo.Recipient rec
where
rec.given_Id = tbl.given_Id
and
rec.emplid in ( select Id from @employeeIds )
)
)
)
)
)
Note the lack of a union - and how this lets SQL decide to use a predicate based on the value of the arguments. Also - notice how the passed in table-valued parameter is used just like it was a table.
Setting up the call is a little noisy - maybe something like:
async Task GetMyData( DateTime fromDate, DateTime toDate, int challenge, params int[ ] emloyeeIds )
{
using ( var connection = new SqlConnection( "my connection string" ) )
{
connection.Open( ); // forgot this earlier
using ( var command = connection.CreateCommand( ) )
{
//--> set up command basics...
command.CommandText = @"
select
given_id, name, message, emplid, category, created_date
from
GetMyTable( @from, @to, @challenge, @employeeIds )";
command.CommandType = System.Data.CommandType.Text;
//--> easy parameters...
command.Parameters.AddWithValue( "@from", fromDate );
command.Parameters.AddWithValue( "@to", toDate );
command.Parameters.AddWithValue( "@challenge", challenge );
//--> table-valued parameter...
var table = new DataTable( "EmployeeIds" );
table.Columns.Add( "Id", typeof( int ) );
foreach ( var Id in emloyeeIds ) table.Rows.Add( Id );
var employeeIdsParameter = new SqlParameter( "@employeeIds", SqlDbType.Structured );
employeeIdsParameter.TypeName = "dbo.IntIdsType";
employeeIdsParameter.Value = table;
command.Parameters.Add( employeeIdsParameter );
//--> do the work...
using ( var reader = await command.ExecuteReaderAsync( ) )
{
while ( await reader.ReadAsync( ) )
{
//...
}
}
}
}
}
An advantage of all this - besides the hoped-for performance gains - you avoid weird logic to build out the SQL strings and the SQL injection attack surface that approach brings.
EDIT:
In light of the comments: In the c# code, it's not returning a string to a caller - it's showing the setup of the command object, how to add parameters to it, and then how to execute it. This is probably currently being done in the procedure that is calling your string-building code now. This is a fairly typical setup for things like this - but there are lots of variations. For example, the calling code could hand you the command object, and you fill it out. Lots of different patterns get employed.
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments