データベースマスター【概要がわかるぞ!】

Pocket

目次

データベースとは

webサービスは データベース を用いてデータを管理しています。
ほとんどのプロジェクトでもデータベースが使われているので、ここではデータベースの概要とデータベースを利用するためのSQLについて学んでいきましょう。

データベースとは、検索を容易にするために整理された情報を集約するミドルウェア(ソフトウエア)です。

例えば、携帯電話の中にはメールアドレスや電話番号が一覧となって登録されていますし、預金口座にも残高が記録されています。 インターネットでwebサイトを検索する際や、ECサイトで商品を検索する際も当然データベースを利用しています。

そういった日常に必要なシステムの中枢で活躍しているのが、データベースという仕組みなのです。

データベースに格納された情報は、外部からアクセスし、検索や書き換えを行うことができるのですが、そのためには専用の操作言語である「 SQL (Structured Query Language) 」というものです。しかし命令数がわかりやすく少ないので気を楽にお読みください。

データベースの利点

基礎を学ぶ前に、データベースを用いて情報を保存することのメリットをご紹介します。

情報の保存方法の例として下記を挙げます。

  • 人の記憶
  • 紙(メモ、付箋など)
  • データベース
 正確性速度容量共有機密性
人の記憶××××
紙(メモ、付箋など)×××
データベース

人の記憶は曖昧で正確さにかけ、自分以外と共有することはできません。 紙は読み書きに時間がかかり、情報量にもかなり制限がかかります。 データベースはこれらと比べて、 「大容量・高速・整合性の保持」 という特徴があります。

ただし、機密性という意味ではデータベースの取り扱いをきちんと行わなければ、情報漏洩や改ざんの恐れがあります。

データベースの利用例

統計情報の蓄積や分析

顧客の属性や行動をデータベースに蓄積していくことで、購買傾向などをデータから導き出すことができます。FaceBookやyahooなども採用されてますので数千万件のデータも当たり前に管理されているのです。

webサイトの構築

サイト内の情報が動的に変わる(見るユーザーによって異なる)ようなサイトを制作する際は、データベースを利用します。 そうすることで個々のユーザーに合った情報を表示することができます。

リレーショナル型データベース

データベースは、おもに「階層型データベース」「ネットワーク型データベース」「リレーショナル型データベース」の3つに分類することができます。
もっともよく利用される「リレーショナル型データベース」について学習します。
(現在では、データベースという言葉は基本的に「リレーショナルデータベース」を指します)

リレーショナルデータベースには、主に2つの特徴があります。

  1. データは2次元(行×列)の表形式で表現される
  2. 複数の表を結合(リレーション)して利用可能である

Microsoft社の表計算ソフト(Excel)やgoogleスプレッドシートを利用したことがある方は多いと思いますが、行・列の概念や関数(SUMやMAXなど)の仕組みがとてもよく似ているので、データベースを操作する際は表計算ソフトを思い浮かべていただくと分かりやすいです。

データは2次元の表形式で表現される

リレーショナルデータベースでは、データを表形式で表現します。
例えば商品のデータを「社員ID」「社員名」「年齢」…のように項目を分けた場合、次のようになります。

alt

1件のデータを「 レコード(行) 」、項目を「 カラム(列) 」、そしてレコードの集合を「 テーブル(表) 」と呼びます。
社員が増えた場合はレコードを追加し、項目が増えた場合はカラムを追加することで、社員の情報を一つのテーブル(表)で表現します。

複数の表を結合して利用可能

もう1つの大きな特徴は、複数の表を結合(リレーション)して利用できることです。

alt

上図のようにテーブルを結合することで、1つのテーブルとして扱うことができます。
このように、1つの社員情報を複数のテーブルで管理することは、データが冗長的にならないようにする工夫( 正規化 )になります。
※正規化に関しては後述

RDBMS

リレーショナル型データベースを管理するシステムを、 RDBMS( Relational Database Management System ) といいます。
RDMBSにはいろいろな種類がありますが、下記に代表的なものを挙げます。

RDBMS説明導入コスト
OracleOracle社製のデータベース。商用RDBMSとして、世界で最も多く利用されている。有償(無償版あり)
Microsoft SQL ServerMicrosoft社製のデータベース。Windowsとの親和性が高い。有償(無償版あり)
MySQL世界で最も利用されているオープンソースRDBMS。無償
PostgreSQLオープンソースRDBMS。上記3件と比べると世界的シェア率は低いが、Oracle社製のデータベースとよく似た機能を備えているということもあり人気が高い。無償

オープンソースとは、ソースコードが無償で公開されており、誰でも自由に改良や再配布を行うことができるものを指します。

本テキストでは前述したとおり、無償で非常に人気の高いRDBMSであるMySQLを利用して学んでいきます。
また、MySQLは非常に高速で動作し、小〜大規模なサービスで幅広く利用されており、CodeCampのサービスの一部でもMySQLを利用しています。

データベース専用の操作言語である「 SQL (Structured Query Language) 」は、異なるRDBMS上でも同じように使用することができるため、本テキストでMySQLを学習した後であれば、OracleやPostgreSQL等でも通用します。
ただし、各RDBMS毎に独自の拡張が行われているため、その部分に関しては各RDBMSに合わせて学習をする必要があります。

phpMyAdminのインストール

AWS Cloud9には既にMySQLというデータベースがインストールされているため、MySQLをGUI(グラフィカル・ユーザ・インタフェースの略で、画面で操作できる方法)で操作できる phpMyAdmin というツールをインストールします。

※MAMPやXAMPは標準でphpMyAdminはインストールされております。

phpMyAdmin

Cloud9でのインストール方法は、「AWS Cloud9にphpMyAdminをインストールする」こちらをクリックしてください(参照ください)

phpMyAdminの停止について【Linuxの場合】

phpMyAdminの停止は、phpMyAdmin起動しているターミナルで「Ctrl + C」キー(macでは「control + C」)を押してください。

10-2 MySQLの起動・停止【Linuxの場合】

Cloud環境のphpMyAdminを起動するには、MySQLが起動している必要があります。
MySQLを起動するには、ターミナルの「bash」タブで以下のコマンドを実行します。

sudo service mysqld start

MySQLを停止するには、ターミナルの「bash」タブで以下のコマンドを実行します。

sudo service mysqld stop

このコマンドはMySQLを操作する時によく使うので覚えておきましょう。

