MySQL – JOIN – INNER/LEFT/RIGHT/FULL OUTER

準備

以下の2つのテーブルを準備する。

students
学生IDと学生名を保存。
courses
学科IDと学科名を保存。

studentsテーブルの作成とデータ登録のクエリー。

実行結果。

coursesテーブルの作成とデータ登録のクエリー。

実行結果。

JOIN~総当たり

FROM句のテーブルに対して単にJOINでテーブルを指定すると、2つのテーブルの全要素の組み合わせが得られる。

実行結果。

結合テーブル

studentsテーブルとcoursesテーブルの結合テーブルを準備する。各学生の履修学科に関するデータ。

クエリーの実行結果、

INNER JOIN

結合テーブルに基づいて、2つのテーブルの両方に存在するデータを取り出す。studentscoursesの積集合に相当する。INNERは書かなくても内部結合になる。

student_coursesテーブルとstudentsテーブルを結合するクエリーの例。結合の結果、studentsテーブルから学生名を得られる。

実行結果。

3つのテーブルの場合、JOIN ... ON句を並べればよい。以下の例ではcoursesテーブルも結合して、学科名も得ている。

実行結果。student_coursesテーブルは学生IDと学科IDしか持っていないが、studentsテーブル、coursesテーブルと結合することで学生名と学科名を使えるようになる。

LEFT OUTER JOIN

LEFT OUTER JOINは、先に指定されたテーブル(左テーブル)の全レコードに対して、JOINで指定したレコードを対応させる。左テーブルに対応するレコードがないときはNULLになる。LEFT JOINと書いても同じ。

以下のクエリーは、studentsの全学生データに対してstudent_coursesの履修科目データを左結合させる。studentsには登録されているがstudent_coursesにない学生(学科を履修していない学生)についてはNULLになる。

実行結果は以下の通りで、学生IDの伊藤君はstudent_coursesテーブルに存在しないので、course_idNULLになっている。

学科名も欲しい場合は、coursesテーブルを左結合させる。

実行結果。学科を履修していない伊藤君のデータは、course_nameについてもNULLになる。

RIGHT OUTER JOIN

RIGHT JOINは後から指定されたテーブルの全データに対して、先に指定されたテーブルを結合させる。先に指定されたテーブルに対応するレコードがない場合にはNULLになる。RIGHT JOINと書いても同じ。

以下は、student_coursesテーブルのデータにcoursesテーブルのデータを右結合させている。どの学生にも履修されていない学科がある場合はNULLになる。

実行結果。化学はどの学生にも履修されていないのでNULLになっている。

3つ以上のテーブルでは、最も右側のテーブルをRIGHT JOINを連ねる。最後以外のJOINRIGHTがなくても同じ結果になる。

実行結果。履修されていない化学の学生IDと学生名がNULLになっている。

RIGHT JOINのテーブルを入れ替えることで、LEFT JOINで同じ結果を得られる。履修科目に関する上の例をLEFT JOINで書きなおすと以下の通り。

実行結果は同じ。

FULL OUTER JOIN

FULL OUTER JOINは指定されたテーブルの全データを含める。いずれかのデータが存在しない場合はNULLとなる。テーブルのデータ群の和集合に相当する。

MySQLにはFULL JOINが定義されていないので、同等の機能はUNIONで実現する。

  • 2つのテーブルの場合にはLEFT JOINRIGHT JOINの結果をUNION
  • 結合テーブルがある場合には、それぞれのLEFT JOINの結果をUNION
  • UNIONの際に、各SELECTのカラムを同じにしておく

以下は、studentscoursesに対するstudent_coursesLEFT JOINの結果をUNIONで結合している。科目を履修していない学生は学科がNULLとなり、履修されていない科目が学生がNULLとなる。

実行結果。学科を履修していない伊藤君の学科欄はNULLになり、だれにも履修されていない化学は学生欄がNULLになっている。

カラム名の指定によって結果が違う

LEFT OUTER JOINのところで、学生IDと学科IDをstudents.idcouses.idのようにそれぞれのテーブルから指定していた。

一方で、同じIDをstudent_courseのstudent_idcouse_idで指定することもできる。こちらで指定すれば、識別のためのエイリアスを定義しなくていいので便利そうだ。

