NULLール〜SQLの基本とNULLについて〜

f:id:monozukuri-bu:20190827200842j:plain

ふっちーです。
SQLのブログは初投稿です。

Oracle MASTER Bronze 12c SQLの資格取得を目指して勉強しております。
今回は基礎の部分とNULLを中心に取り上げていきます。

目次

・用語
・NULLについて
・記述の順番

テーブル

テーブル名:cope

| number | name | Salary | perpay |
|--------|------|--------|--------|
|      1 |佐々木| 180000 | (null) |
|      2 | 富田 | 200000 | (null) |
|      3 | 桑原 | 730000 | (null) |
|      4 | 衛藤 | 360000 |  70000 |
|      5 | 田中 | 310000 |  40000 |
|      6 |  林  | 270000 | (null) |
|      7 | 高木 | 190000 |  50000 |
|      8 | 藤原 | 450000 |  90000 |

以下のSQL文ではこのテーブルを使用しています。

用語

select文の機能とSQL文の分類

機能名 役割 具体例
射影 特定の列を取り出す select
選択 特定の行を取り出す where, having等
結合 表と表を横に繋げる join等

SQLと言ったら外せないselect文についてです。

個人的な経験ですが、試験において意外とつまづきやすいのがこのような名称の問題だと思っています。
他にもSQL文の分類である

DML(Data Manipulation Language)
DDL(Data Definition Language)
・DCL(Data Control Language)
トランザクション制御

SQLにはselect文の様なデータ表を表示する機能だけでなく、内容を書き換えたり、データに接続できる権限の制御まで行えます。
上の4つはその種類分けにつけられている名称です。

select文はDMLに当てはまります。

関数のタイプ

SQLには様々な関数があります。
これはSQLには数値や日付、文字データなどのデータが使用可能です。

SQLの関数には2種類あります。

・単一行関数
・グループ関数

単一行関数

単一行関数は表に対して、一行の結果を出力します。
引数には数値だけではなく、列名や式を入力できるものもあります。

いくつか例を挙げてみましょう。

trunc関数

trunc関数は数値の切り捨てを行います。

select trunc(m, n) from cope;

mを小数点n桁以下を切り捨てします。
nは任意で入力でき、入力しない場合は小数点以下を切り捨てします。
-(マイナス)で入力した場合は小数点の左側を数える。

concat関数

concat関数は文字列の結合を行います。

 select concat('m', 'n') as test from cope;
| TEST |
|------|
|   mn |

2つまでしか結合できないので、3つ以上結合したい場合は(||)を使いましょう。

グループ関数

グループ関数は列に対して、一行の結果を出力します。

こちらもいくつか例を見てみましょう。

avg関数

指定した列の平均値を求めます。
そのため、数値のみの行にしか適用できません。

 select avg("Salary") as test from cope;
|   TEST |
|--------|
| 336250 |

count関数

指定した列内のNULL以外の行数を出力します。

select count("perpay") as test from cope;
| TEST |
|------|
|    4 |

ただし、引数に*(アスタリスク)を入れるとNULLを含めて数えます。

select count(*) as test from cope;
| TEST |
|------|
|    8 |

max関数

指定した列内の最大値を出力します。

select max("Salary") as test from cope;
|   TEST |
|--------|
| 730000 |

ここでいう最大値は数値だけではなく、日付や文字列も含めます。
日付の場合は一番新しい日付が出力されます。
文字列の場合は文字コードが最も大きい文字列が出力されます。

NULLについて

SQLにおけるNULLがややこしかったのでここでまとめてみたいと思います。

四則演算子

NULLは四則演算(+, -, *, /)のどれを適用してもNULLを返します。

select 100*NULL from cope;
| 100*NULL |
|----------|
|   (null) |

NULLを0として扱いたい場合はNVL関数が使えます。
NVL関数については後述しています。

比較演算子

比較を行った場合、基本的にTrueかFalseが返ってくるのが普通ですが、ここでもNULLはNULLを返します。

select * from cope where "perpay" = NULL;

上記も四則演算同様にエラーは発生しませんが、該当なしと出力されます。

しかし、比較自体が行えないわけではありません。
where句でisを使うとNULLかどうかを判断することができます。

select * from cope where "perpay" is NULL;
| number | name | Salary | perpay |
|--------|------|--------|--------|
|      1 |佐々木| 180000 | (null) |
|      2 | 富田 | 200000 | (null) |
|      3 | 桑原 | 730000 | (null) |
|      6 |  林  | 270000 | (null) |

ORDER BYでNULLはどこに来る?

order byは表のソートを行う機能です。

select * from table order by 列名;

この時列にNULLが含まれている場合どうなるでしょうか。
まず、昇順で並べてみましょう。

select "perpay" from cope order by "perpay";
| perpay |
|--------|
|  40000 |
|  50000 |
|  70000 |
|  90000 |
| (null) |
| (null) |
| (null) |
| (null) |

次に、降順で並べてみましょう。

select "perpay" from cope order by "perpay" desc;
| perpay |
|--------|
| (null) |
| (null) |
| (null) |
| (null) |
|  90000 |
|  70000 |
|  50000 |
|  40000 |

このように、ソートでは1番大きいものとして扱われます。

NULLを使った関数

前述した汎用関数はNULLを使用できます。

NVL関数

select nvl("perpay", 0) as test from cope 
|  TEST |
|-------|
|     0 |
|     0 |
|     0 |
| 70000 |
| 40000 |
|     0 |
| 50000 |
| 90000 |

式がNULLの場合、設定された値を戻します。
例の場合はNULLの時0を返します。

NVL2関数

select nvl2("perpay", 1, 0) as test from cope 
| TEST |
|------|
|    0 |
|    0 |
|    0 |
|    1 |
|    1 |
|    0 |
|    1 |
|    1 |

