SQL の基本

これまでに、ネットワーク上のデータをアプリに組み込む方法や、コルーチンを使用して同時実行タスクを処理する方法について学びました。このパスウェイでは、高品質なアプリを構築できる、Android 開発のもう 1 つの基本スキルである「永続性」について学びます。この言葉を聞いたことがなくても、アプリを使用しているときに永続性に遭遇したことがあるかもしれません。買い物リストの作成、写真アプリでの数年前の画像のスクロール、ゲームの一時停止や再開など、アプリは永続性を使用してシームレスなユーザー エクスペリエンスを実現します。ユーザーにとってこうした機能が当たり前である以上、開発者にとってデータの永続化は、質の高いアプリを作成するために不可欠なスキルです。

このユニットでは後ほど、Android での永続性について詳しく学び、アプリからのデータベースの読み書きを可能にする Room というライブラリについて学びます。しかし、Android で永続性を扱う前に、リレーショナル データベースの基本と、SQL(Structured Query Language の略)というものでデータを読み取って操作する方法について理解しておくことが重要です。すでにこうしたコンセプトに精通している場合は、このレッスンを復習として行うことで、Room について学習する前に知識を確認できます。精通していなくてもまったく問題ありません。現時点では、データベースについての知識は必要ありません。この Codelab を修了すると、Android アプリでのデータベースの扱い方を学ぶために必要な基礎知識をすべて習得できます。

前提条件

  • Android Studio でプロジェクトを操作する。

学習内容

  • リレーショナル データベースの構造: テーブル、列、行
  • SELECT ステートメント(WHEREORDER BYGROUP BYLIMIT 句を含む)
  • SQL を使用して行を挿入、更新、削除する方法

必要なもの

  • Android Studio がインストールされているパソコン。

リレーショナル データベースとは

コンピューティングにおいて、データベースとは単に、電子的にアクセスでき書き込みできる構造化されたデータの集まりを指します。データベースには、Kotlin を使用してアプリで表現できる情報と同じ情報を格納できます。モバイル デバイスでは、実行中のアプリのデータを保存し、次回アプリを開いたときにインターネットなど別のソースからデータを取得せずにアクセスできるようにするために、データベースがよく使用されます。これを「データの永続性」といいます。

データの永続性について語るとき、「リレーショナル データベース」という用語をよく耳にします。リレーショナル データベースは、データをテーブル、列、行に分けて整理する一般的な種類のデータベースです。Kotlin コードを記述するときは、オブジェクトを表すクラスを作成します。リレーショナル データベースの「テーブル」も、同じように機能します。データを表すだけでなく、テーブルは他のテーブルを参照することもできるため、テーブル間の関係を設定できます。典型的な例は「学生」、「教師」、「コース」のテーブルです。1 つのコースには教師が 1 人いますが、1 人の学生は複数のコースを取る可能性があります。こうしたテーブル間の関係をデータベースで表すことができるため、「リレーショナル データベース」という用語がよく使われています。

ef61dd2663e4da82.png

現実世界の関係を、テーブル間の関係で表すことができます。

テーブル、列、行

テーブルの定義、つまりどのようなデータを表すかの定義は、リレーショナル データベースを作成する最初のステップにすぎません。また、各テーブルに具体的にどのような情報を格納するかを考える必要があります。具体的なプロパティは「列」で表されます。列は名前とデータ型で構成されています。プロパティについては、すでに Kotlin でクラスを扱う際に説明しました。SQL テーブルも同じように考えることができます。テーブルはクラス定義に似ており、表現する「もの」の種類を記述します。列は、テーブルの各エントリで作成される「もの」の具体的なプロパティです。

PlantidINTEGERspeciesTEXT(文字列)nameTEXT(文字列)colorTEXT(文字列)

GardenidINTEGERnameTEXT(文字列)lengthINTEGERwidthINTEGER

Plant

id

INTEGER

species

TEXT(文字列)

name

TEXT(文字列)

color

TEXT(文字列)

Garden

id

INTEGER

name

TEXT(文字列)

length

INTEGER

width

INTEGER

個々のテーブル エントリを「行」といいます。これは、Kotlin のクラスのインスタンスに似ています。各行には、各列に対応するデータがあります。テーブルはテンプレートを提供しますが、行はテーブルに格納される実際のデータを定義します。

id

species