10-3 phpMyAdminの使い方

ホーム

ロゴをクリックすると、ログイン後のページ(ホーム)が表示されます。

center

データベースとテーブル

データベースとテーブルは以下のような階層構造で表されています。

  • ①データベースの中に
  • ②テーブルが表示される

「information_schema」「mysql」「performance_schema」「phpmyadmin」「test」の5つのデータベースは、デフォルトでAWS Cloud9に用意されているもので、これらはMySQL自体の定義情報や稼働統計が蓄積されるデータベースなので、さわらないようにしてください。

11-1 データベースの作成

phpMyAdminの画面からデータベースとテーブルを作成していきます。
「データベース」タブをクリックします。

データベース

データベース名の入力欄に「camp」と入力し、照合順序では「utf8_general_ci」を選択して「作成」ボタンをクリックしてください。

データベース

すると以下のように、「camp」というデータベースが新しく作成されます。

データベース

SQLでの実行作成では下記となります

CRATE DATABASE camp;

11-2 テーブルの作成

データベースの作成が完了したので、次はテーブルを作成していきます。
ここでは、以下のようなテーブルを作成します。

products (商品テーブル)

idnameprice
1マウスパッド1200
2イヤホン900
3500
4お茶140

データベースを扱う上で、データは2次元(行×列)の表形式で表されます。
カラム(列)が各商品(データ)の情報を扱っており、データが1件増えるごとにレコード(行)が追加されます。
idはその商品に付けられた商品ID、nameは商品名、priceは商品価格になります。

それでは、実際にテーブルを作成していきます。
ウィンドウ左側のメニュー、もしくは「データベース」タブを開き、先程作成したデータベース「camp」をクリックします。

データベース

テーブル名とカラム数を入力する欄が表示されるので、テーブル名は「products」、カラム数は「3」と入力して「実行」をクリックします。

データベース

入力後、実行ボタンをクリックすると下図のような画面に切り替わります。

データベース

いろいろな設定項目が表示されていますが、内容は以下になります。

設定項目内容
名前追加するカラムの名前になります。
データ型どのような形でデータを格納するかを指定します。詳細に関しては後述します。
長さ/値データ型に応じて、どのくらい大きなデータを格納できるかを指定します。
デフォルト値格納する値の初期値です。
照合順序文字コードや、大文字と小文字の区別、テーブル同士の結合の際の指定です。
属性カラムが数値だった場合の符号に対する指定や、指定桁数の0埋めを行うかどうかなどを指定します。
NULLデータ(レコード)の追加や更新を行う際に「値なし」を許可するかどうかを指定します。
インデックス検索時の索引情報の指定です。詳細については後述します。
A_Iオートインクリメント(Auto Increment)の略で、データ(レコード)が追加された際に、自動的に連番にするかどうかを指定します。
コメント備考欄です。

上記を踏まえ、今回は以下の赤枠部分のように設定します。

データベース

データ型等の各所設定に関しては後述しますが、名前、データ型、長さ/値、デフォルト値、照合順序、インデックス、A_Iの項目を以下のように指定し、「保存する」ボタンをクリックしてください。

名前データ型長さ/値デフォルト値照合順序インデックスA_I
idINTなしPREMARY
nameVARCHAR100なしutf8_general_ci
priceINTユーザ定義: 0 

以下のように、ウィンドウ左側のメニューに「products」というテーブルが作成されました。
このメニューはツリー状に表示されており、productsテーブルはcampデータベースに所属していることがわかります。

データベース

ウィンドウ左側のメニューにてテーブルを選択し、「構造」タブをクリックすると、以下のようにテーブルに設定した詳細情報が確認できます。

データベース

※SQLでダイレクトに作成するには

