akiyoko blog

akiyoko の IT技術系ブログです

【ポケモンGO】富士山頂でポケモン獲ったどーー! 頂上でゲットしたポケモンは一体何だった??

こんにちは。akiyoko です。
趣味は、山登り(昨年から追加)です。

今年から「山の日」も創設されて、富士山への登山客もますます増えそうですよね。

そんなわけで、私も有給を一日取って、先週末の日曜から月曜日の一泊二日で、富士山での ポケモン狩り 登山 を楽しんできました。ちなみに、昨年に続けて人生二回目の富士山頂への挑戦でした。


登山への出発前、ふとこんな疑問が。

「富士山頂にポケモンっているの??」 と。


せっかくなので、5合目から山頂までの道中でポケモンを集めながら、最終的に富士山頂でポケモンをゲットしてみようと思い立ったのです。

5合目から富士山頂3776m地点(剣ヶ峰)までは二日間合計でおよそ10時間の道中で、アプリをずっと起動しっぱなしだと電池が持たないので、要所要所でアプリを起動してはポケモンを探すというのを繰り返しました。


富士山での歩きスマホは危険だぞ!
ポケモンを探しに富士山に向かう皆様へ|お知らせ|富士登山オフィシャルサイト



それでは、ポケモンゲットの記録を振り返ってみます!!



5合目

一日目の朝9時過ぎ。新宿からの直通バスで5合目に到着。

アプリを起動してすぐにポケストップやジムが見えたのでひと安心。


ここでは早速、ニドラン♂、スリープ、パラス をゲット。
売店の裏手にある小御嶽(こみたけ)神社付近にたくさん集まっているようです。

f:id:akiyoko:20160809231527p:plain:w300


その他にも、ニャース、ビードル、ポッポ、メノクラゲ がいるようですね。

f:id:akiyoko:20160809093027p:plain:w300


少し体を慣らして、11時頃に5合目を出発。


 

6合目

5合目から50分ほど歩くと6合目に到着。
途中、滑りやすい岩道があるので若干注意が必要です。

そして道中の初ポケモンは、6合目地点の イーブイ でした。

f:id:akiyoko:20160809084703p:plain:w300



 

7合目

6合目から7合目までは苦手な砂利道が長く続きます。
通常60分ほどですが、体を慣らすために90分ほどかけてゆっくり歩きました。


7合目では残念ながらポケモンに遭遇できず。
全国から集まったトレーナーたちに狩り尽くされているのでしょうか。。(涙)


 

山小屋(7合目と8合目の中間地点)

7合目を過ぎると急な岩場が現れ、まさに「登る」感じになります。

一日目の宿は、7合目と8合目の間の「東洋館」。
ここではゆっくりとポケモンと戯れることができました。


まずは、ビードル
雲より高く飛び上がってます。

f:id:akiyoko:20160809094156p:plain:w300


翌日の朝。ポッポと御来光。

f:id:akiyoko:20160809094222p:plain:w300



二日目は4時頃に起床。
4:50頃に山小屋の前で御来光を見て、朝食を食べてから6時頃に出発。


 

8合目

ここから岩場がいっそう険しくなります。
30分後に8合目に到着。

8合目の太子館では、イーブイがお出迎え。かわいい。

f:id:akiyoko:20160809100057p:plain:w300


ふと道中のポケモンジムをチェックしてみたのですが、驚きのジムレベル6!! 下界のジムよりもよっぽど鍛えられているじゃないですか!

f:id:akiyoko:20160809100241p:plain:w300



 

本8合目

8合目から本8合目までは結構長く、90分ほどの道のり。

本8合目の富士山ホテル前では、クラブと遭遇 しました。
あー、カニ寿司食べたい。。

f:id:akiyoko:20160809100418p:plain:w300



 

9合目

本8合目から9合目までは約1時間ほど。

このあたりから急に、近付きつつある台風の影響でガスと風が強くなってきました。


小さな社の前で、スリーパーを発見
でも逃げられてしまいました。。

f:id:akiyoko:20160809094357p:plain:w300


 

頂上(久須志神社付近)

この鳥居と狛犬が見えると、頂上はもう目前。
ズバットを一匹 を捕まえて、さっさと先へ進みます。

f:id:akiyoko:20160809094427p:plain:w300


登山道を登り切ると、久須志(くすし)神社があります。
山小屋を出発して4時間での到着です。

ここが一般に、富士山の「頂上」と言われている地点です(が、最高地点ではありません)。


頂上地点には ポケモンがウヨウヨ いました。


まず、ゴース。

f:id:akiyoko:20160809094714p:plain:w300


ウツドン。初めて遭遇しました。

f:id:akiyoko:20160809094740p:plain:w300


クサイハナ。寝てるのかな?

f:id:akiyoko:20160809094815p:plain:w300


ケーシィが店の前でくつろいでいました。

f:id:akiyoko:20160809094843p:plain:w300


ゴースには逃げられてしまいましたが、ウツドン、クサイハナ、ケーシィ をゲットできました!


3776m 地点(剣ヶ峰)

実はここからが本番。
目指すのは、久須志神社から火口の周りをぐるっと一周する「お鉢巡り」の道中にある、剣ヶ峰の頂上が最高地点(3776m 地点)です。

剣ヶ峰到着までに、郵便局でポストカードを出したり、浅間(せんげん)神社奥宮で御朱印を記帳してもらったりしたので、頂上到着から1時間半ほどかかっています。


で、最高地点で待っていたのは「ゴース」でした!

f:id:akiyoko:20160809100537p:plain:w300


続いて、ビードルも出現!

f:id:akiyoko:20160809100609p:plain:w300


最後は、パラス!!

f:id:akiyoko:20160809100637p:plain:w300


他にもピッピが付近にいたようですが、今回は現れてくれませんでした。

f:id:akiyoko:20160809100737p:plain:w300


というわけで、3776m地点(剣ヶ峰)でゲットしたポケモンは、ゴース、ビードル、パラス でした。



 

行程と装備

ここで、今回の富士山頂登山の行程と装備を紹介します。

富士山頂への登山ルートは、吉田口、富士宮口、須走口、御殿場口の4つがありますが、今回(前回も)は初心者向けと言われている「吉田口」を利用しました。初心者向けと言っても、かなり大変でしたが。。

(参考)富士山の4大登山ルートを紹介|初心者のための富士山登山


一日目は、高山病にならないように体を慣らす、ということをメインに過ごしました。7合目と8合目の中間にある山小屋でゆっくり一晩休んだ後、二日目は朝から夕方まで9時間半も歩き続けています。


ちなみに昨年は、初めてということもあって二泊三日でチャレンジしたので、もう少しゆったりしたスケジュールでした。

f:id:akiyoko:20160810021452p:plain



昨年は山グッズを全部レンタルしたのですが、今回は全部新調しました。

ザック(リュック)

一泊二日なら、容量は 35 L くらいがちょうどいいでしょう。これより大きいとそれだけ重くなるので大変です。
ザックカバーが付いているものを選ぶと吉。

 

レインウェア

GORE-TEX のレインウェアだと数万円しますが、天候次第では使わないこともあるので、上下一万円ほどの初心者用のものを購入しました。今回(雨は降らなかったものの)ガスと暴風で一時期ビショ濡れになりましたが、これで十分でした。

防寒着

ユニクロのウルトラライトダウンを防寒着として。
山小屋で寝るときや頂上付近の低温対策に頻繁に使います。

サポートタイツ

膝の負担を減らすために。(ポケモンじゃない)ジムでも使えるタイプ。

(ミズノ)Mizuno バイオギアタイツ(ロング) A60BP300 92 ブラック×ブルー L

