2 parameterized where clauses in a single query (not 'where _ and _') - c#

Can parameterizing 2 different where clauses (non contiguous) in a single query be done in dapper?
Sample SQL query as C# property
private string GetSalesRepToCustomer => #"
WITH SALES_REP_FILTERED AS (
SELECT
SALES_REP_ID
FROM
SALES_REP
/**where**/ /* 1st where clause. */
)
SELECT
A.SALES_REP_ID,
B.CUSTOMER_ID
FROM
SALES_REP_FILTERED A LEFT JOIN
CUSTOMER B ON A.SALES_REP_ID = B.SALES_REP_ID
/**where**/ /* 2nd where clause */
";
C# parameterization not working, illustration purposes.
SqlTemplate queryTemplate = new SqlBuilder()
.where("SALES_REP_ID = :SALES_REP_ID")
// this 2nd 'where' is illustrative.
// In actual an 'and' clause is created.
// My goal is to have a secondary/non contiguous 'where' clause in the same query.
.where("CUSTOMER_ID IN :CUSTOMER_ID")
.AddTemplate(GetSalesRepToCustomer);
var conn = new OracleConnection();
var queryResults = conn.Query(
queryTemplate.RawSql,
new { SALES_REP_ID = 2021, CUSTOMER_ID = new int[] {11, 22, 33}}
);
Context and reasoning of what I want to achieve:
Read online (Cant recall where. Probably on stackoverflow) the incentive(s) for dapper query parameterization and would like to stick/follow to this recommended practice.
I could split the query up into 2 and make separate round trips to the DB. But there are performance and IO payoffs/benefits when executed as a single query.
My last option is to in line the where clauses in SQL. But this probably would negate the benefits of dapper parameterization? I.e.
private string GetSalesRepToCustomer => #"
WITH SALES_REP_FILTERED AS (
SELECT
SALES_REP_ID
FROM
SALES_REP
where SALES_REP_ID = :SALES_REP_ID
)
SELECT
A.SALES_REP_ID,
B.CUSTOMER_ID
FROM
SALES_REP_FILTERED A LEFT JOIN
CUSTOMER B ON A.SALES_REP_ID = B.SALES_REP_ID
WHERE CUSTOMER_ID IN :CUSTOMER_ID
";
And the where parameterization is taken out.
SqlTemplate queryTemplate = new SqlBuilder()
// No parameterized where clauses. So also no dapper performance improvements?
.AddTemplate(GetSalesRepToCustomer);
var conn = new OracleConnection();
var queryResults = conn.Query(
queryTemplate.RawSql,
new { SALES_REP_ID = 2021, CUSTOMER_ID = new int[] {11, 22, 33}}
);

As I understand it, this should be fine:
var conn = new OracleConnection();
var sql = #"
WITH SALES_REP_FILTERED AS (
SELECT
SALES_REP_ID
FROM
SALES_REP
where SALES_REP_ID = :SALES_REP_ID
)
SELECT
A.SALES_REP_ID,
B.CUSTOMER_ID
FROM
SALES_REP_FILTERED A LEFT JOIN
CUSTOMER B ON A.SALES_REP_ID = B.SALES_REP_ID
WHERE CUSTOMER_ID IN :CUSTOMER_ID
";
var queryResults = conn.Query<SOME_TYPE>(
sql,
new { SALES_REP_ID = 2021, CUSTOMER_ID = new int[] {11, 22, 33}}
);
But your SQL could also be written as:
SELECT
A.SALES_REP_ID,
B.CUSTOMER_ID
FROM
SALES_REP SR
LEFT JOIN CUSTOMER C ON SR.SALES_REP_ID = C.SALES_REP_ID
WHERE
C.CUSTOMER_ID IN :CUSTOMER_ID AND
SR.SALES_REP_ID = :SALES_REP_ID
If you pass 3 customer IDs, then dapper will rewrite your WHERE clause to be:
WHERE C.CUSTOMER_ID IN (:CUSTOMER_ID1, :CUSTOMER_ID2, :CUSTOMER_ID3) AND
and it will populate the command's parameters collection with the three parameters populated with the values supplied