式がNULLでないなら1つ目の値を、NULLなら2つ目の値を戻します。 例の場合はNULL以外で1、NULLで0を出力します。

NULLIF関数

select nullif("Salary", 180000) as test from cope 
|   TEST |
|--------|
| (null) |
| 200000 |
| 730000 |
| 360000 |
| 310000 |
| 270000 |
| 190000 |
| 450000 |

2つの式を比較し、等しい場合はNULLを返す。異なる場合は1つ目の式を戻します。
例の場合はSalaryが180000のときNULLを返します。

まとめ

今回はSQLの用語とNULLについてまとめました。

特にNULLは先月まで勉強していたPythonでは見られない動作をするので、なんとかして慣れたいです。
また、個人的に今一番の壁が相関副問合せなので、これも克服していきたいです。

OracleDatabase SQL VIEW、順序について

f:id:monozukuri-bu:20190827194855j:plain

みちです。
Oracle DatabaseのVIEWと順序の作成・削除についての勉強していきたいと思います。

VIEW

 表や他のVIEWをもとに作成されるオブジェクトです。VIEWはデータを持っていません。
VIEWのもととなる表を「実表」、VIEWを「仮想表」といいます。

VIEWの種類

単一VIEW

 一つの表から構成され、関数やGROUP BY句を含まないVIEWのことです。
VIEWから実表の操作をすることが可能です。(条件による)

複合VIEW

 二つ以上の表から構成されているVIEWもしくは、1つの表から構成され関数やGROUP BY句を含むVIEWのことです。
VIEWから実表の操作をすることは特定の条件を満たす場合可能です。

VIEWの作成

 CREATE VIEW権限を持っていればVIEWの作成が可能となります。

CREATE [OR REPLACE] [FORCE | NOFORCE] VIEW VIEW名[(別名 [,別名 …])]
AS
副問合せ
[WITH CHECK OPTION [CONSTRAINT 制約名]]
[WITH READ ONLY [CONSTRAINT 制約名]];

CREATE VIEW文のオプション

OR REPLACE

 同名のVIEWが存在した場合そのVIEWに上書きします。
同名のVIEWが存在しているのにも関わらずOR REPLACEを省略するとエラーとなります。
同名のVIEWが存在しない場合新たにVIEWが作成されます。

FORCE

 実表が存在しない場合エラーとなるがVIEWは強制的に作成されます。

NOFORCE

 実表がある場合VIEWが作成されます。実表が存在しない場合はエラーとなります。

WITH CHECK OPTION

 VIEWを通じてアクセスできる行のみ追加、更新できます。

WITH READ ONLY

 VIEWが読み取り専用となります。そのためデータの追加、更新、削除が出来なくなります。

VIEWの削除

DROP ANY VIEW権限を持つユーザーがVIEWを削除することが出来ます。

DROP VIEW VIEW名;

VIEWを使用したデータの変更

データの削除の場合(DELETE)

 GROUP BY句、ROWNUM疑似列、グループ関数、DISTINCTキーワードの要素が含まれていない場合削除することが出来ます。

データの更新の場合(UPDATE)

 GROUP BY句、ROWNUM疑似列、グループ関数、DISTINCTキーワード、式によって定義された列が要素が含まれていない場合変更することが出来ます。
副問合せのSELECT句で計算式や関数を使用している場合、列別名を指定する必要があります。

データの追加(INSERT INTO)

 GROUP BY句、ROWNUM疑似列、グループ関数、DISTINCTキーワード、式によって定義された列、VIEWに含まれていない実表の列にNOT NULL制約が定義されている要素が含まれていない場合変更することが出来ます。

順序の作成・削除

順序の作成

 順序は一意な番号を自動で生成するオブジェクトです。
CREATE SEQUENCE権限を持つことでCREATE SEQUENCEを使用することが出来ます。

INCREMENT BY

 デフォルト値は1です。指定した数だけ増幅します。

START WITH

 デフォルト値は1です。初期値を設定することが出来ます。

MAXVALUE / NOMAXVALUE

 最大値を設定することが出来ます。デフォルトはNOMAXVALUEです。

MINVALUE / NOMINVALUE

 最小値を設定することが出来ます。デフォルトはNOMINVALUEです。

CYCLE / NOCYCLE

 順序値が最大値または最小値に達した場合、初期値に戻り繰り返し順序値を生成します。
デフォルトはNO CYCLEです。

CASHE / NOCASHE

 指定された数順序値をメモリに割り当てます。デフォルト値は20です。

順序の使用方法

 主問い合わせのSELECT句のリスト、INSERTの副問合せのSELECT句のリスト、INSERTのVALUES句、UPDATEのSET句で参照することが出来ます。

NEXTVAL疑似列

 新しい順序値が戻されます。

CURRVAL疑似列

 最後に取得された順序値が戻されます。

順序値の欠番の発生

ロールバックが発生した場合、順序が複数の表で指定されていると、システムがクラッシュした場合に欠番が発生することがあります。

順序の定義の削除

DROP SEQUENCEを使うことによって順序の削除をすることが出来ます。
DROP ANY SEQUENCE権限をもつユーザーは削除を行うことが出来ます。

DROP SEQUENCE 順序名;

順序の定義の変更

 ALTER SEQUENCEを使うことによって順序の定義を変更することが出来ます。
START WITH以外のオプションを指定することが出来ます。
初期値を指定する場合は順序を削除してから再度作成する必要があります。

まとめ

 今回はVIEWの作成と順序について勉強しました。

ハードリンクとシンボリックリンク

f:id:monozukuri-bu:20190816184235j:plain

