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 中心の方法で実行することを要求しているのかはわかりません。(Oracle はすべてにおいてそれを要求しているようです。)
明らかではない場合に備えて、後続の各ページに、次に新しく作成された無効になっていない 10 個のレコードを含めることを望みます。SubSonic 2.2 でこれを行うにはどうすればよいですか?
解決
クエリが現在の SQL と同じ 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 を使用している環境にいますが、全く同じ問題に遭遇しました。SubSonic2.2を使用しています。
GitHub からソースをダウンロードしたところ、次のものが見つかりました。
@ranomore によって引用されたコード (OracleDataProvider.GetSelectSql()) は、SubSonic.Query オブジェクトを使用する場合にのみ呼び出されます。OP と私自身は、より新しく強力な SubSonic.SqlQuery オブジェクトから派生した Select オブジェクトを使用しているため、OracleDataProvider.GetSelectSql() が呼び出されることはありません。その代わり、 OracleGenerator.BuildPaggedSelectStatement() が呼び出され、OP によって投稿された SQL が生成されます。このコードには、最終的に生成されるページネーション クエリに WHERE 句と ORDER BY 句が追加されないため、バグがあります。
BuildPaggedSelectStatement() の内容を ANSISqlGenerator.BuildSelectStatement() に基づくものに置き換えました。
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/).