(ミズノ)Mizuno バイオギアタイツ(ロング) A60BP300 92 ブラック×ブルー L

 

トレッキングパンツ

サポートタイツの上に履くパンツ。
膝のところでセパレートできて臨機応変に使えるので重宝しています。


帽子

日差しが出ている場合は必須。

靴下

厚手のものと五本指ソックスの二枚履きで。

登山靴

昨年は安物のレンタル靴でつま先が痛くて大変だったのですが、この靴は非常に快適でした。しかも安い!

 

トレッキングポール(ストック)

砂利道が苦手なので、6合目付近からストックを使っています。
軽くて錆びないカーボン製かアルミ製のものがよいかと。

 

サングラス

日差しが強い日は必須。

ヘッドライト

日の出前など、夜間に登山する場合は必須。というか、無いと死にます。

酸素缶

高山病対策として、酸素缶は必携です。
途中の山小屋でも売っていますが、値段は地上の倍くらいします。
このタイプは小さくて携帯にも便利です。容量は10リットルもあれば、ひどい高山病にならないかぎり十分かと。

ユニコム unicom 携帯酸素 ポケットオキシ クリア

ユニコム unicom 携帯酸素 ポケットオキシ クリア

 

充電器

Pokemon GO するなら充電器は必須!
山小屋にコンセントがあるかどうか、事前にチェックしておきましょう。

 

飲み物

「1〜2リットルくらい必要」というのが一般的ですが、私の場合は 500 ml のペットボトル3本あれば一泊二日でちょうどという感じです。
今回はそのうちの一本を「ヨーグリーナ&南アルプスの天然水」にして凍らせて持って行ったのですが、これが大正解!! 冷えた飲み物が疲れた体をリセットしてくれるので、天気が良い日にはオススメです!

サントリー ヨーグリーナ&南アルプスの天然水(冷凍兼用) 540ml×24本

サントリー ヨーグリーナ&南アルプスの天然水(冷凍兼用) 540ml×24本


その他、行動食(カロリーメイトや飴)、日焼け止め、ゴミ袋、耳栓なども必要になるので、しっかり用意しておきましょう。




 

まとめ

  • 3776m地点(剣ヶ峰)でゲットしたポケモンは、ゴース、ビードル、パラス
  • 頂上(久須志神社付近)でゲットしたポケモンは、ウツドン、クサイハナ、ケーシィ
  • 富士山ならではのレアなポケモンはいなさそう??


f:id:akiyoko:20160809100846p:plain:w300

f:id:akiyoko:20160809100915p:plain:w300

f:id:akiyoko:20160809100943p:plain:w300

Django ORM の SQL を出力する方法まとめ

Django ORM を使っていると、どういった SQL が発行されているか、クエリの内容を出力したいときが多々あります。

SQL を出力する方法についてはいくつか方法がありますが、今回はその方法を思いつく限りピックアップしてみようと思います。

 

1)QuerySet の query を print する

個人的に一番よく使う方法。
実際に SQL を発行せずに、発行予定の SELECT文を出力することができます。

Django shell だろうが、pdb デバッグ中だろうが、PyCharm のデバッグ中だろうが、いつでも利用することができます。

例えば、Django shell で使う場合はこんな感じ。

$ python manage.py shell
>>> from django.contrib.auth.models import User
>>> print User.objects.filter(pk=1).query
SELECT `auth_user`.`id`, `auth_user`.`password`, `auth_user`.`last_login`, `auth_user`.`is_superuser`, `auth_user`.`username`, `auth_user`.`first_name`, `auth_user`.`last_name`, `auth_user`.`email`, `auth_user`.`is_staff`, `auth_user`.`is_active`, `auth_user`.`date_joined` FROM `auth_user` WHERE `auth_user`.`id` = 1


事前に何も設定しなくても、デバッグ中に手っ取り早く発行された SQL が確認できるのが特徴です。しかしながら、発行された SQL をリアルタイムに確認することはできません。


(参考)How can I see the raw SQL queries Django is running? - Stack Overflow


 

2)DefaultConnectionProxy の queries を出力する

直前に発行された SQL を確認することができます。
1)とセットで使うことが多いでしょうか。

$ python manage.py shell
>>> from django.contrib.auth.models import User
>>> User.objects.filter(pk=1)
[<User: user-1>]
>>> User.objects.filter(pk=2)
[<User: admin>]

>>> from django.db import connection
>>> connection.queries
[{u'time': u'0.000', u'sql': u'SET SQL_AUTO_IS_NULL = 0'}, {u'time': u'0.000', u'sql': u'SET SQL_AUTO_IS_NULL = 0'}, {u'time': u'0.000', u'sql': u'SELECT `auth_user`.`id`, `auth_user`.`password`, `auth_user`.`last_login`, `auth_user`.`is_superuser`, `auth_user`.`username`, `auth_user`.`first_name`, `auth_user`.`last_name`, `auth_user`.`email`, `auth_user`.`is_staff`, `auth_user`.`is_active`, `auth_user`.`date_joined` FROM `auth_user` WHERE `auth_user`.`id` = 1 LIMIT 21'}, {u'time': u'0.001', u'sql': u'SELECT `auth_user`.`id`, `auth_user`.`password`, `auth_user`.`last_login`, `auth_user`.`is_superuser`, `auth_user`.`username`, `auth_user`.`first_name`, `auth_user`.`last_name`, `auth_user`.`email`, `auth_user`.`is_staff`, `auth_user`.`is_active`, `auth_user`.`date_joined` FROM `auth_user` WHERE `auth_user`.`id` = 2 LIMIT 21'}]
>>> connection.queries[-1]
{u'time': u'0.001', u'sql': u'SELECT `auth_user`.`id`, `auth_user`.`password`, `auth_user`.`last_login`, `auth_user`.`is_superuser`, `auth_user`.`username`, `auth_user`.`first_name`, `auth_user`.`last_name`, `auth_user`.`email`, `auth_user`.`is_staff`, `auth_user`.`is_active`, `auth_user`.`date_joined` FROM `auth_user` WHERE `auth_user`.`id` = 2 LIMIT 21'}

発行したクエリは、'time'(実行時間)と 'sql' の dict として、発行された順に後ろに追加されていきます。慣れるまで、出力内容が少し読みにくいのが難点でしょうか。

またこちらの方法でも、発行された SQL をリアルタイムに確認することはできません。


(参考)How can I see the raw SQL queries Django is running? - Stack Overflow


 

3)django-debug-toolbar の SQL Panel を使う

プラグインのインストールが必要な物の、一番楽チンな方法。

Django を runserver で起動して、実際に画面を操作してから、右側に表示される SQL Panel を開いて確認するだけです。


django-debug-toolbar の SQL Panel を使うには、条件がいくつかあります。

  • DEBUG = True
  • 'django.contrib.staticfiles' が INSTALLED_APPS に設定済み *1

他にも、settings.py に以下の設定が必要です。 *2

INSTALLED_APPS += ('debug_toolbar',)

def always_show_toolbar(request):
    return True

DEBUG_TOOLBAR_CONFIG = {
    'SHOW_TOOLBAR_CALLBACK': '%s.always_show_toolbar' % __name__,
}


このように、画面からサクサクっと操作が可能です。楽チンですね。

f:id:akiyoko:20160804221039p:plain

f:id:akiyoko:20160804221058p:plain


インストール方法

$ pip install django-debug-toolbar

ちなみに、検証時の環境は以下の通りでした。

  • Python 2.7.6
  • Django (1.9.8)
  • django-debug-toolbar (1.5)


django-debug-toolbar のさらに詳しい説明については、以下の書籍が非常に有用です。「14-04 Django Debug Toolbar」の章に詳しい説明が載っています。
Django 以外にも、Python での開発手法についてのノウハウがいろいろ詰まっていてオススメです。