name

color

1

Camellia Sinensis

Tea Plant

green

2

Echinacea Purpurea

Purple Coneflower

purple

3

Ferula Foetida

Asafoetida

green

主キー

上の例で、id プロパティの列がどのようになっているかに注目してください。自然界の植物種やデータベースで表すものには便利な番号を振られた ID はないかもしれませんが、データテーブルの行にはなんらかの一意の識別子があるということが重要です。これを一般に「主キー」といい、テーブルの各行に対して一意です。これは、あるデータテーブルの行を別のテーブルから参照する必要がある場合に便利です。たとえば「garden」という別のテーブルがあり、ある庭とそこに含まれるすべての植物種を関連付けるとします。plants テーブルの主キーを使用して、garden テーブルやデータベース内の他のテーブルのエントリから植物を参照できます。

主キーを使用すると、リレーショナル データベースの関係性を設定できます。このコースでは、複数のテーブルを持つデータベースを使用することはありませんが、テーブル内の既存アイテムのクエリ、更新、削除にも役立つ一意の ID を使用しています。

データ型

Kotlin クラスのプロパティを定義する場合と同様に、データベース内の列は数多くのデータ型のいずれかになります。列は、文字、文字列、数値(小数の有無は問いません)、またはバイナリデータを表すことができます。日付や時刻のような他のデータは、ユースケースに応じて、数値または文字列として表すことができます。Room を扱う場合、主に Kotlin の型を扱いますが、裏で SQL の型にマッピングされます。

SQL

リレーショナル データベースにアクセスする際は、単体でも、Room などのライブラリを使用する場合でも、「SQL」というものが必要になります。

SQL とは何でしょうか。SQL(「シークル」と発音されることがあります)は Structured Query Language の略であり、リレーショナル データベースのデータの読み取りや操作を行うことができます。アプリに永続性を実装するためだけにまったく新しいプログラミング言語を学ぶ必要はありませんので、ご安心ください。Kotlin のようなプログラミング言語とは異なり、SQL は、データベースを読み書きするための数種類のステートメントのみで構成されています。それぞれの基本的な形式を学んだら、データベースを読み書きする具体的な情報について、空白を埋めるだけです。

以下は最も一般的な SQL ステートメントです。今後これを使用します。

SELECT

データテーブルから具体的な情報を取得します。結果はさまざまな方法でフィルタしたり、並べ替えたりできます。

INSERT

テーブルに新しい行を追加します。

UPDATE

テーブルの既存の行を更新します。

DELETE

テーブルから既存の行を削除します。

SQL で何かを行うには、その前にデータベースが必要になります。次の画面では、SQL クエリを練習するためのデータベースを含むサンプル プロジェクトを設定します。

ダウンロードするスターター コードは、これまでの Codelab とは少し異なります。既存のプロジェクトをベースにするのではなく、SQL クエリの練習に使用できるデータベースを作成するシンプルな Android Studio プロジェクトを提供します。アプリを実行すると、Database Inspector という Android Studio ツールを使用してデータベースにアクセスできるようになります。

この Codelab のコードを取得して Android Studio で開くには、以下の手順に沿って操作します。

コードを取得する

  1. 指定された URL をクリックします。プロジェクトの GitHub ページがブラウザで開きます。
  2. プロジェクトの GitHub ページで、[Code] ボタンをクリックすると、ダイアログが表示されます。

5b0a76c50478a73f.png

  1. ダイアログで、[Download ZIP] をクリックして、プロジェクトをパソコンに保存します。ダウンロードが完了するまで待ってください。
  2. パソコンに保存したファイルを見つけます([ダウンロード] フォルダなど)。
  3. ZIP ファイルをダブルクリックして展開します。プロジェクト ファイルが入った新しいフォルダが作成されます。

Android Studio でプロジェクトを開く

  1. Android Studio を起動します。
  2. [Welcome to Android Studio] ウィンドウで [Open an existing Android Studio project] をクリックします。

36cc44fcf0f89a1d.png

注: Android Studio がすでに開いている場合は、メニューから [File] > [New] > [Import Project] を選択します。