shadowです。
今回はWindouwsを使っている人ならお馴染みのショートカットについて、
Linuxにはそのショートカットが2種類あるので、詳しく説明したいと思います。

ハードリンクとシンボリックリンクの違い

Linuxにはショートカットの種類として、ハードリンクとシンボリックリンクが存在します。
この2つのリンクの違いについて説明する前にまずは、ファイルのinode番号について書いていきます。

inode番号

Linuxでファイルを作成するとinode番号という一意の番号が割り当てられます。
また、inodeにはファイルの属性情報が格納されています。
・ファイル種別
・ファイルサイズ
・アクセス権
・所有者情報
・リンク
・ブロック番号

$ ls -li sample.txt
7454 -rw-r--r--    1 user     user             0 Aug  9  2019 sample.txt

一番左の7454がinode番号です。

ハードリンク

ハードリンクはファイルに別名をつけるようなものです。

ファイル名A -> ファイルの実体 <- ファイル名B

このようにファイル名は違えど同じファイルの実体(inode)を指しています。
また、ファイルのinodeにアクセスしているため、ファイルが移動しても問題ありません。
そして、異なるファイルシステム上や、ディレクトリにはリンクを作成することが出来ません。

シンボリックリンク

シンボリックリンクWindowsのショートカットに近い概念です。
シンボリックリンクはハードリンクと違い、ファイルの実体ではなくファイルパスを指します。

シンボリックリンクファイル -> ファイル名 -> ファイルの実体

シンボリックリンクはinodeに依存せず、パスで参照しているので、
ハードリンクと違い、異なるファイルシステム上のファイルや、ディレクトリに対してもリンクを作成することができます。
ただし、パスでアクセスしているため、ファイルが移動するとアクセス不能なデットリンクになってしまいます。

lnコマンド

リンクを作成するにはlnコマンドを使います。
オプションを指定せずにlnコマンドを実行した場合、ハードリンクが作成されます。
-sオプションをつけることで、シンボリックリンクが作成されます。

動かしてみる

ハードリンク

実際に動かしてみます。

$ mkdir -p testdir/sampledir

$ touch testdir/sampledir/file.txt

$ echo Test > testdir/sampledir/file.txt

$ cd sampledir

$ ln sampledir/file.txt ./sample_hard

$ cat sample_hard
Test

sample_hardの中を見てみるとfile.txtと同じ内容になっています。

次はinode番号をチェックしてみます。

$ ls -li sample_hard sampledir/file.txt                               
   7461 -rw-r--r--    1 root     root             5 Aug  9  2019 sample_hard    
   7461 -rw-r--r--    1 user     user             5 Aug  9  2019 sampledir/file.
txt

どちらも同じになっています。
ファイルの移動もしてみたいと思います。

$ mv sampledir/file.txt .

$ cat sample_hard
Test

移動しても同じ内容を表示しました。

シンボリックリンク

$ mkdir sampledir2

$ touch sampledir2/file2.txt

$ echo Test2 > sampledir2/file2.txt

$ cat sampledir2/file2.txt                            
Test2

$ ln -s sampledir2/file2.txt ./sample_symbolic

$ cat sample_symbolic
Test2

シンボリックリンクの中身も同じでした。
こちらもファイルの移動をします。

$ mv sampledir2/file2.txt .

$ cat sample_symbolic
cat: sample_symbolic: そのようなファイルやディレクトリはありません

こちらはファイルが移動すると参照出来ませんでした。

まとめ

最後にハードリンクとシンボリックリンクの特徴を、ざっくりまとめると以下のようになります。

特徴 ハードリンク シンボリックリンク
参照 inode パス
異なるファイルシステム できない できる
元ファイルの移動 移動しても消えない 移動すると参照できない
元ファイルの削除 削除しても消えない 削除すると消える

以上です。お疲れ様でした。

Pythonのクラスに関する備忘録

f:id:monozukuri-bu:20190816165831j:plain

ふっちーです。
今回はクラスについての備忘録を残します。
今までクラスに触れてきた中で、理解するのに時間が掛かったものや印象に残ったものを、自分の言葉にしてみたいと思います。
クラスの基本的な部分や用語の説明は省略しています。

4つの疑問

・そもそもクラスはどういうときに使えばいいの?
・クラスの関数にあるselfって?
・継承とは?superってどうやって使うの?
・クラスを含んだモジュールは関数だけのモジュールと違う部分はあるの?

今回は上記4つについて書いてみたいと思います。

そもそもクラスはどういうときに使えばいいの?

クラスはデータ(属性)や関数(メソッド)を1つにまとめて、データの保持と処理を一括で管理できます。
そして、同じクラスでも異なるインスタンスを設定すれば、異なるデータをインスタンスごとに別々で処理できます。
確かに便利そうな機能だとは思うのですが、実際にクラスはどんな時に使われるのでしょうか。

結論から言うと、
テンプレートを作成し、似たようなものを手軽に生産できる
という点があげられます。

例を挙げると、ゲーム制作が分かりやすいと思います。
キャラクターには(ゲームにもよりますが)名前や性別など必ず持っているデータ(属性)がいくつか存在します。
レーシングゲームだったら車体の名前や色だったり、アクセルやブレーキなどのデータ処理(メソッド)が該当します。

そして、ゲームではこれらのデータを持つオブジェクト(キャラクター、車など)をたくさん作成する場合があります。
普通に変数1つ1つでこれを作るとなるととんでもない労力がかかります。
この時、クラスの存在がこのような作業をできるだけ少ない労力でコードを書く手助けをしてくれるのです。

試しに名前、年齢、性別が設定されたキャラを作るために、実際にクラスを書いてみましょう。

class Chara:
  def __init__(self, name, age, sex):
    self.name = name
    self.age = age
    self.sex = sex
  
  def status(self):
    print("名前:"+self.name)
    print("年齢:"+str(self.age)+"歳")
    print("性別:"+self.sex)

