如何将关联连接从 SQL 重写为 LINQ
Posted
技术标签:
【中文标题】如何将关联连接从 SQL 重写为 LINQ【英文标题】:How to rewrite correlated join from SQL to LINQ 【发布时间】:2021-09-12 22:45:59 【问题描述】:我正在尝试将以下 SQL 查询重写为 LINQ:
SELECT `i`.`symbol`, `i`.`id`, `t0`.`close`, `t`.`close`, `t`.`close` - `t0`.`close`, (`t`.`close` - `t0`.`close`) / `t0`.`close`
FROM `investment` AS `i`
LEFT JOIN `investment_record` AS `t0` ON `t0`.id = (
SELECT `i0`.id
FROM `investment_record` AS `i0`
WHERE (`i0`.`date` <= @dateFrom) AND i.id = i0.investment_id
ORDER BY `i0`.`date` DESC
LIMIT 1
)
LEFT JOIN `investment_record` AS `t` ON `t`.id =(
SELECT `i0`.id
FROM `investment_record` AS `i0`
WHERE (`i0`.`date` <= @dateTo) AND i.id = i0.investment_id
ORDER BY `i0`.`date` DESC
LIMIT 1
)
WHERE `i`.`id` IN (@id0, @id1, ....)
我的主要问题是 JOIN 的 AND i.id = i0.investment_id
和 LIMIT 1
部分。
目前我能做到的最好的是:
from inv in _context.Investment
join recTo in _context.InvestmentRecord on inv.Id equals recTo.InvestmentId into recToColl
from recToNullable in recToColl.Where(x => x.Date <= dateTo).OrderByDescending(x => x.Date).Take(1).DefaultIfEmpty()
join recFrom in _context.InvestmentRecord on inv.Id equals recFrom.InvestmentId into recFromColl
from recFromNullable in recFromColl.Where(x => x.Date <= dateFrom).OrderByDescending(x => x.Date).Take(1).DefaultIfEmpty()
where investmentIds.Contains(inv.Id)
let amountFrom = recFromNullable.Close
let amountTo = recToNullable.Close
select new InvestmentPerformance(
inv.Symbol,
inv.Id,
amountFrom,
amountTo,
amountTo - amountFrom,
(amountTo - amountFrom) / amountFrom
);
但问题是它不起作用。
它给出了表达式无法翻译的异常:
System.InvalidOperationException:LINQ 表达式 '数据库集() .GroupJoin( 内部:DbSet(), outerKeySelector: inv => inv.Id, innerKeySelector: recTo => recTo.InvestmentId, resultSelector: (inv, recToColl) => new inv = inv, recToColl = recToColl )' 无法翻译。要么以可翻译的形式重写查询,要么显式切换到客户端评估 通过插入对“A sEnumerable”、“AsAsyncEnumerable”、“ToList”的调用, 或“ToListAsync”。见https://go.microsoft.com/fwlink/?linkid=2101038 了解更多信息。
这个丑陋的 SQL(和 LINQ)的要点是计算给定时间间隔的投资性能。用户可以指定从到日期。问题是有时用户可以指定没有任何记录的日期(例如银行假期)。所以对于给定的日期,我想使用最接近的先前记录(这就是 <= @dateFrom
条件和 ORDER BY date DESC LIMIT 1
部分 SQL 的原因。
我用不同形式的连接尝试了许多 LINQ 变体,但没有一个能满足我的需要 :(
我正在使用 EF.Core 5 和 mysql 数据库。
【问题讨论】:
为什么不直接查找最接近的有效日期作为主查询的前兆?顺便说一句,它基本上是select x order by x desc limit 1
select max
如果您已经在 SQL 中使用了查询,我强烈建议您将该查询保存为存储过程或视图并在 EF Core 中使用。每当我的查询逻辑变得复杂时,我都会使用存储过程
另外,我不熟悉您的架构或您正在尝试做什么,但是您正在执行子查询和连接限制 1 的事实让我想知道如果你应该考虑做一个 OUTER APPLY
我明白你的意思,我在工作中经常使用 MS SQL,我也会使用类似的东西,但我认为 MySQL 不支持 OUTER APPLY :(
这绝对应该是@987654330@,或者LEFT JOIN
和ROW_NUMBER
,反正当前代码是错误的/效率低下
【参考方案1】:
原始 SQL 查询对我来说似乎很复杂。我已经使用OUTER APPLY
重写了它,而不是子连接查询。
SELECT `i`.`symbol`, `i`.`id`, `t0`.`close`, `t`.`close`, `t`.`close` - `t0`.`close`, (`t`.`close` - `t0`.`close`) / `t0`.`close`
FROM `investment` AS `i`
OUTER APPLY (
SELECT `i0`.id
FROM `investment_record` AS `i0`
WHERE (`i0`.`date` <= @dateFrom) AND i.id = i0.investment_id
ORDER BY `i0`.`date` DESC
LIMIT 1
) AS t0
OUTER APPLY (
SELECT `i0`.id
FROM `investment_record` AS `i0`
WHERE (`i0`.`date` <= @dateTo) AND i.id = i0.investment_id
ORDER BY `i0`.`date` DESC
LIMIT 1
) AS t
WHERE `i`.`id` IN (@id0, @id1, ....)
然后我会用 EF 的方式来翻译这个,写成OUTER APPLY
。这个SO post 可能会有所帮助。
看起来像这样:
from inv in _context.Investments
from rec1 in _context.InvestmentsRecords.Where(ir => ir.InvestmentId = inv.InvestmentId).Where(ir => ir.Date <= DateFrom).OrderByDescending().Take(1)
from rec1 in _context.InvestmentsRecords.Where(ir => ir.InvestmentId = inv.InvestmentId).Where(ir => ir.Date <= DateTo).OrderByDescending().Take(1)
...
【讨论】:
我正在使用 MySQL,但我认为它不支持 OUTER APPLY。 我的错,然后lateral?以上是关于如何将关联连接从 SQL 重写为 LINQ的主要内容,如果未能解决你的问题,请参考以下文章
将 Linq 中的 NHibernate 应用程序重写为 SQL
将涉及多个表的左外连接从 Informix 重写为 Oracle
使用 UNPIVOT 将代码从 sql 重写为 redshift