Pythonプロフェッショナルプログラミング第2版

Pythonプロフェッショナルプログラミング第2版



(参考)


 

4)django-debug-toolbar の debugsqlshell を使う

3)の django-debug-toolbar をインストールしているのであれば、通常の Django shell ではなく debugsqlshell を起動することで、発行される SQL を随時確認することができます。

$ python manage.py debugsqlshell
>>> from django.contrib.auth.models import User
>>> User.objects.filter()

SET SQL_AUTO_IS_NULL = 0 [0.80ms]
SELECT `auth_user`.`id`,
       `auth_user`.`password`,
       `auth_user`.`last_login`,
       `auth_user`.`is_superuser`,
       `auth_user`.`username`,
       `auth_user`.`first_name`,
       `auth_user`.`last_name`,
       `auth_user`.`email`,
       `auth_user`.`is_staff`,
       `auth_user`.`is_active`,
       `auth_user`.`date_joined`
FROM `auth_user` LIMIT 21 [0.19ms]
[<User: admin>]


 

5)django-extensions の shell_plus を --print-sql オプションで起動する

「django-extensions」は、「Django フレームワークの機能を便利に拡張する、管理コマンドやデータベースフィールドなどの詰め合わせ」 *3 です。

django-extensions の shell_plus を --print-sql オプションで起動すれば、4)と同じような機能を使うことができます。

$ python manage.py shell_plus --print-sql
>>> from django.contrib.auth.models import User
>>> User.objects.filter()

SET SQL_AUTO_IS_NULL = 0

Execution time: 0.000056s [Database: default]

SELECT `auth_user`.`id`,
       `auth_user`.`password`,
       `auth_user`.`last_login`,
       `auth_user`.`is_superuser`,
       `auth_user`.`username`,
       `auth_user`.`first_name`,
       `auth_user`.`last_name`,
       `auth_user`.`email`,
       `auth_user`.`is_staff`,
       `auth_user`.`is_active`,
       `auth_user`.`date_joined`
FROM `auth_user` LIMIT 21

Execution time: 0.000391s [Database: default]

[<User: admin>]

この shell_plus には autoloading という機能があるらしいのですが、正直なところ使ったことはありません。



インストール方法

$ pip install django-extensions

settings.py に「django_extensions」を追加。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_extensions',
]

(参考)


 

6)django.db.backends のログレベルを動的に変更

事前の準備がほとんど必要なく、1)や 2)のように明示的に SQL を出力する必要がないので、お手軽に使えます。

Django shell で使うケースが多いかもしれません。

$ python manage.py shell
>>>import logging
>>>l = logging.getLogger('django.db.backends')
>>>l.setLevel(logging.DEBUG)
>>>l.addHandler(logging.StreamHandler())

>>> from django.contrib.auth.models import User
>>> User.objects.filter(pk=1)
(0.000) SET SQL_AUTO_IS_NULL = 0; args=None
(0.000) SET SQL_AUTO_IS_NULL = 0; args=None
(0.000) SELECT `auth_user`.`id`, `auth_user`.`password`, `auth_user`.`last_login`, `auth_user`.`is_superuser`, `auth_user`.`username`, `auth_user`.`first_name`, `auth_user`.`last_name`, `auth_user`.`email`, `auth_user`.`is_staff`, `auth_user`.`is_active`, `auth_user`.`date_joined` FROM `auth_user` WHERE `auth_user`.`id` = 1 LIMIT 21; args=(1,)
[<User: user-1>]


 

7)settings.py の LOGGING を設定

ログに出力したり、コンソールに出力したりと、いろいろ柔軟に設定可能です。リアルタイムに発行される SQL が確認できるのも、この方法の特徴です。

なお、「DEBUG = True」でないと使えないので注意が必要です。


settings.py の設定例

DEBUG = True
LOGGING = {
    'disable_existing_loggers': False,
    'version': 1,
    'handlers': {
        'console': {
            # logging handler that outputs log messages to terminal
            'class': 'logging.StreamHandler',
            'level': 'DEBUG', # message level to be written to console
        },
    },
    'loggers': {
        '': {
            # this sets root level logger to log debug and higher level
            # logs to console. All other loggers inherit settings from
            # root level logger.
            'handlers': ['console'],
            'level': 'DEBUG',
            'propagate': False, # this tells logger to send logging message
                                # to its parent (will send if set to True)
        },
        'django.db': {
            # django also has database level logging
            'handlers': ['console'],
            'level': 'DEBUG',
            'propagate': False,
        },
    },
}


ただしこの方法だと、ログやコンソールが大量の SQL ですぐに埋め尽くされてしまうので、1)や 2)の方法を使って、確認したい SQL だけをピンポイントに出力するようにした方が開発中は捗るかもしれません。


(参考)django - log all sql queries - Stack Overflow


 

まとめ

今回は、Django ORM の SQL を出力する方法として7種類のやり方を紹介しました。
この中から、目的や状況に応じてやり方を使い分けるようにすると、開発の効率もグンとアップすると思います。

良い Django ライフを!

*1:Django 1.9 では、デフォルトで INSTALLED_APPS に設定済みです。

*2:ミニマムな設定です。

*3:Pythonプロフェッショナルプログラミング第2版 より引用

Django ORM の select_related, prefetch_related の挙動を詳しく調べてみた

Django ORM の QuerySet には、select_related および prefetch_related というメソッドがありますが、イマイチ使い勝手がよく分からなかったりします。


公式ドキュメントにはこう書いてありますが、

select_related works by creating an SQL join and including the fields of the related object in the SELECT statement. For this reason, select_related gets the related objects in the same database query. However, to avoid the much larger result set that would result from joining across a ‘many’ relationship, select_related is limited to single-valued relationships - foreign key and one-to-one.

prefetch_related, on the other hand, does a separate lookup for each relationship, and does the ‘joining’ in Python. This allows it to prefetch many-to-many and many-to-one objects, which cannot be done using select_related, in addition to the foreign key and one-to-one relationships that are supported by select_related.


select_related は、JOIN 句を作って、SELECT 句に関連するオブジェクトのフィールドを含めることによって動作する。これにより、select_related は 1本のデータベースクエリで関連オブジェクトを取得することができる。しかしながら、多くのリレーション先を辿って JOIN することで大規模な結果セットを取得してしまうことを避けるために、select_related は、foreign key や one-to-one といった「対一」リレーションのみを JOIN する。

一方、prefetch_related は、それぞれのリレーションに対して別々の参照を行い、Python コードで JOIN(相当の処理)を行う。これによって、select_related によって取得できる foreign key や one-to-one リレーションのオブジェクトだけではなく、select_related では取得不可能な many-to-many や many-to-one リレーションのオブジェクトの先取り(prefetch)をすることができる。


(akiyoko 訳)

https://docs.djangoproject.com/ja/1.9/ref/models/querysets/#select-related
https://docs.djangoproject.com/ja/1.9/ref/models/querysets/#prefetch-related


よく分からないですよね。


そこで今回は、select_related, prefetch_related を使うことでどのようなメリットがあるのかを確認するためにその挙動を詳しく調べてみたので、その結果をまとめてみたいと思います。



少し長いので、結論(分かったこと)から先に書きます。


分かったこと

Django ORM について

  • 1)多対一、一対一のリレーション先のオブジェクトはフィールドにアクセスした時点でクエリが発行される
    • クエリ発行後は取得したオブジェクトがキャッシュされるため、それ以降のクエリは発行されない
    • リレーション先のレコードが存在しなくても NotFound は発生しない
  • 2)一対多、多対多のリレーション先のオブジェクト群は、(all や filter で)アクセスする度にクエリが発行される
    • リレーション先のレコードが存在しなくても NotFound は発生しない

