<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>アストロデオブログ &#187; MySQL</title>
	<atom:link href="http://astrodeo.com/blog/archives/category/mysql/feed" rel="self" type="application/rss+xml" />
	<link>http://astrodeo.com/blog</link>
	<description>Just another WordPress weblog</description>
	<lastBuildDate>Mon, 11 Jul 2011 01:38:11 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.1.2</generator>
		<item>
		<title>MySQLでdecimalを使うときの注意点</title>
		<link>http://astrodeo.com/blog/archives/1643</link>
		<comments>http://astrodeo.com/blog/archives/1643#comments</comments>
		<pubDate>Tue, 07 Jun 2011 03:49:14 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[MySQL]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/?p=1643</guid>
		<description><![CDATA[たいした話ではないんですが、僕自身がちょいと勘違いをしていたことがあったので、備忘録的なメモってことで……。 MySQLで少数を扱うようなとき、型にdecimalを使うことがあるかと思います。 decimalの場合、桁数を(M,D)の形で指定します。内訳は以下の通りです。 M…扱う少数の最大桁数 D…小数点以下の桁数 例えば、(6,3)という桁数を指定したら、全体で6桁、小数点以下は3桁、という解釈になります。123.456みたいな数値を扱えることになりますね。 僕はそこをちょっと勘違いしてて、上記でいうところのMっていうのは、小数点以上の桁数の指定だと思ってました。だから(6,3)って指定したら、123456.789みたいな数値を扱う状態になるのかなという認識でした。 先日、decimalの桁数を(3,3)で指定して、0～100のパーセンテージを持つカラムを作成したんですけど、どんなにデータを入れてもレコードの中が『0.999』にしかならなくて、おかしいなぁと思っていたんです。 原因は桁数の指定の間違いにありました。(6,3)に直したら解決しました。 もし、僕と同じ間違いをしている人がいたら、この記事が参考になれば幸いです。 ってか、一人くらいはいてほしいな……世界中でそんな勘違いをしたのが俺だけってのも、さびしいし恥ずかしいし……。]]></description>
			<content:encoded><![CDATA[<p>たいした話ではないんですが、僕自身がちょいと勘違いをしていたことがあったので、備忘録的なメモってことで……。</p>
<p>MySQLで少数を扱うようなとき、型にdecimalを使うことがあるかと思います。</p>
<p>decimalの場合、桁数を(M,D)の形で指定します。内訳は以下の通りです。</p>
<p>M…扱う少数の最大桁数</p>
<p>D…小数点以下の桁数</p>
<p>例えば、(6,3)という桁数を指定したら、全体で6桁、小数点以下は3桁、という解釈になります。123.456みたいな数値を扱えることになりますね。</p>
<p>僕はそこをちょっと勘違いしてて、上記でいうところのMっていうのは、小数点以上の桁数の指定だと思ってました。だから(6,3)って指定したら、123456.789みたいな数値を扱う状態になるのかなという認識でした。</p>
<p>先日、decimalの桁数を(3,3)で指定して、0～100のパーセンテージを持つカラムを作成したんですけど、どんなにデータを入れてもレコードの中が『0.999』にしかならなくて、おかしいなぁと思っていたんです。</p>
<p>原因は桁数の指定の間違いにありました。(6,3)に直したら解決しました。</p>
<p>もし、僕と同じ間違いをしている人がいたら、この記事が参考になれば幸いです。</p>
<p>ってか、一人くらいはいてほしいな……世界中でそんな勘違いをしたのが俺だけってのも、さびしいし恥ずかしいし……。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/1643/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>GREATESTはグレイト…とも言い切れない</title>
		<link>http://astrodeo.com/blog/archives/1565</link>
		<comments>http://astrodeo.com/blog/archives/1565#comments</comments>
		<pubDate>Sat, 27 Nov 2010 11:35:19 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[MySQL]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/?p=1565</guid>
		<description><![CDATA[MySQLでとあるカラムの中の最小値や最大値を取得したい場合には、MIN()やMAX()を使えば実現できる。 SELECT MIN(age) FROM users ユーザーテーブルの中に年齢を持っておくカラムがあったとして、最少年齢はいくつなのかを取るとしたら、上記のような感じになると思う。 ここで、もし男女別にカラムが分かれていて、両方のカラムの中から最小値、あるいは最大値を取得したいときにはどうするか。 そういうときは、LEAST()やGREATEST()で解決できる。 SELECT LEAST(man_age, woman_age) FROM users 一つのユーザーデータの中に男女別の年齢が入っているなんて、まずありえない構成だとは思いますけどね。男性としての年齢と女性としての年齢を同時に持つ人…完全に新人類じゃん。ニューカマーじゃん。いや、多重人格者なら、性別と年齢の違う人格を備えていることも…まあそこは何でもいいです。 いい例が思い浮かばなかったので、今回はこれでいきましょう。 ちなみにこの書き方だと、各レコードのman_ageとwoman_ageのデータのうち、値の小さい方を持ってくるだけになってしまうので、全データの中で最小値を持ってくるのであれば、これとMIN()を組み合わせるといい。 SELECT MIN(LEAST(man_age, woman_age)) FROM users 何かもっといい書き方があるような気もするけれど、とりあえずこれで複数のカラムの中での最小値やら最大値を取得することは可能になる。 ただ、一つ注意が必要なのは、データの中にNULLが入っていると、どんな数値が入っていても最小値や最大値にはNULLが返ってきてしまう。LEAST()やGREATEST()ってのは、そういう風に動作する関数らしい。 今までLEAST()やGREATEST()という関数の存在を知らなかったから、こいつを使えばいけるじゃんと思いつつも、構成的にNULLを許可しないわけにはいかない仕様のせいで、残念ながら今回はこのやり方を生かすことはできませんでした。が、これは別の機会に使える日が来るような気がするので、忘れないうちにここにメモっておきたいと思います。]]></description>
			<content:encoded><![CDATA[<p>MySQLでとあるカラムの中の最小値や最大値を取得したい場合には、MIN()やMAX()を使えば実現できる。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

SELECT MIN(age) FROM users

</textarea>
<p>ユーザーテーブルの中に年齢を持っておくカラムがあったとして、最少年齢はいくつなのかを取るとしたら、上記のような感じになると思う。</p>
<p>ここで、もし男女別にカラムが分かれていて、両方のカラムの中から最小値、あるいは最大値を取得したいときにはどうするか。</p>
<p>そういうときは、LEAST()やGREATEST()で解決できる。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

SELECT LEAST(man_age, woman_age) FROM users

</textarea>
<p>一つのユーザーデータの中に男女別の年齢が入っているなんて、まずありえない構成だとは思いますけどね。男性としての年齢と女性としての年齢を同時に持つ人…完全に新人類じゃん。ニューカマーじゃん。いや、多重人格者なら、性別と年齢の違う人格を備えていることも…まあそこは何でもいいです。</p>
<p>いい例が思い浮かばなかったので、今回はこれでいきましょう。</p>
<p>ちなみにこの書き方だと、各レコードのman_ageとwoman_ageのデータのうち、値の小さい方を持ってくるだけになってしまうので、全データの中で最小値を持ってくるのであれば、これとMIN()を組み合わせるといい。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

SELECT MIN(LEAST(man_age, woman_age)) FROM users

</textarea>
<p>何かもっといい書き方があるような気もするけれど、とりあえずこれで複数のカラムの中での最小値やら最大値を取得することは可能になる。</p>
<p>ただ、一つ注意が必要なのは、データの中にNULLが入っていると、どんな数値が入っていても最小値や最大値にはNULLが返ってきてしまう。LEAST()やGREATEST()ってのは、そういう風に動作する関数らしい。</p>
<p>今までLEAST()やGREATEST()という関数の存在を知らなかったから、こいつを使えばいけるじゃんと思いつつも、構成的にNULLを許可しないわけにはいかない仕様のせいで、残念ながら今回はこのやり方を生かすことはできませんでした。が、これは別の機会に使える日が来るような気がするので、忘れないうちにここにメモっておきたいと思います。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/1565/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>テーブル内のカラムの移動方法</title>
		<link>http://astrodeo.com/blog/archives/424</link>
		<comments>http://astrodeo.com/blog/archives/424#comments</comments>
		<pubDate>Tue, 16 Feb 2010 09:35:17 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[MySQL]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/?p=424</guid>
		<description><![CDATA[めったに使うことはないような気もするけれど、ＤＢのテーブル内のカラムの位置を変更したいとき。 ALTER TABLE テーブル名 CHANGE 旧カラム名 新カラム名 カラムの型 AFTER カラム名 カラム名を変更せずに場所だけ移動したい場合は、旧カラム名と新カラム名に同じ名前を入れる。上記の書き方だと、AFTERの後に書いたカラム名の一つ下に新カラム名のカラムが移動する。]]></description>
			<content:encoded><![CDATA[<p>めったに使うことはないような気もするけれど、ＤＢのテーブル内のカラムの位置を変更したいとき。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

ALTER TABLE テーブル名 CHANGE 旧カラム名 新カラム名 カラムの型 AFTER カラム名

</textarea>
<p>カラム名を変更せずに場所だけ移動したい場合は、旧カラム名と新カラム名に同じ名前を入れる。上記の書き方だと、AFTERの後に書いたカラム名の一つ下に新カラム名のカラムが移動する。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/424/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>真面目にエロサイトを作ってみた【プログラマ編】</title>
		<link>http://astrodeo.com/blog/archives/257</link>
		<comments>http://astrodeo.com/blog/archives/257#comments</comments>
		<pubDate>Mon, 01 Feb 2010 02:54:22 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[ウェブサービス]]></category>
		<category><![CDATA[サイト発表]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/?p=257</guid>
		<description><![CDATA[真面目にエロサイトを作ってみた【デザイナー編】はコチラから。 エロサイトを作るにあたって 「スタイリッシュなアダルトサイト思いついたから作るぞ」 （たぶん）そんな感じの一言で始まったSheCoolのサイト制作。見た目がどれくらいスタイリッシュかってのはデザイナーの裁量次第なので、自分の担当はあくまでもシステム部分。発案者の意にできるだけ添えられるように、未熟ながらも自分の技術力を駆使してサイト制作に乗り出しました。 デザインの前に、とりあえず大まかな仕様を決めた時点で、今までやったことのない技術や、そこまで強く意識していなかったことがいくつも出てきました。主なところでは データのスクレイピング cronを利用したバッチ処理(DBの自動更新) SQLのチューニング 辺りが自分にとっての未開領域でした。特にスクレイピングなんて、正直なところそういう単語すら初めて聞くくらいのレベル。所詮は素人プログラマです。ついでに、性欲は人よりあるわりに普段はほとんどアダルト動画を見ないもんですから、どんなアダルト動画の配信サイトがあるのかというのもよく知りませんでした。所詮は素人アダルト動画ウォッチャーです。 しかし今回の制作は何よりもスクレイピングが重要な部分なので、とにかくやってみるしかないってことでやってみました。 開発はCakePHPで行いました。どうしてCakePHPなのかは、今まで開発は基本的にCakePHPを使ってやってきたので一番使い慣れてるっていう、まあそれだけの、水たまりより浅い理由ですよ、ええ。海より深い理由などありゃしません。 データベースはMySQLを使用、ストレージエンジンは全テーブルがMyISAMです。基本的に検索なので、MyISAMが一番早いしトランザクションの必要な処理もないってことなで、MyISAMにしてます。それから後でもまた述べますが、表示側の動的な部分については基本的にjQueryを用いています。 必要なデータのスクレイピング 何をおいてもまずはデータを取得することが必要。データがないと何も始まらない。ナニも始められない。まあ、俺ならデータがなくても妄想だけで始められｍ 最初はfile_get_contents関数を使ってページの情報を持って来て、必要な部分を正規表現で抜き取っていましたが、これが案外しんどい。意外と思い通りの形にデータを取れなかったりする。 そんなときに出会ったのがhtmlSQLというライブラリ。SQLを発行する感覚でスクレイピングが行える。しかもデータがHTMLタグ単位で返って来るので、欲しいところだけを抜き取りやすい。こいつは素晴らしい。中にはどうしてもhtmlSQLでは取れないデータがあったりもしたのですが、たいていのデータはhtmlSQLでいける。使い勝手が良いことは間違いないでしょう。 この解釈が正しいかはちょっと分かりませんが、タグの階層が深くなると、htmlSQLでは取れない場合があるような気がする。divタグの中にdivタグがあって、さらにその中にdivが…って感じでどんどん深くなると、divタグの中身を取って来ようと思った時に、一番深いところのdivの情報が上手く取れないようなケースがあるっぽい。でも取れてる場合もあるから、確かなことは言えないです。 女優データの取得 デザイナー編でもあったように、女優のデータは全てDMMから取得しています。DMMには女優の一覧ページと各女優の詳細情報のページがあるので、まず女優一覧のページから各女優の詳細情報ページへのURLを取得し、それぞれのURLにリクエストを投げて、情報を取得する。名前を複数持っている女優に関しては、名前を分割してDBにデータを入力しています。 一旦全女優のデータを取得した後は、新人の女優さんだけを定期的に見に行くようにして、その女優のデータがDBに入っていなければINSERT処理を行うようにしました。 それにしても、最近の女優さんはみんな可愛いですね。 DMMの商品データの取得 サーバー代、生ビール代をゲットするために必要なデータだから、ここはしっかり取得しなければならない。いえ、しっかり取得しなければならないのは他のデータも同じですけどね。 DMMは、各女優さんとその女優さんが出演しているDVDや動画がすでに関連づいているので、女優のデータを取得するときに一緒に商品データも取得するようにしています。女優ごとの商品の一覧ページへリクエストを投げて、その一覧ページから必要な情報を取得する。商品情報が複数ページにわたっている場合は、もちろんその全てのページのデータを取得する。 DMMではこの商品情報が人気順や価格順で見られるようになっているので、shecoolでもそこは連動させるようにしました。DMMを見ていると人気順はちょくちょく変化しているようなので、定期的に商品情報のページを見に行って、DBの方も更新するようにしています。人気順用のカラムを作っておいて、その中身を更新する感じ。毎回全商品の順位が変動するわけではないので、順位の変動があった商品だけを更新するようにしています。でないと、サーバーに無駄な負荷をかけてしまうからね。 DMMの方に追加された新しい商品情報の取得も、この人気順を入れ替える処理と同時に行っています。商品の一覧ページに載っている商品の情報がすでにDBに入っているかどうかを検証し、DBにデータがあった場合は、順位が変動していたら順位を入れ替えるUPDATE処理を行い、変動していなかったら何もしない、そしてDBにデータがなかった場合はINSERT処理を行うような分岐処理をしています。 動画データの取得 考え方としては、取得先のサイトの検索ボックスにキーワードを入力して、見つかった動画の詳細な情報を取得する感じ。スクレイピングの場合は直接検索ボックスにキーワードを入力するわけではないけど、フォームから送信されるデータは、特にそれが検索フォームのような場合は、多くがGETメソッドによってURLの後ろにパラメータを追加するように作られているので、最初からパラメータのついたURLに接続すれば情報を取得できる。 今回は、DBに入っている女優の名前とタグをそれぞれパラメータとしてURLに追加し、見つかった動画を取得しました。例えば『麻美ゆま』というパラメータをURLに追加して見つかった動画を取得するということは、検索ボックスに『麻美ゆま』というワードを入力して見つかった動画の情報を取得することに等しいことになります。 当然ながらサイトによってデータの取得の仕方が多少変わってきますが、htmlSQLが存分に活躍してくれたおかげで、何とかなりました。 データを取得させていただいた諸サイトさんには、この場を借りてお礼を申し上げます。 スクレイピングにおける注意点 スクレイピングは相手のサイトからデータを取ってくる作業なわけで、だからリクエストをたくさん投げ続ければそれだけサーバーにかかる負荷が大きくなります。自分側のサーバーはともかく、相手側のサーバーには極力迷惑をかけないようにしたいですね。連続してリクエストを投げすぎないようにする、取得する情報は必要最小限に抑える、余計なリクエストを投げない、その辺を意識しながらコーディングする必要があるかと思います。 DBへのデータ入力 データを取得したら当然次は入力です……が、しかし今回は入力する情報量が非常に多く、スクレイピングで必要な情報を取得する度にINSERT処理を行うと、1000件ほどのデータを入力するのに丸一日掛かってしまうようなこともありました。最初は数万件の動画情報を取得してDBに入れなければならなかったため、1000件の動画情報を入力するのに一日掛かっていたら時間がいくらあっても足りません。一日が40時間になってもきっと足りない。 更には、情報の取得だけならともかく、shecoolはタグや女優が動画やDMMの商品とHABTMで関連付けているため（関連付けに関しては後述）、アソシエーションを設定してCakePHPのsaveallメソッドでまとめて入力処理を行うと、一件辺りの動画情報の入力時間がもっと長くなる可能性も出てきます。というか、実際に長くなってました。 少しでも処理時間を短縮するためには、余計なインスタンスを生成しないようにするなど、考えられる点は多々あると思うのですが、今回は大幅な時間の短縮が求められたため、そんな焼け石にぬるま湯をかける程度の改善ではどうにもなりませんでした。かけるなら最低でも液体窒素くらいでないといけない。 そこで今回は、バッチ処理時にsaveメソッドやsaveallメソッドでのINSERT処理は行わずに、スクレイピングで必要な情報を持ってきたら、その情報を入力するINSERT文をテキストファイルに書き出す処理を行い、シェルから直接SQLを実行するようにしました。関連付けも、動画とタグ、動画同士、それぞれにINSERT用のテキストファイルを作成し、同様の処理を行いました。 shell_exec("mysql --user=ユーザー名 --password=パスワード DB名 &#60; ".ファイルパス); コードはこんな感じ。 CakePHP 1.2にはシェル機能があり、それを利用すればバッチ処理をサーバー側で実行できるみたいです。サーバーでcronの設定を行い、毎日決まった時間に自動で処理を実行するようにしておけば、DBへのアクセスが比較的少ないと思われる時間帯（早朝とか）などに処理を走らせることができる。この辺りの設定とかに関しては、先輩プログラマのお力を借りました。というかほとんどやってもらいました。 おかげで、入力処理の時間は大幅に短縮されました。液体ヘリウムをかけたかのようでした（？） 動画との関連付け shecoolでは、動画、タグ、女優がそれぞれ動画とHABTMで関連付いています（動画と動画は正確にはHABTMではないですが、多対多で結び付いているという意味で）。また、DMMの商品データとタグもHABTMで関連付いています。DMMの商品と女優は一対多。 動画と動画 動画同士は、タイトルがどれだけ類似しているかで関連付けを行っています。similar_text関数を使い、一定以上のパーセンテージ（今回は70%に設定している）が返って来たら関連していると判断し、バッチで処理を行うINSERT文をテキストファイルに書き出す。 処理自体はループ処理なので、ソースとしてはそんなに複雑にはなりません。ただ、10000件の動画があったら10000件の動画それぞれが他の9999件の動画と関連するのかどうかをチェックしなければならないため、処理自体はかなり重いです。動画の件数が増えれば増えるほど、処理はどんどん重くなる。なので、うっかりループの中で全動画データをSELECTするような処理を書くとメモリが大変なことになってしまうので、こういう時は、処理を行う前に一度全動画のデータを適当なメンバ変数などに持たせておくようにした方が良いと思います。処理時間を短くするには、とにかく必要以上にクエリは投げない。これが大事のようです。 本来なら、新規に動画データをDBに入力した時に同時に関連付けも行えば良いのですが、前述のデータ入力の項目にもあるように、それらを同時に行うのは負荷が大きいので、関連付けは関連付けで別個にINSERT処理を行っています。一度関連付けを行った動画データにもう一度同じ処理を行わせるのは無意味なので、今回は動画のテーブルに関連付けを行っていないかどうかの判定フラグ用のカラムを用意し、関連付けの処理を行うのはそのフラグが立っているデータのみ、つまりまだ関連付けが行われていない動画だけを抽出して処理を行うようにしました。 ただ、similar_text関数の検証という記事でも書いたように、マルチバイト文字の比較に関しては、そこまでの正確性はないように思えます。一つの動画を複数に分割してあるような動画（サンプル動画１、サンプル動画２、みたいな）を関連付けるのが一番の目的であり、それについては問題なく関連付いているので目的は達成できていますが、それ以外にも、およそ関連しているとは思えないような動画同士が関連している可能性は多分にあります。youtubeとかに比べると、関連付けのレベルは全然劣りますね。 [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://astrodeo.com/blog/archives/259">真面目にエロサイトを作ってみた【デザイナー編】</a>はコチラから。</p>
<h3>エロサイトを作るにあたって</h3>
<p><strong>「スタイリッシュなアダルトサイト思いついたから作るぞ」</strong></p>
<p>（たぶん）そんな感じの一言で始まった<a href="http://shecool.jp/">SheCool</a>のサイト制作。見た目がどれくらいスタイリッシュかってのはデザイナーの裁量次第なので、自分の担当はあくまでもシステム部分。発案者の意にできるだけ添えられるように、未熟ながらも自分の技術力を駆使してサイト制作に乗り出しました。</p>
<p>デザインの前に、とりあえず大まかな仕様を決めた時点で、今までやったことのない技術や、そこまで強く意識していなかったことがいくつも出てきました。主なところでは</p>
<ul>
<li>データのスクレイピング</li>
<li>cronを利用したバッチ処理(DBの自動更新)</li>
<li>SQLのチューニング</li>
</ul>
<p>辺りが自分にとっての未開領域でした。特にスクレイピングなんて、正直なところそういう単語すら初めて聞くくらいのレベル。所詮は素人プログラマです。ついでに、性欲は人よりあるわりに普段はほとんどアダルト動画を見ないもんですから、どんなアダルト動画の配信サイトがあるのかというのもよく知りませんでした。所詮は素人アダルト動画ウォッチャーです。</p>
<p>しかし今回の制作は何よりもスクレイピングが重要な部分なので、とにかくやってみるしかないってことでやってみました。</p>
<p>開発はCakePHPで行いました。どうしてCakePHPなのかは、今まで開発は基本的にCakePHPを使ってやってきたので一番使い慣れてるっていう、まあそれだけの、水たまりより浅い理由ですよ、ええ。海より深い理由などありゃしません。</p>
<p>データベースはMySQLを使用、ストレージエンジンは全テーブルがMyISAMです。基本的に検索なので、MyISAMが一番早いしトランザクションの必要な処理もないってことなで、MyISAMにしてます。それから後でもまた述べますが、表示側の動的な部分については基本的にjQueryを用いています。</p>
<h3>必要なデータのスクレイピング</h3>
<p>何をおいてもまずはデータを取得することが必要。データがないと何も始まらない。ナニも始められない。まあ、俺ならデータがなくても妄想だけで始められｍ</p>
<p>最初は<a href="http://php.net/manual/ja/function.file-get-contents.php" target="_blank">file_get_contents関数</a>を使ってページの情報を持って来て、必要な部分を正規表現で抜き取っていましたが、これが案外しんどい。意外と思い通りの形にデータを取れなかったりする。</p>
<p>そんなときに出会ったのが<a href="http://www.jonasjohn.de/lab/htmlsql.htm" target="_blank">htmlSQL</a>というライブラリ。SQLを発行する感覚でスクレイピングが行える。しかもデータがHTMLタグ単位で返って来るので、欲しいところだけを抜き取りやすい。こいつは素晴らしい。中にはどうしてもhtmlSQLでは取れないデータがあったりもしたのですが、たいていのデータはhtmlSQLでいける。使い勝手が良いことは間違いないでしょう。</p>
<p>この解釈が正しいかはちょっと分かりませんが、タグの階層が深くなると、htmlSQLでは取れない場合があるような気がする。divタグの中にdivタグがあって、さらにその中にdivが…って感じでどんどん深くなると、divタグの中身を取って来ようと思った時に、一番深いところのdivの情報が上手く取れないようなケースがあるっぽい。でも取れてる場合もあるから、確かなことは言えないです。</p>
<h4>女優データの取得</h4>
<p><a href="http://astrodeo.com/blog/archives/259">デザイナー編</a>でもあったように、女優のデータは全てDMMから取得しています。DMMには女優の一覧ページと各女優の詳細情報のページがあるので、まず女優一覧のページから各女優の詳細情報ページへのURLを取得し、それぞれのURLにリクエストを投げて、情報を取得する。名前を複数持っている女優に関しては、名前を分割してDBにデータを入力しています。</p>
<p>一旦全女優のデータを取得した後は、新人の女優さんだけを定期的に見に行くようにして、その女優のデータがDBに入っていなければINSERT処理を行うようにしました。</p>
<p>それにしても、最近の女優さんはみんな可愛いですね。</p>
<h4>DMMの商品データの取得</h4>
<p>サーバー代、生ビール代をゲットするために必要なデータだから、ここはしっかり取得しなければならない。いえ、しっかり取得しなければならないのは他のデータも同じですけどね。</p>
<p>DMMは、各女優さんとその女優さんが出演しているDVDや動画がすでに関連づいているので、女優のデータを取得するときに一緒に商品データも取得するようにしています。女優ごとの商品の一覧ページへリクエストを投げて、その一覧ページから必要な情報を取得する。商品情報が複数ページにわたっている場合は、もちろんその全てのページのデータを取得する。</p>
<p>DMMではこの商品情報が人気順や価格順で見られるようになっているので、shecoolでもそこは連動させるようにしました。DMMを見ていると人気順はちょくちょく変化しているようなので、定期的に商品情報のページを見に行って、DBの方も更新するようにしています。人気順用のカラムを作っておいて、その中身を更新する感じ。毎回全商品の順位が変動するわけではないので、順位の変動があった商品だけを更新するようにしています。でないと、サーバーに無駄な負荷をかけてしまうからね。</p>
<p>DMMの方に追加された新しい商品情報の取得も、この人気順を入れ替える処理と同時に行っています。商品の一覧ページに載っている商品の情報がすでにDBに入っているかどうかを検証し、DBにデータがあった場合は、順位が変動していたら順位を入れ替えるUPDATE処理を行い、変動していなかったら何もしない、そしてDBにデータがなかった場合はINSERT処理を行うような分岐処理をしています。</p>
<h4>動画データの取得</h4>
<p>考え方としては、取得先のサイトの検索ボックスにキーワードを入力して、見つかった動画の詳細な情報を取得する感じ。スクレイピングの場合は直接検索ボックスにキーワードを入力するわけではないけど、フォームから送信されるデータは、特にそれが検索フォームのような場合は、多くがGETメソッドによってURLの後ろにパラメータを追加するように作られているので、最初からパラメータのついたURLに接続すれば情報を取得できる。</p>
<p>今回は、DBに入っている女優の名前とタグをそれぞれパラメータとしてURLに追加し、見つかった動画を取得しました。例えば『麻美ゆま』というパラメータをURLに追加して見つかった動画を取得するということは、検索ボックスに『麻美ゆま』というワードを入力して見つかった動画の情報を取得することに等しいことになります。</p>
<p>当然ながらサイトによってデータの取得の仕方が多少変わってきますが、htmlSQLが存分に活躍してくれたおかげで、何とかなりました。</p>
<p>データを取得させていただいた諸サイトさんには、この場を借りてお礼を申し上げます。</p>
<h4>スクレイピングにおける注意点</h4>
<p>スクレイピングは相手のサイトからデータを取ってくる作業なわけで、だからリクエストをたくさん投げ続ければそれだけサーバーにかかる負荷が大きくなります。自分側のサーバーはともかく、相手側のサーバーには極力迷惑をかけないようにしたいですね。連続してリクエストを投げすぎないようにする、取得する情報は必要最小限に抑える、余計なリクエストを投げない、その辺を意識しながらコーディングする必要があるかと思います。</p>
<h3>DBへのデータ入力</h3>
<p>データを取得したら当然次は入力です……が、しかし今回は入力する情報量が非常に多く、スクレイピングで必要な情報を取得する度にINSERT処理を行うと、1000件ほどのデータを入力するのに丸一日掛かってしまうようなこともありました。最初は数万件の動画情報を取得してDBに入れなければならなかったため、1000件の動画情報を入力するのに一日掛かっていたら時間がいくらあっても足りません。一日が40時間になってもきっと足りない。</p>
<p>更には、情報の取得だけならともかく、shecoolはタグや女優が動画やDMMの商品とHABTMで関連付けているため（関連付けに関しては後述）、アソシエーションを設定してCakePHPのsaveallメソッドでまとめて入力処理を行うと、一件辺りの動画情報の入力時間がもっと長くなる可能性も出てきます。というか、実際に長くなってました。</p>
<p>少しでも処理時間を短縮するためには、余計なインスタンスを生成しないようにするなど、考えられる点は多々あると思うのですが、今回は大幅な時間の短縮が求められたため、そんな焼け石にぬるま湯をかける程度の改善ではどうにもなりませんでした。かけるなら最低でも液体窒素くらいでないといけない。</p>
<p>そこで今回は、バッチ処理時にsaveメソッドやsaveallメソッドでのINSERT処理は行わずに、スクレイピングで必要な情報を持ってきたら、その情報を入力するINSERT文をテキストファイルに書き出す処理を行い、シェルから直接SQLを実行するようにしました。関連付けも、動画とタグ、動画同士、それぞれにINSERT用のテキストファイルを作成し、同様の処理を行いました。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

shell_exec("mysql --user=ユーザー名 --password=パスワード DB名 &lt; ".ファイルパス);

</textarea>
<p>コードはこんな感じ。</p>
<p>CakePHP 1.2にはシェル機能があり、それを利用すればバッチ処理をサーバー側で実行できるみたいです。サーバーでcronの設定を行い、毎日決まった時間に自動で処理を実行するようにしておけば、DBへのアクセスが比較的少ないと思われる時間帯（早朝とか）などに処理を走らせることができる。この辺りの設定とかに関しては、先輩プログラマのお力を借りました。というかほとんどやってもらいました。</p>
<p>おかげで、入力処理の時間は大幅に短縮されました。液体ヘリウムをかけたかのようでした（？）</p>
<h3>動画との関連付け</h3>
<p>shecoolでは、動画、タグ、女優がそれぞれ動画とHABTMで関連付いています（動画と動画は正確にはHABTMではないですが、多対多で結び付いているという意味で）。また、DMMの商品データとタグもHABTMで関連付いています。DMMの商品と女優は一対多。</p>
<h4>動画と動画</h4>
<p>動画同士は、タイトルがどれだけ類似しているかで関連付けを行っています。<a href="http://jp.php.net/manual/ja/function.similar-text.php" target="_blank">similar_text関数</a>を使い、一定以上のパーセンテージ（今回は70%に設定している）が返って来たら関連していると判断し、バッチで処理を行うINSERT文をテキストファイルに書き出す。</p>
<p>処理自体はループ処理なので、ソースとしてはそんなに複雑にはなりません。ただ、10000件の動画があったら10000件の動画それぞれが他の9999件の動画と関連するのかどうかをチェックしなければならないため、処理自体はかなり重いです。動画の件数が増えれば増えるほど、処理はどんどん重くなる。なので、うっかりループの中で全動画データをSELECTするような処理を書くとメモリが大変なことになってしまうので、こういう時は、処理を行う前に一度全動画のデータを適当なメンバ変数などに持たせておくようにした方が良いと思います。処理時間を短くするには、とにかく必要以上にクエリは投げない。これが大事のようです。</p>
<p>本来なら、新規に動画データをDBに入力した時に同時に関連付けも行えば良いのですが、前述のデータ入力の項目にもあるように、それらを同時に行うのは負荷が大きいので、関連付けは関連付けで別個にINSERT処理を行っています。一度関連付けを行った動画データにもう一度同じ処理を行わせるのは無意味なので、今回は動画のテーブルに関連付けを行っていないかどうかの判定フラグ用のカラムを用意し、関連付けの処理を行うのはそのフラグが立っているデータのみ、つまりまだ関連付けが行われていない動画だけを抽出して処理を行うようにしました。</p>
<p>ただ、<a href="http://astrodeo.com/blog/archives/258">similar_text関数の検証</a>という記事でも書いたように、マルチバイト文字の比較に関しては、そこまでの正確性はないように思えます。一つの動画を複数に分割してあるような動画（サンプル動画１、サンプル動画２、みたいな）を関連付けるのが一番の目的であり、それについては問題なく関連付いているので目的は達成できていますが、それ以外にも、およそ関連しているとは思えないような動画同士が関連している可能性は多分にあります。youtubeとかに比べると、関連付けのレベルは全然劣りますね。</p>
<h4>動画とタグ</h4>
<p>動画とタグの関連付けは、DBに入っているタグ名をループで回し、動画のタイトルとディスクリプションの中にそのタグ名が入っているかどうかを判定し、入っていればその動画とタグを関連付けています。</p>
<p>それ以外に、スクレイピングのところで述べたように、動画は、タグ名をURLのパラメータに追加して、該当する動画が見つかったらそれらの情報を取得しています。だから例えば『女教師』というタグ名で動画を検索して引っかかった動画に関しては、タイトルやディスクリプションに『女教師』という単語がなくても、『女教師』のタグとは関連付けるようにしてあります。</p>
<h4>動画と女優</h4>
<p>動画と女優は、処理自体は動画とタグの関連付けと同様です。動画のタイトルやディスクリプションに女優名があるかどうかを判定し、あればその動画と女優を結び付ける。さらには女優名をURLのパラメータに追加して、例えば『麻美ゆま』という女優名で引っかかった動画に関しては、タイトルやディスクリプションの内容に関係なく、『麻美ゆま』とは関連付けています。</p>
<p>最初は、動画に女優のIDを持たせて、一対多で結び付けていました。つまり『麻美ゆま』で検索して見つかった動画に関しては、その動画の中に他の女優が出ていても、関連する女優は麻美ゆま一人だけでした。ですが話し合いの結果、複数の女優が出演している動画にはそれら全ての女優を関連付けた方が良いだろうという結論に達し、急遽HABTMに切り替えました。</p>
<p>ただし関連付けの判定方法がタイトルやディスクリプションの中に女優名が入っているかどうかだけなので、タイトルにもディスクリプションにも名前のない女優は、動画の中に出てきたとしても関連付いてはいないです。</p>
<h4>DMM商品とタグ</h4>
<p>DMMの商品の詳細情報が載っているページには、その商品に関連するジャンルの情報が載っています。そのジャンルとこちらのDBに入っているタグ名を比較し、一致するものがあった場合にはそのタグを関連付けるようにしています。</p>
<p>一つの商品に対し関連付いているジャンルは複数ある場合がほとんどですが、今回はそれら一つ一つをループさせてタグ名と一致するかどうか比較するような処理は行わずに、複数あるジャンルを一つの文字列として連結させ、正規表現を使ってタグ名がその文字列の中に存在するかどうかを判定して関連付けを行うようにしました。クエリが減るからこっちの方が処理時間は速いです。</p>
<h4>DMM商品と女優</h4>
<p>特別な処理は何もしてないです。商品データの取得でも述べたように、商品は基本的に誰かしらの女優と結び付いているので、その女優さんのIDを商品テーブルに持たせているだけです。</p>
<h3>サーバーの選定・設定</h3>
<p>日本のサーバーは、アダルトコンテンツを許可していないところが多いみたいですね。なので今回は<a href="http://www.megafactory.com/" target="_blank">MEGAFACTORY</a>という海外のサーバーを利用しました。</p>
<h4>サーバーの複数台の利用</h4>
<p>当初は一台のサーバーで運用を考えていました。しかしクライアント（弊社社長）の度重なる要求に応えているうちにどんどんと仕様が膨らみ、それに伴って裏側の処理（動画の取得など）の負荷がかなり大きくなってしまったため、一台では運用が厳しくなってしまいました。</p>
<p>そこで、サーバーを二台に分割することにしました。複数台の運用、そして二つのDBを連動させるという試みは自分にとっては初めてだったため、何をどう設定すれば良いのかさっぱりでしたが、づや先輩やとりよし先輩のお力を最大限に借りることで、無事に乗り切りました。借りたっていうか、任せたの方が正しい表現ですけど。</p>
<p>要は、スレーブとマスターの二つにサーバーを分け、スレーブの方はSELECT処理のみ接続し、INSERTやUPDATE、DELETE処理を行う時には、マスターの方に接続するように設定している……らしいです。裏側の処理はずっとマスターの方に接続していれば問題ないですが、表示側はその時々に応じて接続先を切り替えなければなりません。shecoolでは動画や女優にランキングをつけているため、表示側でもINSERTやUPDATEの処理が行われることはあります。そういう時だけ、接続先をマスターの方に切り替えるわけです。</p>
<p>これはsaveメソッドやupdateメソッドをオーバーライドすることで切り替えを実現しています。一時的にマスターに接続して処理を行い、終わったら接続先をスレーブに戻す、そんな処理を書いています。</p>
<h4>Sennaの導入</h4>
<p>SQLにおいて、LIKE検索は速度が遅い。だから特定の文字列を検索したい時にはLIKE検索よりも全文検索を利用するのが常套だと思うのですが、しかしMySQLの全文検索はマルチバイトに対応していない。FULLTEXTのインデックスを張るだけでは残念ながら日本語の全文検索は上手くできないみたいです。</p>
<p>対応策はいくつかあるようですが、今回は<a href="http://qwik.jp/senna/FrontPageJ.html" target="_blank">Senna</a>という組み込み型の全文検索エンジンを利用して、日本語での全文検索を可能にしました。</p>
<p>SennaをMySQLに組み込むにはMySQLをコンパイルし直す必要があるらしく、そこがちょっと大変で、結構苦戦するみたいです。しかしここでも当然のように先輩方のお力を借りた私は、特に苦労はしていないです。使えない後輩を持つと先輩は大変なんだってのがこれでよく分かりますね。気をつけましょう。</p>
<h3>表示側の動き</h3>
<p>サイトの性質上、shecoolは画像や動画のデータが多いです。デザイナー編にある、どういうサイトにするかのところで、画像をあまりごちゃごちゃ使わないようにしたいとか何とか言っちゃおりますが、できるだけ使わないようにしたって、一度に結構な数の画像が表示されます。まあ、そういうサイトなんだから仕方ないわけなんですが、加えて、関連動画の表示や検索ワードに該当する動画データと関連するDMM商品の表示など、複雑なSQLクエリを発行する箇所が多々あり、ページ全体はかなり重いです。全体の読み込みが終わるまでにかなりの時間を要してしまう。</p>
<p>そういうわけで、少しでも速くするために、さまざまな試みが要求されました。Sennaの導入もその一つですね。</p>
<h4>jQueryの利用</h4>
<p>shecoolでは、表示の動的な変化にjQueryを多用しています。細かい動きがあるたびにページ全体を読み込んでいたら目的の動画にたどりつくまでに日がくれてしまいますからね。気持ちが萎えることこの上ないですね。萎えるのは気持ちだけではないと思います。</p>
<p>jQueryでなければならない理由は特にありませんが、Ajaxでスライドなどのアニメーション効果をつけたいと思った時に、個人的にはjQueryを利用するのが手軽に実現できて良いと思っています。HTTP通信でのページ読み込みも簡単に行えるし、自分はprototypeよりはjQueryを使うことが多いです。</p>
<h4>クエリの最適化</h4>
<p>本気で最適化をするとなったら、これから書くこと以外にもかなりいろいろとやらないといけませんが、とりあえずは明日のためのその１。技術レベルが素人以上素人未満の僕みたいな奴でもすぐに手を出せるような項目を、いくつか挙げてみます。</p>
<ul>
<li>SELECT文を発行する際、取得するカラムは常に必要なものだけにする。ワイルドカード（*）は使わない方が良い。</li>
<li>COUNTの場合も、ワイルドカードは使わずにカラムに主キーなどを指定することで、実行速度は上がる。</li>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

SELECT COUNT(*) FROM `movies`
⇒SELECT COUNT(`id`) FROM `movies`

</textarea>
<li>WHERE句にORを使うようなときはINに置き換えた方が良い。</li>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

SELECT `title` FROM `movies` WHERE `id` = 1 OR `id` = 2
⇒SELECT `title` FROM `movies` WHERE `id` IN(1,2)

</textarea>
<li>インデックスをちゃんと張る。</li>
<p>DBのテーブルを見直して、ちゃんとインデックスが張られているかチェックしましょう。主キーだけでなく、特にWHERE句に関わってくるようなカラムは、インデックスを張っておいた方がSELECT文の場合には実行速度が速くなります。</ul>
<p>あとはCake側でアソシエーションの設定をしている場合は、必要なところ以外はunbindModelをしておくことも時間短縮の一つですね。joinしていると遅くなったりするから。</p>
<p>何にせよ、まずはどのクエリがボトルネックになっているかを洗い出すことが肝心です。基本的にはEXPLAINで地味にこつこつと見るのが良い。クエリを解析すればどのクエリが遅いのかは分かるので、そこに改善を施すわけです。今回は全文検索のインデックスがjoinの時にうまく効かなくて、構成から変えたところもあったりしました。</p>
<p>先輩プログラマ曰く、だいぶ頑張って実行時間を短縮したけどまだまだ遅い感じがする、だそうです。</p>
<h4>キャッシュを使う</h4>
<p>ページにキャッシュを利かせれば、表示は当然速くなります。でも当サイトではユーザーの動きがダイレクトに出るところがあるから（最近見た動画とか）、ページキャッシュを作ってもすぐに消される羽目になり、それではキャッシュを利かせるメリットがない。</p>
<p>ということで、今回はクエリの方にキャッシュを利かせました。検索のように、毎回同じクエリが投げられるとは限らないようなところはともかく、人気の動画や人気の女優を取得してくるようなところは常に同じクエリが投げられるので、クエリ自体をキャッシュ化しても特に問題はないわけです。ページ読み込みの際に、すでにクエリのキャッシュが存在している場合にはそちらを見に行くようにすれば、これまた時間短縮につながります。</p>
<h4>Ajaxで画像を読み込むようにする</h4>
<p>画像が多いと、どうしても全体が読み込み終わるまでにある程度の時間を要してしまいますね。そこで、画像の読み込みは後回しにしてしまっても良いような部分は、ページの読み込みが完了してからAjaxで読み込ませるようにする方法もあります。前述の通り、shecoolはjQueryを多用しているので、例えばフッター部など、スクロールしないと見えないような部分はonLoadでAjax通信を行って画像を読み込ませるというのも、有効な手段だと思います。読み込みが終わるまではloading用のgif画像でも出しておけば良いんじゃないでしょうか。</p>
<h3>そして公開へ</h3>
<p>こんな感じで、ことあるごとに追加される機能や、その都度発生するバグ、そしてデザインの修正、<strong>果てはリリースしてないのに大幅にリニューアルする</strong>という、これはもはやパワハラなんじゃねーのってくらいの仕様変更に耐えながら地道なチューニングを行い、ようやくリリースまでこぎつけたわけです。</p>
<p>分からないことだらけで大変ではありましたが、とても有意義な制作でした。特に、今までそんなに重いページを作ったことがなかったせいか、クエリの最適化とかは全然意識したことがなかったんですけど、これは今後も必ず役に立ってくるところだと思うので、とても勉強になりました。</p>
<p>それに何といっても、動作テストと称して<strong>毎日仕事中に堂々とアダルト動画をひたすら見続けること</strong>ができたのでとても有意義な制作でした。お気に入りの動画を探してマイリストに登録するところなんて、テストに見せかけてかなり真剣に選抜してました。とても有意義な制作でした。</p>
<p>ただ、大真面目にアダルト動画をチェックしてマイリストを作っている自分とか、取得するサイトの中でもどのサイトが優良な動画をたくさん持っているかを入念に吟味している自分とかを客観的に見たら、「<strong>ああ、こいつは絶対にモテないな。右手しか恋人候補がいねえ</strong>」って思いました。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/257/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>DBに入っている文字列データから濁点や半濁点を検索する</title>
		<link>http://astrodeo.com/blog/archives/237</link>
		<comments>http://astrodeo.com/blog/archives/237#comments</comments>
		<pubDate>Mon, 02 Nov 2009 00:53:23 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[MySQL]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/archives/237</guid>
		<description><![CDATA[たとえば住所録なんかを作るような場合、表示側であ行～わ行で名前の一覧を表示分けしたいとする。 まあ、DBにデータを登録するときに、その何行なのかというのもデータで持っていれば特に問題はないのだろうけど、そういうカラムが（うっかり）作成されていなくて、名前とふりがなだけが入っていたら、ふりがなの先頭の文字からその名前が何行に属するのかを見ることになると思う。 ではここで、名前の先頭が「か」の名前のデータを取得したいとする。加賀まりこ（かがまりこ）とか鹿賀丈史（かがたけし）とかカート・コバーン（かーとこばーん）とかガイル（ソニックブームの人）とかが該当しますね。 SELECT * FROM users WHERE LEFT(kana,1) LIKE '%か%' SQL文はこんな感じで大丈夫。kanaってのは、ここではふりがなが入っているカラムです。LEFT(kana,1)というのは、kanaのカラムに入っているデータの左から1文字を取得するものです。当然LEFT(kana,2)なら2文字取ってくる。 さて、上記のSQL文だとふりがなの先頭の文字が「か」の人のデータを取ってきます。つまり加賀まりこさん、鹿賀丈史さん、カート・コバーンさんのデータを取ってくる。ただし、ガイルは取ってこない。ソニックブームの人とか書いてるけどふりがなは「がいる」ですので、先頭が「そ」だからいけないんじゃねーのってわけじゃない。 まあ、当然と言うべきか、「か」と「が」は違う文字なので、これでは先頭が「が」の人は引っかからない。 SQL文にORを書いても良いのだけど、それもちょっとだるい。そんなときはどうするか。 実はカラムの照合順序をutf8_unicode_ciってのにすると、&#8217;%か%&#8217;と書くだけで「が」の方も取ってきてくれる。それどころかカタカナの「カ」とかも取ってきてくれるらしい。カートコバーンとかも、ふりがなの方にそのままカタカナで名前を入れても何とかなる。 これは知っとくと意外と便利だと思う。]]></description>
			<content:encoded><![CDATA[<p>たとえば住所録なんかを作るような場合、表示側であ行～わ行で名前の一覧を表示分けしたいとする。</p>
<p>まあ、DBにデータを登録するときに、その何行なのかというのもデータで持っていれば特に問題はないのだろうけど、そういうカラムが（うっかり）作成されていなくて、名前とふりがなだけが入っていたら、ふりがなの先頭の文字からその名前が何行に属するのかを見ることになると思う。</p>
<p>ではここで、名前の先頭が「か」の名前のデータを取得したいとする。加賀まりこ（かがまりこ）とか鹿賀丈史（かがたけし）とかカート・コバーン（かーとこばーん）とかガイル（ソニックブームの人）とかが該当しますね。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

SELECT * FROM users WHERE LEFT(kana,1) LIKE '%か%'

</textarea>
<p>SQL文はこんな感じで大丈夫。kanaってのは、ここではふりがなが入っているカラムです。LEFT(kana,1)というのは、kanaのカラムに入っているデータの左から1文字を取得するものです。当然LEFT(kana,2)なら2文字取ってくる。</p>
<p>さて、上記のSQL文だとふりがなの先頭の文字が「か」の人のデータを取ってきます。つまり加賀まりこさん、鹿賀丈史さん、カート・コバーンさんのデータを取ってくる。ただし、ガイルは取ってこない。ソニックブームの人とか書いてるけどふりがなは「がいる」ですので、先頭が「そ」だからいけないんじゃねーのってわけじゃない。</p>
<p>まあ、当然と言うべきか、「か」と「が」は違う文字なので、これでは先頭が「が」の人は引っかからない。</p>
<p>SQL文にORを書いても良いのだけど、それもちょっとだるい。そんなときはどうするか。</p>
<p>実はカラムの照合順序をutf8_unicode_ciってのにすると、&#8217;%か%&#8217;と書くだけで「が」の方も取ってきてくれる。それどころかカタカナの「カ」とかも取ってきてくれるらしい。カートコバーンとかも、ふりがなの方にそのままカタカナで名前を入れても何とかなる。</p>
<p>これは知っとくと意外と便利だと思う。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/237/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>OR検索とAND検索を同時に使う</title>
		<link>http://astrodeo.com/blog/archives/221</link>
		<comments>http://astrodeo.com/blog/archives/221#comments</comments>
		<pubDate>Thu, 06 Aug 2009 08:51:29 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[MySQL]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/archives/221</guid>
		<description><![CDATA[CakePHPで検索条件を入力してページング処理を行うことはわりとよくあると思うけど、その際、検索条件が複数あり、そして条件によってOR検索やAND検索を組み合わせて使わなければいけない場合も結構あるのではないかと思う。 これが最適なやり方かどうかは自分でも分からないけれど、いくつかのパターンをここにメモっておこうと思う。 AND検索のみの場合 ANDのみの場合はそんなに難しくはない。全ての条件を配列にぶっこんでやればOK。 面倒なのでテーブルの中身とかは一切書かないけど、ここではname（名前）とage（年齢）というカラムがあって、その中から名字が佐藤さんで年齢が20歳以上のデータをAND検索するとしよう。モデル名はUser。 $conditions[] = array('User.name' =&#62; '佐藤', 'User.age &#62;=' =&#62; '20'); $this-&#62;paginate('User', $conditions); //SQLのWHERE句の部分 WHERE User.name = '佐藤' AND User.age &#62;= '20' WHERE句のところはちょっと適当に書いたけど、たぶん出力結果はこんな感じで合ってると思う。 OR検索のみの場合 OR検索のみも、まあ別に難しくはないと思う。 では、上記と同じようなテーブルがあるとして、今回は名字が佐藤さんか中村さんのデータをOR検索する場合。 $conditions['OR'] = array('User.name' =&#62; array('佐藤', '中村')); $this-&#62;paginate('User', $conditions); //SQL WHERE User.name = '佐藤' OR User.name = '中村' こんな感じ。要は$conditionsのORというキーの中に必要な条件を全部ぶっこめば、CakeがOR検索をしてくれる。ときにはforeach文とかで回すこともあるでしょうが、やり方は同じ。 $array = array(1, 2, 3); foreach($array as $val) [...]]]></description>
			<content:encoded><![CDATA[<p>CakePHPで検索条件を入力してページング処理を行うことはわりとよくあると思うけど、その際、検索条件が複数あり、そして条件によってOR検索やAND検索を組み合わせて使わなければいけない場合も結構あるのではないかと思う。</p>
<p>これが最適なやり方かどうかは自分でも分からないけれど、いくつかのパターンをここにメモっておこうと思う。</p>
<h3>AND検索のみの場合</h3>
<p>ANDのみの場合はそんなに難しくはない。全ての条件を配列にぶっこんでやればOK。</p>
<p>面倒なのでテーブルの中身とかは一切書かないけど、ここではname（名前）とage（年齢）というカラムがあって、その中から名字が佐藤さんで年齢が20歳以上のデータをAND検索するとしよう。モデル名はUser。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

$conditions[] = array('User.name' =&gt; '佐藤', 'User.age &gt;=' =&gt; '20');

$this-&gt;paginate('User', $conditions);

//SQLのWHERE句の部分

WHERE User.name = '佐藤' AND User.age &gt;= '20'

</textarea>
<p>WHERE句のところはちょっと適当に書いたけど、たぶん出力結果はこんな感じで合ってると思う。</p>
<h3>OR検索のみの場合</h3>
<p>OR検索のみも、まあ別に難しくはないと思う。</p>
<p>では、上記と同じようなテーブルがあるとして、今回は名字が佐藤さんか中村さんのデータをOR検索する場合。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

$conditions['OR'] = array('User.name' =&gt; array('佐藤', '中村'));

$this-&gt;paginate('User', $conditions);

//SQL

WHERE User.name = '佐藤' OR User.name = '中村'

</textarea>
<p>こんな感じ。要は$conditionsのORというキーの中に必要な条件を全部ぶっこめば、CakeがOR検索をしてくれる。ときにはforeach文とかで回すこともあるでしょうが、やり方は同じ。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

$array = array(1, 2, 3);

foreach($array as $val) {

     $data[] = array('User.id'=&gt; $val);

}

$conditions['OR'] = $data;

//SQL

WHERE User.id = '1' OR User.id = '2' OR User.id = '3'

あるいは

WHERE User.id  IN(1,2,3)

みたいな感じにもなるかもしれない

</textarea>
<h3>ANDとORが組み合わさった場合</h3>
<p>基本的には、上の二つを適当に組み合わせれば問題はない。</p>
<p>上の例にのっとって、年齢が20歳以上で名字が佐藤さんか中村さんのデータを検索してみる。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

$conditions['OR'] = array('User.name' =&gt; array('佐藤', '中村'));

$conditions[] = array('User.age &gt;=' =&gt; '20');

$this-&gt;paginate('User', $conditions);

//SQL

WHERE (User.name = '佐藤' OR User.name = '中村') AND User.age &gt;= '20'

</textarea>
<p>とまあ、こんな要領でいいのだけれど、ちょっと面倒なのは、OR同士をANDで結ぶような場合。たとえば、名字が佐藤さんか中村さんで、年齢が20歳以下と60歳以上のデータだけを出力したいなんてことがあったとする。SQLでいうと、以下のような出力結果が欲しい場合。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

WHERE (User.name = '佐藤' OR User.name ='中村') AND (User.age &lt;= '20' OR User.age &gt;= '60')

</textarea>
<p>こんなときは、配列の次元をもう一つ増やす。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

$conditions[] = array('OR' =&gt;

                       array('User.name' =&gt; '佐藤', '中村'),

                       array('User.age &lt;=' =&gt; '20', 'User.age &gt;=' =&gt; '60'),

                    );

$this-&gt;paginate('User', $conditions);

</textarea>
<p>余談だけど、検索条件にHABTMで関連づいているデータを使うの、結構めんどいですな。ソースがどうも奇麗にまとまらないっす……。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/221/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>CakePHPでのSQL</title>
		<link>http://astrodeo.com/blog/archives/214</link>
		<comments>http://astrodeo.com/blog/archives/214#comments</comments>
		<pubDate>Tue, 07 Jul 2009 07:07:27 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[MySQL]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/archives/214</guid>
		<description><![CDATA[CakePHPを使っていると、自分で直接SQL文を書く機会が少ない。っていうか、ほとんどない。 でもそれに慣れていると、たま～にどう書いたらいいのか分からなくなることがある。今回はそんな話。 DBにdateというカラム名でDATE型のデータが入っているとして、そこから今月のデータだけを取りたいような場合があるとする。 SQL文だとこんな感じになる。テーブル名はcalendars（仮）。 $month = date('m'); SELECT * FROM calendars WHERE MONTH(date) = $month; date(&#8216;m&#8217;)で現在の月（これを書いている今なら７月）を取得し、DBから７月のデータを抽出している。 これをCake的に書くにはどうすればいいのか最初分からなくて、ちょびっと困った。 $month = date('m'); $param = array('conditions' =&#62; array('date' =&#62; $month)); $this-&#62;Calendar-&#62;find('all', $param); 通常、CakePHPで条件を指定してSELECTするにはこんな書き方になるけど、これだと今月のデータを取ってきてはくれない。SQL的には下のようになってしまう。 SELECT * FROM calendars WHERE date = 07 dateフィールドに07なんていうデータはないので、これだとデータが一件も抽出されないで終わってしまう。 $month = date('m'); $param = array('conditions' =&#62; array("MONTH(date) = $month")); $this-&#62;Calendar-&#62;find('all', $param); こう書けばOK。ちゃんと今月のデータだけを取ってきてくれる。 配列だからarrayの中身は常に($key =&#62; $val)っていう書き方なんだって思いこんでると、こういうささいなところになかなか気づかなかったりするものでして…もっと気をつけたいもんです。]]></description>
			<content:encoded><![CDATA[<p>CakePHPを使っていると、自分で直接SQL文を書く機会が少ない。っていうか、ほとんどない。</p>
<p>でもそれに慣れていると、たま～にどう書いたらいいのか分からなくなることがある。今回はそんな話。</p>
<p>DBにdateというカラム名でDATE型のデータが入っているとして、そこから今月のデータだけを取りたいような場合があるとする。</p>
<p>SQL文だとこんな感じになる。テーブル名はcalendars（仮）。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

$month = date('m');

SELECT * FROM calendars WHERE MONTH(date) = $month;

</textarea>
<p>date(&#8216;m&#8217;)で現在の月（これを書いている今なら７月）を取得し、DBから７月のデータを抽出している。</p>
<p>これをCake的に書くにはどうすればいいのか最初分からなくて、ちょびっと困った。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

$month = date('m');

$param = array('conditions' =&gt; array('date' =&gt; $month));

$this-&gt;Calendar-&gt;find('all', $param);

</textarea>
<p>通常、CakePHPで条件を指定してSELECTするにはこんな書き方になるけど、これだと今月のデータを取ってきてはくれない。SQL的には下のようになってしまう。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

SELECT * FROM calendars WHERE date = 07

</textarea>
<p>dateフィールドに07なんていうデータはないので、これだとデータが一件も抽出されないで終わってしまう。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

$month = date('m');

$param = array('conditions' =&gt; array("MONTH(date) = $month"));

$this-&gt;Calendar-&gt;find('all', $param);

</textarea>
<p>こう書けばOK。ちゃんと今月のデータだけを取ってきてくれる。</p>
<p>配列だからarrayの中身は常に($key =&gt; $val)っていう書き方なんだって思いこんでると、こういうささいなところになかなか気づかなかったりするものでして…もっと気をつけたいもんです。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/214/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>MySQLのデータが壊れた</title>
		<link>http://astrodeo.com/blog/archives/141</link>
		<comments>http://astrodeo.com/blog/archives/141#comments</comments>
		<pubDate>Tue, 06 May 2008 13:36:40 +0000</pubDate>
		<dc:creator>づや</dc:creator>
				<category><![CDATA[MySQL]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/archives/141</guid>
		<description><![CDATA[idolPicsの写真が表示されなくなっていた。 テーブルのデータをチェックしたら、存在はするらしいので、 SQLのログを見たらGot error 130 from table handlerとのこと。 調べてみるとテーブルが破壊されてるらしい。 CHECK TABLE テーブル名 で怪しいテーブルを探して、 REPAIR TABLE テーブル名 を実行したところ直ったようで、表示されるようになった。 問題はなんでこうなったかだなぁ。 テーブルサイズの上限はまだ超えてないのだけどなぁ。]]></description>
			<content:encoded><![CDATA[<p>idolPicsの写真が表示されなくなっていた。</p>
<p>テーブルのデータをチェックしたら、存在はするらしいので、</p>
<p>SQLのログを見たらGot error 130 from table handlerとのこと。</p>
<p>調べてみるとテーブルが破壊されてるらしい。</p>
<p>CHECK TABLE テーブル名</p>
<p>で怪しいテーブルを探して、</p>
<p>REPAIR TABLE テーブル名</p>
<p>を実行したところ直ったようで、表示されるようになった。</p>
<p>問題はなんでこうなったかだなぁ。</p>
<p>テーブルサイズの上限はまだ超えてないのだけどなぁ。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/141/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>