CREATE TABLE products (
    id INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
    name VARCHAR(100),
    price INT,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

11-3 テーブルの操作

よく使用するテーブルの項目です。

データベース
項目機能
表示テーブルに保存されたデータを確認・編集を行う
構造フィールドの設定の編集を行う
SQLSQL文を使ってデータベース内の情報を更新・変更を行う
検索条件を指定し、データの検索を行う
挿入新たなデータの挿入を行う
エクスポート保存したデータのバックアップを行う
インポートデータの取り込みを行う

11-4 データ型

テーブルを作成した時に、データ型という項目を設定しました。
よく利用するデータ型には、大きく分けると「 数値型 」、「 文字列型 」、「 日付/時刻型 」の3つの種類が存在します。

データ型は、基本的には互換性がないため厳密に指定する必要があります。
例えば、数値型に対して文字列を格納することはできませんし、日付/時刻型に数値を格納することもできません。

ここでは、よく利用する代表的なデータ型を大きく3つに分けて解説していきます。

数値型

型名値の範囲UNSIGNED指定の範囲
TYNYINT-128 〜 1270 〜 255
INT-2147483648 〜 21474836470 ~ 4294967295
BIGINT-9223372036854775808 〜 92233720368547758070 ~ 18446744073709551615
FLOAT浮動小数点付き数値指定しても範囲は変わらない

カラムを定義した際に、UNSIGNEDを指定すると負の数を扱うことができなくなり、その分格納できる値の最大値を大きくすることができます。

数値型は主に商品の価格や数量を表す際に用いられます。
また、性別をデータとして取り扱う際等で、「男」や「女」を文字として格納するのではなく、0が不明、1が男性、2が女性というようにする場合があります。

電話番号や郵便番号などは、数値型だと先頭が0で始まることができないのと、「 – (ハイフン)」が用いられるケースがあるため一般的には次に挙げる文字列型として格納します。
また、UNSIGNEDのTINYINTは値の範囲が 0 〜 255 なので、IPアドレスやRGBカラーコードの範囲と同様のため、これらはUNSIGNEDのTINYINTを用いるのが一般的です。

文字列型

型名値の範囲(文字数)
VARCHAR255
TEXT65535

商品名やユーザー名、またはプロフィール情報等を表す際に用いられます。

日付/時刻型

型名フォーマット
DATEYYYY-MM-DD
TIMEhh:mm:ss
DATETIMEYYYY-MM-DD hh:mm:ss

フォーマットのYYYYは4桁の年を表す数値、MMは2桁の月を表す数値、DDは2桁の日付を表す数値になります。 つまり、その桁数に満たない場合は前に0が付き、強制的にその桁数になります。
また、hhは時刻、mmは分、ssは秒を表します。この表記はデータベースだけではなく、プログラミングでも広く利用される表現です。

用途としては、ユーザーの登録日時やポイントの期限などに利用します。
また、更新日時というカラムを設け、データが更新される度に更新日時を最新化すると、いつそのデータが更新されたのかがわかりやすくなるため、よく利用されています。

商品テーブル「products」について

商品テーブルは以下のように設定しています。ここで各カラムのデータ型について見ていきましょう。

カラム名データ型長さ/値デフォルト値A_I
idINT
nameVARCHAR100
priceINT0

まず、idはINTで定義しています。
ここでさらに注目していただきたいのが、A_Iという項目にチェックを入れていた点です。
前述しましたが、A_Iはオートインクリメント(Auto Increment)の略で、データ(レコード)が追加された際に、自動的に連番になります。 つまり、同じIDの商品を存在させないようにしています。

INTの最大値はUNSIGNEDを指定しない場合、2147483648となりますのでそれ以上の商品数を登録したい場合はINTではなくBIGINTを使用しましょう。

nameはVARCHARで定義しています。
商品名なので文字列として格納するのですが、注意点が1点あります。
長さ/値を「100」として定義しているため、100文字までしか格納することができません。 101文字以上の長さの文字列を商品名にした場合に、超過した文字列は切り捨てられた状態で格納されます。

priceは商品価格なのでINTとして定義しています。
デフォルト値を0としているのは、その商品に価格が設定されていない場合は自動的に0円とするためです。

商品などの価格をデータとして扱う場合、今回は「INT」として定義していますが、小数点以下の金額も扱いたい場合があります。 そういった場合には、「FLOAT」や、前述はしていませんが「DECIMAL」という型を利用しましょう。

11-5 PREMARY KEY(主キー)

作成した商品テーブルにはidというカラムがありますが、このidは A_I(AUTO INCREMENT) を指定しています。 自動採番のカラムの値は必ず一意(ユニーク)になります。

値が一意になるカラムを設けることで、特定のレコードに対してのアクセスが容易になります。
例えば、特定の商品の価格を+300したい場合に、商品idに同じものがあると重複するすべての商品の価格を+300してしまいます。 そのため、一意になる値を持つカラムを作成することは重要です。

例えば、以下のような社員情報テーブルがあったとします。

社員情報テーブル

社員名年齢役職
山本56課長
田中42部長
竹下32一般
里中26一般

新しく社員が入社したため、上記社員情報テーブルに対して、以下のデータを追加するとします。

社員名年齢役職
田中42一般

新入社員追加後の社員情報テーブル

社員名年齢役職
山本56課長
田中42部長
竹下32一般
里中子26一般
田中42一般

すると、「田中」という同名・同年齢の社員が存在してしまうため、「田中さんの役職を課長に変更する」という時に、どちらの田中さんを変更すればいいのかわからなくなってしまいます。
この事態を回避するためには、以下のように社員番号を設ける必要があります。

社員番号社員名年齢役職
1山本56課長
2田中42部長
3竹下32一般
4里中26一般
5田中42一般

社員番号は一意のため、データを操作する時に条件として社員番号を指定することで、レコードを特定することが可能になります。

このように、レコード上で他と重複しない一意のカラムを作成する場合は「 PRIMARY KEY 」を設定します。 PRIMARY KEYを設定することで以下の制約が追加されます。

  • 値の重複がない(ユニーク)
  • データは必ず入力しなければならない(NULLにはならない)

商品テーブル(products)のidにはPRIMARY KEYが設定されています。

データベース

12-1 SQLとは

前章でデータベースとテーブルを作成しました。
ここでは、テーブルに対してデータを「追加」「参照」「更新」「削除」する処理を行います。

データベースへのデータの追加や更新、削除などの命令は、 SQL (Structured Query Language) という言語を使用します。
また、SQLで記述されたデータベースへの命令を「 クエリ (query)」と呼びます。

このSQLという言語は、業界標準として様々なRDBMSに採用されているため、MySQL以外のデータベースを利用する場合も使うことができます。
つまり、MySQLの学習をすることで、OracleやPostgreSQL等のRDBMSの基本的な操作は可能になります。

なお、前章までに行ったデータベースの作成やテーブルの作成も、SQLを使ってクエリを発行することでも可能です。

12-2 CRUDについて

データベースには CRUD と呼ばれる基本操作が存在します。
CRUDはそれぞれ、CreateのC、ReadのR、UpdateのU、DeleteのDの頭文字を繋げて表しています。
詳しい内容は後述しますが、この4つの基本操作さえ覚えてしまえば、SQLで自由にデータにアクセスすることが可能になります。

CRUD内容構文
C (Create)データを新規作成するINSERT INTO テーブル名 …
R (Read)データを参照するSELECT カラム名 FROM テーブル名 WHERE …
U (Update)データを更新するUPDATE テーブル名 SET カラム名 WHERE …
D (Delete)データを削除するDELETE FROM テーブル名 WHERE …

12-3 SQLのサンプル

以下にSQLのサンプルを紹介します。以下に記述されているサンプルは実際に動作するものになります。

  • データの追加 (Create)
INSERT INTO products  (name, price)VALUES  ('マウスパッド', 1200);
  • データの参照 (Read)
SELECT  id,  name,  priceFROM  productsWHERE  name = 'マウスパッド';
  • データの更新 (Update)
UPDATE  productsSET  price = 100WHERE  id = 1;
  • データの削除 (Delete)
DELETEFROM  productsWHERE  name = 'マウスパッド';

12-4 サンプルSQLの実行

以下のように、ウィンドウ左側のメニューからデータベース(camp)を選択し、上方部のSQLタブをクリックします。
すると、SQL記述エリアが表示されるので、前ページのサンプルSQLをコピー&ペーストし、実行ボタンを押下することでクエリを発行することができます。

SQL

以降、SQLを実行する際は、対象のデータベースを選択後、SQLタブのSQL記述エリアに記述していきましょう。

12-5 SQLの基本的なルール

  • 大文字小文字は区別しない
    SQLは大文字/小文字を区別しません。このため「INSERT」でも「insert」でも構いません。
    ただしデータベース名やテーブル名、カラム名は、環境によっては区別されますので、正確に入力してください。
  • 文字列は「 ‘ (シングルクォート) 」で囲む
    文字列を扱う場合は、「 ‘ (シングルクォート) 」で囲みます。
    これは、SQLの文法上、構文の一部なのか、文字列なのかを区別するためです。
  • カラムの設定文字数を超えない
    テーブルを定義した際のカラムの最大文字数を超えないようにしましょう。
  • 命令の最後に「 ; (セミコロン)」を付ける
    SQLの構文の最後にはクエリの終端を意味する「 ; (セミコロン)」が必要です。

13-1 データを作成する「INSERT」

それでは、SQLを使ってデータを操作していきましょう。

データの作成には、 INSERT文 と呼ばれる、「INSERT 〜 」から始まるクエリを実行することで実現できます。
CRUDのCに該当します。

INSERT文の書式は以下のとおりです。

INSERT INTO テーブル名  (カラム1, カラム2, ...)VALUES  (値1, 値2, ...);

例えば、商品テーブル(products)に価格が1,200円のマウスパッドを追加するINSERT文は、以下のようになります。
phpMyAdminで実際に実行してみましょう。

INSERT INTO products  (name, price)VALUES  ('マウスパッド', 1200);

A_I (オートインクリメント)が指定されたカラムは、自動的に採番されるためデータ追加は行う必要はありません。
ただし、明示的にデータ追加をすることも可能です。

テーブル名の後の「( )」中にカラムを、VALUESの後の「( )」中にカラムの中に入れるデータを、「 , (カンマ)」で区切って記述します。
カンマで区切ったカラムと、データの順番は等しくなければいけません。また、文字列と数値、日付は区別して記述しなければいけません。

  • エラーになる例① (文字列がシングルクォートで囲まれていない)
INSERT INTO products  (name, price)VALUES  (マウスパッド, 1200);
  • エラーになる例② (VALUESの順序がカラムと異なる)
INSERT INTO products  (name, price)VALUES  (1200, 'マウスパッド');

INSERT文を実行すると、以下のようにテーブルにデータが追加されます。

insert
insert

13-2 データを参照する「SELECT」

データの参照には、 SELECT文 と呼ばれる、「SELECT 〜 」から始まるクエリを実行することで実現できます。
CRUDのRに該当します。

SELECT文の書式は以下のとおりです。

SELECT  カラム1, カラム2, ...FROM  テーブル名WHERE  カラム1 = 値;
  • SELECT句 (必須)
    • 参照対象のカラムをカンマ区切りで指定します。
      ただし、最後のカラムの後はカンマは不要です。 また、「SELECT * 」と指定することで、全てのカラムを対象とすることができます。
  • FROM句 (必須)
    • 対象となるテーブルを指定します。
  • WHERE句
    • 抽出条件を指定します。
      また、ANDで繋ぐことで複数の条件を指定することができます。

以下のクエリを実行し、サンプルデータを用いて例文を実行してみましょう。

DELETE FROM products; -- 一度登録したデータを削除します INSERT INTO products  (id, name, price)VALUES  (1, 'マウスパッド', 1200),(2, 'イヤホン', 900),(3, '傘', 500),(4, 'お茶', 140);
select

例1:商品テーブル(products)から、IDが「1」のデータを参照する

SELECT  name, priceFROM  productsWHERE  id = 1;
ex1

例2:商品テーブル(products)から、商品名が「マウスパッド」のデータを参照する

SELECT  name, priceFROM  productsWHERE  name = 'マウスパッド';
ex2

例3:商品テーブル(products)から、商品名が「マウスパッド」で、価格が「1200」のデータを参照する

SELECT  name, priceFROM  productsWHERE  name = 'マウスパッド'  AND price = 1200;
ex3

例4:商品テーブル(products)から、IDが「1」のデータ全カラムを対象とし、参照する

SELECT  *FROM  productsWHERE  id = 1;
ex4

例5:商品テーブル(products)から、条件を指定せずに全カラムを参照する

SELECT  *FROM  products;
ex5

13-3 データを更新する「UPDATE」

データの更新には、 UPDATE文 と呼ばれる、「UPDATE 〜 」から始まるクエリを実行することで実現できます。
CRUDのUに該当します。

UPDATE文の書式は以下のとおりです。

UPDATE  テーブル名SET  更新対象カラム1 = 更新値,  更新対象カラム2 = 更新値,  ...WHERE  カラム1 = 値;
  • UPDATE句 (必須)
    • 対象となるテーブルを指定します。
  • SET句 (必須)
    • 更新対象のカラムと更新後の値を指定します。
      カンマで繋ぐことで、複数のカラムを指定することができます。
  • WHERE句
    • 更新対象を絞り込む条件を指定します。
      ANDで繋ぐことで、複数の条件を指定することができます。

例1:商品テーブル(products)のIDが「1」のデータの金額を「1000」に更新する

UPDATE  productsSET  price = 1000WHERE  id = 1;
ex1

例2:商品テーブル(products)のIDが「1」のデータの金額と商品名を更新する

UPDATE  productsSET  price = 100,  name = '中古マウスパッド'WHERE  id = 1;
ex2

例3:商品テーブル(products)のIDが「1」で商品名が「中古マウスパッド」のデータの金額を更新する

UPDATE  productsSET  price = 10WHERE  id = 1  AND name = '中古マウスパッド';
ex3

例4:商品テーブル(products)の全てのデータを更新する

UPDATE  productsSET  price = 0;
ex4

WHERE句は必須ではないため省略することが可能ですが、対象テーブル内の全てのデータが更新されてしまうので注意してください。

13-4 データを削除する「DELETE」

データの削除には、 DELETE文 と呼ばれる、「DELETE 〜 」から始まるクエリを実行することで実現できます。
CRUDのDに該当します。

DELETE文の書式は以下のとおりです。

DELETE FROM  テーブル名WHERE  カラム1 = 値;
  • DELETE句 (必須)
    • 接頭句として必須です。
  • FROM句 (必須)
    • 対象となるテーブルを指定します。
  • WHERE句
    • 削除対象の絞り込み条件を指定します。 ANDで繋ぐことで、複数の条件を指定することができます。

例1:商品テーブル(products)のIDが「1」のデータを削除する

DELETEFROM  products WHERE  id = 1;
ex1

例2:商品テーブル(products)のIDが「2」で商品名が「イヤホン」のデータを削除する

DELETEFROM  products WHERE  id = 2AND name = 'イヤホン';
ex2

例3:商品テーブル(products)のデータを全て削除する

DELETE FROM  products;
ex3

WHERE句は省略可能ですが、省略すると全レコード削除となります。
WHERE句をつけ忘れて全てのデータを消してしまうことがないよう、DELETE文を実行する際は、条件に誤りがないことを必ず確認しましょう。

14-1 比較演算子

CRUDのうち、「R (SELECT)」「U (UPDATE)」「D (DELETE)」は、WHERE句で条件を指定することにより、特定のレコードのみに対して参照/更新することができます。
また、ANDで条件を複数指定することも可能です。

条件を指定する際によく使用するのが、「 比較演算子 」と呼ばれるものです。
前章まではWHERE句を「 = 」で条件を指定していましたが、ここでは「 = 」以外の様々な条件指定方法を説明します。

主な比較演算子

比較演算子内容
カラムA = 値カラムAと指定された値が等しい
カラムA <> 値カラムAと指定された値が異なる
カラムA != 値カラムAと指定された値が異なる
カラムA > 値カラムAは指定された値よりも大きい
カラムA < 値カラムAは指定された値より小さい
カラムA >= 値カラムAは指定された値以上
カラムA <= 値カラムAは指定された値以下
カラムA IN (値1, 値2, 値3, 値4, 値5)カラムAは、値1〜5のいずれかと等しい(値はいくつでも指定可能)
カラムA BETWEEN 値1 AND 値2カラムAは値1以上値2以下

以下のクエリを実行し、サンプルデータを用いて例文を実行してみましょう。

DELETE FROM products; -- 一度登録したデータを削除します INSERT INTO products  (id, name, price)VALUES  (1, 'マウスパッド', 1200),(2, 'イヤホン', 900),(3, '傘', 500),(4, 'お茶', 140);
select

例1:価格が「1200」ではない商品を参照する

SELECT  id, name, priceFROM  productsWHERE  price != 1200;
ex1

例2:価格が「500」よりも大きい商品を参照する

SELECT  id, name, priceFROM  productsWHERE  price > 500;
ex2

例3:価格が「900」よりも小さい商品を参照する

SELECT  id, name, priceFROM  productsWHERE  price < 900;
ex3

例4:価格が「100」「500」「900」「1000」いずれかの値である商品を参照する

SELECT  id, name, priceFROM  productsWHERE  price IN (100, 500, 900, 1000);
ex4

例5:価格が「500 ~ 1000」の間である商品を参照する

SELECT  id, name, priceFROM  productsWHERE  price BETWEEN 500 AND 1000;
ex5

14-2 論理演算子

前章でWHERE句に「AND」を使用することで複数の条件を指定していましたが、これが「 論理演算子 」です。
ANDの他にORを使用することができます。

論理演算子内容
条件1 AND 条件2条件1が成立している 「かつ」 条件2が成立している
条件1 OR 条件2条件1が成立している 「または」 条件2が成立している

例1:価格が「1200」 かつ 名前が「マウスパッド」の商品を参照する

SELECT  id, name, priceFROM  productsWHERE  price = 1200  AND name = 'マウスパッド';
ex1

例2:価格が「100 ~ 1000」の間、 または 名前が「マウスパッド」の商品を取得

SELECT  id, name, priceFROM  productsWHERE  price BETWEEN 100 AND 1000  OR name = 'マウスパッド';
ex2

14-3 あいまい検索

商品名などの文字列を条件として指定する際、今までは「 = 」を使って 完全一致 の条件で絞り込みを行ってきました。
完全一致ではなく 部分一致 によってデータの参照・更新を行うこともできます。
部分一致の検索方法を「 あいまい検索 」といいます。

あいまい検索を行う際は、WHERE句の中で「 LIKE 」を使います。
LIKEを用いた書式は以下のとおりです。

SELECT  カラム1, カラム2, ...FROM  テーブル名WHERE  カラム1 LIKE '%文字列_';

上記で「LIKE ‘%文字列_’」となっている部分ですが、この「%」と「_」を ワイルドカード と呼び、LIKEの後の文字列中に使用すると、次のような意味になります。

記号内容
%任意の0文字以上の文字
_任意の1文字

以下のクエリを実行し、サンプルデータを用いて例文を実行してみましょう。

DELETE FROM products; -- 一度登録したデータを削除します INSERT INTO products  (id, name, price)VALUES  (1, 'マウスパッド', 1200),(2, 'マウスウォッシュ', 900),(3, 'トラックパッド', 500),(4, 'ラバーマウスパッド2', 1400);

例1:商品名が「マウス」で始まる(前方一致)商品を参照する

SELECT  id, name, price FROM  products WHERE  name LIKE 'マウス%';
ex1

例2:商品名が「パッド」で終わる(後方一致)商品を参照する

SELECT  id, name, priceFROM  productsWHERE  name LIKE '%パッド';
ex2

例3:商品名に「パッド」を含み、パッドの後に任意の1文字を含む商品を参照する

SELECT  id, name, priceFROM  productsWHERE  name LIKE '%パッド_';
ex3

このように、LIKEとワイルドカードを使うことで、キーワードによる検索などが行えます。

15-1 正規化について

正規化 とは、テーブル設計の中でも重要な手法(テクニック)のひとつです。
テーブルの整合性を保ったまま、データの冗長性を排除し、データを効率的に管理できるようにすることが目的です。

まず、データの冗長性を排除するために、以下の方法を用いてテーブルを分解していきます。

  • 非正規形 ※正規化する前の状態
  • 第1正規化
  • 第2正規化
  • 第3正規化
  • ボイスコッド正規化
  • 第4正規化
  • 第5正規化

ここでは、設計の際によく使われる 第3正規化 までを説明していきます。

15-2 非正規系

正規化を行う前の状態を指します。1行の中に「複数の重複項目」が存在するようなテーブルを指します。

image

例として、ECサイトなどでユーザーが商品を購入する際に必要な情報を、発注表としてまとめました。

1つの発注に対して、購入した商品の情報「商品名」「単価」「数量」は、複数存在する場合があります。
画像の例では正規化されていないことにより非効率的な状態となってしまっています。

まずは 第1正規化 で、繰り返し項目をそれぞれ別テーブルとして独立させます。

15-3 第1正規化

非正規形から第1正規化へは、「 導出属性の削除 」と「 繰り返し項目の分離 」を行います。

導出属性とは、同じテーブルの中で「その値が他のカラムから導くことができる」値のことを指します。
非正規形のテーブルを第一正規化する際、導出属性は削除します。

今回の場合、合計金額は「単価 × 数量」から導くことができるので、合計金額が導出属性となります。

image

導出属性の削除では、導出属性である「合計金額」をテーブルから削除します。
この例では導出属性が1つだけですが、複数あった場合、全て削除を行います。

繰り返し項目の分離

image

繰り返し項目である「商品名」「単価」「数量」に関しては、発注詳細テーブルへと分離します。

分離の際、発注テーブルで主キーとなっていた「注文番号」を、発注詳細テーブルにも設定しています。
これにより、「注文番号」は発注テーブルと発注詳細テーブルを紐付けるキーとなります。
この例では、注文番号が元々主キーとして存在しておりましたが、もし主キーが存在しない場合は、主キーを付け加えます。

第1正規化の手順についてまとめると、以下のようになります。

  1. 導出項目を削除する
  2. テーブルの繰り返し項目を別のテーブルへ分離する

これにより、整合性を保ったまま重複しているデータが減り、冗長性が低下します。
また、第1正規化を行ったテーブルを 第1正規形 と呼びます。

15-4 第2正規化

第1正規形から第2正規化への正規化では、 主キー以外の項目(非キー)の情報に依存している情報の分離 を行います。

発注詳細テーブルの「注文番号」「商品名」「単価」「数量」のうち、単価は商品ごとに決められているものであり、注文したユーザには依存しておりません。
つまり、「単価」は主キーである「注文番号」に依存しておらず、非キーである「商品名」に依存しています。

このように、非キー依存の情報の分離を行い、全てのテーブルで非キーに依存している情報をなくして、全ての非キー属性にキーが1つある状態にすることが、第2正規化の内容となります。

文章だけでは理解しづらいと思いますので、発注詳細のテーブルに対して、主キー以外に依存している情報の分離により、第2正規化を行ってみます。

非キー依存の情報を分離

image

発注詳細テーブルから「商品名」「単価」の情報を商品テーブルへと分離しました。
分離の際、発注詳細テーブルと商品テーブルを紐付けるキーが存在しなかったため、「商品番号」というキーを新たに付け加えています。

15-5 第3正規化

第2正規形から第3正規化への正規化では、 主キーに依存しているが、情報として独立できる情報の分離 を行います。

発注テーブルの「注文番号」「注文日」「名前」「住所」「電話番号」「支払方法」のうち、「名前」「住所」「電話番号」は「注文番号」に依存はしてますが、それ単独で顧客情報としての独立が可能です。

第1正規化、第2正規化と同様、文章だけでは理解しづらいと思いますので、発注テーブルに対して、第3正規化を行ってみます。

主キーに依存しているが、情報として独立できるものを分離

image

発注テーブルから「名前」「住所」「電話番号」の情報を顧客情報テーブルへと分離しました。
分離の際、発注テーブルと顧客情報テーブルを紐付けるキーが存在しなかったため、「顧客番号」というキーを新たに付け加えています。

15-6 正規化のまとめ

第1正規化

  • 導出項目を削除する
  • テーブルの繰り返し項目を別のテーブルへ分離する

第2正規化

  • 非キー依存の情報を別のテーブルへ分離する

第3正規化

  • 主キーに依存しているが、情報として独立できるものを別のテーブルへ分離する

正規化を行うことにより、データの整合性を保ったまま、データの冗長性が軽減され、データを効率的に管理できるようになります。

実際に非正規形である発注テーブルを第3正規化まで行った結果が以下となります。

image

次に、正規化したテーブルを結合する方法について紹介していきますので、既に作成してあるproductsを除く以下3つのテーブル作成とレコード追加を行います。

image
  • テーブル作成の際、ordersのorder_idとcustomersのcustomer_idにはA_Iを必ず設定してください。
  • データ型は適切な設定を行ってください。
    phone_numberは「VARCHAR」型、order_dateは「DATETIME」型を設定する必要があります。

15-7 第3正規化テーブルの作成

第3正規化後のテーブルのCREATE文は以下になります。以下のSQLをphpMyAdminで実行してください。

発注テーブル「orders」

CREATE TABLE orders (  order_id INT AUTO_INCREMENT,  customer_id INT,  order_date DATETIME,  primary key(order_id));

商品テーブル「products」

一度既存のproductsテーブルを削除(DROP)し、再度作成してください。

DROP TABLE products; CREATE TABLE products (  product_id INT AUTO_INCREMENT,  product_name VARCHAR(100) COLLATE utf8_general_ci,  price INT DEFAULT 0,  primary key(product_id));

COLLATE文では、テーブルにおける照合順序の設定をしています。

発注詳細テーブル「order_details」

CREATE TABLE order_details (  order_id INT,  product_id INT,  quantity INT DEFAULT 0);

顧客テーブル「customers」

CREATE TABLE customers (  customer_id INT AUTO_INCREMENT,  customer_name VARCHAR(100) COLLATE utf8_general_ci,  phone_number VARCHAR(11),  primary key(customer_id));

サンプルデータ

以下のクエリを一括実行することで、サンプルデータが挿入されます。サンプルデータは以降の章で利用します。

INSERT INTO products (product_name, price) VALUES('マウスパッド', 100),
('イヤホン', 2000),('傘', 500),('お茶', 100),('サイダー', 100); 
INSERT INTO customers (customer_name, phone_number) VALUES('佐藤一郎', '0345670000'),('鈴木誠', '09099991111'),('山田葵', '0378902222'); 
INSERT INTO orders (customer_id, order_date) VALUES(1, '2017-10-01 10:22:30'),(2, '2017-10-01 18:51:06'),(3, '2017-10-02 09:14:35'),(1, '2017-10-03 11:00:57'); 
INSERT INTO order_details (order_id, product_id, quantity) VALUES(1, 1, 3),(1, 5, 3),(2, 2, 1),(3, 1, 10),(3, 4, 10),(4, 1, 5);

16-1 テーブルの結合方法

リレーショナルデータベースの大きな特徴の1つである、 テーブル結合 について見ていきましょう。

結合にはいくつか方法がありますが、ここでは実際によく利用する INNER JOIN と LEFT OUTER JOIN をメインに説明していきます。

結合方法種別内容
INNER JOIN内部結合結合したそれぞれのテーブルの、指定したカラムの値が一致するレコードだけを取得する
LEFT OUTER JOIN外部結合結合したテーブルの、左側に位置するテーブルを軸にして外部結合する。指定したカラムの値が一致するレコードに加えて、左側にしか存在しないデータも取得する

16-2 INNER JOIN

INNER JOIN(内部結合)は、 指定したカラムの値が一致するレコードだけ を取得します。

以下は、受注テーブル(orders)と顧客テーブル(customers)をINNER JOINした場合の図です。

image

このように、同内容のカラムの値が一致するレコード同士を結合し、1つのレコードにまとめることができます。

INNER JOINのサンプルSQLは以下のとおりです。

SELECT  *FROM  orders  INNER JOIN customers  ON orders.customer_id = customers.customer_id;

理解を深めることを優先し、「SELECT * ~」を使用していますが、本テキストでは「*」は非推奨のため、開発では利用しないようにしましょう。

image

上記サンプルでは、受注テーブルと顧客テーブルに共通して存在する「customer_id」を使って、一致する両テーブルのレコード同士を結合しています。
受注テーブルには「customer_id」が 1 のレコードが2件ありますが、どちらも顧客テーブルの「customer_id」が 1 のレコードと結合します。
テーブル結合をすると、結合した両テーブルを併せて1つのテーブルとして扱います。

続いて、結合したテーブルから参照するカラムを指定します。

image

参照するカラムを指定したサンプルSQLは以下のとおりです。

SELECT  orders.order_id,  orders.order_date,  customers.customer_nameFROM  orders  INNER JOIN customers  ON orders.customer_id = customers.customer_id;
image

同じく、受注テーブルと顧客テーブルを結合していますが、SELECT句で参照するカラムを絞っています。
また、SELECT句の中で参照するカラムを指定する際に、 orders. や customers. を付加しています。
これは結合した両テーブルのうち、どちらのテーブルに所属するカラムなのかを明示的に指定するためです。
もし「customer_id」を参照する場合は、受注テーブルと顧客テーブルのどちらにも所属するカラムのため、orders.でもcustomers.でもどちらでも構いません。

16-3 INNER JOINの特徴

次に、INNER JOINの具体的な特徴について説明します。

INNER JOINは「 ONの条件に指定したカラムの値が一致するレコードだけ 」を取得します。
商品テーブルと受注詳細テーブルを結合するSQLを作成し、実行してみましょう。

image

商品テーブルと受注詳細テーブルを内部結合するサンプルSQLは以下のとおりです。

SELECT  products.product_id,  products.product_name,  products.price,  order_details.quantityFROM  products  INNER JOIN order_details  ON products.product_id = order_details.product_id;
image

INNER JOINによって「product_idに紐づくレコードのみ取得」が行われ、結果は6レコードとなりました。

「product_id」が 3 のレコードは受注詳細テーブルには存在しません。 これは、受注詳細テーブルに「product_id」が 3 のレコードが存在せず、「カラム値が等しい」という条件に一致しないためです。

このように、指定したカラムの値が一致するレコードだけ を取得することがINNER JOIN(内部結合)の特徴となります。

16-4 LEFT OUTER JOIN

LEFT OUTER JOIN(外部結合)は、指定したカラムの値が一致するレコードに加えて、左側にしか存在しないデータも取得します

以下は、商品テーブル(products)と受注詳細テーブル(order_details)をLEFT OUTER JOINした場合の図です。

image

商品テーブルと受注詳細テーブルを外部結合するサンプルSQLは以下のとおりです。

SELECT  products.product_id,  products.product_name,  products.price,  order_details.quantityFROM  products  LEFT OUTER JOIN order_details  ON products.product_id = order_details.product_id;
image

商品テーブルの「product_id」の値が一致するレコードに加えて、商品テーブルにしか存在しない「product_id」が 3 のデータも取得され、結果は7レコードとなりました。

「product_id」が 3 のレコードは受注詳細テーブルに存在しませんが、LEFT OUTER JOINでは結合元(左側のテーブル)のレコードは全て取得するため、quantityを「 NULL 」としてレコードを表示しています。
これがLEFT OUTER JOIN(外部結合)の特徴となります。

LEFT OUTER JOINは、商品テーブルと受注詳細テーブルのように商品情報と売上情報のテーブルが分かれている場合、売上がない商品も表示してくれるため、全ての商品の売上を一覧で参照することができます。

16-5 複数テーブルの結合

これまでのテーブル結合は2つのテーブルのみでしたが、3つ以上のテーブルもINNER JOINやLEFT OUTER JOINにより結合することができます。

受注テーブル(orders)、受注詳細テーブル(order_details)、商品テーブル(products)を結合し、全レコードを表示してみましょう。

image

受注テーブル、受注詳細テーブル、商品テーブルを結合するサンプルSQLは以下のとおりです。

SELECT  *FROM  orders  INNER JOIN order_details  ON orders.order_id = order_details.order_id  INNER JOIN products  ON order_details.product_id = products.product_id;

理解を深めることを優先し、「SELECT * ~」を使用していますが、本テキストでは「*」は非推奨のため、開発では利用しないようにしましょう。

image

2つのテーブルを結合した後、さらに結合したテーブルと3つ目のテーブルを結合します。

image

4つ以上結合する場合も考え方は同じです。
また、今回は内部結合を行いましたが、外部結合で3つ以上のテーブルを結合することも可能です。

16-6 テーブルの別名

SQLを記述する際に、SELECT句のカラム名やFROM句のテーブル名に AS を指定することで、別名をつけることができます。

ASを用いたテーブルの別名

SELECT  p.product_id,  p.product_name,  p.priceFROM  products AS pwhere  p.product_id = 1;

テーブル名を複数箇所で使用する場合、SQLが長くなりがちですが、 AS を利用することで別名をつけることができ、SQLを短く記述することができます。

ASを用いたカラムの別名

SELECT  p.product_id AS 商品ID,  p.product_name AS 商品名,  p.price AS 価格FROM  products AS pwhere  p.product_id = 1;

テーブル名だけでなく、カラム名にも AS が利用できます。
ASには日本語も使用することができるので、SQLの実行結果を自分以外の誰かに確認してもらう場合や、分析に利用する際にとても便利です。

別名をつけるだけなのでSQLの結果に変わりはありませんが、結合するテーブル数や条件が多くなるほどSQLは長くなりますので、上手にAS句を活用しましょう。

17-1 データのグルーピング

特定のカラムで同じ値を持つデータをまとめる グルーピング と、昇順または降順での並び替えを行うことができる 順序 について見ていきましょう。

GROUP BY

GROUP BY句 を利用することで、特定のカラムで同じ値を持つデータをまとめることができます。
これをグルーピング(グループ化)と呼びます。
データのグルーピングを行うことで、 集計やレコード数の合計を求める といった処理を簡単に行うことができます。

GROUP BYを使ったSQLの書式は以下のとおりです。

SELECT  カラム名FROM  テーブル名GROUP BY  カラム名;

ただし、上記のような単純にGROUP BYを使っただけのSQLはほぼ利用することはなく、 何かしらの値を集計して利用したい場合 によくGROUP BYを利用します。

GROUP BYを利用することで実現できる主な集計処理は以下のとおりです。

集計処理記述内容
合計件数COUNT(カラム名)グルーピングされた値のレコード件数を求める
合計値SUM(カラム名)グルーピングされた値の合計値を求める
最大値MAX(カラム名)グルーピングされた値の中での最大値を求める
最小値MIN(カラム名)グルーピングされた値の中での最小値を求める
平均値AVG(カラム名)グルーピングされた値の中での平均値を求める

以下の社員テーブルを作成し、GROUP BYを実際に使ってみましょう。

社員テーブル(employees)

カラム名内容
id社員ID
name社員名
age社員の年齢
post社員の役職
salary社員の年俸

社員テーブルを作成するSQLは以下のとおりです。

CREATE TABLE employees (  id INT,  name VARCHAR(100) COLLATE utf8_general_ci,  age INT,  post VARCHAR(10) COLLATE utf8_general_ci,  salary INT);

サンプルデータを登録するSQLは以下のとおりです。

INSERT INTO employees (id, name, age, post, salary) VALUES(1, '社員A', 24, '一般', 3600000),(2, '社員B', 27, '一般', 3800000),(3, '社員C', 26, '一般', 3700000),(4, '社員D', 32, '係長', 3900000),(5, '社員E', 33, '係長', 4000000),(6, '社員F', 38, '課長', 4300000),(7, '社員G', 52, '部長', 5200000),(8, '社員H', 46, '社長', 9000000);
image

合計件数

社員数のように、レコードの件数を求めるには、 COUNT を使用します。

  • 社員の合計人数を求める場合
SELECT  COUNT(id) AS 合計人数FROM  employees;
image
  • 役職ごとの社員の合計人数を求める場合
SELECT  post AS 役職,  COUNT(id) AS 合計人数FROM  employeesGROUP BY  post;
image

合計値

給与合計のように、カラムの合計値を求めるには、 SUM を使用します。

  • 社員の給与の合計値を求める場合
SELECT  SUM(salary) AS 給与の合計値FROM  employees;
image
  • 役所ごとの給与の合計値を求める場合
SELECT  post AS 役職,  SUM(salary) AS 給与の合計値FROM  employeesGROUP BY  post;
image

最大値

カラムの最大値を求めるには、 MAX を使用します。

  • 年齢の最大値を求める場合
SELECT  MAX(age) AS 年齢の最大値FROM  employees;
image
  • 役職ごとの年齢の最大値を求める場合
SELECT  post AS 役職,  MAX(age) AS 年齢の最大値FROM  employees GROUP BY  post;
image

最小値

カラムの最小値を求めるには、 MIN を使用します。

  • 年齢の最小値を求める場合
SELECT  MIN(age) AS 年齢の最小値FROM  employees;
image
  • 役職ごとの年齢の最小値を求める場合
SELECT  post AS 役職,  MIN(age) AS 年齢の最小値FROM  employeesGROUP BY  post;
image

平均値

カラムの平均値を求めるには、 AVG を使用します。

  • 給与の平均値を求める場合
SELECT  AVG(salary) AS 給与の平均値FROM  employees;
image
  • 役職ごとの給与の平均値を求める場合
SELECT  post AS 役職,  AVG(salary) AS 給与の平均値FROM  employeesGROUP BY  post;
image

GROUP BYを使ってそれぞれの結果をまとめると、次のようなSQLになります。

SELECT  post AS 役職,  COUNT(id) AS 合計人数,  SUM(salary) AS 給与の合計値,  MAX(age) AS 年齢の最大値,  MIN(age) AS 年齢の最小値,  AVG(salary) AS 給与の平均値FROM  employeesGROUP BY  post;
image

17-2 データの順序

SELECTを利用しデータを参照する際に、データの順序(並び順)を指定することができます。

ORDER BY

ORDER BY を指定することで、指定されたカラムの値に応じて昇順・降順に並び替えを行うことができます。
ORDER BYの書式は以下のとおりです。

SELECT  カラム名FROM  テーブル名ORDER BY  カラム名;

ORDER BYで順序を指定しない場合、順序が保証されない(基本的にはデータが追加された順番になります)ため、順序を指定する必要がある場合はORDER BYを利用するようにしましょう。

  • 年齢順に社員一覧を表示する場合
SELECT  id,  name,  age,  post,  salaryFROM  employeesORDER BY  age;
image
  • 給与順に社員一覧を表示する場合
SELECT  id,  name,  age,  post,  salaryFROM  employeesORDER BY  salary;
image
  • WHERE句と組み合わせて、31歳以上の社員を給与順に表示する場合
SELECT  id,  name,  age,  post,  salaryFROM  employeesWHERE  age > 30ORDER BY  salary;
image
  • GROUP BYと組み合わせて、31歳以上の社員のうち、役職ごとに一番高い年齢を給与順に表示する場合
SELECT  post,  MAX(age),  salaryFROM  employeesWHERE  age > 30GROUP BY  postORDER BY  salary;
image

降順・昇順

順序を決める際に、ORDER BY句のカラムの後ろに ASC または DESC を付加することで、昇順または降順での並び替えを行うことができます。
何も指定しない場合は昇順となります。また、複数指定することも可能です。

順序書式内容
昇順ORDER BY カラム名 ASC指定したカラムの値を昇順に並び替える(デフォルト)
降順ORDER BY カラム名 DESC指定したカラムの値を降順に並び替える
  • 給与の降順で社員一覧を表示する場合
SELECT  id,  name,  age,  post,  salaryFROM  employeesORDER BY  salary DESC;
image

最後に

どうでしたか?何度も繰り返しながら覚えていってくださいね

SQLコマンド一覧は下記に整理いたしましたのでご活用くださいね

Comments

comments