select_related について

  • 3)select_related を使うことで、一度のクエリで多対一、一対一のリレーション先のオブジェクトを取得してキャッシュしてくれる
    • null=False(デフォルト) の ForeignKey の場合は INNER JOIN で結合
    • null=True の ForeignKey の場合は LEFT OUTER JOIN で結合
  • 4)select_related の引数を指定しない場合は、多対一、一対一になっているリレーション先を全て取得してくれるのではなく、null=False の ForeignKey のみが取得対象

prefetch_related について

  • 5)prefetch_related を使うことで、先行してクエリを発行して、一対一、多対一、一対多、多対多のリレーション先のオブジェクト(群)を取得してキャッシュしてくれる
    • ただし、クエリの総数は減らない

注意点

  • select_related, prefetch_related の引数は明示的に指定しよう!



 

検証内容

今回は、一対一のリレーションの検証はしていません。 *1

検証環境

  • Ubuntu 14.04.4 LTS
  • Python 2.7.6
  • Django 1.9.8
  • MySQL 5.5.50

サンプルテーブル

ブログの投稿を想定したいくつかのテーブル群を検証に使用します。
ブログ投稿(blogpost)とブログ画像(blogimage)が多対一、ブログ投稿と投稿者(auth_user)が多対一、ブログ投稿とブログカテゴリが多対多のリレーションを持っていると仮定します。

以下は、論理モデルを ER図にしたものです。

f:id:akiyoko:20160803233339p:plain


参考までに、実際に作成したテーブルから、物理モデルの ER図を自動出力したものが以下になります。 *2

f:id:akiyoko:20160803071330p:plain


事前準備

Mezzanine プロジェクトの開発環境を PyCharm で設定する - akiyoko blog
を参考に、Ubuntu に Django アプリを作成します。

$ sudo apt-get update
$ sudo apt-get -y install python-dev git tree
$ sudo apt-get -y install mysql-server
$ sudo apt-get -y install mysql-client libmysqlclient-dev python-mysqldb
$ sudo mysql_install_db
$ sudo vi /etc/mysql/my.cnf
$ sudo service mysql restart
$ sudo mysql_secure_installation
$ mysql -u root -p
mysql> create database myproject character set utf8;
mysql> create user myprojectuser@localhost identified by "myprojectuserpass";
mysql> grant all privileges on myproject.* to myprojectuser@localhost;
mysql> flush privileges;
mysql> exit

$ wget https://bootstrap.pypa.io/get-pip.py
$ sudo -H python get-pip.py
$ sudo -H pip install virtualenv virtualenvwrapper
$ cat << EOF >> ~/.bash_profile

if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi
EOF
$ cat << EOF >> ~/.bashrc

source /usr/local/bin/virtualenvwrapper.sh
export WORKON_HOME=~/.virtualenvs
EOF
$ source ~/.bashrc
$ mkvirtualenv myproject
$ sudo mkdir -p /opt/webapps/myproject
$ sudo chown -R `whoami`. /opt/webapps
$ pip install MySQL-python
$ cd /opt/webapps/myproject/
$ pip install Django
$ django-admin startproject config .
$ python manage.py startapp blog
$ vi config/settings.py
(INSTALLED_APPS に blog を追加、DATABASES の設定を MySQL に変更)

config/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',
]
    ・
    ・
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'myproject',
        'USER': 'myprojectuser',
        'PASSWORD': 'myprojectuserpass',
        'HOST': '',
        'POST': '',
    }
}
$ vi blog/models.py
from __future__ import unicode_literals

from django.db import models
from django.utils.translation import ugettext_lazy as _


class BlogPost(models.Model):
    """
    A blog post.
    """
    class Meta:
        verbose_name = _("Blog post")
        verbose_name_plural = _("Blog posts")

    categories = models.ManyToManyField("BlogCategory", verbose_name=_("Categories"),
                                        blank=True, related_name="blogposts")
    image = models.ForeignKey("BlogImage", verbose_name=_("Featured Image"),
                              related_name="blogposts", blank=True, null=True)
    user = models.ForeignKey("auth.User", verbose_name=_("Author"), related_name="blogposts")
    title = models.CharField(_("Title"), max_length=255)
    content = models.TextField(_("Content"), blank=True)


class BlogCategory(models.Model):
    """
    A category for grouping blog posts into a series.
    """
    class Meta:
        verbose_name = _("Blog Category")
        verbose_name_plural = _("Blog Categories")

    title = models.CharField(_("Title"), max_length=255)


class BlogImage(models.Model):
    """
    A featured image.
    """
    class Meta:
        verbose_name = _("Blog Image")
        verbose_name_plural = _("Blog Images")

    caption = models.CharField(_("Caption"), max_length=255)
    image_url = models.FileField(verbose_name=_("Image URL"), max_length=255, blank=True, null=True)
$ python manage.py makemigrations
$ python manage.py migrate


 

検証

まずは事前準備として、初期データを投入します。

$ python manage.py shell
>>> import logging
>>> l = logging.getLogger('django.db.backends')
>>> l.setLevel(logging.DEBUG)
>>> l.addHandler(logging.StreamHandler())

>>> from blog.models import BlogPost, BlogCategory, BlogImage
>>> from django.contrib.auth.models import User

>>> User(username='user-1').save()
>>> BlogCategory(title='cat-1').save()
>>> BlogCategory(title='cat-2').save()
>>> BlogImage(caption='image-1', image_url='blog/image1.jpg').save()
>>> user1 = User.objects.get(pk=1)
>>> cat1 = BlogCategory.objects.get(pk=1)
>>> cat2 = BlogCategory.objects.get(pk=2)
>>> image1 = BlogImage.objects.get(pk=1)
>>> BlogPost(image=image1, user=user1, content='post-1').save()
>>> post1 = BlogPost.objects.get(pk=1)
>>> post1.categories = [cat1, cat2]
>>> BlogPost(user=user1, content='post-2').save()
>>> post2 = BlogPost.objects.get(pk=2)


 
ここからが、本番です。

 

