如何在 SubSonic 2.2 中执行 Oracle top-n(分页)查询?
-
26-09-2019 - |
题
(免责声明: 出于安全原因,我在此处更改/混淆了一些变量/表/列名称。如果看起来有些不对劲,请原谅我。)
我正在构建 Oracle 10g 数据库的前端,并且正在尝试获取分页数据。除了寻呼之外,以下 SubSonic 2.2 代码按照我想要的顺序提供了我想要的内容:
var q = new Select()
.From(AppDb.MyTable.Schema)
.Where(AppDb.MyTable.DisabledDateColumn).IsNull()
.OrderDesc(AppDb.MyTable.CreatedDateColumn.ColumnName)
System.Console.Out.Writeline(q.BuildStatement());
这会产生以下 SQL:
SELECT
MYSCHEMA.MYTABLE.ID,
MYSCHEMA.MYTABLE.DISABLED_DATE
FROM
MYSCHEMA.MYTABLE
WHERE
MYSCHEMA.MYTABLE.DISABLED_DATE IS NULL
ORDER BY
CREATED_DATE DESC
然后我尝试引入分页:
var q = new Select()
.From(AppDb.MyTable.Schema)
.Where(AppDb.MyTable.DisabledDateColumn).IsNull()
.OrderDesc(AppDb.MyTable.CreatedDateColumn.ColumnName)
.Paged(0, 10);
它消除了我的 WHERE 和 ORDER BY 子句:
SELECT * FROM (
SELECT
MYSCHEMA.MYTABLE.ID,
MYSCHEMA.MYTABLE.DISABLED_DATE,
ROWNUM as row_number
FROM
MYSCHEMA.MYTABLE
)
WHERE
row_number BETWEEN 1 AND 10
我是 SubSonic 的新手,所以我不知道这是否是一个错误,或者我只是没有以首选方式执行此操作,或者 Oracle 是否要求以不同的、更以 Oracle 为中心的方式完成此操作。(甲骨文似乎要求一切都如此。)
我想要的是(以防不明显)每个后续页面都包含 10 个下一个最近创建的非禁用记录。我怎样才能在 SubSonic 2.2 中做到这一点?
解决方案
看起来您的查询没有生成与当前相同的 SQL 适用于 SubSonic 的 Oracle 数据提供程序 应该。下面是相关代码(从第 852 行开始):
if(qry.PageIndex < 0)
query = String.Format("{0} {1} FROM {2}.{3} {4} {5}",select ,columns, table.SchemaName, table.Name, where, order);
else
{
int start = qry.PageIndex * qry.PageSize;
int end = (qry.PageIndex + 1) * qry.PageSize;
const string cteFormat =
"WITH pagedtable AS (SELECT {0}, ROW_NUMBER () OVER ({1}) AS rowindex FROM {2}.{3} {4}) SELECT {5}, rowindex FROM pagedtable WHERE rowindex >= {6} AND rowindex < {7} ORDER BY rowindex";
query = string.Format(cteFormat, columns, order,table.SchemaName, table.Name, where, columns.Replace(table.Name + ".", String.Empty), start, end);
}
return query;
也许更新当前源可以解决这个问题。如果实际提供程序出现问题,您可以将其与 SqlDataProvider 进行比较,以获取可能存在问题的提示。
其他提示
我在使用.NET 2.0和Oracle 9i R2的环境和我遇到同样的确切的问题。我使用亚音速2.2。
我下载从GitHub源,这是我的发现:
通过@ranomore引用的代码(OracleDataProvider.GetSelectSql())仅使用SubSonic.Query对象时调用。由于OP和我使用的是从较新的,更强大的SubSonic.SqlQuery对象得出的选择对象,OracleDataProvider.GetSelectSql()不会被调用。相反, OracleGenerator.BuildPagedSelectStatement()被调用,并生成你看到张贴的OP的SQL。此代码是越野车,因为它从来没有添加WHERE和ORDER BY子句将其最终产生的分页查询。
我基于ANSISqlGenerator.BuildSelectStatement()的东西替换BuildPagedSelectStatement()的内容
public override string BuildPagedSelectStatement()
{
int startnum = query.PageSize * query.CurrentPage + 1;
int endnum = query.PageSize * query.CurrentPage + query.PageSize;
string orderBy = String.Empty;
if (this.query.OrderBys.Count > 0)
orderBy = GenerateOrderBy();
//The ROW_NUMBER() function in Oracle requires an ORDER BY clause.
//In case one is not specified, we need to halt and inform the caller.
if(orderBy.Equals(String.Empty))
throw new ArgumentException("There is no column specified for the ORDER BY clause", "OrderBys");
System.Text.StringBuilder sql = new System.Text.StringBuilder();
//Build the command string
sql.Append("WITH pagedtable AS (");
sql.Append(GenerateCommandLine());
//Since this class is for Oracle-specific SQL, we can add a hint
//which should help pagination queries return rows more quickly.
//AFAIK, this is only valid for Oracle 9i or newer.
sql.Replace("SELECT", "SELECT /*+ first_rows('" + query.PageSize + "') */");
sql.Append(", ROW_NUMBER () OVER (");
sql.Append(orderBy);
sql.Append(") AS rowindex ");
sql.Append(Environment.NewLine);
sql.Append(GenerateFromList());
sql.Append(GenerateJoins());
sql.Append(GenerateWhere());
if (query.Aggregates.Count > 0)
{
sql.Append(GenerateGroupBy());
sql.Append(Environment.NewLine);
sql.Append(GenerateHaving());
}
sql.Append(") SELECT * FROM pagedtable WHERE rowindex >= ");
sql.Append(startnum);
sql.Append(" AND rowindex < ");
sql.Append(endnum);
sql.Append(" ORDER BY rowindex");
return sql.ToString();
}
以上为我工作。希望这可以帮助别人!
如果内部 SQL 由第一个语句(包括 where 和 order by)组成,Oracle 就可以使用 Top-N 查询。
所以,我想说 Oracle 没有特定的理由来忽略它们。
从未使用过亚音速,不知道是否需要在那里做不同的事情。
从性能角度来看,DISABLED_DATE、CREATED_DATE 上的索引应该可以解决问题(请参阅: http://blog.fatalmind.com/2010/07/30/analytic-top-n-queries/).