21f3eec988dcfbe9.png

  1. [Import Project] ダイアログで、解凍したプロジェクト フォルダがある場所([ダウンロード] フォルダなど)に移動します。
  2. そのプロジェクト フォルダをダブルクリックします。
  3. Android Studio でプロジェクトが開かれるまで待ちます。
  4. 実行ボタン 11c34fc5e516fb1c.png をクリックして、アプリをビルドし、実行します。期待どおりにビルドされることを確認します。
  5. [Project] ツール ウィンドウでプロジェクト ファイルを見て、アプリがどのように設定されているかを確認します。

次のセクションに進む前に、以下の手順に沿って、スターター プロジェクトが設定されていることを確認します。

  1. アプリを実行します。アプリは、次のような画面のみを表示します。

144a660216180865.png

  1. Android Studio で Database Inspector を開きます([View] > [Tool Windows] > [Database Inspector])。
  2. 下部に [Database Inspector] という新しいタブが表示されます。読み込みには数秒かかる場合がありますが、左側にデータテーブルのリストが表示されます。クエリを実行する対象はこの中から選択できます。

af7dd66d193c7d55.png

次の演習では、Database Inspector でクエリを実行します。左ペインで正しいテーブル(park)が選択されていることを確認し、「Open New Query Tab」ボタンをクリックすると、SQL コマンドを入力できるテキスト ボックスが表示されます。

ce1c1d86d460427d.png

SQL ステートメントは、1 行のコードのようなコマンドであり、データベースにアクセス(読み書き)します。SQL で行える最も基本的なことは、単にテーブル内のすべてのデータを取得することです。そのためには、データを読み取ることを意味する「SELECT」という単語で開始します。次にスター(*)を追加します。ここは選択する列を指定するところですが、簡略表記としてスターを使用すると、すべての列を選択できます。その後、FROM キーワードに続いて、データテーブルの名前 park を使用します。Database Inspector で次のコマンドを実行し、すべての行と列を含むテーブル全体を確認します。

SELECT * FROM park

データテーブルのすべての列ではなく、特定の列のみを選択する場合は、列名を指定できます。

SELECT city FROM park

カンマ区切りで複数の列を選択することもできます。

SELECT name, established, city FROM park

データベース内のすべての行を選択する必要がない場合もあります。SQL ステートメントの句の部分を追加すると、結果をさらに絞り込むことができます。

LIMIT 句を使用すると、返される行数の上限を設定できます。そのため、次のクエリは 23 件の結果をすべて返すのではなく、最初の 5 件のみを返します。

SELECT name FROM park
LIMIT 5

非常に一般的で便利な句として、WHERE 句があります。WHERE 句を使用すると、1 つ以上の列に基づいて結果をフィルタできます。

SELECT name FROM park
WHERE type = "national_park"

「等しくない」(!=)という演算子もあります。次のクエリは、100,000 エーカーを超える公園のうち、recreation_area でないものをすべてリストします。WHERE 句には、ANDOR などのブール演算子を使用して複数の条件を指定することもできます。

SELECT name FROM park
WHERE type != "recreation_area"
AND area_acres > 100000

練習

SQL クエリは、データに関するさまざまな疑問に答える場合に便利です。一番の練習方法は独自のクエリを記述することです。以降の数ステップでは、特定の質問に回答するクエリを記述します。先に進む前に、必ず Database Inspector でテストしてください。

練習問題はすべて、これまでのセクションで蓄積された知識に基づいており、Codelab の最後には解答を確認するためのチュートリアルがあります。

最初に記述したクエリは、単にデータベース内のすべての行を返しました。

SELECT * FROM park

しかし、長い結果リストを返したくはないでしょう。SQL には集計関数があり、データを意味のある 1 つの値にまとめることができます。たとえば、park テーブルの行数を知りたいとします。SELECT * ... ではなく COUNT() 関数を使用し、*(すべての行)または列名を渡すと、クエリはすべての行の数を返します。

SELECT COUNT(*) FROM park

もうひとつの便利な集計関数は、列の値を合計する SUM() 関数です。次のクエリは国立公園のみをフィルタし(park_visitors が null でない唯一のエントリであるため)、各公園の総訪問者数を合計します。

SELECT SUM(park_visitors) FROM park
WHERE type = "national_park"

なお、SUM() は null 値にも使用できますが、値は単にゼロとして扱われます。次のクエリは上のクエリと同じものを返します。ただし、アプリで SQL の使用を開始する際は、バグを回避するため、できるだけ具体的に記述することをおすすめします。