1)多対一、一対一のリレーション先のオブジェクトはフィールドにアクセスした時点でクエリが発行される
>>> post1 = BlogPost.objects.filter(pk=1)[0]
(0.002) SELECT `blog_blogpost`.`id`, `blog_blogpost`.`image_id`, `blog_blogpost`.`user_id`, `blog_blogpost`.`title`, `blog_blogpost`.`content` FROM `blog_blogpost` WHERE `blog_blogpost`.`id` = 1 LIMIT 1; args=(1,)
>>> post1.user
(0.000) SELECT `auth_user`.`id`, `auth_user`.`password`, `auth_user`.`last_login`, `auth_user`.`is_superuser`, `auth_user`.`username`, `auth_user`.`first_name`, `auth_user`.`last_name`, `auth_user`.`email`, `auth_user`.`is_staff`, `auth_user`.`is_active`, `auth_user`.`date_joined` FROM `auth_user` WHERE `auth_user`.`id` = 1; args=(1,)
<User: user-1>
>>> post1.image
(0.001) SELECT `blog_blogimage`.`id`, `blog_blogimage`.`caption`, `blog_blogimage`.`image_url` FROM `blog_blogimage` WHERE `blog_blogimage`.`id` = 1; args=(1,)
<BlogImage: BlogImage object>
1−1)クエリ発行後は取得したオブジェクトがキャッシュされるため、それ以降のクエリは発行されない
>>> post1 = BlogPost.objects.filter(pk=1)[0]
(0.002) SELECT `blog_blogpost`.`id`, `blog_blogpost`.`image_id`, `blog_blogpost`.`user_id`, `blog_blogpost`.`title`, `blog_blogpost`.`content` FROM `blog_blogpost` WHERE `blog_blogpost`.`id` = 1 LIMIT 1; args=(1,)
>>> post1.user
(0.000) SELECT `auth_user`.`id`, `auth_user`.`password`, `auth_user`.`last_login`, `auth_user`.`is_superuser`, `auth_user`.`username`, `auth_user`.`first_name`, `auth_user`.`last_name`, `auth_user`.`email`, `auth_user`.`is_staff`, `auth_user`.`is_active`, `auth_user`.`date_joined` FROM `auth_user` WHERE `auth_user`.`id` = 1; args=(1,)
<User: user-1>
>>> post1.user
<User: user-1>
>>> post1.image
(0.001) SELECT `blog_blogimage`.`id`, `blog_blogimage`.`caption`, `blog_blogimage`.`image_url` FROM `blog_blogimage` WHERE `blog_blogimage`.`id` = 1; args=(1,)
<BlogImage: BlogImage object>
>>> post1.image
<BlogImage: BlogImage object>
1−2)リレーション先のレコードが存在しなくても NotFound は発生しない
>>> post2 = BlogPost.objects.filter(pk=2)[0]
(0.001) SELECT `blog_blogpost`.`id`, `blog_blogpost`.`image_id`, `blog_blogpost`.`user_id`, `blog_blogpost`.`title`, `blog_blogpost`.`content` FROM `blog_blogpost` WHERE `blog_blogpost`.`id` = 2 LIMIT 1; args=(2,)
>>> post2.image
>>> post2.image is None
True


 

2)一対多、多対多のリレーション先のオブジェクト群は、all や filter でアクセスする度にクエリが発行される
>>> post1 = BlogPost.objects.filter(pk=1)[0]
>>> post1.categories
<django.db.models.fields.related_descriptors.ManyRelatedManager object at 0x7f2509080a10>
>>> post1.categories.all()
(0.001) SELECT `blog_blogcategory`.`id`, `blog_blogcategory`.`title` FROM `blog_blogcategory` INNER JOIN `blog_blogpost_categories` ON (`blog_blogcategory`.`id` = `blog_blogpost_categories`.`blogcategory_id`) WHERE `blog_blogpost_categories`.`blogpost_id` = 1 LIMIT 21; args=(1,)
[<BlogCategory: BlogCategory object>, <BlogCategory: BlogCategory object>]
>>> post1.categories.all()
(0.000) SELECT `blog_blogcategory`.`id`, `blog_blogcategory`.`title` FROM `blog_blogcategory` INNER JOIN `blog_blogpost_categories` ON (`blog_blogcategory`.`id` = `blog_blogpost_categories`.`blogcategory_id`) WHERE `blog_blogpost_categories`.`blogpost_id` = 1 LIMIT 21; args=(1,)
[<BlogCategory: BlogCategory object>, <BlogCategory: BlogCategory object>]
2−1)リレーション先のレコードが存在しなくても NotFound は発生しない
>>> post2 = BlogPost.objects.filter(pk=2)[0]
(0.001) SELECT `blog_blogpost`.`id`, `blog_blogpost`.`image_id`, `blog_blogpost`.`user_id`, `blog_blogpost`.`title`, `blog_blogpost`.`content` FROM `blog_blogpost` WHERE `blog_blogpost`.`id` = 2 LIMIT 1; args=(2,)
>>> post2.categories.all()
(0.001) SELECT `blog_blogcategory`.`id`, `blog_blogcategory`.`title` FROM `blog_blogcategory` INNER JOIN `blog_blogpost_categories` ON (`blog_blogcategory`.`id` = `blog_blogpost_categories`.`blogcategory_id`) WHERE `blog_blogpost_categories`.`blogpost_id` = 2 LIMIT 21; args=(2,)
[]


 

3)select_related を使うことで、一度のクエリで多対一、一対一のリレーション先のオブジェクトを取得してキャッシュしてくれる

なお、

  • null=False(デフォルト) の ForeignKey の場合は INNER JOIN で結合
  • null=True の ForeignKey の場合は LEFT OUTER JOIN で結合

となる。

>>> post1 = BlogPost.objects.filter(pk=1).select_related('user', 'image')[0]
(0.001) SELECT `blog_blogpost`.`id`, `blog_blogpost`.`image_id`, `blog_blogpost`.`user_id`, `blog_blogpost`.`title`, `blog_blogpost`.`content`, `blog_blogimage`.`id`, `blog_blogimage`.`caption`, `blog_blogimage`.`image_url`, `auth_user`.`id`, `auth_user`.`password`, `auth_user`.`last_login`, `auth_user`.`is_superuser`, `auth_user`.`username`, `auth_user`.`first_name`, `auth_user`.`last_name`, `auth_user`.`email`, `auth_user`.`is_staff`, `auth_user`.`is_active`, `auth_user`.`date_joined` FROM `blog_blogpost` LEFT OUTER JOIN `blog_blogimage` ON (`blog_blogpost`.`image_id` = `blog_blogimage`.`id`) INNER JOIN `auth_user` ON (`blog_blogpost`.`user_id` = `auth_user`.`id`) WHERE `blog_blogpost`.`id` = 1 LIMIT 1; args=(1,)
>>> post1.user
<User: user-1>
>>> post1.image
<BlogImage: BlogImage object>


 

4)select_related の引数を指定しない場合は、多対一、一対一になっているリレーション先を全て取得してくれるのではなく、null=False の ForeignKey のみが取得対象

なので、select_related の引数は明示的に指定しましょう。

>>> post1 = BlogPost.objects.filter(pk=1).select_related()[0]
(0.001) SELECT `blog_blogpost`.`id`, `blog_blogpost`.`image_id`, `blog_blogpost`.`user_id`, `blog_blogpost`.`title`, `blog_blogpost`.`content`, `auth_user`.`id`, `auth_user`.`password`, `auth_user`.`last_login`, `auth_user`.`is_superuser`, `auth_user`.`username`, `auth_user`.`first_name`, `auth_user`.`last_name`, `auth_user`.`email`, `auth_user`.`is_staff`, `auth_user`.`is_active`, `auth_user`.`date_joined` FROM `blog_blogpost` INNER JOIN `auth_user` ON (`blog_blogpost`.`user_id` = `auth_user`.`id`) WHERE `blog_blogpost`.`id` = 1 LIMIT 1; args=(1,)
>>> post1.user
<User: user-1>
>>> post1.image
(0.000) SELECT `blog_blogimage`.`id`, `blog_blogimage`.`caption`, `blog_blogimage`.`image_url` FROM `blog_blogimage` WHERE `blog_blogimage`.`id` = 1; args=(1,)
<BlogImage: BlogImage object>


 

5)prefetch_related を使うことで、先行してクエリを発行して、一対一、多対一、一対多、多対多のリレーション先のオブジェクト(群)を取得してキャッシュしてくれる

prefetch_related の場合は、JOIN 句を使うのではなく、クエリを別々に発行して、プログラム的にオブジェクト内に結合してくれます。クエリの総数が減ることはありませんので、クエリの発行数を減らすという目的で prefetch_related を使用することはできません。


(多対一の場合)

