エントリー
YiiとNode.jsのファンであるすべてのKhabrovitesにこんにちは。 PHPフレームワークとサーバーサイドJavaScriptの愛好家が組み合わされているのはなぜですか?
Yiiが(Node.jsとブラウザの両方で)JavaScriptで利用可能になったためです!
この記事では、Yii2のAPIを完全に保持し、Node.jsで実行されるクエリビルダーについて説明します。
クエリデザイナーは、Jiiの実装された部分の1つにすぎません(Yiiと混同しないでください)。この記事では、フレームワークを部分的にも使用できるため、フレームワーク全体を特に検討しません。
Jiiとは
JiiはコンポーネントベースのJavaScript MVCフレームワークであり、伝説的なPHPフレームワークYii 2のアーキテクチャを繰り返し、ほとんどの場合、そのAPIを保持します。 したがって、名前Jiiの起源-JavaScript Yii。
設置
Jiiとそのパーツは、npm managerパッケージとして配布されます。 1つのコマンドでインストールできます。
npm install jii jii-model jii-ar-sql
インストール後、名前空間とJiiクラスが使用可能になります。これは、すべてのJiiクラスにアクセスするための唯一のエントリポイントです。
jiiパッケージはコアjiiパッケージで、名前空間とクラスJiiを宣言して返すだけです。 Jiiの他のすべての部分(jii -ar-sqlを含む)もパッケージとして提供されますが、基本的な名前空間のみを満たします。
ネタバレ
お気づきかもしれませんが、文字arには文字arが含まれています。 はい、これはYii2 APIを使用したアクティブレコードです。既に作成されており、多数のテストでカバーされています。 次の記事で説明します。 それまでは、クエリビルダのみを検討します。
以下で説明するすべての例では、インストール済みのMySQL、Node.jsなどが存在することを前提としています。
var Jii = require('jii-ar-sql'); var db = new Jii.sql.mysql.Connection({ host: '127.0.0.1', database: 'example', username: 'root', password: '', charset: 'utf8' }); db.open().then(function() { (new Jii.sql.Query()) .from('user') .where({last_name: 'Smith'}) .count('*', db) .then(function(count) { console.log('Records count:', count); db.close(); }); });
データベースアクセスオブジェクト
これらのオブジェクトは、データベースにクエリを送信し、特定の形式で応答を受信できるインターフェイスを実装します。 これらは、クエリデザイナーとActive Recordによって使用されます。
各データアクセスオブジェクトは、各データベースに独自のドライバーを備えたDBMSにアクセスします。 それらはすべて、DBMSを変更できる単一のAPIを実装しています。
現時点では、 MySQLへのアクセスのオブジェクトが実装されています 。これは、
npm mysql 。 他のDBMSのサポートは将来提供され、計画されます。
データベース接続の作成
データベースにアクセスするには、 Jii.sql.Connection接続のインスタンスを作成する必要があります。 次に、接続を開いてデータベーススキーマをダウンロードし、永続的な接続を確立する必要があります。
var db = new Jii.sql.mysql.Connection({ host: '127.0.0.1', database: 'example', username: 'root', password: '', charset: 'utf8' }); db.open().then(function() { // ... });
Jiiアプリケーションを作成している場合、アプリケーションコンポーネントは `Jii.app.db`からアクセスできるため、この接続をアプリケーション構成に登録する方が便利です。
module.exports = { // ... components: { // ... db: { className: 'Jii.sql.mysql.Connection', host: '127.0.0.1', database: 'example', username: 'root', password: '', charset: 'utf8', } }, // ... };
SQLクエリの実行
データベース接続のインスタンスがある場合、以下を実行してSQLクエリを実行できます。
- プレーンSQLを使用してJii.sql.Commandのインスタンスを作成します。
- 必要に応じて、リクエストにパラメーターを追加します。
- Jii.sql.Commandメソッドの1つを呼び出します。
データベースからのデータサンプリングの例をいくつか見てみましょう。
var db = new Jii.sql.mysql.Connection(...); db.open().then(function() { // , , // - , - // . . db.createCommand('SELECT * FROM post') .queryAll() .then(function(posts) { }); // , ( ) // `null` db.createCommand('SELECT * FROM post WHERE id=1') .queryOne() .then(function(post) { }); // , ( ) // db.createCommand('SELECT title FROM post') .queryColumn() .then(function(titles) { }); // . `null` db.createCommand('SELECT COUNT(*) FROM post') .queryScalar() .then(function(count) { }); });
オプションを追加する
パラメーターを使用してコマンドを作成するときは、SQLインジェクション攻撃を防ぐために、常に「bindValue」または「bindValues」メソッドの呼び出しを通じてパラメーターを追加する必要があります。 例:
db.createCommand('SELECT * FROM post WHERE id=:id AND status=:status') .bindValue(':id', request.id) .bindValue(':status', 1) .queryOne() .then(function(post) { });
データサンプリングではなくクエリを実行する
データ変更リクエストは、 `execute()`メソッドを使用して完了する必要があります。
db.createCommand('UPDATE post SET status=1 WHERE id=1') .execute();
Jii.sql.Command.execute()メソッドは、クエリの結果を含む情報があるオブジェクトを返します。 各アクセスオブジェクトは独自の特定のパラメーターを追加できますが、その中のパラメーターの最小セットは次のとおりです。
- `affectedRows`-影響を受ける(変更される)行の数
- 「insertId」は一意に生成された識別子です。 列にAUTO_INCREMENTを持つPK列がある場合、INSERTクエリに対して返されます。
INSERT、UPDATE、およびDELETEクエリの場合、通常のSQLクエリを記述する代わりに、次を呼び出すことができます。
Jii.sql.Command.insert() 、 Jii.sql.Command.update() 、 Jii.sql.Command.delete()作成用のメソッド
関連するSQL。 これらのメソッドは、テーブル、列、およびパラメーター値の名前を正しく保護します。
// INSERT (table name, column values) db.createCommand().insert('user', { name: 'Sam', age: 30 }).execute().then(function(result) { // result.affectedRows // result.insertId }); // UPDATE (table name, column values, condition) db.createCommand().update('user', {status: 1}, 'age > 30').execute(); // DELETE (table name, condition) db.createCommand().delete('user', 'status = 0').execute();
Jii.sql.Command.batchInsert()を呼び出して、1つのクエリに複数の行を挿入することもできます。
パフォーマンスの観点から効果的:
// table name, column names, column values db.createCommand().batchInsert('user', ['name', 'age'], { ['Tom', 30], ['Jane', 20], ['Linda', 25], }).execute();
データベーススキーマの変更
Jii DAOは、データベーススキーマを変更するための一連のメソッドを提供します。
- Jii.sql.Command.createTable() :テーブルの作成
- Jii.sql.Command.renameTable() :テーブルの名前変更
- Jii.sql.Command.dropTable() :テーブルの削除
- Jii.sql.Command.truncateTable() :テーブルからすべての行を削除する
- Jii.sql.Command.addColumn() :列を追加する
- Jii.sql.Command.renameColumn() :列の名前を変更する
- Jii.sql.Command.dropColumn() :列を削除します
- Jii.sql.Command.alterColumn() :列の変更
- Jii.sql.Command.addPrimaryKey() :主キーの追加
- Jii.sql.Command.dropPrimaryKey() :主キーの削除
- Jii.sql.Command.addForeignKey() :外部キーを追加する
- Jii.sql.Command.dropForeignKey() :外部キーを削除する
- Jii.sql.Command.createIndex() :インデックスの作成
- Jii.sql.Command.dropIndex() :インデックスの削除
これらの方法の使用例:
// CREATE TABLE db.createCommand().createTable('post', { id: 'pk', title: 'string', text: 'text' });
Jii.sql.Connection.getTableSchema()メソッドを使用してテーブル情報を取得することもできます
table = db.getTableSchema('post');
このメソッドは、テーブル列、主キー、外部キーなどに関する情報を含むJii.sql.TableSchemaオブジェクトを返します。 このデータはすべて、データベースでの作業を簡素化するために、主にクエリデザイナとアクティブレコードで使用されます。
クエリコンストラクタ
クエリデザイナは、JavaScriptでSQLクエリを作成できるデータベースアクセスオブジェクトを使用します。 クエリデザイナにより、SQLコードの可読性が向上し、データベースでより安全なクエリを生成できます。
クエリデザイナの使用は、2つの段階に分けられます。
- Jii.sql.Queryクラスのインスタンスを作成して、SQL式のさまざまな部分を表します(たとえば、「SELECT」、「FROM」)。
- Jii.sql.Query インスタンスでメソッド(たとえば、 `all()`)を呼び出して、データベースにクエリを実行し、非同期でデータを取得します。
次のコードは、クエリデザイナーを使用する最も簡単な方法を示しています。
(new Jii.sql.Query()) .select(['id', 'email']) .from('user') .where({last_name: 'Smith'}) .limit(10) .all() .then(function(rows) { // ... });
上記のコードは、 `:last_name`パラメーターが` `Smith ''の値に関連付けられている次のSQLコードを生成して実行します。
SELECT `id`, `email` FROM `user` WHERE `last_name` = :last_name LIMIT 10
クエリを作成する
クエリを作成するには、 Jii.sql.Queryオブジェクトのさまざまなメソッドを呼び出して、SQLコマンドのさまざまな部分を埋める必要があります。 メソッド名は、SQLステートメントの名前に似ています。 たとえば、「FROM」を指定するには、「」から「」を呼び出す必要があります。 すべてのメソッドは要求オブジェクト自体を返すため、複数の呼び出しを組み合わせることができます。
次に、各クエリデザイナーメソッドの使用について説明します。
Jii.sql.Query.select()
Jii.sql.Query.select()メソッドメソッドは、 `SELECT` SQLクエリの一部を決定します。 次の列を指定できます
が選択されます。
query.select(['id', 'email']); // : query.select('id, email');
列名には、テーブル名や列エイリアスが含まれる場合があります。
例えば
query.select(['user.id AS user_id', 'email']); // : query.select('user.id AS user_id, email');
キーが列エイリアスであるオブジェクトを渡すことができます。
たとえば、上記のコードは次のように書き換えることができます。
query.select({user_id: 'user.id', email: 'email'});
デフォルトでは( Jii.sql.Query.select()メソッドを呼び出さない場合でも)、アスタリスク「*」がリクエストで生成されます
すべての列を選択します。
列名に加えて、SQL式も指定できます。 例:
query.select(["CONCAT(first_name, ' ', last_name) AS full_name", 'email']);
サブクエリもサポートされています。このため、選択する要素の1つとしてJii.sql.Queryオブジェクトを渡す必要があります。
var subQuery = (new Jii.sql.Query()).select('COUNT(*)').from('user'); // SELECT `id`, (SELECT COUNT(*) FROM `user`) AS `count` FROM `post` var query = (new Jii.sql.Query()).select({id: 'id', count: subQuery}).from('post');
単語「DISTINCT」をSQLクエリに追加するには、 Jii.sql.Query.distinct()メソッドを呼び出す必要があります:
// SELECT DISTINCT `user_id` ... query.select('user_id').distinct();
Jii.sql.Query.addSelect()メソッドを呼び出して、列を追加することもできます。
query.select(['id', 'username']) .addSelect(['email']);
Jii.sql.Query.from()
Jii.sql.Query.from()メソッドは、SQLクエリから `FROM`フラグメントを埋めます。 例:
// SELECT * FROM `user` query.from('user');
テーブル名には、プレフィックスやテーブルエイリアスが含まれる場合があります。 例:
query.from(['public.user u', 'public.post p']); // : query.from('public.user u, public.post p');
オブジェクトを転送するとき、オブジェクトのキーはテーブルのエイリアスになります。
query.from({u: 'public.user', p: 'public.post'});
さらに、テーブル名にはサブクエリ-Jii.sql.Queryオブジェクトを含めることができます。
var subQuery = (new Jii.sql.Query()).select('id').from('user').where('status=1'); // SELECT * FROM (SELECT `id` FROM `user` WHERE status=1) u query.from({u: subQuery});
Jii.sql.Query.where()
Jii.sql.Query.where()メソッドは、SQLステートメントの `WHERE`セクションにデータを取り込みます。 SQL式の条件を指定するには、いくつかの形式を使用できます。
- 文字列、 `` status = 1``
- オブジェクト、 `{ステータス:1、タイプ:2}`
- 演算子のステートメント、 `['like'、 'name'、 'test']`
文字列形式
文字列形式は、単純な条件を示すのに非常に適しています。 指定された文字列は、SQL式に直接書き込まれます。
query.where('status=1'); // query.where('status=:status', {':status': status});
Jii.sql.Query.params()またはJii.sql.Query.addParams()メソッドを使用して、クエリにパラメーターを追加できます。
query.where('status=:status') .addParams({':status': status});
オブジェクトとしての条件(ハッシュ)
オブジェクトはいくつかの `(AND`)サブ条件を示すのに最適です。各サブ条件には
単純な平等。 オブジェクトのキーは列であり、値は条件に渡される対応する値です。
例:
// ...WHERE (`status` = 10) AND (`type` IS NULL) AND (`id` IN (4, 8, 15)) query.where({ status: 10, type: null, id: [4, 8, 15] });
クエリデザイナは、配列をNULL値として正しく処理するのに十分スマートです。
ハッシュ形式でサブクエリを使用することもできます。
var userQuery = (new Jii.sql.Query()).select('id').from('user'); // ...WHERE `id` IN (SELECT `id` FROM `user`) query.where({id: userQuery});
演算子の形式
この形式により、プログラム形式で任意の条件を設定できます。 一般的な形式は次のとおりです。
[operator, operand1, operand2, ...]
オペランドは、文字列、オブジェクト、または演算子の形式で指定できます。 演算子は1つにすることができます
次のいずれか:
- 「and」:オペランドは「AND」を使用して結合する必要があります。 たとえば、「[」と「ID = 1」、「ID = 2」]」は「ID = 1 AND ID = 2」を生成します。 オペランドが配列の場合、ここで説明する規則を使用して文字列に変換されます。 たとえば、 `['and'、 'type = 1'、['or'、 'id = 1'、 'id = 2']] 'will generate` type = 1 AND(id = 1 OR id = 2)`。
メソッドはエスケープを実行しません。 - `or`:` AND`演算子に似ていますが、オペランドは `OR`を使用して結合されます。
- `between`:オペランド1は列の名前であり、オペランド2と3は列の値が存在する範囲の開始値と終了値です。
たとえば、 `['between'、 'ID'、1、10]`は式 `id BETWEEN 1 AND 10`を生成します。 - `not between`:like` between`ですが、生成された式では` BETWEEN`は `NOT BETWEEN`に置き換えられます。
- `IN`:オペランド1は列またはSQL式でなければなりません。 2番目のオペランドは、配列または `Jii.sql.Query`オブジェクトのいずれかです。 たとえば、 `['in'、 'id'、[1、2、3]]`は、 `id IN(1、2、3)`を生成します。
このメソッドは列名をエスケープし、範囲内の値を処理します。
「IN」演算子は複合列もサポートします。 この場合、オペランド1は列の配列である必要があり、オペランド2は配列の配列または列の範囲を表す `Jii.sql.Query`オブジェクトである必要があります。 - 「NOT IN」:生成された式で「IN」が「NOT IN」に置き換えられることを除いて、「IN」演算子に似ています。
- 「いいね」:最初のオペランドは列またはSQL式である必要があり、オペランド2は検索する値を表す文字列または配列である必要があります。 たとえば、 `['like'、 'name'、 'tester']`は、 `name LIKE '%tester%' 'を生成します。
配列が指定されている場合、複数の「LIKE」が生成され、「AND」演算子によって結合されます。 たとえば、 `['like'、 'name'、['test'、 'sample']] 'name LIKE'%test% 'AND name LIKE'%sample% ''を生成します。 - 「またはlike」:「like」に似ていますが、配列が第2オペランドとして渡されるときにOR演算子を使用して連結します。
- 「not like」:「like」演算子と似ていますが、生成される式で「LIKE」が「NOT LIKE」に置き換えられる点が異なります。
- 「or not like」:「not like」と似ていますが、配列が第2オペランドとして渡されるときにOR演算子を使用して連結します。
- `exists`:1つのオペランドが必要です。これはJii.sql.Queryのインスタンスでなければなりません。 式を生成します
「EXISTS(サブクエリ)」。 - 「存在しない」:演算子「存在する」と同様に、「NOT EXISTS(サブクエリ)」という式を生成します。
- `>`、 `<=`、またはその他のデータベース演算子。 最初のオペランドは列の名前でなければならず、2番目は値でなければなりません。 たとえば、 `['>'、 'age'、10]`は `age> 10`を生成します。
条件を追加する
Jii.sql.Query.andWhere()またはJii.sql.Query.orWhere()メソッドを使用して、条件を追加できます。
既存のリクエスト。 たとえば、これらのメソッドを複数回呼び出すことができます。
var status = 10; var search = 'jii'; query.where({status: status}); if (search) { query.andWhere(['like', 'title', search]); }
`search`が空でない場合、上記のコードは次のSQLクエリを生成します:
... WHERE (`status` = 10) AND (`title` LIKE '%jii%')
フィルター条件
ユーザーデータに基づいてWHERE句を構築する場合、通常は空を無視する必要があります
値。 たとえば、名前とメールで検索できる検索フォームでは、次のものが必要です。
ユーザーが何も入力していない場合、フィールドを無視します。 これはメソッドを使用して行うことができます
Jii.sql.Query.filterWhere() :
// username email query.filterWhere({ username: username, email: email, });
Jii.sql.Query.filterWhere()とJii.sql.Query.where()の最初の違いは無視されます
空の値。
値が「null」、「false」、空の配列、空の文字列、またはスペースのみで構成される文字列の場合、値は空と見なされます。
Jii.sql.Query.andWhere()およびJii.sql.Query.orWhere()メソッドと同様に、使用できます
Jii.sql.Query.andFilterWhere()およびJii.sql.Query.orFilterWhere()を使用して、条件を追加します。
Jii.sql.Query.orderBy()
Jii.sql.Query.orderBy()メソッドは、SQLクエリに「ORDER BY」部分を追加します。 例:
// ... ORDER BY `id` ASC, `name` DESC query.orderBy({ id: 'asc', name: 'desc', });
上記のコードでは、オブジェクトキーは列名であり、値は並べ替え方向に対応しています。
ソート条件を追加するには、 Jii.sql.Query.addOrderBy()メソッドを使用します。
例:
query.orderBy('id ASC') .addOrderBy('name DESC');
Jii.sql.Query.groupBy()
Jii.sql.Query.orderBy()メソッドは、SQLクエリに「GROUP BY」部分を追加します。 例えば
// ... GROUP BY `id`, `status` query.groupBy(['id', 'status']);
`GROUP BY`に単純な列名のみが含まれる場合、通常のSQLを書いているかのように、文字列を使用して指定できます。 例:
query.groupBy('id, status');
Jii.sql.Query.addGroupBy()メソッドを使用して、追加の列を「GROUP BY」部分に追加できます。
例:
query.groupBy(['id', 'status']) .addGroupBy('age');
Jii.sql.Query.having()
Jii.sql.Query.having()メソッドは、SQLステートメントの「HAVING」部分を定義します。 このメソッドは、 Jii.sql.Query.where()メソッドと同じように機能します。 例えば
// ... HAVING `status` = 1 query.having({status: 1});
Jii.sql.Query.andHaving()またはJii.sql.Query.orHaving()メソッドを使用して、追加の条件を追加します。
例:
// ... HAVING (`status` = 1) AND (`age` > 30) query.having({status: 1}) .andHaving(['>', 'age', 30]);
Jii.sql.Query.limit()およびJii.sql.Query.offset()
Jii.sql.Query.limit()およびJii.sql.Query.offset()メソッドは、SQLステートメントの `LIMIT`および` OFFSET`部分を埋めます。 例:
// ... LIMIT 10 OFFSET 20 query.limit(10).offset(20);
間違った「limit」値と「offset」値を渡すと、それらは無視されます。
Jii.sql.Query.join()
Jii.sql.Query.join()メソッドは、SQLステートメントの `JOIN`部分にデータを取り込みます。 例:
// ... LEFT JOIN `post` ON `post`.`user_id` = `user`.`id` query.join('LEFT JOIN', 'post', 'post.user_id = user.id');
メソッドには4つのパラメーターがあります。
- `type`:タイプ、たとえば、` `INNER JOIN'`、` `LEFT JOIN'`。
- `table`:結合されたテーブルの名前。
- `on`:(オプション)条件、` ON` SQL式の一部。 構文はJii.sql.Query.where()メソッドに似ています。
- `params`:(オプション)、条件パラメーター(` ON`パーツ)。
次のメソッドを使用して、それぞれ「INNER JOIN」、「LEFT JOIN」、「RIGHT JOIN」を指定できます。
- Jii.sql.Query.innerJoin()
- Jii.sql.Query.leftJoin()
- Jii.sql.Query.rightJoin()
例えば
query.leftJoin('post', 'post.user_id = user.id');
複数の列を結合するには、 `join`メソッドを数回呼び出す必要があります。
さらに、サブクエリを添付できます。 この場合、キーが結合要求のエイリアスになるオブジェクトを渡す必要があります。 例:
var subQuery = (new Jii.sql.Query()).from('post'); query.leftJoin({u: subQuery}, 'u.id = author_id');
Jii.sql.Query.union()
Jii.sql.Query.union()メソッドは、SQLクエリの「UNION」部分にデータを取り込みます。 例えば
var query1 = (new Jii.sql.Query()) .select('id, category_id AS type, name') .from('post') .limit(10); var query2 = (new Jii.sql.Query()) .select('id, type, name') .from('user') .limit(10); query1.union(query2);
このメソッドを複数回呼び出して、複数の「UNION」フラグメントを追加できます。
リクエスト方法
Jii.sql.Queryクラスは、さまざまなクエリ結果のための一連のメソッドを提供します。
- Jii.sql.Query.all() :キーが列名であるオブジェクトの配列を返します。
- Jii.sql.Query.one() :クエリの最初の結果-見つかった文字列に対応するオブジェクトを返します。
- Jii.sql.Query.column() :クエリ結果の最初の列の値に対応する配列を返します。
- Jii.sql.Query.scalar() :結果の最初のセルにあるスカラー値を返します。
- Jii.sql.Query.exists() :クエリに結果が含まれているかどうかを示すブール値を返します。
- Jii.sql.Query.count() :見つかった行の数を返します。
- Jii.sql.Query.sum(q) 、 Jii.sql.Query.average(q)を含む他のクエリ集約メソッド
Jii.sql.Query.max(q) 、 Jii.sql.Query.min(q) 。 これらのメソッドには「q」パラメーターが必要です。
列名またはSQL式のいずれかです。
これらのメソッドはすべて、非同期応答を処理するために `Promise`のインスタンスを返します。
例:
// SELECT `id`, `email` FROM `user` (new Jii.sql.Query()) .select(['id', 'email']) .from('user') .all().then(function(rows) { // ... }); // SELECT * FROM `user` WHERE `username` LIKE `%test%` (new Jii.sql.Query()) .from('user') .where(['like', 'username', 'test']) .one().then(function(row) { // ... });
これらのメソッドはすべて、 Jii.sql.Connectionを表すオプションのパラメーター `db`を受け入れます。 このパラメーターが
設定すると、データベースへの接続に「db」アプリケーションコンポーネントが使用されます。 以下は、 `count()`メソッドを使用する別の例です。
// executes SQL: SELECT COUNT(*) FROM `user` WHERE `last_name`=:last_name (new Jii.sql.Query()) .from('user') .where({last_name: 'Smith'}) .count() .then(function(count) { // ... })
クエリ結果のインデックス
Jii.sql.Query.all()を呼び出すと、連続する整数でインデックス付けされた文字列の配列を返します。 ただし、たとえば、 Jii.sql.Query.all()メソッドの前に呼び出されるJii.sql.Query.indexBy()メソッドを使用して、特定の列または式の値にインデックスを付けることができます。 この場合、オブジェクトが返されます。
例:
// returns {100: {id: 100, username: '...', ...}, 101: {...}, 103: {...}, ...} var query = (new Jii.sql.Query()) .from('user') .limit(10) .indexBy('id') .all();
複雑なインデックスを指定するには、匿名関数をJii.sql.Query.indexBy()メソッドに渡すことができます。
var query = (new Jii.sql.Query()) .from('user') .indexBy(function (row) { return row.id + row.username; }).all();
無名関数は、現在の行のデータを含む `row`パラメータを受け入れ、現在の行のインデックス値(オブジェクトキー)として使用される文字列または数値を返す必要があります。
最後に
Jiiはオープンソースプロジェクトですので、誰かがJiiの開発に参加してくれたらとても嬉しいです。 affka@affka.ruに書き込みます。
Jiiはすでに多くのことを実装しています。次の記事でActive Recordについて説明する予定です。