しかしこれらを使うと、OUTER JOINの際に存在しないIDとして扱われてNULLになってしまう。

上のクエリーを実行した結果が以下で、化学を履修している学生がいないので、student_coursesに該当するデータがなく、student_idcourse_idNULLになっている。

これらを表示させたいときには、LEFT OUTER JOINの例のように、左結合される側(全データが使われる側)のテーブルでカラムを指定するとよい。

 

MySQL – 外部キー制約

外部キーの設定

テーブル構成

学生の履修科目に関する簡単なデータベースを考える。

  • studentsテーブルは学生に関するデータを保存
    • 学生ID(主キー)
    • 学生の名前
  • student_coursesテーブルは学生の履修科目に関するデータを保存
    • 履修科目ID(主キー)
    • 学生ID
    • 科目名

学生テーブル

以下のクエリーでテーブルを作成し、学生データを登録。

登録結果の確認。

履修科目テーブル~外部キーの設定

以下のクエリーで履修科目テーブルを作成。student_idを外部キーとして、studentsテーブルのid(学生ID)を参照している。

外部キーを設定するカラムについて、以下のように定義する。

FOREIGN_KEY (参照するキー) REFERENCES 参照されるテーブル(参照されるキー)

テーブル構造を確認すると、student_idKey欄がMULとなる。このMULについてはぴったりの情報が得られなかったが、以下のようなことらしい。

  • 外部参照するキーにはインデックスが設定される
  • PRIMARY KEYINIQUEが設定されていないインデックスは同じ値を取り得る
  • なのでmultiple keyMULと表示される

外部キー制約の確認

正常な登録

新しいデータをstudent_coursesテーブルに登録する。student_idとして登録済みのstudents.id = 1の学生IDを指定する。

正常に登録されて、students_coursesテーブルは以下のようになる。

存在しないデータの登録はエラー

student_idに、studentsテーブルでは登録されていない値を指定する。

外部キー制約でエラーとなる。

参照されている親のデータは消せない

student_coursesテーブルのデータから参照されている、studentsテーブルのid=1のデータを削除しようとするとエラーになる。

参照されていない親のデータは削除できる

student_coursesのデータから参照されていない、studentsテーブルのid=2のデータは削除できる。

不整合時の挙動~RESTRICTやCASCADEなど

FOREIGN KEY指定時に、参照先のデータの削除時と変更時の挙動を指定できる。

ON DELETE 挙動指定 ON UPDATE 挙動指定

挙動指定には以下の4つがあり、指定しない場合はRESTRICTとなる。

RESTRICT
DELETE, UPDATEともエラーになる。
CASCADE
DELETEでは参照元のデータも削除され、UPDATEでは参照先の変更が参照元のデータに反映される。
SET NULL
DELETE, UPDATEともNULLに置き換わる。
NO ACTION
RESTRICTと同じ挙動。

外部キー制約をCASCADEに変更する

CREATE TABLEの確認

外部キー制約設定後のテーブル定義を以下のコマンドで確認してみる。

SHOW CREATE TABLE テーブル名\G;

  • 最初に実行したクエリーの内容に即している
  • CONSTRAINT `外部制約名`が追加されている

挙動の変更

外部キー制約の挙動をRESTRICTからCASCADEに変更する。外部キーの操作はALTER TABLEで行うが、一度に変更できないので一旦外部キーを削除し、新たな条件で外部キーを追加する。

外部キーの削除

外部キー削除のコマンドは以下の通り。

ALTER TABLE 参照元テーブル DROP FOREIGN KEY 外部制約名;

外部制約名は、先のSHOW CREATE TABLEで確認した内容で指定する。

外部キー削除後もインデックスは残っている。

外部キー削除後のCREATE TABLEを確認すると、student_idにインデックスが設定されている。

外部キーの追加

以下のクエリーで削除・更新時の挙動を指定して外部キーを追加。

クエリー実行。

CREATE TABLE確認。

CASCADEの挙動

ON UPDATE CASCADE

参照先、親テーブルstudentsのデータのid=39に変更する。RESTRICTではエラーになったが、CASCADEの場合は変更が通る。