このようにキャラの基本情報を設定することで、これを基にしたキャラの情報を多く作ることができます。

a = Chara("John", 14, "male")
b = Chara("Alice", 8, "female")
c = Chara("Robin", 40, "male")

これだけで、3人分のキャラを設定できました。
もしこれを変数だけで作ろうとすると、9個の変数が必要になることを考えると、クラスがいかにデータを楽に管理できるかが分かります。
statusメソッドでデータを確認してみましょう。

a.status()
出力結果:
名前:John
年齢:14歳
性別:male

このようにクラスで基となるデータやメソッドを用意することで、大量のデータを簡単に管理することができます。

また、キャラをPCキャラとNPCキャラにわけて、PC側には職業や体力を、NPC側にはセリフを設定することもできます。
この場合は継承を使うときれいに設定できます。
継承については後述します。

クラスの関数にあるselfって?

クラスで関数を使うときに疑問に思った方もいるかと思います。
中途半端に理解してしまい、混乱することが多かったので、まとめることにしました。

まず、selfの正体は何なのでしょうか。
selfはその名の通り自分自身を指します。
ここで言う自分自身とは、インスタンス化したクラスを示しています。
さっきの例で言うとa, b, cがそれに当てはまります。

a = Chara("John", 14, "male")
b = Chara("Alice", 8, "female")
c = Chara("Robin", 40, "male")

インスタンス化したクラスを格納するための変数名(a, b, c)は自分で勝手に決められますが、
それをクラス内部で使用する場合はselfで統一しています。
クラスは各インスタンス毎に異なる情報を持つため、selfでどのインスタンスなのか判断する必要があるのです。
つまりこの引数selfは、インスタンス化されたクラスを入れて識別する為の引数と言えます。

実際に書いてみましょう。

class Chara:
  def __init__(self, name, age, sex):
    self.name = name
    self.age = age
    self.sex = sex
    
  def get_old(self):
    self.age += 1
    print(self.age)  

先ほどのクラスに歳をとるget_oldメソッドを新しく追加しました。
このget_oldメソッドを使って以下のコードを実行してみましょう。

a.get_old()     #インスタンス関数を使う場合はこれが一般的
Chara.get_old(b)  #実はこれでも動く
出力結果:
15
9

上の例にあるように、変数名.get_old()は、クラス名.get_old(x)と同じと言えます。
2つ目の例が本来の書き方といった感じですが、毎回クラス名を書く必要がない1つ目の方が効率的ですね。

継承とは? superってどうやって使うの?

継承はクラスの機能拡張として使われます。
継承を使うことでクラスに新しい属性やメソッドを追加したり、別のものに変更することができます。
これにより新しい機能を実装する際に一からクラスを作り直す手間を省くことができます。

実際に継承を使ったクラスを見てみましょう。

import random
class Sleep(Chara):
  def __init__(self, name, age, sex, sleep):
    super().__init__(name, age, sex)
    self.sleep = sleep
  
  def status(self):
    if self.sleep == 0:
      print("ZZZZZ...")
    
    else:
      print("Good morning!")
  
  def n_status(self):
    super().status()
      
  def wake_up(self, name, sleep):
    if self.sleep == 0:
      print("Wake up! "+self.name+"!")
      r = random.randint(0, 1)
      if r == 0:
        print("Oh, come on!")
      
      else:
        self.sleep = 1
        print("Good morning, "+self.name+".")
        
    else:
      print("Oh, sorry.")

最初のクラスを継承したSleepクラスを作りました。
早速キャラを設定し、以下のコードを実行します。

a = Sleep("John", 14, "male", 0)
a.status()
a.get_old()
出力結果:
ZZZZZ...
15

ここで注目してほしいのは2点です。
・Sleepクラスのstatusメソッドが実行されている
・Sleepクラスにないget_oldメソッドが使用できている

基にしたクラスにあるメソッドは継承したクラスでも使用することができます。
継承しなければ、当然使用することはできません。

b.wake_up()
AttributeError: 'Chara' object has no attribute 'wake_up'

継承の大まかな内容を掴んだところで、superに触れていきましょう。
先ほどstatusメソッドがSleepクラスのものに上書きされていました。
この場合Charaクラスのstatusメソッドは使えないのでしょうか。

そこでsuperが役に立ちます。
n_statusメソッドがsuperを使っているので、実行してみましょう。

a.n_status()
出力結果:
名前:John
年齢:15歳
性別:male

Charaクラスのstatusメソッドを使用することができました。

また、Sleepクラスのinitにもsuperは使われています。
これは継承がデータも引き継がれることを利用して、superで基のクラスに既にあるデータを保持するためです。

最後にwake_upメソッドで、Johnを起こしましょう。

a.wake_up()  #起きるまで何回もやろう!
出力結果:
Wake up! John!
Good morning, John.

何回でJohnを起こせましたか?
私は2回でした。

クラスを含んだモジュールは関数だけのモジュールと違う部分はあるの?

モジュールでよく使うのは関数の集まりなので、あまり気にした人はいないかもしれません。
しかし、私は関数を囲むクラスの姿を見て、これをモジュールとして使用する場合どんな動作になるだろうと思いました。

最初のCharaクラスをモジュール(character.py)としてみます。

class Chara:
  def __init__(self, name, age, sex):
    self.name = name
    self.age = age
    self.sex = sex
  
  def status(self):
    print("名前:"+self.name)
    print("年齢:"+str(self.age)+"歳")
    print("性別:"+self.sex)

次に以下のコードを記入します。

import character

d = character.Chara("Asbel", 18, "male")
d.status()
出力結果:
名前:Asbel
年齢:18歳
性別:male

