- 优化 Limit + Sum/Avg/Max/Min 为嵌套查询;

This commit is contained in:
2881099 2022-05-25 01:14:27 +08:00
parent 3b961681dc
commit 27cb11e4a7
5 changed files with 199 additions and 149 deletions

View File

@ -9,7 +9,7 @@
</PropertyGroup>
<PropertyGroup>
<Version>3.2.651</Version>
<Version>3.2.660-preview22020525</Version>
</PropertyGroup>
<ItemGroup>

View File

@ -1001,10 +1001,15 @@ limit 0,10", t1);
var subquery = select.ToSql(a => new
{
all = a,
count = (long)select.As("b").Sum(b => b.Id)
count = (long)select.As("b").Sum(b => b.Id),
sum2 = (long)select.As("b").Limit(10).Sum(b => b.Id)
});
Assert.Equal(@"SELECT a.""Id"" as1, a.""Clicks"" as2, a.""TypeGuid"" as3, a.""Title"" as4, a.""CreateTime"" as5, ifnull((SELECT sum(b.""Id"")
FROM ""tb_topic22"" b), 0) as6
FROM ""tb_topic22"" b), 0) as6, ifnull((SELECT sum(ftba.""Id"") FROM (
SELECT b.""Id""
FROM ""tb_topic22"" b
limit 0,10
) ftba), 0) as7
FROM ""tb_topic22"" a", subquery);
var subqueryList = select.ToList(a => new
{

View File

@ -1362,9 +1362,15 @@ namespace FreeSql.Internal
var exp3Args0 = (exp3.Arguments.FirstOrDefault() as UnaryExpression)?.Operand as LambdaExpression;
if (exp3Args0.Parameters.Count == 1 && exp3Args0.Parameters[0].Type.FullName.StartsWith("FreeSql.Internal.Model.HzyTuple`"))
exp3Args0 = new ReplaceHzyTupleToMultiParam().Modify(exp3Args0, fsqltables);
var sqlSum = fsqlType.GetMethod("ToSql", new Type[] { typeof(string) })?.Invoke(fsql, new object[] { $"{exp3.Method.Name.ToLower()}({ExpressionLambdaToSql(exp3Args0, tscClone1)})" })?.ToString();
var sqlSumField = $"{exp3.Method.Name.ToLower()}({ExpressionLambdaToSql(exp3Args0, tscClone1)})";
var sqlSum = tscClone1.subSelect001._limit <= 0 && tscClone1.subSelect001._skip <= 0 ?
fsqlType.GetMethod("ToSql", new Type[] { typeof(string) })?.Invoke(fsql, new object[] { $"{exp3.Method.Name.ToLower()}({ExpressionLambdaToSql(exp3Args0, tscClone1)})" })?.ToString() :
tscClone1.subSelect001.GetNestSelectSql(exp3Args0, sqlSumField, tosqlField =>
fsqlType.GetMethod("ToSql", new Type[] { typeof(string) })?.Invoke(fsql, new object[] { tosqlField })?.ToString());
if (string.IsNullOrEmpty(sqlSum) == false)
return _common.IsNull($"({sqlSum.Replace(" \r\n", " \r\n ")})", 0);
return tscClone1.subSelect001._limit <= 0 && tscClone1.subSelect001._skip <= 0 ?
_common.IsNull($"({sqlSum.Replace(" \r\n", " \r\n ")})", 0) :
_common.IsNull($"({sqlSum})", 0);
break;
case "ToList":
case "ToOne":

View File

@ -243,6 +243,113 @@ namespace FreeSql.Internal.CommonProvider
}
return newExp;
}
public ReadAnonymousTypeAfInfo GetExpressionField(Expression newexp, FieldAliasOptions fieldAlias = FieldAliasOptions.AsIndex)
{
var map = new ReadAnonymousTypeInfo();
var field = new StringBuilder();
var index = fieldAlias == FieldAliasOptions.AsProperty ? CommonExpression.ReadAnonymousFieldAsCsName : 0;
_commonExpression.ReadAnonymousField(_tables, field, map, ref index, newexp, this, null, _whereGlobalFilter, null, null, true);
return new ReadAnonymousTypeAfInfo(map, field.Length > 0 ? field.Remove(0, 2).ToString() : null);
}
public string GetNestSelectSql(Expression select, string affield, Func<string, string> ToSql)
{
var allMemExps = new FindAllMemberExpressionVisitor(this);
allMemExps.Visit(select);
var fieldAlias = new Dictionary<string, bool>();
var fieldReplaced = new Dictionary<string, bool>();
var field = new StringBuilder();
foreach (var memExp in allMemExps.Result)
{
var gef = GetExpressionField(memExp.Item1, FieldAliasOptions.AsProperty);
var geffield = gef.field;
if (fieldReplaced.ContainsKey(geffield)) continue;
fieldReplaced.Add(geffield, true);
field.Append(", ").Append(gef.field);
if (fieldAlias.ContainsKey(memExp.Item2.Attribute.Name))
{
field.Append(_commonUtils.FieldAsAlias($"aas{fieldAlias.Count}"));
affield = affield.Replace(geffield, $"ftba.aas{fieldAlias.Count}");
}
else
{
fieldAlias.Add(memExp.Item2.Attribute.Name, true);
affield = affield.Replace(geffield, $"ftba.{string.Join(".", geffield.Split('.').Skip(1))}");
}
}
var sql = ToSql(field.Remove(0, 2).ToString());
sql = $"{_select} {affield} FROM ( \r\n {sql.Replace("\r\n", "\r\n ")}\r\n) ftba";
field.Clear();
return sql;
}
public class FindAllMemberExpressionVisitor : ExpressionVisitor
{
public List<NativeTuple<MemberExpression, ColumnInfo>> Result { get; set; } = new List<NativeTuple<MemberExpression, ColumnInfo>>();
Select0Provider _select;
public FindAllMemberExpressionVisitor(Select0Provider select) => _select = select;
protected override Expression VisitMember(MemberExpression node)
{
var exps = new Stack<Expression>();
Expression exp = node;
while (exp != null)
{
switch (exp.NodeType)
{
case ExpressionType.Parameter:
exps.Push(exp);
exp = null;
continue;
case ExpressionType.MemberAccess:
exps.Push(exp);
exp = (exp as MemberExpression)?.Expression;
continue;
}
return base.VisitMember(node);
}
if (exps.Any() == false) return base.VisitMember(node);
var firstExp = exps.Pop() as ParameterExpression;
if (firstExp == null) return base.VisitMember(node);
var tb = _select._tables.Find(a => a.Parameter == firstExp)?.Table;
if (tb == null) return base.VisitMember(node);
while (exps.Any())
{
var memExp = exps.Pop() as MemberExpression;
if (tb.ColumnsByCs.TryGetValue(memExp.Member.Name, out var trycol) && exps.Any() == false)
{
Result.Add(NativeTuple.Create(node, trycol));
return node;
}
if (tb.Properties.ContainsKey(memExp.Member.Name))
{
tb = _select._commonUtils.GetTableByEntity(memExp.Type);
if (tb == null) return base.VisitMember(node);
}
}
return base.VisitMember(node);
}
}
public class ReplaceMemberExpressionVisitor : ExpressionVisitor
{
Expression _findExp;
Expression _replaceExp;
public Expression Replace(Expression exp, Expression find, Expression replace) // object repval)
{
_findExp = find;
_replaceExp = replace;
//_replaceExp = Expression.Constant(repval, find.Type);
return this.Visit(exp);
}
protected override Expression VisitMember(MemberExpression node)
{
if (_findExp == node) return _replaceExp;
return base.VisitMember(node);
}
}
}
public abstract partial class Select0Provider<TSelect, T1> : Select0Provider, ISelect0<TSelect, T1> where TSelect : class