student_coursesstudent_idも変更されている。

ON DELETE CASCADE

参照先、親テーブルのid=9のレコードを削除する。RESTRICTではエラーになったが、CASCADEの場合は削除される。

student_coursesstudent_id=9のレコードも削除されている。

外部キー制約の追加・削除

外部キーの追加は、ALTER TABLE ... ADD FOREIGIN KEYで行う。被参照キーにインデックスが設定されている必要がある。

外部キーの削除はALTER TABLE ... DROP FOREIGN KEYで行う。制約名はSHOW CREATE TABLEで確認できる。

 

MySQL – テーブルの作成クエリーを表示する

作成済みのテーブルの作成クエリーを表示させることができる。

SHOW CREATE TABLE テーブル名\G

列は1列だが幅が広いので\Gで縦表示している。

 

MySQL – レコード登録・更新のタイムスタンプ

レコードを登録・更新した場合のタイムスタンプを保存するためのテーブル設定。

  • 日時の型はDATETIMEで設定
  • 作成時の初期値を作成時点の日時とするにはDEFAULTを指定
    • DEFAULT CURRENT_TIMESTAMP
  • 更新時に更新時点の日時で書き換える場合はON UPDATEを指定
    • ON UPDATE CURRENT_TIMESTAMP

テーブル作成のSQL例。

SQL実行結果。

テストデータ登録。

2つ目のデータを変更して、更新日時が変更されていることを確認。

 

Laravel – 現在のURLの取得

url()ヘルパー

ヘルパーLaravel - url()を参照。現在のドメインやURLに関する情報が得られる。

Route::current()

以下のメソッドでドメイン名を除いた現在のフルパスが得られる。

これとurl()ヘルパーを組み合わせて、以下でもカレントパスを得られる。

 

Git – リモート – 新しいリポジトリー

概要

新しくリポジトリーを作成してリモート~ローカルの連携を始めるには、以下の2つの方法がある。何れの方法でも、まずリモートリポジトリーを作成しておく必要がある。

  • リモートのリポジトリーをローカルにcloneする方法
  • リモートのリポジトリーにローカルリポジトリーをpushする方法

また、その後の接続・認証方法(https/SSH)に応じて、URLの指定方法が異なる。

リモートリポジトリー作成

いずれにしてもリモート上に新たなリポジトリーを作成し、リモートリポジトリー本体とそのURLを確定させておく必要がある。

ここではGitHub上に新たなリポジトリーtestを作成する。

方法1:Gitでclone

流れ

リモートで作成したリポジトリーをローカルでcloneして変更を加えていく。リポジトリーに対して、プロジェクトディレクトリーなどを加えていく。

最初のcloneで指定したURLによって、その後のアクセスがhttpsかSSHが決まる。

git cloneコマンドの実行

リポジトリーのワーキングツリーの「親ディレクトリーで」、リモートリポジトリーをclone。リモートリポジトリー作成直後では、「空のリポジトリーをcloneしている」旨の警告が表示される。リモートでREADME.mdを作成していればこの警告は出ない。

cloneの際のURLの指定方法によって、その後のアクセス方法がhttpsかSSHかが決まる。ここではSSH認証になるようにURLを指定している。

git@github.com:アカウント/リポジトリー名.git

ワーキングツリーへの移動

cloneの結果作成されたワーキングツリーに移動する(意外にこれを忘れる)。

空のリモートリポジトリーをcloneした状態ではディレクトリーは空で、ブランチも定義されていない。リモートでREADME.mdを作成していればディレクトリーにはファイルが存在し、GitHubならブランチ名はmainになっている。

ローカルでの作業・コミット

ここではテキストファイルtest.txtを1つ作ってコミットする。

プッシュ

cloneの際にSSH認証のURLを指定しているので、以後はSSHによるpush/pullになる。

方法2:Gitからのpush

リモートリポジトリーを作成しておき、そこにGitで作成したローカルリポジトリーをプッシュする。プロジェクトディレクトリーそのものをリポジトリーとしてpush可能。

ローカルリポジトリー作成

ワーキングツリーのディレクトリーを作成し、そこでgit init