これでモジュールのクラスが使えるようになりました。
関数モジュールを使うときのようにモジュール名.クラスとしてインスタンス化すれば、後は通常通りクラスを使用できるようです。

今回はここまでです。
お疲れさまでした。

まとめ

今回はクラスで疑問に思ったところをまとめてみました。
・クラスは似たようなものを大量に用意する場合に便利だと分かりました
・selfはどのインスタンスなのか見分けるために、変数の名前を受け取る引数です
・継承は元のクラスを拡張して多くのデータやメソッドを追加してバリエーションを持たせることができます
・superで親クラスのデータを記述せずに使用することができます
・クラスを含んだモジュールでもインスタンス化の際にモジュール名を付けるだけで大きな差はないです

今回の勉強でクラスについての理解が深まったように思えます。
しかし、クラスを活かすには個人だと限界があるので、何か面白いアイデアはないか探してみたいと思っています。

Oracle Database 表の作成

f:id:monozukuri-bu:20190816152328j:plain

みちです。
Oracle Databaseの表の作成について、勉強していきたいと思います。

スキーマ

 データの論理構造の集合で、ユーザーごとに1つのスキーマを持っています。
各ユーザーが作成したオブジェクトは、作成したユーザーのスキーマに格納されます。

別のユーザーが所有しているオブジェクトを参照する方法

スキーマ名.オブジェクト名

オブジェクトの命名規則

・使用できるの文字は英数字、漢字、ひらがな、カタカナ、記号「_」、「#」、「$」
・先頭の文字は数字、記号以外
・アルファベットの大文字・小文字は区別されない
・長さは30バイト以下
・同一のスキーマ内で重複する名前は指定できない
予約語は使用できない

表の作成

CREATE TABLE [スキーマ名.]表名
(
    列名 データ型
    [, 列名 データ型]
);

DEFAULTオプション

 表の作成したときにデフォルト値を指定することが出来ます。

列名 データ型 [DEFAULT 式]

表の削除

 表を削除すると表内のすべてのデータと定義されている制約、索引が削除されます。しかし、ビューとシノニムは削除されません。
DROP TABLE文を実行すると、表はゴミ箱へ移動します。完全に削除したい場合は、PURGE句を指定します。
DROP ANY TABLE権限を持っているユーザーか、表の所有者でないと削除することが出来ません。

DROP TABLE 表名 [PURGE];

データ型

分類 データ型の例
文字型 CHAR
VARCHAR2
LONG
CLOB
数値型 NUMBER
日付型 DATE
バイナリ型 RAW
LONG RAW
BLOB
BFILE
ROWID ROWID

文字型

 CHAR型は固定長であり、VARCHAR2型は可変長です。
そのため、CHAR(5)に「abc」という文字列を指定した場合、5バイトの扱いなります。
また、VARCHAR(5)に「abc」という文字列を指定した場合、3バイトの扱いとなります。

数値型

列名 NUMBER[(最大精度[,位取り])]

NUMBER(6, 3)に定義されている列に123.4567を指定した場合、
全体で6桁、整数部分は三桁、小数点以下が第3位までであるため、
小数点第4位の7が四捨五入で繰り上がり、123.457となります。

日付型

 日付型のデータのサイズは固定長であり、7バイトです。

列名 DATE

バイナリデータ型

 RAW型、LONG RAW型、BLOB型、BFILE型の四種類です。

列名 RAW(最大サイズ)
列名 LONG RAW
列名 BLOB
列名 BFILE

ROWID型

 各行に割り当てられている一意なアドレスです。
ROWIDをSELECTで取得する場合は*では取れないため、明記する必要があります。

列名 ROWID

TIMESTAMP型

 DATE型を拡張したデータ型です。年月日時分秒と秒の小数点以下の値を格納することが出来ます。
小数点以下の桁数を指定することができ、デフォルト値は6です。

列名 TIMESTAMP[(小数点以下の桁数)]

INTERVAL YEAR TO MONTH型

 2つの日付と時刻の間隔を年及び月の単位で格納します。

INTERVAL DAY TO SECOND型

 2つの日付と時刻の間隔を日時分秒の単位で格納します。

制約の定義

 制約は列または表レベルで定義することが出来ます。
制約名を省略すると、制約名は「SYS_Cn」となります。

制約の種類

種類
PRIMARY KEY制約(主キー制約)
FOREIGN KEY制約(外部キー制約)
CHECK制約
NOT NULL制約
UNIQUE制約(一意制約)

PRIMARY KEY制約

 表内の各行を一意に識別できる値のみ許可されています。重複値とNULL値は許可されていません。
この制約は表に一つしか定義することが出来ません。

FOREIGN KEY制約

 参照先の列にある値またはNULL値は許可されています。
子表内に親表を参照する行が存在する場合、親表の参照されている行を削除することが出来ません。
また、FOREIGN KEY制約を定義すると親表は削除することが出来ません。
削除する場合は子表から削除する必要があります。

CHECK 制約

 条件に合う値のみ許可されています。条件に合わない値の場合はエラーとなります。

NOT NULL制約

 NULL値は許可されません。列レベルで定義することが出来ます。

UNIQUE制約

 重複した値は許可されません。NULL値は許可されています。
列の組み合わせにUNIQUE制約を定義する場合、表レベルの制約を使う必要があります。
その場合、組み合わせで一意であれば良いため、1つの列で同じ値でも他の列で異なる値が入っていればデータを登録することが出来ます。

副問合せを使用した表の作成

 列名の指定は省略することが出来ます。
その場合、副問合せでSELECT句で指定された列名と同じ名前の列が定義されます。

列名を指定する場合、副問合せのSELECT句と同じ個数の列名を指定する必要があります。
NOT NULL以外の制約はコピーされません。