>>> post1 = BlogPost.objects.filter(pk=1).prefetch_related('user')[0]
(0.001) SELECT `blog_blogpost`.`id`, `blog_blogpost`.`image_id`, `blog_blogpost`.`user_id`, `blog_blogpost`.`title`, `blog_blogpost`.`content` FROM `blog_blogpost` WHERE `blog_blogpost`.`id` = 1 LIMIT 1; args=(1,)
(0.000) SELECT `auth_user`.`id`, `auth_user`.`password`, `auth_user`.`last_login`, `auth_user`.`is_superuser`, `auth_user`.`username`, `auth_user`.`first_name`, `auth_user`.`last_name`, `auth_user`.`email`, `auth_user`.`is_staff`, `auth_user`.`is_active`, `auth_user`.`date_joined` FROM `auth_user` WHERE `auth_user`.`id` IN (1); args=(1,)
>>> post1.user
<User: user-1>
>>> post1.__dict__
{'user_id': 1L, 'title': u'', '_user_cache': <User: user-1>, '_state': <django.db.models.base.ModelState object at 0x7f2509033310>, 'content': u'post-1', 'image_id': 1L, '_prefetched_objects_cache': {}, 'id': 1L}


(一対多の場合)

>>> user1 = User.objects.filter(pk=1).prefetch_related('blogposts')[0]
(0.001) SELECT `auth_user`.`id`, `auth_user`.`password`, `auth_user`.`last_login`, `auth_user`.`is_superuser`, `auth_user`.`username`, `auth_user`.`first_name`, `auth_user`.`last_name`, `auth_user`.`email`, `auth_user`.`is_staff`, `auth_user`.`is_active`, `auth_user`.`date_joined` FROM `auth_user` WHERE `auth_user`.`id` = 1 LIMIT 1; args=(1,)
(0.000) SELECT `blog_blogpost`.`id`, `blog_blogpost`.`image_id`, `blog_blogpost`.`user_id`, `blog_blogpost`.`title`, `blog_blogpost`.`content` FROM `blog_blogpost` WHERE `blog_blogpost`.`user_id` IN (1); args=(1,)
>>> user1.blogposts.all()
[<BlogPost: BlogPost object>, <BlogPost: BlogPost object>]


(多対多の場合)

>>> post1 = BlogPost.objects.filter(pk=1).prefetch_related('categories')[0]
(0.000) SELECT `blog_blogpost`.`id`, `blog_blogpost`.`image_id`, `blog_blogpost`.`user_id`, `blog_blogpost`.`title`, `blog_blogpost`.`content` FROM `blog_blogpost` WHERE `blog_blogpost`.`id` = 1 LIMIT 1; args=(1,)
(0.000) SELECT (`blog_blogpost_categories`.`blogpost_id`) AS `_prefetch_related_val_blogpost_id`, `blog_blogcategory`.`id`, `blog_blogcategory`.`title` FROM `blog_blogcategory` INNER JOIN `blog_blogpost_categories` ON (`blog_blogcategory`.`id` = `blog_blogpost_categories`.`blogcategory_id`) WHERE `blog_blogpost_categories`.`blogpost_id` IN (1); args=(1,)
>>> post1.categories.all()
[<BlogCategory: BlogCategory object>, <BlogCategory: BlogCategory object>]
>>> post1.categories.__dict__
{'source_field': <django.db.models.fields.related.ForeignKey: blogpost>, 'reverse': False, 'source_field_name': 'blogpost', '_constructor_args': ((<BlogPost: BlogPost object>,), {}), 'creation_counter': 32, 'target_field_name': u'blogcategory', '_inherited': False, '_db': None, 'query_field_name': u'blogposts', '_hints': {}, 'prefetch_cache_name': 'categories', 'instance': <BlogPost: BlogPost object>, 'through': <class 'blog.models.BlogPost_categories'>, 'core_filters': {u'blogposts__id': 1L}, 'symmetrical': False, 'model': <class 'blog.models.BlogCategory'>, 'related_val': (1L,), 'target_field': <django.db.models.fields.related.ForeignKey: blogcategory>, 'name': None}


 
なお、filter() ではクエリが再発行されてしまうようです。all() を先取りしてキャッシュしているのかな。。

>>> post1 = BlogPost.objects.filter(pk=1).prefetch_related('categories')[0]
>>> post1.categories.filter()
(0.000) SELECT `blog_blogcategory`.`id`, `blog_blogcategory`.`title` FROM `blog_blogcategory` INNER JOIN `blog_blogpost_categories` ON (`blog_blogcategory`.`id` = `blog_blogpost_categories`.`blogcategory_id`) WHERE `blog_blogpost_categories`.`blogpost_id` = 1 LIMIT 21; args=(1,)
[<BlogCategory: BlogCategory object>, <BlogCategory: BlogCategory object>]


 
あと、prefetch_related の引数を指定しないと、リレーション先を取得してくれないように見えます。どこまでリレーション先を取ってくるか分からないから、明示的に指定しないと取得しないという仕様なのかもしれません。

なので、prefetch_related の引数は明示的に指定しましょう。

>>> post1 = BlogPost.objects.filter(pk=1).prefetch_related()[0]
(0.000) SELECT `blog_blogpost`.`id`, `blog_blogpost`.`image_id`, `blog_blogpost`.`user_id`, `blog_blogpost`.`title`, `blog_blogpost`.`content` FROM `blog_blogpost` WHERE `blog_blogpost`.`id` = 1 LIMIT 1; args=(1,)

 

まとめ

select_related や prefetch_related の使い道としては主に、後続の処理で何度もアクセスされるオブジェクトを先に取得しておきたいときに使うのがよい と思います。特に、一側のオブジェクトを取得する場合でクエリの本数を減らしたいなら select_related を、多側のオブジェクト群を取得したいなら prefetch_related を検討すればよいでしょう。


Django ORM は、クエリ(SQL)をあまり意識せずに使えて便利な半面、何も分からずに使っていると、クエリの本数や実行速度がボトルネックになって、応答速度の遅い処理を作ってしまいがちです。

クエリの実行を含む処理の応答速度が遅い場合は、まずはクエリを確認し、select_related や prefetch_related を使って実行速度を改善することも検討すべきです。

*1:一対一リレーションの挙動は、多対一リレーションの場合とほぼ同じでした。双方から ForeignKey が付与されていると考えればよいのかも。

*2:PyCharm Professional のデータベース機能を使いました。http://akiyoko.hatenablog.jp/entry/2016/03/13/141600 を参照

「一対一」「一対多」「多対多」のリレーションを分かりやすく説明する

こんにちは、akiyoko です。

今回はデータベース設計の話です。
分かりそうでよく分からない、「一対一」「一対多」「多対多」のリレーションを分かりやすく説明してみます。

f:id:akiyoko:20160801054222p:plain


一対一リレーション

f:id:akiyoko:20160731193333p:plain

分かりやすい定義

  • 双方のレコードが一対一に対応する

あるいは、

  • 双方の主キーが同じ


 

設計例

一対一リレーションは、分割しなくてもよいテーブルが分割されている状態です。既存のテーブル構成を変えずに項目を追加したい場合などを除き、積極的に使用する機会はあまり無いように思います。


 

一対多リレーション

f:id:akiyoko:20160731214014p:plain

分かりやすい定義

  • A のレコードは B の複数のレコードと関連する可能性があるが、B のレコードは A のレコードと最大一件のみ関連する

もう少し詳しく説明すると、

  • A から見れば、A の 1つのレコードが同時に複数の B のレコードと関連している(関連のないレコードもある)
  • B から見れば、B の 1つのレコードが A の 1つのレコードのみと関連している(関連のないレコードもある)

分かりやすい具体例

  • 例1)A:ブログの投稿者、B:ブログの投稿
  • 例2)A:顧客、B:注文
  • 例3)A:注文、B:注文明細
  • 例4)A:部署、B:従業員

 

設計例

A(一側)は、正規化によって B(多側)から分割されたテーブルである場合が多いです。

一対多リレーションでは、一側のテーブルの主キーを参照する外部キーを、多側のテーブルに設けて対応する場合が多いでしょう。


 