View File

@ -64,11 +64,12 @@ namespace FreeSql.Internal.CommonProvider
return ret;
}
public List<TTuple> ToList<TTuple>(string field)
public List<TTuple> ToList<TTuple>(string field) => ToListQfPrivate<TTuple>(this.ToSql(field), field);
public List<TTuple> ToListQfPrivate<TTuple>(string sql, string field)
{
var ret = new List<TTuple>();
if (_cancel?.Invoke() == true) return ret;
var sql = this.ToSql(field);
if (string.IsNullOrEmpty(sql)) return ret;
var dbParms = _params.ToArray();
var type = typeof(TTuple);
var before = new Aop.CurdBeforeEventArgs(_tables[0].Table.Type, _tables[0].Table, Aop.CurdType.Select, sql, dbParms);
@ -394,15 +395,6 @@ namespace FreeSql.Internal.CommonProvider
return ToListMrPrivate<TReturn>(sql, af, otherData);
}
protected List<TReturn> ToListMapReader<TReturn>(ReadAnonymousTypeAfInfo af) => ToListMapReaderPrivate<TReturn>(af, null);
protected ReadAnonymousTypeAfInfo GetExpressionField(Expression newexp, FieldAliasOptions fieldAlias = FieldAliasOptions.AsIndex)
{
var map = new ReadAnonymousTypeInfo();
var field = new StringBuilder();
var index = fieldAlias == FieldAliasOptions.AsProperty ? CommonExpression.ReadAnonymousFieldAsCsName : 0;
_commonExpression.ReadAnonymousField(_tables, field, map, ref index, newexp, this, null, _whereGlobalFilter, null, null, true);
return new ReadAnonymousTypeAfInfo(map, field.Length > 0 ? field.Remove(0, 2).ToString() : null);
}
static ConcurrentDictionary<string, GetAllFieldExpressionTreeInfo> _dicGetAllFieldExpressionTree = new ConcurrentDictionary<string, GetAllFieldExpressionTreeInfo>();
public class GetAllFieldExpressionTreeInfo
{
@ -771,12 +763,41 @@ namespace FreeSql.Internal.CommonProvider
protected double InternalAvg(Expression exp)
{
var list = this.ToList<double>($"avg({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}");
var field = $"avg({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}";
if (_limit <= 0 && _skip <= 0)
{
var list = this.ToList<double>(field);
return list.Sum() / list.Count;
}
protected TMember InternalMax<TMember>(Expression exp) => this.ToList<TMember>($"max({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}").Max();
protected TMember InternalMin<TMember>(Expression exp) => this.ToList<TMember>($"min({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}").Min();
protected decimal InternalSum(Expression exp) => this.ToList<decimal>($"sum({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}").Sum();
var sql = GetNestSelectSql(exp, field, ToSql);
var list2 = ToListQfPrivate<double>(sql, field);
return list2.Sum() / list2.Count;
}
protected TMember InternalMax<TMember>(Expression exp)
{
var field = $"max({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}";
if (_limit <= 0 && _skip <= 0) return this.ToList<TMember>(field).Max();
var sql = GetNestSelectSql(exp, field, ToSql);
return ToListQfPrivate<TMember>(sql, field).Max();
}
protected TMember InternalMin<TMember>(Expression exp)
{
var field = $"min({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}";
if (_limit <= 0 && _skip <= 0) return this.ToList<TMember>(field).Min();
var sql = GetNestSelectSql(exp, field, ToSql);
return ToListQfPrivate<TMember>(sql, field).Min();
}
protected decimal InternalSum(Expression exp)
{
var field = $"sum({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}";
if (_limit <= 0 && _skip <= 0) return this.ToList<decimal>(field).Sum();
var sql = GetNestSelectSql(exp, field, ToSql);
return ToListQfPrivate<decimal>(sql, field).Sum();
}
public ISelectGrouping<TKey, TValue> InternalGroupBy<TKey, TValue>(Expression columns)
{
@ -829,71 +850,6 @@ namespace FreeSql.Internal.CommonProvider
return this.OrderBy($"{_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, column, true, null)} DESC");
}
class FindAllMemberExpressionVisitor : ExpressionVisitor
{
public List<NativeTuple<MemberExpression, ColumnInfo>> Result { get; set; } = new List<NativeTuple<MemberExpression, ColumnInfo>>();
Select0Provider _select;
public FindAllMemberExpressionVisitor(Select0Provider select) => _select = select;
protected override Expression VisitMember(MemberExpression node)
{
var exps = new Stack<Expression>();
Expression exp = node;
while (exp != null)
{
switch (exp.NodeType)
{
case ExpressionType.Parameter:
exps.Push(exp);
exp = null;
continue;
case ExpressionType.MemberAccess:
exps.Push(exp);
exp = (exp as MemberExpression)?.Expression;
continue;
}
return base.VisitMember(node);
}
if (exps.Any() == false) return base.VisitMember(node);
var firstExp = exps.Pop() as ParameterExpression;
if (firstExp == null) return base.VisitMember(node);
var tb = _select._tables.Find(a => a.Parameter == firstExp)?.Table;
if (tb == null) return base.VisitMember(node);
while (exps.Any())
{
var memExp = exps.Pop() as MemberExpression;
if (tb.ColumnsByCs.TryGetValue(memExp.Member.Name, out var trycol) && exps.Any() == false)
{
Result.Add(NativeTuple.Create(node, trycol));
return node;
}
if (tb.Properties.ContainsKey(memExp.Member.Name))
{
tb = _select._commonUtils.GetTableByEntity(memExp.Type);
if (tb == null) return base.VisitMember(node);
}
}
return base.VisitMember(node);
}
}
class ReplaceMemberExpressionVisitor : ExpressionVisitor
{
Expression _findExp;
Expression _replaceExp;
public Expression Replace(Expression exp, Expression find, Expression replace) // object repval)
{
_findExp = find;
_replaceExp = replace;
//_replaceExp = Expression.Constant(repval, find.Type);
return this.Visit(exp);
}
protected override Expression VisitMember(MemberExpression node)
{
if (_findExp == node) return _replaceExp;
return base.VisitMember(node);
}
}
public List<TReturn> InternalToList<TReturn>(Expression select)
{
var map = new ReadAnonymousTypeInfo();
@ -1087,34 +1043,7 @@ namespace FreeSql.Internal.CommonProvider
var af = new ReadAnonymousTypeAfInfo(map, field.Length > 0 ? field.Remove(0, 2).ToString() : null);
if (GetTableRuleUnions().Count <= 1) return this.ToListMapReader<TReturn>(af).FirstOrDefault();
var affield = af.field;
var allMemExps = new FindAllMemberExpressionVisitor(this);
allMemExps.Visit(select);
field.Clear();
var fieldAlias = new Dictionary<string, bool>();
var fieldReplaced = new Dictionary<string, bool>();
foreach (var memExp in allMemExps.Result)
{
var gef = GetExpressionField(memExp.Item1, FieldAliasOptions.AsProperty);
var geffield = gef.field;
if (fieldReplaced.ContainsKey(geffield)) continue;
fieldReplaced.Add(geffield, true);
field.Append(", ").Append(gef.field);
if (fieldAlias.ContainsKey(memExp.Item2.Attribute.Name))
{
field.Append(_commonUtils.FieldAsAlias($"aas{fieldAlias.Count}"));
affield = affield.Replace(geffield, $"ftba.aas{fieldAlias.Count}");
}
else
{
fieldAlias.Add(memExp.Item2.Attribute.Name, true);
affield = affield.Replace(geffield, $"ftba.{string.Join(".", geffield.Split('.').Skip(1))}");
}
}
var sql = this.ToSql(field.Remove(0, 2).ToString());
sql = $"{_select} {affield} FROM ( \r\n {sql.Replace("\r\n", "\r\n ")}\r\n) ftba";
var sql = GetNestSelectSql(select, af.field, ToSql);
return ToListMrPrivate<TReturn>(sql, af, null).FirstOrDefault();
}
finally
@ -1173,11 +1102,12 @@ namespace FreeSql.Internal.CommonProvider
return ret;
}
async public Task<List<TTuple>> ToListAsync<TTuple>(string field, CancellationToken cancellationToken)
public Task<List<TTuple>> ToListAsync<TTuple>(string field, CancellationToken cancellationToken) => ToListQfPrivateAsync<TTuple>(this.ToSql(field), field, cancellationToken);
async public Task<List<TTuple>> ToListQfPrivateAsync<TTuple>(string sql, string field, CancellationToken cancellationToken)
{
var ret = new List<TTuple>();
if (_cancel?.Invoke() == true) return ret;
var sql = this.ToSql(field);
if (string.IsNullOrEmpty(sql)) return ret;
var dbParms = _params.ToArray();
var type = typeof(TTuple);
var before = new Aop.CurdBeforeEventArgs(_tables[0].Table.Type, _tables[0].Table, Aop.CurdType.Select, sql, dbParms);
@ -1380,12 +1310,41 @@ namespace FreeSql.Internal.CommonProvider
async protected Task<double> InternalAvgAsync(Expression exp, CancellationToken cancellationToken)
{
var list = await this.ToListAsync<double>($"avg({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}", cancellationToken);
var field = $"avg({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}";
if (_limit <= 0 && _skip <= 0)
{
var list = await this.ToListAsync<double>(field, cancellationToken);
return list.Sum() / list.Count;
}
async protected Task<TMember> InternalMaxAsync<TMember>(Expression exp, CancellationToken cancellationToken) => (await this.ToListAsync<TMember>($"max({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}", cancellationToken)).Max();
async protected Task<TMember> InternalMinAsync<TMember>(Expression exp, CancellationToken cancellationToken) => (await this.ToListAsync<TMember>($"min({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}", cancellationToken)).Min();
async protected Task<decimal> InternalSumAsync(Expression exp, CancellationToken cancellationToken) => (await this.ToListAsync<decimal>($"sum({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}", cancellationToken)).Sum();
var sql = GetNestSelectSql(exp, field, ToSql);
var list2 = await ToListQfPrivateAsync<double>(sql, field, cancellationToken);
return list2.Sum() / list2.Count;
}
async protected Task<TMember> InternalMaxAsync<TMember>(Expression exp, CancellationToken cancellationToken)
{
var field = $"max({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}";
if (_limit <= 0 && _skip <= 0) return (await this.ToListAsync<TMember>(field, cancellationToken)).Max();
var sql = GetNestSelectSql(exp, field, ToSql);
return (await ToListQfPrivateAsync<TMember>(sql, field, cancellationToken)).Max();
}
async protected Task<TMember> InternalMinAsync<TMember>(Expression exp, CancellationToken cancellationToken)
{
var field = $"min({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}";
if (_limit <= 0 && _skip <= 0) return (await this.ToListAsync<TMember>(field, cancellationToken)).Min();
var sql = GetNestSelectSql(exp, field, ToSql);
return (await ToListQfPrivateAsync<TMember>(sql, field, cancellationToken)).Min();
}
async protected Task<decimal> InternalSumAsync(Expression exp, CancellationToken cancellationToken)
{
var field = $"sum({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}";
if (_limit <= 0 && _skip <= 0) return (await this.ToListAsync<decimal>(field, cancellationToken)).Sum();
var sql = GetNestSelectSql(exp, field, ToSql);
return (await ToListQfPrivateAsync<decimal>(sql, field, cancellationToken)).Sum();
}
static ConcurrentDictionary<Type, MethodInfo[]> _dicGetMethodsByName = new ConcurrentDictionary<Type, MethodInfo[]>();
async protected Task<List<TReturn>> InternalToListAsync<TReturn>(Expression select, CancellationToken cancellationToken)
@ -1577,34 +1536,7 @@ namespace FreeSql.Internal.CommonProvider
var af = new ReadAnonymousTypeAfInfo(map, field.Length > 0 ? field.Remove(0, 2).ToString() : null);
if (GetTableRuleUnions().Count <= 1) return (await this.ToListMapReaderAsync<TReturn>(af, cancellationToken)).FirstOrDefault();
var affield = af.field;
var allMemExps = new FindAllMemberExpressionVisitor(this);
allMemExps.Visit(select);
field.Clear();
var fieldAlias = new Dictionary<string, bool>();
var fieldReplaced = new Dictionary<string, bool>();
foreach (var memExp in allMemExps.Result)
{
var gef = GetExpressionField(memExp.Item1, FieldAliasOptions.AsProperty);
var geffield = gef.field;
if (fieldReplaced.ContainsKey(geffield)) continue;
fieldReplaced.Add(geffield, true);
field.Append(", ").Append(gef.field);
if (fieldAlias.ContainsKey(memExp.Item2.Attribute.Name))
{
field.Append(_commonUtils.FieldAsAlias($"aas{fieldAlias.Count}"));
affield = affield.Replace(geffield, $"ftba.aas{fieldAlias.Count}");
}
else
{
fieldAlias.Add(memExp.Item2.Attribute.Name, true);
affield = affield.Replace(geffield, $"ftba.{string.Join(".", geffield.Split('.').Skip(1))}");
}
}
var sql = this.ToSql(field.Remove(0, 2).ToString());
sql = $"{_select} {affield} FROM ( \r\n {sql.Replace("\r\n", "\r\n ")}\r\n) ftba";
var sql = GetNestSelectSql(select, af.field, ToSql);
return (await ToListMrPrivateAsync<TReturn>(sql, af, null, cancellationToken)).FirstOrDefault();
}
finally