暗黙的にNOT NULL制約がつくPRIMARY KEY制約もNOT NULL制約が付与されません。
副問合せのSELECT句で計算式や関数を使用している場合、列別名を指定する必要があります。

ALTER TABLE

ALTER TABLE 表名 {READ WRITE | READ ONRY};

READ WRITE

 表のデータを変更することが出来ます。読み取り、書き込みモード。

READ ONRY

 表のデータを変更することが出来ません。表自体の削除だけ可能です。読み取りモード。

まとめ

 今回は表の作成について勉強しました。 細かいところも含めてしっかりと覚えておく必要があると感じました。

Python 便利なリスト演算まとめ

f:id:monozukuri-bu:20190816145258j:plain

最近やっと暑くなってきたので海に行きたいhashiです。
現在、Python3エンジニア認定基礎試験の勉強に励んでいます。
資格勉強のためチュートリアルを見返すとリストへの操作には、融通の利くいろいろな演算があると気づかされました。
分かりにくい所もあるので、実行結果とともにまとめてみます。

そもそもリストとは

ドキュメントによると、

リストはミュータブルなシーケンスで、一般的に同種の項目の集まりを格納するために使われます (厳密な類似の度合いはアプリケーションによって異なる場合があります)。

「同種の項目の集まりを格納する」はわかりやすいですが、「ミュータブル」とは……?
用語集に頼ることにします。

docs.python.org

  • mutable(ミュータブル)
    ミュータブルなオブジェクトは、 id() を変えることなく値を変更できます。イミュータブル (immutable) も参照してください。

うーん……。イミュータブルの解説を見てみます。

  • immutable(イミュータブル)
    固定の値を持ったオブジェクトです。イミュータブルなオブジェクトには、数値、文字列、およびタプルなどがあります。これらのオブジェクトは値を変えられません。別の値を記憶させる際には、新たなオブジェクトを作成しなければなりません。イミュータブルなオブジェクトは、固定のハッシュ値が必要となる状況で重要な役割を果たします。辞書のキーがその例です。

数値aに対してa = a + 1という操作をするときには、aという箱の中身を変えているわけではなく、
新しいオブジェクトにaという名前を付けているという感じでしょうか。
それに対してリストは外観を変えずに箱の中身(が見ているオブジェクト)を変えられるという理解でよさそうです。

演算

特定の値がリストに含まれているかの判定

x in s
リストsの要素の中にxと等しいものがあればTrue、なければFalseを返す。

x not in s
リストsの要素の中にxと等しいものがなければTrue、あればFalseを返す。

わざわざfor文を回すことなく要素の存在を確認できるのは、コードの分量も減りうれしいですね。

sample_list = [1, 2, 3, 100, 1, 7, 9]
print("sample_list:" + str(sample_list))

"""
もうこんな行数いらない!
for i in sample_list:
    if i == 0:
        print(True)
        break
else:
    print(False)
"""

# 特定の値が含まれているかの判定
print("0 in sample_list")
print(0 in sample_list)

# 特定の値が含まれていないかの判定
print("0 not in sample_list")
print(0 not in sample_list)

実行結果

sample_list:[1, 2, 3, 100, 1, 7, 9]
0 in sample_list
False
0 not in sample_list
True

結合

s + t
リストsの後ろにリストtが続くリストを返す。

s*x
リストsをx(整数)個つなげたリストを返す。x<=0の時は空のリストを返す。

リスト同士の足し算、リストと整数の掛け算によってリストの結合が可能です。

sample_list = [1, 2, 3, 100, 1, 7, 9]
print("sample_list:" + str(sample_list))

print(sample_list + [7])    # 2つのリストの結合
print(sample_list * 3)       # 同じリストを3つ結合
print(sample_list * (0))    # 同じリストを0つ結合=空のリスト
print(sample_list * (-6))  # 0未満の値は0と扱われる
print(sample_list)  # 元のリストに影響はない

実行結果

sample_list:[1, 2, 3, 100, 1, 7, 9]
[1, 2, 3, 100, 1, 7, 9, 7]
[1, 2, 3, 100, 1, 7, 9, 1, 2, 3, 100, 1, 7, 9, 1, 2, 3, 100, 1, 7, 9]
[]
[]
[1, 2, 3, 100, 1, 7, 9]

要素の抜き出し

s[i]
i番目の要素を抜き出す。

s[i:j]
i番目からj-1番目まで抜き出す。

s[i:j:k]
i番目からj-1番目までの範囲の、k個ごとに抜き出す。

sample_list = [1, 2, 3, 100, 1, 7, 9]
print("sample_list:" + str(sample_list))
i, j, k = 1, 7, 2
print(sample_list[i])  # i番目の値を抜き出す
print(sample_list[i:j])  # i番目からj-1番目までのリストを抜き出す
print(sample_list[i:j:k])  # i+nk (i<=i+nk<j, nは非負の整数)番目の値のリストを抜き出す

実行結果

sample_list:[1, 2, 3, 100, 1, 7, 9]
2
[2, 3, 100, 1, 7, 9]
[2, 100, 7]

また、i,jに負数を指定するとリストの後ろからの距離でインデックスを指定できたり、
スライスのi,jを省略しても良きに計らってくれるのが便利です。

sample_list = [1, 2, 3, 100, 1, 7, 9, 10]
print("sample_list:" + str(sample_list))
i, j, k = 1, 7, 2

