全て クラス 名前空間 関数 変数 型定義 列挙型 列挙値 ページ
activeTableとrecordsetの使い方

基本編

activeTableと関連オブジェクト

フィルターを使ったread multi recordsは結果セットを持ちませんがここで説明する activeTable は 結果セット( recordset ) を持ちます。 activeTable はデータベースへのアクセスを担当します。 recordset は結果の保持とソートやグルピングなどを担当します。 この2つのオブジェクトと要求を表現するクエリーオブジェクトでSQL文並の読み取りクエリーを実行できます。

activeTable recordset query オブジェクトなどのアクセサ get setメソッドは、setが省略されています。たとえば setIndex(V)ならindex(V)となります。また同時にSQL文のようなイメージにするために、*thisを返しメソッドチェーンをできるようにしています。 setIndexとreadをつなげると以下のように記述できます。(at は activeTable )

at.index(0).read();

またクエリーで(qはquery) setWhereとsetAnd_でしたら

q.where("id",">", 0).and_("id","<=", 100);

といったように記述できます。

テーブルの読み取り

では具体的にそれぞれのオブジェクトを説明します。最初にactiveTableです。

activeTable の主なメソッドは、 read() と join() 、 outerJoin() の3つです。それぞれのメソッドに query オブジェクトを渡すことで必要なレコードを指定します。

activeTable の生成または直後(COMの場合)にデータベースオブジェクト(またはデータベースマネージャ)とテーブル名を渡します。 以下はusersテーブルを指定して生成します。

activeTable at(db, "users");

activeTable では alias() メソッドで結果セットで扱うフィールド名に別名を付けることができます。例えば、"名前"という日本語のフィールド名を "name"という別名を付けます。

at.alias("名前", "name");

複数ある時は、必要な回数 alias() を呼び出してください。いくつでも指定できます。 aliasを指定した場合は、以降クエリーなどで使うフィールド名は別名で指定するようにしてください。

それでは、テーブルからデータを読み出してみましょう。 最初に、インデックスを指定します。インデックスを指定することで、サーバーサイドでのデータの仮想的な並び順が決まります。 多くの場合、その並びのある範囲を検索対象にします。まず、範囲の先頭はそのキー値を指定します。 キー値100以降が検索対象でしたら

at.index(0).keyValue(100);

マルチセグメントキーでも大丈夫です。keyValueにセグメント順にその値を指定します。

at.index(0).keyValue(100, "A");

テーブル全体が検索対象でしたら、キー値の最も小さい値を指定します。intでマイナスを入れることが無ければ 0 を指定します。

at.index(0).keyValue(0);

でもテーブル全体を検索対象にする場合は、レコード数が増えるにつれパフォーマンスが悪くなっていきますので 実際の運用での最大レコード数を良く考えてインデックスの設計をしてください。 (これはSQLでも同じですけれど、Transactdで書くとテーブル設計のまずさがコードから解りやすくなります)

次は絞り込みです。絞り込みにはqueryオブジェクトを使います。 多くの場合、範囲の終了値を最初に指定します。

query q;
q.where("id", "<=", 200);

これで、id 100〜200のレコードが取得対象になります。 さらに、groupidが2〜5として絞り込むには

q.where("id", "<=", 200).and_("group", ">=", 2).and_("group", "<=", 5);

と書きます。簡単ですね。 では読み取ってみましょう。

recordset rs;
at.index(0).keyValue(0).read(rs, q);

rsはレコードセットで結果セットです。ほんの2 3行のコードで読み取りできます。 各フィールドの値を読み出すにはrecordsetをrow colの2次元配列のようにアクセスできます。

int row = 0;
const char* col="name";
const char* name1 = rs.[row][col].c_str();

colはインデックス番号とフィールド名のどちらでも指定できます。 列定義は rs.fieldDefs()でアクセスできます。

const fielddefs& fds = *rs.fieldDefs();
const char* fieldname1 = fds[0].name();

まとめとしてここまでの全コードと、列名と各行の値をすべてpirntする完全なコードです。

disbDbManager db;
query q;
recordset rs;
connectParams param(_T("tdap://localhost/querytest?dbfile=test.bdf"));
db->use(&param);
activeTable at(db, "users");
at.alias("名前", "name");
q.where("id", "<=", 200);
at.index(0).keyValue(100).read(rs, q);
//print all
//Hedaer
const fielddefs& fields = *rs.fieldDefs();
for (int i=0;i<(int)fields.size();++i)
std::tcout << fields[i].name() << "\t";
std::tcout << "\n";
//Each row
for (int i=0;i<(int)rs.size();++i)
{
row& m = rs[i];
for (int j=0;j<(int)m.size();++j)
{
std::cout << m[(short)j].c_str() << "\t";
if (j == (int)m.size() -1)
std::cout << "\n";
}
}

ここまでが基本です。これ以降はもう少し複雑なクエリーを説明しましょう。

結果セットに別テーブルをJoinする

先ほどの結果セットに別テーブルの関連するレコードをjoinしてみましょう。 まずは、blongsToです。userはgroupに属していてuserテーブルのgroupidフィールドがgroupsテーブルのcodeに関連づけられているとします。 また、codeはユニークキーでキー番号0とします。

activeTable atg(db, "groups");
q.reset().select("name");
atg.index(0).join(rs, q, "groupid");

最初に、groupsテーブル用の activeTable を作ります。 query オブジェクトは使いまわしで、最初に reset() を呼び出します。 フィールドはnameだけで良いので、select("name")でフィールドを絞ります。atg.index(0)でインデックスを指定しjoinに取得済のrecordset、queryとrecordset内のキーとなるフィールド名"groupid"を渡します。これでグループ名がjoinされました。