SELECT SUM(park_visitors) FROM park

値の集計だけでなく、MAX()(最大値を取得)や MIN()(最小値を取得)などの便利な関数もあります。

SELECT MAX(area_acres) FROM park
WHERE type = 'national_park'

DISTINCT 値の取得

行によっては、列の値が他の行と同じである場合もあります。たとえば、type 列に入る可能性のある値の数には限りがあります。DISTINCT キーワードを使用すると、クエリ結果から重複する値を削除できます。たとえば、type 列の一意の値をすべて取得するには、次のクエリを使用します。

SELECT DISTINCT type FROM park

DISTINCT は集計関数でも使用でき、一意の type をリストして自分で数える代わりに、リストの行数のみを返すようにできます。

SELECT COUNT(DISTINCT type) FROM park

練習

学習した内容を応用して次のクエリを記述できるかどうかを確認しましょう。必ず Database Inspector を使用して、コードが機能することを確認してください。

これまでの例では、特定のエントリを見つけることが難しかったかもしれません。幸いなことに、ORDER BY 句を使用して SELECT ステートメントの結果を並べ替えることもできます。クエリの最後に、WHERE 句(存在する場合)に続けて ORDER BY 句を追加し、並べ替えに使用する列名を指定するだけです。次の例は、データベース内の公園の名前をすべて取得し、結果をアルファベット順に並べ替えます。

SELECT name FROM park
ORDER BY name

デフォルトでは、結果は昇順に並べられますが、ASC キーワードか DESC キーワードを ORDER BY 句に追加すると、昇順または降順に並べ替えることができます。最初のクエリではそもそも昇順で結果がリストされるため ASC を指定する必要はありませんが、降順で結果を取得する場合は、ORDER BY 句の最後に DESC キーワードを追加します。

SELECT name FROM park
ORDER BY name DESC

結果を読みやすくするために、列でグループ化することもできます。ORDER BY 句(存在する場合)の前に、オプションで GROUP BY 句と列を指定できます。これにより、結果は GROUP BY の列に固有のサブセットに分けられ、列ごとに、クエリの残りの部分に従って結果がフィルタされ、並べ替えられます。

SELECT type, name FROM park
GROUP BY type
ORDER BY name

例を使用するとよくわかります。データベース内の公園をすべてカウントするのではなく、各タイプの公園がいくつ存在するのかを確認し、それぞれについて個別のカウントを取得します。

SELECT type, COUNT(*) FROM park
GROUP BY type
ORDER BY type

練習

学習した内容を応用して次のクエリを記述できるかどうかを確認しましょう。必ず Database Inspector を使用して、コードが機能することを確認してください。

問題 4: 訪問者数が多い上位 5 つの公園名とその訪問者数を降順で表示する SQL クエリを記述してください。

Room を使用して Android でデータの永続化を最大限に活用するには、データを書き込める必要があります。データベースのクエリだけでなく、行の挿入、更新、削除を行う SQL ステートメントもあります。こうした基本的な知識は、パスウェイ 2 で Room を使用したデータの書き込み方を学ぶ際に必要となります。

INSERT ステートメント

新しい行を追加するには、INSERT ステートメントを使用します。INSERT ステートメントの後に、INTO キーワードと、行を追加するテーブルの名前を続けます。VALUES キーワードの後に、各列の値を(順番に)丸かっこで囲み、カンマ区切りで指定します。INSERT ステートメントの形式は次のとおりです。

INSERT INTO table_name
VALUES (column1, column2, ...)

park テーブルに行を追加する場合、INSERT ステートメントは次のようになります。値の順序は、park テーブルに対して定義されている列の順序と一致します。一部のデータは指定されていません。挿入した行はいつでも更新できるため、今は問題ありません。

INSERT INTO park
VALUES (null, 'Googleplex', 'Mountain View', 12, null, 0, '')

また、ID として null を渡していることに注目してください。具体的な数字を指定することもできますが、あまり便利とは言えません。数字が重複しないよう、アプリで最新の ID を追跡する必要があるためです。そこで、主キーが自動的に増えるようにデータベースを設定できます(ここではそうなっています)。これにより、null を渡すことで次の ID が自動的に選択されます。

WHERE 句を使用して "Googleplex" という公園を指定し、エントリが作成されたことを確認します。

SELECT * FROM park
WHERE name = 'Googleplex'