print(sample_list[-i])  # (最後尾を1として)最後からi番目の値を抜き出す
print(sample_list[i:])  # i番目から(リストのサイズ-1番目まで)のリストを抜き出す
print(sample_list[-5:])  # リストの後ろから5つの要素を抜き出す
print(sample_list[:j])  # (0番目から)j-1番目までのリストを抜き出す
print(sample_list[:-5])  # (0番目から)リストの後ろから5つを除いた要素を抜き出す
print(sample_list[::-1])  # (0番目からリストのサイズ-1番目までの)リストを-1刻みで抜き取ることで得られるリストを抜き出す

実行結果

sample_list:[1, 2, 3, 100, 1, 7, 9, 10]
10
[2, 3, 100, 1, 7, 9, 10]
[100, 1, 7, 9, 10]
[1, 2, 3, 100, 1, 7, 9]
[1, 2, 3]
[10, 9, 7, 1, 100, 3, 2, 1]

print(sample_list[::-1])はリストを逆順で表示するという意味になるのでlist(reversed(sample_list))とも書けますが、
スライスを使ったほうが簡単ですね。

リストの編集

s[i]=x
i番目の要素をxに入れ替える。

s[i:j]=l
i番目からj-1番目までをリストlに入れ替える。

s.append=x
リストの最後尾にxを追加する。

s.reverse()
リストの中身を逆順にする

ここまでの演算では元のリストの中身に影響がありませんでしたが、これ以降はリストの中身を編集します。

sample_list = [1, 2, 3, 100, 1, 7, 9]
print("sample_list:" + str(sample_list))
sample_list[0] = 2  # 指定したインデックスの要素を変える
print(sample_list)

sample_list = [1, 2, 3, 100, 1, 7, 9]
sample_list[1:4] = [0, 0, 0, 0, 0]  # 指定した範囲を入れ替える(長さは同じでなくてもよい)
print(sample_list)

sample_list = [1, 2, 3, 100, 1, 7, 9]
sample_list.append(9)  # 最後尾に要素を追加
print(sample_list)  # 最後尾に9が追加されている

sample_list = [1, 2, 3, 100, 1, 7, 9]
sample_list.reverse()  # 逆順にする
print(sample_list)

実行結果

sample_list:[1, 2, 3, 100, 1, 7, 9]
[2, 2, 3, 100, 1, 7, 9]
[1, 0, 0, 0, 0, 0, 1, 7, 9]
[1, 2, 3, 100, 1, 7, 9, 9]
[9, 7, 1, 100, 3, 2, 1]

リストのソート

s.sort()
リストsを昇順でソートする(実行前後でsの中身が変わる)
引数にkey=...,reverse=...をとることができ、ソートの条件を指定できる

 - key=(1引数をとる関数)
 リストの要素を関数に渡したときの戻り値を使ってソートを行うようにする

 - reverse=(真偽値)
Trueならば降順、Falseなら昇順にソートを行う

keyの指定方法がちょっとわかりにくいですが、リスト中の要素でソートしたい時などに便利です。
また、ソート前のリストを保持しておきたい場合はsorted()を使うと元のリストをいじらずにソート済みリストを取得できます。

sample_list = [1, 2, -3, 100, -1, 7, -9]
print("sample_list:" + str(sample_list))

sample_list.sort()  # ソート(デフォルトは昇順)
print(sample_list)

sample_list.sort(reverse=True)  # 逆順ソート
print(sample_list)

sample_list.sort(key=abs)  # 値の絶対値でソート
print(sample_list)

listlist = [[100, 1], [0, 3], [9, 2]]
print("listlist:"+str(listlist))

listlist.sort(key=lambda x: x[1])  # リストの1番目の値をソートに使う
print(listlist)

実行結果

sample_list:[1, 2, -3, 100, -1, 7, -9]
[-9, -3, -1, 1, 2, 7, 100]
[100, 7, 2, 1, -1, -3, -9]
[1, -1, 2, -3, 7, -9, 100]
listlist:[[100, 1], [0, 3], [9, 2]]
[[100, 1], [9, 2], [0, 3]]

まとめ

Python3でのリストの仕様や便利なメソッドなどをまとめました。
活用して短く読みやすいコードを書きたいと思います。

Python 参照先アドレスについて

f:id:monozukuri-bu:20190730112500j:plain

Python3エンジニア認定基礎試験に合格したふっちーです。
合格ラインギリギリでしたがなんとか1発で受かりました。
ありがとうございます。

というわけで今回もPythonをやっていきます。
勝って兜の緒を締めよ、です。

今回は参照先アドレス特集です。
前回でも少し触れた内容ですが、これがきっかけでPythonの学習が捗りました。
ちょっとしたもやもやが晴れるかもしれません。
さあ、一緒に学んでいきましょう。

環境

今回もGoogle Colabolatory(Colab)を使用しました。
colab.research.google.com

参照先アドレスとは

参照先アドレスとは変数の情報(数値や文字列など)を保管している住所の様なものです。
変数が持っている情報をコンピュータが知りたい場合に、この参照先アドレスでどこに保管してあるかを調べるのに使います。
参照先アドレスは組み込み関数id()で確認できます。
実際に見てみましょう。

n = 3
print(id(n))
出力結果:
10968864

変数(n)が持っている情報(3)は10968864という場所に保管されていることが分かりました。
つまり、コンピュータがnの情報を知りたい場合は、10968864という場所に行けば、3を見つけられるという事です。

次に変数nの情報を変更してみましょう。

n = 9
print(id(n))
出力結果:
10969056

このとき、3は10968864に残ったままです。あくまでnの参照先アドレスが9の10969056に変わっただけです。
nに9という情報が設定されたので、コンピュータがその分新しく参照先アドレスを割り振ったにすぎません。
このように参照先アドレスは変数に情報を設定した瞬間にコンピュータが自動的に決定します。

変更不能体と変更可能体

前回にも説明しましたが、Pythonには変更不能体と変更可能体が存在します。