Related

How to compare result of two subqueries in where clause SQLKata

I try to compare results of two subqueries in where clause in SQLKata.
In SQL it should be like this:
WHERE (SELECT count(id) FROM main.someTable) = (SELECT count(id) FROM main.anotherTable)
In SQLKata I can compare result of subquery with scalar value:
var mainSubquery = new Query("main.someTable")
.SelectRaw("count(id)");
var anotherSubquery = new Query("main.anotherTable")
.SelectRaw("count(id)");
query
.WhereSub(mainSubquery, "=", 0)
But I can't compare results of two subqueries this way:
query
.WhereSub(mainSubquery, "=", anotherSubquery),
How can I fix it? Maybe I should execute both of the subqueries and only then compare their results?
No overload accepts queries on both the left and right sides together.
But you can always compile the queries and use the WhereRaw. One gotcha is to be aware of the bindings orders.
Take a look on this example:
using SqlKata;
using SqlKata.Compilers;
var sub1 = new Query("A").SelectRaw("count(1)");
var sub2 = new Query("B").SelectRaw("count(1)");
var compiler = new SqlServerCompiler();
var c1 = compiler.Compile(sub1);
var c2 = compiler.Compile(sub2);
var bindings = c1.Bindings;
bindings.AddRange(c2.Bindings); // order (c1, c2) is important here
var query = new Query("Table").WhereRaw($"({c1.Sql}) = ({c2.Sql})", bindings);
var result = compiler.Compile(query);
Console.WriteLine(result.ToString());
This will output the following
SELECT * FROM [Table] WHERE (SELECT count(1) FROM [A]) = (SELECT count(1) FROM [B])

SqlKata NotIn Command