UPDATE ステートメント

行を作成した後は、その内容をいつでも変更できます。そのためには、UPDATE ステートメントを使用します。これまで見てきた他のすべての SQL ステートメントと同様に、最初にテーブル名を指定する必要があります。SET 句で、変更する各列を単に新しい値に設定します。

UPDATE table_name
SET column1 = ...,
column2 = ...,
...
WHERE column_name = ...
...

Googleplex エントリについて、既存のプロパティを 1 つ更新し、他のいくつかのフィールドに値を入力します(これらのフィールドには前から値がありましたが、空の文字列 "" でした)。UPDATE ステートメントを使用すると、複数(またはすべて)のフィールドを一度に更新できます。

UPDATE park
SET area_acres = 46,
established = 1088640000,
type = 'office'
WHERE name = 'Googleplex'

更新がクエリ結果に反映されていることを確認します。

SELECT * FROM park
WHERE name = 'Googleplex'

DELETE ステートメント

最後に、SQL コマンドを使用してデータベースから行を削除することもできます。この場合もテーブル名を指定し、SELECT ステートメントと同様に、WHERE 句を使用して削除する行の条件を指定します。WHERE 句で複数の行に一致させることもできるため、1 つのコマンドで複数の行を削除することも可能です。

DELETE FROM table_name
WHERE <column_name> = ...

Googleplex は国立公園ではないため、DELETE ステートメントを使用して、このエントリをデータベースから削除してみてください。

DELETE FROM park
WHERE name = 'Googleplex'

SELECT ステートメントを使用して、行が削除されたことを確認します。このクエリは結果を返しません。つまり、name が「Googleplex」の行はすべて正常に削除されています。

SELECT * FROM park
WHERE name = 'Googleplex'

データの挿入、更新、削除については以上です。知っておく必要があるのは、実行する SQL コマンドの形式のみです。あとは、データベースの列に一致する値を指定するだけです。次の Codelab で Room を紹介するときは、主にデータベースからの読み取りに重点を置きます。データの挿入、更新、削除については、パスウェイ 2 で詳しく説明します。

上の練習問題が SQL のコンセプトを理解するために役立つことを願っています。行き詰まった場合や答え合わせをする場合は、以下に示す解答を参照してください。

問題 1: 訪問者数が 100 万人未満である公園の名前をすべて取得する SQL クエリを記述してください。

この問題は、WHERE 句で指定できる「訪問者数が 100 万人未満」という要件に合致する公園名(1 つの列)を求めています。

SELECT name FROM park
WHERE park_visitors < 1000000

問題 2: park テーブルに含まれる個別の都市の数を取得する SQL クエリを記述してください。

列の総数は COUNT() 関数を使用して計算できますが、(公園が複数ある都市もあるため)個別の都市のみ求める場合は、COUNT() 関数で列名の前に DISTINCT キーワードを使用します。

SELECT COUNT(DISTINCT city) FROM park

問題 3: サンフランシスコの公園の合計訪問者数を取得する SQL クエリを記述してください。

訪問者の総数は SUM() 関数を使用して計算できます。また、サンフランシスコにある公園のみを指定するには、WHERE 句も必要です。

SELECT SUM(park_visitors) FROM park
WHERE city = "San Francisco"

問題 4: 訪問者数が多い上位 5 つの公園名とその訪問者数を降順で表示する SQL クエリを記述してください。

クエリで、name 列と park_visitors 列を両方とも取得する必要があります。結果は、ORDER BY 句を使用して park_visitors 列で降順に並べ替えます。別の列で結果をグループ化して「そのグループ内で並べ替える」わけではないため、GROUP BY 句は必要ありません。

SELECT name, park_visitors FROM park
ORDER BY park_visitors DESC
LIMIT 5

まとめ:

  • リレーショナル データベースを使用すると、データをテーブル、列、行に整理して格納できる。
  • SQL の SELECT ステートメントを使用してデータベースからデータを取得できる。
  • SELECT ステートメントで WHEREGROUP BYORDER BYLIMIT などのさまざまな句を使用して、クエリをより具体的に指定できる。
  • 集計関数を使用して、複数の行のデータを 1 つの列にまとめることができる。
  • SQL の INSERTUPDATEDELETE ステートメントを使用して、データベースの行を追加、更新、削除できる。

詳細