joinの場合無効なgroupidがあるとその行は無くなりますが、outerJoinを使うとnameフィールドは空のままでその行を残すことができます。 但し、その場合nameが解決できない長さゼロの文字なのか解決できた結果、長さゼロの文字であるかの判別はできません。

今度は逆に、hasManyをしてみましょう。 グループには複数のユーザーが所属しています。code=1に所属するユーザーを取得する例を示します。 まず、code=1のレコードを取得します。

atg.alias("name", "groupName");
q.reset().select("code", "groupName").where("code", "=", 1);
rs = atg.index(0).keyValue("1").read(q);

次に、code=1に属する複数のuserをを取得します。

q.reset().select("id", "name", "tel").optimize(queryBase::joinHasOneOrHasMany);
atg.index(1).join(rs, q, "code");

ポイントは2つあります。optimize(queryBase::joinHasOneOrHasMany)はこれから行うjoinが1対多のhasManyであることを明示します。 index(1)はgroupidフィールドのキーを指定します。このキーは非ユニークキーです。

最後にhasOneのJoinですが、これはblongsToとほとんど同じです。唯一異なるのは、optimizeにqueryBase::joinHasOneOrHasManyを指定してください。これを指定すると、レコードセット内の各行のJoinするキー値がすべて異なるユニークな値であるとマークされます。マークされると、サーバーに要求するキー値に重複値がないか探してまとめる処理をバイパスします。これにより、パフォーマンスが向上します。

結果セットのソート

では次に結果セットをソートして見ましょう。

rs.orderBy("tel");

これで電話番号順ですね。

rs.reverse();

これで電話番号順の降順です。

rs.orderBy("tel", "name");

これで、電話番号順で同じ番号はname順になります。ソートしたい列名を順に列挙するだけです。

列ごとに昇順、降順を指定したい場合はsortFieldsを使います。

sortFields sfs;
sfs.add("tel", true/*昇順*/).add("name", false/*降順*/);
rs.orderBy(sfs);

簡単ですね。

結果セットのグルーピング

次は、グルーピング。グルーピングでは同時に簡単な計算もできます。sum avg count min max関数が用意されています。 では、groupidでグルーピングしgroupidごとにその数をcountしてみましょう。

groupQuery gq;
client::count c("count");
gq.keyField("groupid").addFunction(&c);
rs.groupBy(gq);

c("count")は計算の結果を"count"という名前の列に格納することを意味します。

複数のフィールドを組み合わせてグルーピングする場合は

gq.keyField(field1, field2, field3);

のように指定できます。

addFunctionでは1回のグルーピングに複数の関数を追加できます。 例えば、伝票明細の合計金額の計算で区分Aの商品の金額合計とそれ以外の合計を出すには

client::sun sa("金額", "区分A合計").when("区分", "=", "A");
client::sun se("金額", "区分A以外合計").when("区分", "<>", "A");
gq.keyField("id").addFunction(&sa).addFunction(&se);
rs.groupBy(gq);

とすると、"区分A合計"列と"区分A以外合計"列が追加されてそれぞれのgruopごとの合計金額が入ります。 whenで条件を指定すると、条件にマッチしたレコードだけが計算の対象になります。

結果セットのフィルタリング

次は結果セットのフィルタリングです。結果セットをgroupid=1だけに絞ってみましょう。

recordsetQuery rq;
rq.when("groupid", "=", 1)
rs.matchBy(rq);

これで、groupid=1の行だけが残ります。

そのほかの結果セットの操作

最後はその他のrecordset操作です。 まず、コピー

recordset& rs2 = *rs.clone();

先頭10行だけをコピー

rs.top(rs2, 10);

先頭行、最終行

row& first = rs.first();
row& last = rs.last();

結合(Union)、unionは2つのrecordsetの列がすべて同じでなければなりません。異なる場合は例外がスローされます。

rs += rs2;

解放、clone()やnewで生成されたrecordsetは最後にrelease()を呼び出して解放してください。DLL内のメモリマネージャが正しく解放します。

rs2.release();

さらに詳細なトピックス

joinするテーブルで指定するindexがマルチセグメント

at.join(rs, q, "id", "name");

のように、セグメント順にレコードセットのフィールド名を指定します。また、[]で囲むと固定値で指定することもできます。 例えば、伝票ヘッダーと明細があって、明細のキーは id + row number でidは伝票ヘッダーと関連付けするid, row numberは行番号だとします。各伝票の先頭の明細行 row number = 1だけを取得したい場合は Join(rs, q, "id", "[1]")とすると各ヘッダーに明細の先頭行だけJoinできます。

合計も得たいし、明細も得たい

SQLでは、結果セットは1つですので、合計と明細を得るには2回似たようなクエリーを投げるか自前でグルーピングしなければなりません。 しかし、Transactdでは途中結果を簡単にコピーできます。

...
rs.index(0).read(q);
recordset& rs2 = *rs.clone();
groupQuery gq;
...
rs.groupBy(gq);

とすることで、rsに合計の結果セット、rs2に明細の結果セットと2つの結果を簡単に得ることができます。 とても効率良く処理できます。

recordset同士のjoin

recordset::join を使って recordset 同士をJoinすることができます。結合条件は recordsetQuery を使用します。以下の例を見てください。

// 取得済の recordset rs と rs2
recordsetQuery rq;
rq.when("group", "=","id");
rs.join(rs2, rq); // rsにrs2を結合

上記の例は、rs.group = rs2.id で結合します。
whenの最初のパラメータに結合元の列名、3番目に結合先の列名を指定します。 条件は = だけでなくすべて使用できます。また複数の条件の指定も可能です。

Transactd SDK 2018年07月31日(火) 19時40分24秒 doxygen