メディアアートのためのバックエンド構築入門(Chap.04 リレーション編)

Chap.04 リレーション編

前回はデータの取得、追加、更新、削除について書きました。 今回は、より複雑なデータの扱いについて書きます。

今回も先にサンプルコードを実行した動画をあげておきます。

リレーションとは?

直訳すると関係という意味になります。

第二回の記事に書いたように、 SQLiteはRDBMSというデータベース管理システムです。 なので、IDなどの値を使ってテーブル間を紐付けることができます。

例えば「学生のテーブル」と「大学のテーブル」を紐付けて 「所属する大学」を表現しようとすると以下のようになります。

studentsテーブル

id name age university_id
1 太郎 22 1
2 花子 23 2
3 次郎 20 1

universitiesテーブル

id name address
1 X大学 A県a市1-1-1
2 Y大学 B府a市1-1-1
3 Z大学 C都a区1-1-1
-- studentsテーブルとデータを作成する
create table students(id integer primary key autoincrement, name text, age integer, university_id integer);
insert into students(name, age, university_id) values('太郎', 22, 1);
insert into students(name, age, university_id) values('花子', 23, 2);
insert into students(name, age, university_id) values('次郎', 20, 1);
-- universitiesテーブルとデータを作成する
create table universities(id integer primary key autoincrement, name text, address text);
insert into universities(name, address) values('X大学', 'A県a市1-1-1');
insert into universities(name, address) values('Y大学', 'B府a市1-1-1');
insert into universities(name, address) values('Z大学', 'C都a区1-1-1');
-- テーブルを結合して取得する
select students.id, students.name, universities.name
from students
inner join universities on students.university_id = universities.id;
students.id students.name universities.name
1 太郎 X大学
2 花子 Y大学
3 次郎 X大学

他にも外部結合や交差結合など、 join にもいくつかの方法があるので、 詳しくはこちらの参考ページを見たり調査してみてください。

https://www.javadrive.jp/sqlite/join/

このようなリレーションについて、 ドローイングソフトを例に説明していきます。

テーブル設計

まずは線のテーブルと線を構成する点のテーブルを考えます。

linesテーブル

id
1
2

pointsテーブル

id x y line_id
1 10.0 200.89 1
2 50.2 10.25 1
3 100.5 300.44 2
4 299.3 43.2 2

これで「線を構成する点」を表現できます。

点を線にグルーピングするだけならlinesテーブルは無くてもいい気はしますが、 線ごとに色や描いたユーザのIDが設定できるように切り分けておきます。

サーバ

実装したWeb APIは以下の通りです。

  • GET /lines: 全ての lines を取得する
    • lines を構成する points の情報も取得する
  • POST /lines: 線を追加する
    • lines テーブルに一行追加する
    • 作成した lines を一件取得する
    • 取得した lines を構成する points を追加する

こちらがSQLでデータを取得して、 JSでデータを整形するコードです。

const query = `
select lines.id as 'line_id', points.id as 'point_id', points.x as 'x', points.y as 'y'
from lines 
inner join points on lines.id = points.line_id`;
db.all(query, (err, rows) => {
    if (err) {
        throw err
    }
    const lineIds = Array.from(new Set(rows.map(row => row.line_id)))
    const lines = lineIds.map(lineId => {
        const points = rows.filter(row => row.line_id === lineId)
            .map(row => {
                return {
                    id: row.point_id,
                    x: row.x,
                    y: row.y,
                }
            })
        return {
            id: lineId,
            points: points,
        }
    })
    callback(lines)
})

クエリの結果はこのようになっています。

line_id point_id x y
1 1 235.0 65.0
1 2 224.0 65.0
1 3 209.0 66.0
1 4 194.0 71.0
1 5 185.0 79.0
1 6 175.0 90.0
1 7 171.0 100.0

またコメントアウトしてあるコードは、 SQLで取得してくる段階でJSONにフォーマットするサンプルです。 こちらの方がJSの処理は少なくなりますが、SQLite側の処理が増えます。

// *** Formatting with SQLite **
const query = `
select json_object(
    'id', lines.id,
    'points', json_group_array(
        json_object('id', points.id, 'x', points.x, 'y', points.y)
    )
) line 
from lines 
inner join points on lines.id = points.line_id 
group by lines.id`;
db.all(query, (err, rows) => {
    const lines = rows.map(row => JSON.parse(row.line))
    callback(lines)
})

クエリの結果はこのようになっています。

line
{"id":1,"points":[{"id":1,"x":235.0,"y":65.0},{"id":2,"x":224.0,"y":65.0},{"id":3,"x":209.0,"y":66.0},{"id":4,"x":194.0,"y":71.0},{"id":5,"x":185.0,"y":79.0}]}
{"id":2,"points":[{"id":23,"x":324.0,"y":330.0},{"id":24,"x":308.0,"y":321.0},{"id":25,"x":297.0,"y":309.0},{"id":26,"x":291.0,"y":299.0},{"id":27,"x":290.0,"y":289.0},{"id":28,"x":296.0,"y":277.0},{"id":29,"x":314.0,"y":273.0},{"id":30,"x":335.0,"y":273.0}]}
{"id":3,"points":[{"id":49,"x":471.0,"y":115.0},{"id":50,"x":471.0,"y":126.0},{"id":51,"x":471.0,"y":142.0},{"id":52,"x":468.0,"y":157.0},{"id":53,"x":460.0,"y":168.0},{"id":54,"x":449.0,"y":176.0},{"id":55,"x":433.0,"y":181.0}]}
{"id":4,"points":[{"id":81,"x":125.0,"y":383.0},{"id":82,"x":123.0,"y":371.0},{"id":83,"x":123.0,"y":360.0},{"id":84,"x":139.0,"y":368.0},{"id":85,"x":156.0,"y":381.0},{"id":86,"x":172.0,"y":392.0},{"id":87,"x":184.0,"y":400.0}]}
{"id":5,"points":[{"id":106,"x":582.0,"y":308.0},{"id":107,"x":566.0,"y":310.0},{"id":108,"x":551.0,"y":318.0},{"id":109,"x":568.0,"y":332.0}]}

どちらも一長一短ですが、前者の方がDBの役割とサーバの役割が分離できていると思うので 自分は前者をコード選ぶと思います。

スケッチ

こちらがProcessingスケッチです。 マウスをドラッグ&ドロップすることで線を描区ことができます。 マウスリリースのタイミングで、 POST /lines にリクエストして線を追加し、 GET /lines にリクエストして線を取得しています。

感想

軽い気持ちで記事を連載してしましました。 いつネタ切れが来るかわかりませんが、 外部サービスを使ってインターネット越しに連動するところまでを目標にしたいと思ってます。

次の記事はこちらに書く予定です。


メディアアートやクリエイティブコーディングのためのバックエンド構築入門(Chap.04 リレーション編)

By Katsuya Endoh, 2024-06-25