多対多リレーション

f:id:akiyoko:20160731193422p:plain

分かりやすい定義

  • A のレコードは B の複数のレコードと関連する可能性があり、B のレコードも A の複数のレコードと関連する可能性がある

もう少し詳しく説明すると、

  • A から見れば、A の 1つのレコードが同時に複数の B のレコードと関連している(関連のないレコードもある)
  • B から見れば、B の 1つのレコードが同時に複数の A のレコードと関連している(関連のないレコードもある)

 

分かりやすい具体例

  • 例1)A:ブログのカテゴリ、B:ブログの投稿 *1
  • 例2)A:ユーザ、B:権限 *2

 

設計例

多対多リレーションでは、双方に外部キーを設け、中間テーブルを利用するケースが多いでしょう。


 

*1:※投稿にはカテゴリが複数設定できるという前提

*2:※ユーザには権限が複数設定できるという前提

「SEO初心者に贈るWebライティング講座 ~キーワードからの記事作成編~」に参加しました

主催

株式会社クリーク・アンド・リバー社


会場

株式会社クリーク・アンド・リバー社
東京都千代田区一番町8番地 住友不動産一番町ビル 麹町制作ルーム5F


感想など

二週間以上経ってしまいましたが、今月中旬に「SEO初心者に贈るWebライティング講座 ~キーワードからの記事作成編~」というイベントに参加してきました。


イベントの内容は、

  • ① SEOを意識したWebライティングに欠かせない、キーワードの選定
  • ② キーワードの検索ボリュームやSEO難易度をチェック
  • ③ 複合語で絞り込むユーザーニーズと記事テーマ
  • ④ 設定キーワードを軸にした文章構成術

ということで、Webライティングにあたっての SEOノウハウについての
ちなみに有料(1,000円)でしたが、かなり盛り沢山の内容で気になっていたことも直接質問できたので、個人的には安いくらいでした。


最近、人の気持ちに訴えかけるライティングのコツを身につけるために、ライティングのレジェンドと言われているダン・ケネディの本を読んでみたのですが、今回の内容は、Google のランキングアルゴリズムに訴えかけるライティングのコツについての勉強会です。

究極のセールスレター シンプルだけど、一生役に立つ!お客様の心をわしづかみにするためのバイブル

究極のセールスレター シンプルだけど、一生役に立つ!お客様の心をわしづかみにするためのバイブル

  • 作者: ダン・ケネディ,神田昌典,齋藤慎子
  • 出版社/メーカー: 東洋経済新報社
  • 発売日: 2007/03/30
  • メディア: 単行本(ソフトカバー)
  • 購入: 22人 クリック: 143回
  • この商品を含むブログ (13件) を見る


SEO テクニックについては、少し前の「タイ全裸事件」で 辻正浩さんの Twitter をフォローしたのがきっかけで最近興味を持っていたのですが、SEO についての最新情報や全体像を確認しておきたいと思い、今回のイベントに参加した次第です。


講師は、現在フリーで Web制作・ライターをされている方で、これまで 10年くらい雑誌系のライターや Web制作をしながら、オンライン Webスクールの主催および、ブログ、SEOなどの講師等も歴任してきたとのことです。




メモ

  • Webライティングには 3つのポイント
    • タイトルと説明文
    • パッと見の印象(ファーストビュー)
    • 読みやすく、分かりやすい文章術
  • 被リンク(外部要因)も重要だが、Google はコンテンツ(内部要因)重視
    • 過去のパンダアップデート、ペンギンアップデートによって、業者が 3〜4年くらい前に壊滅状態に
    • コンテンツ is king.
  • テクニック
    • タイトルにサイトの最重要キーワードを必ず含める!
      • タイトルの頭の方に重要なキーワードを入れておく
    • タイトルは 30文字以内で!
    • タイトルは、単語の羅列ではなくきちんとした文章にする
    • タイトルの重複に注意!
    • 内容はユーザへの訴求力も必要
    • まず結論を
    • ユーザーにとってのメリット、ターゲットを明確に
      • 「何杯食べても太らない」「初心者のための」
    • 具体的な表現を。数字を入れると効果高い
    • meta description は SEO的にはもはや効果がないとされているが、SERPs に説明文として表示されるケースがあるので気をつけるべし
      • 検索語で説明文が変わることも
    • 見出しや本文でも適切にキーワードを使用する
    • 代名詞はなるべく使わず、一般名詞を(くどくならない範囲で)使うようにする
    • 内部リンクも SEO に影響するので、「こちら」にリンクを張らないように気をつける(リンク先の要約などにする)
    • 主要な画像には alt を入れる。キャプション(近くに説明文)もあった方がいい
    • 本文の文字数は多い方がいいが、最終的には質が大事
      • ミラーリングは嫌われる。オリジナル要素が重要
  • キーワード選定に役立つツール
  • 検索した人の意図を考えるべし!

Google Analytics の Kindle 本

Mac の MySQL クライアントに「Sequel Pro」を使っているなら PostgreSQL クライアントは「PSequel」がオススメ

タイトル通りですが、Mac の MySQL クライアントに「Sequel Pro」を使っているなのであれば、PostgreSQL クライアントは「PSequel」がオススメです。


長年、Mac の PostgreSQL クライアントに不満があり、使い勝手の良いアプリを探し求めていたのですが、ついにその答えを見つけたような気がします。

私が個人的に一番使い勝手が良いと思っている MySQL クライアントが「Sequel Pro」なのですが、その Sequel Pro の PostgreSQL 版とも言うべきアプリがこの「PSequel」なのです。


PSequel の作者も、このように言っています。

Well, pgAdmin is great for its feature-richness. However, I found its UI is clumsy and complicated. I know there is a list of PostgreSQL GUI Tools. However, they are either web-based, Java-based* or don't support the features I want. In the good old MySQL world, my favorite client is Sequel Pro, but its support for PostgreSQL doesn't seem to be happening. So, I decided to make one myself.


pgAdmin って機能が抱負で良いんだけどさ、UI がイケてないよね。他にも PostgreSQL の GUI ツールはいろいろあるんだけど、Web ベースだったり、Java ベースだったり、欲しい機能が無かったりでちょっとアレだよね。MySQL だったら、僕のお気に入りのクライアントは Sequel Pro なんだけど、どうやら PostgreSQL のサポートはしなさそうだし、じゃあ自分で作っちゃえって思っちゃったわけ。


(akiyoko 意訳)


 
さて今回は、PSequel の使い方、特に PostgreSQL への接続方法について紹介します。


そもそも実際のシステムでは、PostgreSQL 等のデータベースは通常、サーバの外にはポートは開放しておらず、SSH でリモートサーバに乗り込んでサーバ上のデータベースに接続するというパターンが多いでしょう。本番環境や検証環境では、公開鍵認証でログインすることが一般的でしょうが、開発環境では Vagrant 等で仮想サーバを利用することも多く、その場合はパスワード認証を使うこともあるでしょう。


そこで今回は、リモートサーバ上の PostgreSQL に SSH トンネル経由で接続する方法として、

  • 1)サーバに SSH公開鍵認証でログイン ⇒ サーバ上の PostgreSQL に接続
  • 2)サーバにパスワード認証でログイン ⇒ サーバ上の PostgreSQL に接続

という二つのパターンを紹介します *1


接続する PostgreSQL の設定は、以下の通りとします。

項目 設定内容
データベース名 myproject
データベースユーザ myprojectuser
データベースユーザパスワード myprojectuserpass


 

インストール

インストールするには、Homebrew で

$ brew cask install psequel

とするか、公式ページ から app ファイルをダウンロードしてインストールします。


www.psequel.com


 

PostgreSQL への接続