I am using SqlKata purely to build SQL queries in C#. The query I need contains the NOT IN command but I don't know how to write it in C#.
This is my SQL query:
SELECT [User].UID as UserUID, FirstName, LastName FROM [USER]
WHERE [User].[AccountUID] = #p2
AND UID NOT IN (SELECT [User]
FROM [User]
OUTER APPLY [User].[UserProducts].nodes('/ArrayOfUserProduct/UserProduct') AS XmlData(UserProductXMLData)
LEFT OUTER JOIN [UserFormProducts] ON [UserFormProduct].[UserUID] = [User].[UID]
WHERE [User].[DeleteDate] IS NULL
AND (([UserFormProduct].[Product] = 0 AND [UserFormProduct].[FormUID] = #p1) OR [UserFormProduct].[UserUID] IS NULL)
AND [User].[AccountUID] = #p2
I am trying to get the above query, I am here now
var countQuery = new Query("User")
.Join("User.[UserProduts].nodes('/ArrayOfUserProducts/UserProducts') as XmlData(UserProductXMLData)", j => j, "OUTER APPLY")
.LeftJoin("UserFormProducts", "UserFormProducts.UserUID", "User.UID")
.WhereNull("User.DeleteDate")
.Where(x => x.Where("UserFormProducts.Product", 0).OrWhereNull("UserFormProducts.UserUID"))
.Where("UserFormProducts.FormUID", formUID)
.Where("User.AccountUID", accountUID)
Does anyone know how can I write the part "UID NOT IN" I wrote in SQL?
Use WhereNotIn(). Documentation.
Pass an IEnumerable to apply the SQL WHERE IN condition.
new Query("Posts").WhereNotIn("AuthorId", new [] {1, 2, 3, 4, 5});
SELECT * FROM [Posts] WHERE [AuthorId] NOT IN (1, 2, 3, 4, 5)
You can pass a Query instance to filter against a sub query
var blocked = new Query("Authors").Where("Status", "blocked").Select("Id");
new Query("Posts").WhereNotIn("AuthorId", blocked);
SELECT * FROM [Posts] WHERE [AuthorId] NOT IN (SELECT [Id] FROM [Authors] WHERE [Status] = 'blocked')
Note: The sub query should return one column

SQL Unions with table counts using EntityFramework LINQ query

I am trying replicate the SQL below using LINQ and Entity Framework and cannot figure out how this should be written.
My simplistic LINQ version does a query per table
public IActionResult Index()
{
dynamic view = new ExpandoObject();
view.AppUsers = Context.AppUsers.Count();
view.CustomerShops = Context.CustomerShops.Count();
view.FavouriteOrders = Context.FavouriteOrders.Count();
view.Items = Context.Items.Count();
view.ItemVariations = Context.ItemVariations.Count();
view.MenuCategories = Context.MenuCategories.Count();
view.MenuCategoryProducts = Context.MenuCategoryProducts.Count();
view.Orders = Context.Orders.Count();
view.Products = Context.Products.Count();
view.ProductVariations = Context.ProductVariations.Count();
view.Shops = Context.Shops.Count();
view.Staffs = Context.Staffs.Count();
return View(view);
}
I use this pattern from time to time to for reporting on my column counts and thought this should be easy to do in LINQ, but no luck so far.
This pure SQL UNION would only generate 1 SQL request, instead of a request per table.
select * from (
select 'asp_net_roles' as type, count(*) from asp_net_roles
union
select 'asp_net_user_roles' as type, count(*) from asp_net_user_roles
union
select 'asp_net_users' as type, count(*) from asp_net_users
union
select 'app_users' as type, count(*) from app_users
union
select 'shops' as type, count(*) from shops
union
select 'staffs' as type, count(*) from shops
union
select 'items' as type, count(*) from items
union
select 'item_variations' as type, count(*) from item_variations
union
select 'products' as type, count(*) from products
union
select 'product_variations' as type, count(*) from product_variations
union
select 'menu_categories' as type, count(*) from menu_categories
) as counters
order by 1;
I saw a partial implementation [linq-group-by-multiple-tables] (https://stackoverflow.com/a/3435503/473923) but this is based of grouping data.
FYI: I'm new to C#/Linq, so sorry if this seams obvious.
Use the this code from my answer
And fill ExpandoObject with result:
var tablesinfo = Context.GetTablesInfo();
var expando = new ExpandoObject();
if (tablesinfo != null)
{
var dic = (IDictionary<string, object>)expando;
foreach(var info in tablesinfo)
{
dic.Add(info.TableName, info.RecordCount);
}
}
Idea is that you can UNION counts if you group entities by constant.
Schematically function builds the following IQueryable Expression:
var tablesinfo =
Context.AppUsers.GroupBy(x => 1).Select(g => new TableInfo{ TableName = "asp_net_roles", RecordCount = g.Count() })
.Concat(Context.MenuCategories.GroupBy(x => 1).Select(g => new TableInfo{ TableName = "menu_categories", RecordCount = g.Count() }))
.Concat(Context.Items.GroupBy(x => 1).Select(g => new TableInfo{ TableName = "items", RecordCount = g.Count() }))
....
There is nothing wrong with your LINQ query. It's very acceptable approach. However it's not the most efficient.
There is no need to fetch count from individual tables one by one. You can get the counts from all the tables at once using the System tables Sys.Objects and Sys.Partitions. Just try running this query in your database.
SELECT A.Name AS TableName, SUM(B.rows) AS RecordCount
FROM sys.objects A INNER JOIN sys.partitions B
ON A.object_id = B.object_id
WHERE A.type = 'U' AND B.index_id IN (0, 1)
GROUP BY A.Name
For quick response and cleaner code, you can store this SQL query in a string variable, and run the LINQ
var result = dataContext.ExecuteQuery<YOUR_MODEL_CLASS>
(your_string_query);
I would put something like this:
Dictionary<string, int> view = new() {
new() {'asp_net_roles', Context.AppUsers.Count() },
...
}
return View(view);
maybe not the most pure way, but does the job (unless I misunderstood what you try to accomplish)

Inefficient LINQ - Know What SQL Should Be - Can't get there

I know the SQL I want to produce:
SELECT qdt, pbn, cid, pid, SUM(Amount) AS TotalAmount
FROM SomeDb.SomeTable
WHERE Status = 'Open'
GROUP BY cid, pid, qdt, pbn
ORDER BY qdt
I have LINQ, that I was hoping would produce something as clean as the above SQL:
var query = (
from someTable in SomeTable
where someTable.Status == "Open"
group someTable by new { someTable.cid, someTable.pid } into grouped
select new
{
lid = grouped.FirstOrDefault().lid,
qdt = grouped.FirstOrDefault().qdt,
pbn = grouped.FirstOrDefault().pbn,
cid = grouped.FirstOrDefault().cid,
cn = grouped.FirstOrDefault().cn,
pid = grouped.FirstOrDefault().pid,
amount = grouped.Sum(o => o.Amount),
Status = grouped.FirstOrDefault().Status
});
But that produces many lines of nasty SQL. Each grouped value ends up getting its own SELECT statement within the query, like this, for qdt:
SELECT [t5].[qdt]
FROM (
SELECT TOP (1) [t4].[qdt]
FROM [SomeDb].[SomeTable] AS [t4]
WHERE ([t1].[cid] = [t4].[cid]) AND ([t1].[pid] = [t4].[pid]) AND ([t4].[Status] = #p0)
) AS [t5]
) AS [qdt]
Is there a way to change the LINQ to produce the simpler SQL?
There are two issues I see with your LINQ attempt. For starters, you are not grouping by the same fields in the SQL query as in your LINQ. Then, you are using FirstOrDefault on the results of the group instead of selecting the group's key data.
Your query should look like this:
var query =
(
from someTable in SomeTable
where someTable.Status == "Open"
group someTable by new
{
someTable.lid,
someTable.qdt,
someTable.pbn,
someTable.cid,
someTable.cn,
someTable.pid,
someTable.Status,
} into grouped
select new
{
lid = grouped.Key.lid,
qdt = grouped.Key.qdt,
pbn = grouped.Key.pbn,
cid = grouped.Key.cid,
cn = grouped.Key.cn,
pid = grouped.Key.pid,
amount = grouped.Sum(o => o.Amount),
Status = grouped.Key.Status
}
);
I cannot test right now whether it will generate the exact same SQL though.
You don't seem to have all the grouped by columns in the desired sql in your linq;
var query = (
from someTable in SomeTable
where someTable.Status == "Open"
group someTable by new { someTable.cid, someTable.pid, someTable.qdt,someTable.pbn, someTable.Status } into grouped
select new
{
qdt = grouped.FirstOrDefault().qdt,
pbn = grouped.FirstOrDefault().pbn,
cid = grouped.FirstOrDefault().cid,
pid = grouped.FirstOrDefault().pid,
Status = grouped.FirstOrDefault().Status,
amount = grouped.Sum(o => o.Amount)
});
I can't really test it but that looks a little more like the sql you are trying to replicate in linq.

Convert sql code to linq (Inner join Query)

Could somebody help me please to convert this sql code to linq .
SQL query
select distinct coursecode
from UnitSet_Unit
where UnitCode in ('FDFFSACA' ,'FDFFSCFSAA', 'FDFOPTHCP3A ')
and CourseCode in (Select distinct coursecode
from Trainee_course
where TraineeID =10000088 )
Where UnitCode in IN clause come dynamic and in the form of array .
and the course code in the second part is also have variable count
Off the top of my head, assuming we have the following inputs (and you are working in C#):
var unitCodes = new List<string> { "FDFFSACA" ,"FDFFSCFSAA", "FDFOPTHCP3A" };
var traineeID = 10000088;
This should work:
var result = (from us in db.UnitSet_Unit
where unitCodes.Contains(us.UnitCode)
&& us.CourseCode == (from tc in db.Trainee_course
where tc.TraineeID == traineeID
select tc.CourseCode).Distinct().SingleOrDefault()
select us.CourseCode).Distinct();

Categories

Resources