ローカルリポジトリーで作業・コミット

ここではテキストファイルtest.txtを1つ作ってコミットする。

単にpushするとエラー

このままpushしようとしても、相手先のリモートの所在がわからないためエラーになる。

リモートリポジトリー追加

リモートリポジトリーのURLを指定して追加。

git remote add リモート名 URL

URLはhttpsとSSHで異なってくるが、ここではSSH認証のURLを指定。

git@github.com:アカウント/リポジトリー

追加されたリモートリ名は以下で確認できる。

git remote -v

プッシュ

ブランチ名のmainへの変更

git init直後にブランチ名がmasterになっている場合、リモートがGitHubのデフォルトならmainに変更する必要がある。

ブランチ名をmainに変更する。カレントブランチのブランチ名変更はgit branchコマンドで-m/-M/--moveオプションを使う。

git branch -m/-M/--move 変更後ブランチ名

初期プッシュ

最初のみ上流ブランチを指定してプッシュ

git push -u/--set-upstream リモート リモートブランチ

上流ブランチを指定しないとpushのたびにリモートとリモートブランチを指定する必要がある。

これ以後はgit push/pullのみで可能となる。

プッシュ結果の確認

方法1・方法2とも、GitHubなどのリモートでプッシュの反映を確認できる。

 

 

 

Git – rebase -i~コミットの統合

概要

git rebaseは今いるブランチのベースを変更するが、-i (intaractive)オプションを付けることで、複数コミットを統合することができる。

interactiveという通り、その過程で何度かエディターが起動して操作内容が表示され、コミット統合に必要な内容に書き換えていく。

準備

以下のように、sample.txtに1行ずつ追加するコミットを含むリポジトリーを準備する。

2つのコミットの統合

rebaseコマンド

main-2main-3のコミットを統合するため、その直前=main-1のコミット直後を指定してrebaseする。このとき-iオプションを付けることで、リベースの過程での操作を指定できる。

各コミットへの対応記述

rebaseを実行するとエディターが起動して以下のように表示される。

  • この段階では、ベースとなるmain-1より後のmain-2main-3の2つのコミットについて、そのまま適用されることになる
  • つまり、main-1コミットの後にmain-2main-3がそのまま連なることになり、何も変わらない
  • これを、main-2main-3をまとめた上でmain-1の後に来るようにしたい

Commands:のところを見ると、以下のように読める。

  • p, pickはそのコミットをそのまま使う
  • s, squashはそのコミットを使うが、1つ前のコミットに統合(融合?)させる

そこで、後の方のコミットmain-3pickからsquashに変更する。こうすれば、main-2コミットはそのまま使われ、そこにmain-3コミットも統合されるはず。

コミットメッセージの編集

上記の内容で保存・終了すると、さらにエディターが起動して以下のように表示される。

2つのコミットを1つにするので、コミットメッセージを1つだけにして、終了の空行を入れる。

処理結果

上記の内容を保存・終了すると、rebaseの処理内容が以下のように表示される。

ログで確認すると、main-2main-3が1つにまとめられ、指定したコミットメッセージになっている。

コミットをまとめただけなので、sample.txtの内容は変化しない。

3つ以上のコミットの統合

main-13の3つの連続するコミットをまとめるため、その直前のコミットを指定してrebase。

エディターが起動。

以下のようにpicksquashに変更して保存。これにより、main-2main-1に統合され、そこにmain-3も統合される。

再度エディターが起動。

統合後のコミットメッセージを編集して保存・終了。

実行結果。

3つのコミットが1つにまとめられた。

イニシャルコミットとの統合

一番最初のコミットと統合するには、以下のコマンドを使う。

git rabase -i --root

以下の例では、README.mdを修正した2つ目のコミットを1つ目のInitial commitに統合している。

リモートへの反映

リモートからpullしたリポジトリーの場合、そのままではpushできない。

ローカルでのrebaseの結果をリモートにそのまま反映するには、push -fで強制的に上書きする。

ただし他のローカルでその内容を反映させるには、改めてcloneする必要がある。

 

Git – マージ – squash~ブランチをまとめて取り込む

概要