1)サーバに SSH公開鍵認証ログイン ⇒ サーバ上の PostgreSQL に接続

サーバに SSH公開鍵認証でログインして、SSHトンネル経由で PostgreSQL に接続する方法です。

本番環境(PostgreSQL on EC2)として、各種設定は以下を想定しています。

サーバ IPアドレス 52.100.100.100
ログインユーザ ubuntu
SSH秘密鍵 ~/.ssh/aws_p1.pem


f:id:akiyoko:20160729012558p:plain


2)サーバにパスワード認証ログイン ⇒ サーバ上の PostgreSQL に接続

サーバに SSH公開鍵認証でログインして、SSHトンネル経由で PostgreSQL に接続する方法です。

開発環境(PostgreSQL on Vagrant)として、各種設定は以下を想定しています。

サーバ IPアドレス 192.168.32.10
ログインユーザ vagrant
ログインパスワード vagrant


しかしながら、ここでハマりポイントがあります。
実は、PSequel はパスワード認証による SSHトンネリング機能をサポートしていないため(そして今後もサポートする予定は無さそう *2)、Vagrant のセッティングから SSH秘密鍵のパスを参照することができるという裏ワザ(?)を利用します。

The biggest requirement for a GUI of any sort (whether it is for a database client or something else), is the ability to use SSH tunneling to connect to the server. SSH tunneling allows an application to first connect to a machine (in this case a virtual machine managed by Vagrant), and act as normally as if it were connected directly to a service on your personal machine.


While PSequel does support SSH tunneling, what it does not do is support password authentication. I’m sure there was a good reason for this, but in the case of Vagrant it is irritating nontheless. Most Vagrant boxes have a default username and password (vagrant and vagrant respectively). This makes it incredibly simple to use applications that support SSH tunneling.


In the case of PSequel, the only authentication method that is supported is key authentication. Luckily for us, Vagrant does typically utilize this method of authentication (when you SSH into the Vagrant machine, for example, a private key is used so you don’t have to enter a password). Unfortunately for us, the location of this private key can be different for every machine. If you don’t know how to find it, using PSequel can seem impossible.


To find the private key for a Vagrant box, open up a terminal, navigate to the project folder, and type vagrant ssh-config. You’ll see something like this:


http://zacharyflower.com/using-psequel-with-vagrant/

操作は簡単で、Vagrant のホームに移動して「vagrant ssh-config」コマンドを実行すれば OK です。

$ cd vagrant/ubuntu14/
$ vagrant ssh-config
Host default
  HostName 127.0.0.1
  User vagrant
  Port 2200
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile /Users/akiyoko/vagrant/ubuntu14/.vagrant/machines/default/virtualbox/private_key
  IdentitiesOnly yes
  LogLevel FATAL


 
これを利用して、以下の接続設定を行えばよいでしょう。

f:id:akiyoko:20160729012628p:plain

f:id:akiyoko:20160729012651p:plain


ただし正確に言えば、パスワード認証でサーバにログインできない問題が解決できたわけではないので、Vagrant 以外だったらどうするか?については今後の課題でしょうか。




ここからは、MySQL クライアントの「Sequel Pro」*3 の接続設定についてのメモです。

「Sequel Pro」の接続設定

1)サーバに SSH公開鍵認証ログイン ⇒ サーバ上の PostgreSQL に接続

本番環境(PostgreSQL on EC2)として、各種設定は以下を想定。

サーバ IPアドレス 52.100.100.100
ログインユーザ ubuntu
SSH秘密鍵 ~/.ssh/aws_p1.pem

f:id:akiyoko:20160729012257p:plain

2)サーバにパスワード認証ログイン ⇒ サーバ上の PostgreSQL に接続

開発環境(PostgreSQL on Vagrant)として、各種設定は以下を想定。

サーバ IPアドレス 192.168.32.10
ログインユーザ vagrant
ログインパスワード vagrant

f:id:akiyoko:20160729012527p:plain

*1:2015年1月より、PSequel に SSH トンネリング機能がサポートされました。https://twitter.com/psequel/status/550881414540173312

*2:https://github.com/psequel/psequel/issues/17

*3:ちなみに、Sequel は「シクォル」と発音するようです。

「PayPal Tech Meetup #2」に参加してきました

会場

イベント&コミュニティスペース dots.
東京都渋谷区宇田川町20-17 NOF渋谷公園通りビル 8F


Twitter

twitter.com


Togetter

togetter.com


感想など

今年の 3月くらいから個人的に PayPal 決済連携の検証をしていたので、今回の Meetup はすごく勉強になりました。特に、PayPal の Okamura Junichi さんに直接疑問をぶつけることができたのが一番の収穫でした。


今回ゲットした情報ですごく有益だったものをいくつかピックアップ。



あと、ドキュメントがとっ散らかっていた印象を受けた 3月頃に比べて、現在は、PayPal の公式ページが徐々にリニューアルされて情報が整理されつつあるようです。

有益な情報源をいくつかゲット。



前回のイベント「PayPal Tech Meetup #1」の存在を知らなかったのですが、知りたかった機能についていくつか紹介されているっぽいです。検証時に知っていればもっと楽だったのになぁ。。



それはそうと、今回の Meetup は豪華賞品付きのデモ大会でした。テーマは「PayPalまたはBraintree APIを使ったアプリ(Web、モバイル、デバイスなど種類は問いません)」で、10人の参加者が LT形式で 5分ずつの枠でプレゼンを行いました。


私は 5番目に発表した @eiden 永田雅文さんの「Botにコーヒーを頼むアプリ」に投票しましたが、結果は二位でした。惜しいっ!!
一位は @kameturu 亀井浩明さんの「暮らしの中のPayPal」でした。
おめでとうございます!!


f:id:akiyoko:20160615212945j:plain



 

1. PayPalボタンで商品を実際に売ってみたよ

@n0bisuke さん(株式会社LIG)

  • サーバ側から push する機能を作ってる
  • Service Worker + WebPush
  • PayPal Button + Notifications API
  • PayPal の Webhook は Https しか対応してない
  • RequestBin を使う
  • netlify
  • SendPulse


 

2. Instagramでお買い物

新井健三さん(Rascal)


 

3. 暮らしの中のPayPal

@kameturu 亀井浩明さん(Origami)


  • ECじゃなくて、Raspberry Pi を使って通常の店舗でスマホを使って PayPal を使う
  • One-Touch を使いたかった。。
  • PayPal REST API (Payments API)
  • Eddystone で飛ばした(iBeacon は将来対応?)


 

4. 専属家政婦くん

@tokutoku393 ちゃんとくさん

  • 何の成果も得られませんでした!


 

5. Botにコーヒーを頼むアプリ

@eiden 永田雅文さん(株式会社Showcase Gig)



 

6. MessangerAPIで課金

@bboobbaa 吉澤和香奈さん(株式会社CAMON.TOKYO)


  • ベトナム人向けに簡単にチケットを売りたい
  • ベトナム人ほとんどFacebook使ってる
  • Messenger でボットを使う


 

7. PayPal APIとAWSで作る動画コンテンツマーケットサイト

@takeyuweb 竹内雄一さん



 

8. テラレン@LINE窓口

@matsubokkuri 松倉友樹さん


  • テラレン
  • PayPal Subscription を使ってる
  • LINE bot で窓口を自動化


 

9. ちょっと寄付休憩はいりまーす

keigohtr 服部圭悟さん(Apitore)

  • 寄付と昼寝
  • PayPal In-Context Checkout


 

10. ブロックチェーンで資産管理

jkkch 菊池条さん


  • デジタルコンテンツの所有権を管理
  • オンラインカードショップの ECサイト
  • ブロックチェーンは Eris を使用