変更可能体 変更不能
リスト、ディクショナリ タプル、文字列、数値

変更不能体とは要素の一部が変更不可であり、変更可能体は要素の一部が変更可能であるという規則があります。
そして、この区別は参照先アドレスについて考えると一気に理解が深まります。

リストが要素の変更をするときの参照先アドレスを見てみましょう。

v_to_z = ['v', 'w', 'x', 'd', 'z']
print(id(v_to_z))
v_to_z[2] = 'k'
print(id(v_to_z))
出力結果:
140656074921096
140656074921096

このように変化がありません。

タプルと文字列は変更不能体なので、上と同じことをしようとするとエラーが発生します。
また、数値を変化させると参照先アドレスが変化しましたが、これはタプルや文字列でも同じです。

w = 'double'
print(id(w))
w = 'world'
print(id(w))
出力結果:
140103509005120
140103134067488

つまり、以下のようにまとめられます。

変更不能体:同じ参照先アドレスでは要素を変更できない
      →変数の再設定は可能だが、要素の変更は不可である
変更可能体:同じ参照先アドレスで要素を変更できる
      →変数の再設定も要素の変更も可能である

要素の変更と変数の再設定

「要素の変更」と「変数の再設定」で変更不能体と変更可能体を区別できることを説明しました。
では、具体的にどのような場合で「要素の変更」と「変数の再設定」は起こるのでしょうか。

実際に確認してみましょう。

a = [6]
print(id(a))

a[0] = 8
print(id(a))

a = [7]
print(id(a))
出力結果:
140094475436616
140094475436616
140094475304712

3行目はaの0番目を8に「要素の変更」をしているのに対し、5行目ではaそのものを[7]に「変数の再設定」をしています。
確かに、参照先アドレスが変化していますね。

続いては以下の例です。

b = [2]
print(id(b))

b.append(5)
print(id(b))
b = [2]
print(id(b))

b = b + [5]
print(id(b))

どちらも[2, 5]が出力されますが、参照先アドレスはどうなっているでしょうか。

出力結果:
140094475437320
140094475437320
出力結果:
140094475304392
140094475437512

このように出力だけ見ても変化がないものでも、参照先アドレスが異なる場合があるのです。
結論としては、「変数名 = 」が再設定で、それ以外は要素の変更である、というのが一番覚えやすいと思います。

スコープ

スコープとは、変数が適用される範囲の事を指します。
スコープにはローカルスコープやグローバルスコープなどがあります。
他にもスコープはありますが、今回取り扱うのはこの2つです。

例とともに説明していきます。

x = 1

def local1():
  x = 2
  print(x)
  
local1()
print(x)

まず、ローカルスコープは関数の内側が範囲となります。
defから1つ目のprint(x)までの部分です。

def local1():
  x = 2
  print(x)

次に、グローバルスコープはモジュール全体が範囲となります。

x=1グローバル変数x=2はローカル変数と呼ばれています。

このスコープによって変数を参照できる範囲が変化します。
ローカルスコープは、その外(今回はグローバルスコープのみ)のスコープにある変数を参照できます。
反対にグローバルスコープは、ローカルスコープにある変数を参照できません。

言葉だけでは理解しにくいと思うので、上の例を動かしてみましょう。

出力結果:
2
1

まず初めに、x=1が読み込まれ、グローバル変数xに1が設定されます。
次に関数(local1)が読み込まれ、ローカル変数xに2が設定されます。
そして、local1を呼び出し、関数内のprintが動きます。→出力:2
関数の処理が終わると、最後のprintが動いて全体の処理が終わります。→出力:1

つまり、順番ではx=2の方が新しく設定されていますが、グローバルスコープのx=1に影響がありません。
これが上の書いてあるグローバルスコープはローカルスコープにある変数を参照できないの意味です。
ローカルスコープでグローバルスコープにある同じ変数名の情報が変化しても、グローバルスコープ側には関係ないのです。

同じxでも参照先アドレスはグローバルスコープ版とローカルスコープ版で分かれていると考えられます。

グローバル変数とローカル変数の例外

ローカルスコープ側で変数が変化しても、グローバルスコープ側には関係ないと説明しました。
しかし、例外が存在します。
それが今回解説した「要素の変更」と「変数の再設定」に関係しています。

もしかしたら、気づいた方もいるかもしれませんね。
結論から言うと「要素の変更」はグローバル変数にも影響を与えます。

例とともに解説します。

x = [90]

def local2():
  x[0] = 50

print(x)

local2()
print(x)
出力結果:
[90]
[50]

printはグローバルスコープにしかありませんが、要素の変更でxが変化しました。

この謎を解くカギは参照先アドレスとローカル変数はグローバル変数を参照できるという点です。

まず、グローバルスコープでx=[90]が設定されました。
次に関数local2の定義でxを参照していますが、ローカルスコープには変数xの定義が存在しません。
この場合は、一つ外側のスコープに変数を探しにいくため、グローバルスコープの変数xを参照することになります。 つまり、同じ参照先アドレスに「要素の変更」をしているという事です。
よって、最後のprintはxに設定されている参照先アドレスの情報を出力するため、変更されたものが出力されました。

まとめ

今回は参照先アドレスについてまとめました。
・参照先アドレスとは変数の所有物(数値や文字列など)を保管している場所を表した住所の様なものである。
・参照先アドレスの中身が変更できるかできないかで変更可能体と変更不能体でわかれる。
・「要素の変更」が参照先アドレスに影響を与えず、「変数の再設定」は参照先アドレスを変更することになる。
・スコープでもこの法則は適用される。

私はスコープや変更不能体などが理解しづらかったのですが、参照先アドレスのおかげでスムーズに理解することが出来ました。
拙い説明でしたが皆さんの理解に役立てられたら光栄です。