git merge--squashオプションを付けると、指定したブランチの全コミットを1つにまとめて今のブランチに追加する。

準備

以下のようなリポジトリーを使う。

  • mainブランチのコミットでREADME.txtに新規書き込み
  • topicブランチの2つのコミットでREADME.txtに新規書き込みと1行追加

マージ

mainブランチで、--suquashオプションを指定してgit mergetopicブランチをマージ。今回は同じREADME.txtを編集しているので競合が発生している。

README.txtの競合状態を確認。

README.txtを編集して競合を解消。

結果をadd、コミット。

no-fast-forwardマージとしてエディターが起動。内容を確認して保存・終了。

コミット結果が表示される。

ログで確認。topicブランチでの2つのコミットが1つのsquashed commitとしてつなげられた。

マージの取り消し

git reset --hard ORIG_HEADでマージ前に戻ることができる。

ログも元に戻る。

 

Git – cherry-pic – コミットを取り込む

概要

  • git cherry-pickは他のブランチのコミットの一部を今のブランチに取り込む
  • 1つないし複数のコミットを指定可能
  • 結果は自動的にコミットされるが、-nオプションでステージングのレベルに留めることができる

英語の”cherry pick”はサクランボ狩りのことで、いいサクランボを選んで摘むことから、次のような使われ方をするようだ。

  • 自分の気に入ったものだけを選ぶ
  • 選り好みする
  • 品物や人材を厳選する
  • 目玉商品を探し漁る
  • 都合のいいデータ・証拠だけを選んで偽装する

準備

次のような内容のローカルリポジトリーを使う。それぞれのコミットで、対応するファイルが作成される。

cherry-pick実行

mainブランチに、topicブランチのtopic1コミットだけを取り込みたいとする。

mainブランチで、topic1コミットのチェックサムを指定して取り込む。

git cherry-pic コミット指定

結果は以下の通りで、topicブランチのtopic1コミットがmainブランチにマージされている。

暫定的なcherry-pick

-nオプション

コミットせずに確認だけしたい場合は-nオプションを付ける。そうすると、コミットはされずにステージングエリアに上げられた状態で留まる。

ワーキングツリーではファイルは作成されている。

しかしコミットされていないので、ログには反映されていない。

ファイルはステージングエリアにあり、コミット前の状態。

暫定cherry-pickの破棄

暫定的なcherry-pickの内容を破棄したい場合は、addを取り消してファイルを削除する。

まず、git reset HEADaddを取り消す。

そして作成されたファイルを削除。

暫定cherry-pickのコミット

-nオプションによる暫定的なcherry-pickの結果を正式にコミットしたい場合は、git commitを実行する。

ログで確認。

 

Git – reset – コミットの巻き戻し

概要

  • git resetは指定したコミットの実行直後まで処理を巻き戻す
  • それより後のコミットの実行結果は取り消され、履歴も消される
  • コミットの指定には、HEADからの相対指定による方法と対象コミットの絶対指定による方法がある
  • 直前のresetのコミット位置はORIG_HEADに保持されているので、resetの取り消しは可能

準備

Git – タグで使った以下のリポジトリーを流用。

最新のsample.txtの内容を確認。

HEADからの相対位置でreset

HEADから2つ遡ったcommit-1終了時までresetするため、HEAD~~を指定。

get reset --hard HEAD~~

あるいは2つ前を数値指定することもできる。

get reset --hard HEAD~2

ログを確認すると、commit-1以降のコミットがなくなっている。

sample.txtの内容も、commit-1実行直後の内容になっている。

resetの取り消し~ORIG_HEAD

reset前のコミット位置はORIG_HEADに保持されている。以下のコマンドで、直前のresetを取り消すことができる。

git reset --hard ORIG_HEAD

ログも元に戻っている。

コミットを絶対指定したreset

遡りたいコミットのチェックサム(の一部)やタグを指定してリセットできる。

先の例でcommit-1まで遡りたい場合、以下は等価。

  • git reset --hard f69f176435cf6436c3b3329af644999ba96da862
  • git reset --hard f69f176
  • git reset --hard beta

チェックサムの一部は元のチェックサムの一部になっていて、git log --onelineでも表示される。