<?xml version="1.0" encoding="UTF-8" ?>
<feed
	xmlns="http://www.w3.org/2005/Atom"
	xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"
	xml:lang="ja-JP"
>
	<title>私とMySQL</title>
	<subtitle>最新15件を表示します。</subtitle>
	<id>tag:txqz.net,1970:/genre/MySQL</id>
	<link rel="self" href="http://txqz.net/genre/MySQL.atom"/>
	<link rel="alternate" type="application/rss+xml" href="http://txqz.net/genre/MySQL.rdf"/>
	<link rel="alternate" type="application/xhtml+xml" href="http://txqz.net/genre/MySQL.xhtml"/>
	<link rel="alternate" type="text/html" href="http://txqz.net/genre/MySQL.html"/>
	<link rel="contents" href="http://txqz.net/genre" title="タグ一覧"/>
	<link rel="first" href="http://txqz.net/genre/%28%E7%A6%8F%29" title="(福)"/>
	<link rel="prev" href="http://txqz.net/genre/MVC" title="MVC"/>
	<link rel="next" href="http://txqz.net/genre/N700" title="N700"/>
	<link rel="last" href="http://txqz.net/genre/%EF%BD%83%EF%BD%83TLD" title="ｃｃTLD"/>
	<author>
		<name>陽坂智佐</name>
		<email>spambasket@txqz.net</email>
	</author>
	<entry>
		<title>記事-タグの関係をMySQLicious化</title>
		<id>tag:txqz.net,2008-05-05:/blog/2008/05/05/1837</id>
		<link rel="alternate" href="http://txqz.net/blog/2008/05/05/1837"/>
		<content type="xhtml">
			<div xmlns="http://www.w3.org/1999/xhtml">
<p><a href="http://txqz.net/blog/2008/03/20/1834" title="1:nなデータを検索しやすくDBに入れたい">先日の正規化ネタ</a>の件で<a href="http://b.hatena.ne.jp/teramako/20080321#bookmark-7947357" title="はてなブックマーク - 特別でないただのブックマーク / 2008年03月21日">id:teramako</a>さんが<a href="http://www.pui.ch/phred/archives/2005/04/tags-database-schemas.html" title="Tags: Database schemas">del.icio.usでの例</a>を紹介して下さったので移行してみた。</p>
<h3>今までの構造</h3>
<p>今までこのブログで記事とタグの関係がどうなっていたかというと、教科書どおりに正規化していた。ただ、タグそのものにIDをふって [items] - [item_tag] - [tags] という構造ではなくて、[items] - [tags]という形になっていた。以下のような感じ:</p>
<table>
<caption>items</caption>
<thead>
<tr><th>id</th><th>title</th><th>body</th><th>date</th></tr>
</thead>
<tbody>
<tr><th>1</th><td>地球空冷化</td><td>&lt;p&gt;最近、私の ...</td><td>2001-08-04 00:01:00</td></tr>
<tr><th>2</th><td>台風一過</td><td>&lt;p&gt;幸運なことに ...</td><td>2001-08-22 00:01:00</td></tr>
</tbody>
</table>
<table>
<caption>tags</caption>
<thead>
<tr><th>item_id</th><th>tag</th></tr>
</thead>
<tbody>
<tr><th>1</th><td>生活</td></tr>
<tr><th>1</th><td>自分のこと</td></tr>
<tr><th>2</th><td>2ch</td></tr>
<tr><th>2</th><td>コロッケ</td></tr>
<tr><th>2</th><td>台風</td></tr>
</tbody>
</table>
<p>この構造の問題点は、たとえば「Opera」と「ノウハウ」を含んで「演劇」を含まない記事を出したい! というのが面倒なこと。そのかわり正規化されているからとってもリレーショナル。ある時期に頻出するタグを求めるみたいなことがTF-IDFの応用ですぐできる。<a href="http://www.r.dl.itc.u-tokyo.ac.jp/~nakagawa/infoDB/ir-vector.pdf">HarmanとSparck Jonesによる正規化</a>を組み合わせると、ある期間に頻出するタグ<var>i</var>は、<code class="math">log<sub>2</sub>(タグ<var>i</var>のある期間内での登場数+1) / log<sub>2</sub>(ある期間内に登場したタグの種類数) * (log<sub>2</sub>(全記事数 / タグ<var>i</var>がつけられた記事数) + 1)</code>で求められる。<a href="http://txqz.net/blog/2006/12/19/2347" title="MySQLでTF-IDFの計算、あと2つのベクトルの内積の計算">ちゃんと正規化されていればこの計算をSQLで行うのは簡単</a>だが、1セルにすべて閉じ込めてしまうととたんに面倒になる。ただ、そんな頻度の少ない処理が面倒になることよりもいつも行う処理が1つのテーブルを参照するだけですむことのほうが重要だ。記事のCRUDが1テーブルでできるようになれば、記事の更新をphpMyAdminから行うことだってできる。</p>
<h3>移行作業</h3>
<p>ということで、"MySQLicious" solution を目指す。以下のような感じ:</p>
<table>
<caption>items</caption>
<thead>
<tr><th>id</th><th>title</th><th>body</th><th>date</th><th>tags</th></tr>
</thead>
<tbody>
<tr><th>1</th><td>地球空冷化</td><td>&lt;p&gt;最近、私の ...</td><td>2001-08-04 00:01:00</td><td>生活 自分のこと</td></tr>
<tr><th>2</th><td>台風一過</td><td>&lt;p&gt;幸運なことに ...</td><td>2001-08-22 00:01:00</td><td>2ch コロッケ 台風</td></tr>
</tbody>
</table>
<p>これをSQLだけでやるにはどうすればいいか。カラムの更新なんだからUPDATEだろjkと思い、下のようなクエリを発行したら<samp>#1242 - Subquery returns more than 1 row</samp>と怒られた:</p>
<pre><code class="mysql">UPDATE items SET tags = concat(tags, ' ', (SELECT tag FROM tags WHERE items.id = tags.id));</code></pre>
<p>これが数値だったら、group byしてsum()したのをupdateすれば終わってたのになー。そんなこといってもしょうがないのでもう少し考えてみたところ、on duplicate keyを使う案をひらめいた:</p>
<pre><code class="mysql">INSERT INTO items(id, tags) SELECT old_items.id, tag FROM old_items LEFT JOIN tags USING (id) ON duplicate KEY UPDATE tags = concat(tags, ' ', tag);</code></pre>
<p>いやーon duplicate keyって便利ですねー。</p>
<p>ただ、これ逆のことをやろうと思ったらSQLだけじゃできないだろうなあ。</p>
<ins class="ps" datetime="2008-10-16T19:10:36+09:00" id="PS1224151836">
<p>4.1以降なら<a href="http://dev.mysql.com/doc/refman/4.1/ja/group-by-functions.html" title="MySQL :: MySQL 4.1 リファレンスマニュアル :: 6.3.7.1 GROUP BY 関数">group_concat()関数</a>が使える。この関数の存在に今日気づいたので追記。</p>
</ins>
<h3>副作用</h3>
<p>先に触れた、「ある期間中に特に現れるタグ」の計算が面倒になった。今まではこんな感じで計算していた (一部テヌキのためにテキトーな数字で決め打ちしているところがある):</p>
<pre><code class="mysql">SELECT tag, log(count(tag) + 1) / log(5) * (log(2240/count) + 1) score FROM tags LEFT JOIN df USING(tag) LEFT JOIN items USING(id) WHERE date between ? and ? and count > 1 GROUP BY tag ORDER BY score desc, count(tag) desc LIMIT 10;</code></pre>
<p>変更後はこうなった:</p>
<pre><code class="mysql">SELECT tag, log(sum(CASE WHEN length(tags) = length(replace(tags, tag, '')) THEN 0 ELSE 1 END) + 1 ) / log(5) * (log(3350/count) + 1) score FROM df LEFT JOIN items ON tags like concat('%', tag ,'%') WHERE date between ? and ? and count > 1 GROUP BY tag ORDER BY score desc LIMIT 10;</code></pre>
<p>ということで、はじめてCASE式を使ってみた。<a href="http://www.geocities.jp/mickindex/database/db_case.html" title="CASE式のススメ">CASE式は面白い</a>ので、ぜひSELECT句で分岐させていきたいですね。</p>
<p>副作用だといえるものは、多分これくらいだと思う。LIKE検索で検索が遅くなるのは、このサイトくらいの記事保持数やアクセス数だったらそこまで問題にはならないだろうし、それより内部処理が簡単になる利点を求めたい。</p>
			</div>
		</content>
		<category term="このサイト"/>
		<category term="MySQL"/>
		<category term="スキーム"/>
		<category term="SQL"/>
		<trackback:ping>http://txqz.net/blog/2008/05/05/1837/tb</trackback:ping>
		<published>2008-05-05T18:37:20+09:00</published>
		<updated>2008-10-16T19:10:36+09:00</updated>
	</entry>
	<entry>
		<title>1:nなデータを検索しやすくDBに入れたい</title>
		<id>tag:txqz.net,2008-03-20:/blog/2008/03/20/1834</id>
		<link rel="alternate" href="http://txqz.net/blog/2008/03/20/1834"/>
		<content type="xhtml">
			<div xmlns="http://www.w3.org/1999/xhtml">
<p>いま後輩が悩んでいるネタ。たとえば、以下のようなデータをデータベースにどう格納するかを考える。</p>
<table>
<caption>夜行バスと割引</caption>
<thead>
<tr><th>名前</th><th>行き先</th><th>片道運賃</th><th>割引</th></tr>
</thead>
<tbody>
<tr><th>セレナーデ号</th><td>広島駅</td><td>8400</td><td>早売14、ネット割2%、往復割引</td></tr>
<tr><th>出雲・松江ドリーム名古屋号</th><td>出雲市駅</td><td>9000</td><td>早売21、ネット割2%、往復割引</td></tr>
<tr><th>オリーブ松山号</th><td>JR松山支店</td><td>10000</td><td>往復割引</td></tr>
</tbody>
</table>
<p>手元に資料があるもので適当に問題を置き換えたので例が悪いが、とにかくこういうデータをどういうスキーマで格納するかを考える。教科書どおりに沿って考えると、このデータは正規化されていないので、正規形に変える必要があるだろう。</p>
<table>
<caption>夜行バス</caption>
<thead>
<tr><th>ID</th><th>名前</th><th>行き先</th><th>片道運賃</th></tr>
</thead>
<tbody>
<tr><th>1</th><td>セレナーデ号</td><td>広島駅</td><td>8400</td></tr>
<tr><th>2</th><td>出雲・松江ドリーム名古屋号</td><td>出雲市駅</td><td>9000</td></tr>
<tr><th>3</th><td>オリーブ松山号</td><td>JR松山支店</td><td>10000</td></tr>
</tbody>
</table>
<table>
<caption>割引</caption>
<thead>
<tr><th>ID</th><th>割引</th></tr>
</thead>
<tbody>
<tr><th>1</th><td>早売14</td></tr>
<tr><th>1</th><td>ネット割2%</td></tr>
<tr><th>1</th><td>往復割引</td></tr>
<tr><th>2</th><td>早売21</td></tr>
<tr><th>2</th><td>ネット割2%</td></tr>
<tr><th>2</th><td>往復割引</td></tr>
<tr><th>3</th><td>往復割引</td></tr>
</tbody>
</table>
<p>このままデータベースに突っ込めば、「ネット割2%」が適用されるバスの情報が欲しければ以下のSQL文で参照できるはず:</p>
<pre><code class="sql">SELECT 名前 FROM 夜行バス LEFT JOIN 割引 USING(id) WHERE 割引='ネット割2%';</code></pre>
<p>問題はここからで、このスキームに従うと、「早売14とネット割2%の両方が指定されている夜行バス」の一覧が欲しいときに割引テーブルを自己結合するか、早売14が指定されているバスの集合とネット割2%が指定されているバスの集合を求めて積集合を取るかしないといけなくなる。指定が3つ4つと増えていくと計算量がみるみる増えていくので、あまり現実的な話ではない。</p>
<p>MySQLには<a href="http://dev.mysql.com/doc/refman/4.1/ja/set.html" title="MySQL :: MySQL 4.1 リファレンスマニュアル :: 6.2.3.4 SET 型">SET型</a>というものがあるので、これからもずっとMySQLで運用する気なら、割引カラムをSET型で宣言すればよい。</p>
<pre><code class="sql">CREATE TABLE 夜行バス (
  id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
  名前 VARCHAR(20) NOT NULL,
  行き先 VARCHAR(15) NOT NULL,
  片道運賃 SMALL INT UNSIGNED NOT NULL
  割引 SET('早売14','早売21','ネット割2%','往復割引')
);</code></pre>
<p>「早売14とネット割2%の両方が指定されている夜行バス」は以下のSQL文で求められるはず:</p>
<pre><code class="sql">SELECT 名前 FROM 夜行バス WHERE FIND_IN_SET('早売14',割引) AND FIND_IN_SET('ネット割2%',割引);</code></pre>
<p>ただ、どうせMySQLでいくなら、割引カラムにFulltextインデックスをつけてmatch() against() したほうが楽だったりして。</p>
<p>問題はほかにもあって、なんか元データはエクセルに以下のような感じで収まっているのだという:</p>
<table>
<thead>
<tr><th>名前</th><th>行き先</th><th>片道運賃</th><th>早売14</th><th>早売21</th><th>ネット割2%</th><th>往復割引</th></tr>
</thead>
<tbody>
<tr><td>セレナーデ号</td><td>広島駅</td><td>8400</td><td>○</td><td></td><td>○</td><td>○</td></tr>
<tr><td>出雲・松江ドリーム名古屋号</td><td>出雲市駅</td><td>9000</td><td></td><td>○</td><td>○</td><td>○</td></tr>
<tr><td>オリーブ松山号</td><td>JR松山支店</td><td>10000</td><td></td><td></td><td></td><td>○</td></tr>
</tbody>
</table>
<p>確かにそれは面倒だ。強まった人なら1発でいい感じに整形できるのだろうけど、私だったら正規表現の使えるエディタにコピペして</p>
<pre>s/○$/往復割引/g
s/○(\t[^\t]*)$/ネット割2%\1/g
s/○(\t[^\t]*)(\t[^\t]*)$/早売21\1\2/g
s/○(\t[^\t]*)(\t[^\t]*)(\t[^\t]*)$/早売14\1\2\3/g</pre>
<p>と置換するかなあ。置換すべき項目がもっと多いならもう少しエレガントな方法を考えるけど、10個くらいだったら上のようなやり方でやっちゃったほうが早そう。</p>
<ins class="ps" datetime="2008-03-20T23:30:40+09:00" id="PS20080320233040">
<p></p>
<blockquote title="はてなブックマーク - hijiのブックマーク / 2008年03月20日" cite="http://b.hatena.ne.jp/hiji/20080320#bookmark-7947357">
<p>set型か。覚えておこう。あとどうでもいいが、早「売」14。→<a href="http://www.kousokubus.net/PC/BPGD/BPGD221_01.htm">http://www.kousokubus.net/PC/BPGD/BPGD221_01.htm</a> というか、実在のデータじゃないのね。</p>
</blockquote>
<p>これはやってしまいましたね。修正しました。ご指摘ありがとうございました。あとデータはJR東海バスの『JRハイウェイバス時刻表2008.3.1→6.30』の26ページから引用しました。</p>
</ins>
			</div>
		</content>
		<category term="MySQL"/>
		<category term="SQL"/>
		<category term="データベース"/>
		<category term="正規表現"/>
		<trackback:ping>http://txqz.net/blog/2008/03/20/1834/tb</trackback:ping>
		<published>2008-03-20T18:34:29+09:00</published>
		<updated>2008-03-20T23:30:40+09:00</updated>
	</entry>
	<entry>
		<title>tritonn-1.0.3リリースでインストールがより簡単に</title>
		<id>tag:txqz.net,2007-07-21:/blog/2007/07/21/1821</id>
		<link rel="alternate" href="http://txqz.net/blog/2007/07/21/1821"/>
		<content type="xhtml">
			<div xmlns="http://www.w3.org/1999/xhtml">
<blockquote cite="http://qwik.jp/tritonn/FrontPage.html" title="Tritonnプロジェクト ～ MySQL+Sennaによる全文検索 ～ - Tritonnプロジェクト">
<p>変更点は主にBug Fixとなります。詳しくはChangelogを御覧下さい。　なお、今回のリリースから配布形式をパッチ単体ではなく、MySQLのソースコードにパッチ組み込んだ形に変更しました</p>
</blockquote>
<p>これはうれしい。</p>
			</div>
		</content>
		<category term="MySQL"/>
		<category term="senna"/>
		<category term="tritonn"/>
		<category term="全文検索"/>
		<trackback:ping>http://txqz.net/blog/2007/07/21/1821/tb</trackback:ping>
		<published>2007-07-21T18:21:44+09:00</published>
		<updated>2007-07-21T18:21:44+09:00</updated>
	</entry>
	<entry>
		<title>Sennaとかのアレの続き Tritonn編</title>
		<id>tag:txqz.net,2007-06-07:/blog/2007/06/07/2026</id>
		<link rel="alternate" href="http://txqz.net/blog/2007/06/07/2026"/>
		<content type="xhtml">
			<div xmlns="http://www.w3.org/1999/xhtml">
<p><a href="blog/2006/08/27/0253" title="CentOSにMeCab+Senna+MySQLを入れる">以前書いたMySQLにSennaを当てたときのメモ</a>が地味に注目を集めているらしく、ときたまSBMにブクマされたりしている。あのときは、お手本がDebianでパスの指定がCentOSと違っていたり、何か自分の環境で用を全うするのに必要な知識が分散していたりして無駄に時間がかかったので、一応CentOSユーザな私がやったことを一括に並べておいて、後から同じことをするのに都合の良いようにするためにあれを書いた。今は<a href="http://qwik.jp/tritonn/" title="Tritonnプロジェクト ～ MySQL+Sennaによる全文検索 ～ - Tritonnプロジェクト">Tritonn</a>があり、<a href="http://qwik.jp/tritonn/userguide.html" title="Tritonnプロジェクト ～ MySQL+Sennaによる全文検索 ～ - Tritonnの使い方">Tritonnのドキュメント</a>をそのままなぞれば (--prefixとかはちゃんと変える)、それで用が済んでしまう。</p>
<p>ちなみに、今回私がMySQLをmakeしたときのconfigureオプションはこんな感じ:</p>
<pre>./configure \
 --prefix=/usr \
 --with-charset=utf8 \
 --with-extra-charsets=complex \
 --localstatedir=/var/lib/mysql \
 --libexecdir=/var/lib/mysql/bin \
 --enable-thread-safe-client \
 --enable-local-infile \
 --enable-assembler \
 --with-pic \
 --with-fast-mutexes \
 --disable-shared \
 --with-zlib-dir=bundled \
 --with-big-tables \
 --with-yassl \
 --with-readline \
 --with-archive-storage-engine \
 --with-blackhole-storage-engine \
 --with-example-storage-engine \
 --with-federated-storage-engine \
 --with-mysql-idflags=-all-static \
 --with-client-idflags=-all-static \
 --with-mysql-idflags=-all-static \
 --with-mysql-user=mysql \
 --with-innodb \
 --with-extra-charsets=complex \
 --with-senna \
 --with-mecab \
 --with-unix-socket-path=/var/lib/mysql/mysql.sock</pre>
<p>前指定したのと、Tritonnのところに書いてあったのを重ねただけなので、これで良いのかはよく分からん。</p>
<p>それで全文検索システムをより簡単に提供できるようになった。検索用インデックスはMeCab付属のIPA辞書で事足りる。目下の問題は表示用インデックスだ。連続する名詞はひとつにまとめるとか、ちゃんと「接頭詞」とか「接尾」とかを理解するようにすれば、「緑資源開発機構」や「参院選」のような辞書にない語彙も表示できる。しかし、作品名とか略語とかはやっぱり辞書にないとうまく取り出せない。サフィックスアレイを使うって? やっぱり? 勉強しなくちゃ。</p>
<ins class="ps" datetime="2008-09-16T16:18:44+09:00" id="PS1221549524">
<p>「<a href="http://www.akiyan.com/blog/archives/2008/09/tritonnmysqlsen.html" title="Tritonn(MySQL+Senna)+Mecab+Apahce+PHPをCentOS5にインストールする : akiyan.com">Tritonn(MySQL+Senna)+Mecab+Apahce+PHPをCentOS5にインストールする</a>」も参考になる。</p>
</ins>
			</div>
		</content>
		<category term="MeCab"/>
		<category term="MySQL"/>
		<category term="Senna"/>
		<category term="SuffixArray"/>
		<category term="Tritonn"/>
		<category term="全文検索"/>
		<category term="語彙"/>
		<category term="辞書"/>
		<trackback:ping>http://txqz.net/blog/2007/06/07/2026/tb</trackback:ping>
		<published>2007-06-07T20:26:40+09:00</published>
		<updated>2008-09-16T16:18:44+09:00</updated>
	</entry>
	<entry>
		<title>Snippetしようとするとcannot add conditions</title>
		<id>tag:txqz.net,2007-05-18:/blog/2007/05/18/1525</id>
		<link rel="alternate" href="http://txqz.net/blog/2007/05/18/1525"/>
		<content type="xhtml">
			<div xmlns="http://www.w3.org/1999/xhtml">
<p>いままでMySQL5.0.33+<a href="http://qwik.jp/senna/FrontPageJ.html" title="Senna 組み込み型全文検索エンジン - Senna 組み込み型全文検索エンジン">Senna</a>0.8.2だった環境をMySQL5.0.37+Senna1.0.4+<a href="http://mecab.sourceforge.net/" title="MeCab: Yet Another Part-of-Speech and Morphological Analyzer">MeCab</a>0.95+<a href="http://qwik.jp/tritonn/" title="Tritonnプロジェクト ～ MySQL+Sennaによる全文検索 ～ - Tritonnプロジェクト">Tritonn</a>1.0.2に変えた。</p>
<p>従来の環境では、MeCabの辞書で全文検索インデックスを作るよりN-gramの方が早いし良いかと思っていたが、Sennaがついにバージョン1になったり、やっぱり多少速度がアレになってもより正確な方が良いよねということになったりしたので、バージョンアップをすることにした。</p>
<p>いろいろ試行錯誤があったものも、最終的にはMySQL5.0.33を make uninstall して、5.0.37を make installしたのち、古い全文検索インデックスをいったん削除してから貼りなおした。そしてsnippet関数を使おうとしたら</p>
<pre><samp>ERROR:
cannot add conditions</samp></pre>
<p>よく考えると、5.0.37に変えた後、とくに<a href="http://qwik.jp/senna/old_mysql_binding_docs.html" title="Senna 組み込み型全文検索エンジン - 旧ブラジル版MySQLバインディングのドキュメント置き場">Snippet UDF</a>をインストールしていないのにsnippetできる方がおかしいので、senna1.0.4についていたSnippet UDFをインストールしようとしたら、</p>
<pre><samp>Libraries have been installed in:
   /usr/lib

If you ever happen to want to link against installed libraries
in a given directory, LIBDIR, you must either use libtool, and
specify the full pathname of the library, or use the `-LLIBDIR'
flag during linking and do at least one of the following:
   - add LIBDIR to the `LD_LIBRARY_PATH' environment variable
     during execution
   - add LIBDIR to the `LD_RUN_PATH' environment variable
     during linking
   - use the `-Wl,--rpath -Wl,LIBDIR' linker flag
   - have your system administrator add LIBDIR to `/etc/ld.so.conf'

See any operating system documentation about shared libraries for
more information, such as the ld(1) and ld.so(8) manual pages.</samp></pre>
<p>make load すると</p>
<pre><samp>ERROR 1125 (HY000) at line 1: Function 'snippet' already exists</samp></pre>
<p>こりゃ、ご指示通りマニュアルを読む必要があるのかー。</p>
<p><a href="http://www.google.co.jp/search?q=%22cannot+add+conditions%22&amp;lr=lang_ja" title="&quot;cannot add conditions&quot; - Google 検索">ぐぐってみると日本語のリソースが1個しか引っかからない</a>のが悲しい。くだんの人は、<a href="http://lists.sourceforge.jp/mailman/archives/senna-dev/2007-February/000479.html" title="[Senna-dev 480] Re: snippet関数を使うとクラッシュする">libの中のファイルが古かったのが原因だった</a>そうな。同じように確認してみたら、私の場合はファイルが古いわけではなかった。1台まっさらなのを用意するほうが早いかなー。</p>
<ins class="ps" datetime="2007-05-25T18:19:17+09:00" id="PS20070525181917">
<p>古いsnippetをmake uninstall→新しいsnippetをmake install→MySQLにログインして<a href="http://dev.mysql.com/doc/refman/5.1/ja/drop-procedure.html" title="MySQL AB :: MySQL 5.1 リファレンスマニュアル :: 8.2.3 DROP PROCEDURE および　DROP FUNCTION 構文">drop function</a>→make loadで解決した。そういわれれば、確かに関数がすでに存在しているなら削除してあげればいいだけの話だった。</p>
</ins>
			</div>
		</content>
		<category term="MySQL"/>
		<category term="Senna"/>
		<category term="snippet"/>
		<category term="トラブル"/>
		<trackback:ping>http://txqz.net/blog/2007/05/18/1525/tb</trackback:ping>
		<published>2007-05-18T15:25:41+09:00</published>
		<updated>2007-05-25T18:19:17+09:00</updated>
	</entry>
	<entry>
		<title>データベーススキーマの設計</title>
		<id>tag:txqz.net,2007-05-01:/blog/2007/05/01/1552</id>
		<link rel="alternate" href="http://txqz.net/blog/2007/05/01/1552"/>
		<content type="xhtml">
			<div xmlns="http://www.w3.org/1999/xhtml">
<blockquote title="TAKESAKO @ Yet another Cybozu Labs: ニコニコ動画勉強会に行ってきました" cite="http://labs.cybozu.co.jp/blog/takesako/2007/04/nicovideo.html">
<dl><dt>複数テーブルにまたがるJOINは避ける</dt><dd>非正規化ロジックで対応</dd></dl>
</blockquote>
<blockquote title="フツーな日常 - MySQLのTips" cite="http://d.hatena.ne.jp/futsu-9/20061122/p1">
<ol><li>まっとうなクエリスキーマをデザインすること。テーブルの結合を怖がることはない、大抵非正規化よりも速い </li></ol>
</blockquote>
<p>結局どっちなんだ?</p>
			</div>
		</content>
		<category term="MySQL"/>
		<category term="RDBMS"/>
		<category term="SQL"/>
		<trackback:ping>http://txqz.net/blog/2007/05/01/1552/tb</trackback:ping>
		<published>2007-05-01T15:52:24+09:00</published>
		<updated>2007-05-01T15:52:24+09:00</updated>
	</entry>
	<entry>
		<title>なければ INSERT、あれば UPDATE</title>
		<id>tag:txqz.net,2007-01-17:/blog/2007/01/17/1920</id>
		<link rel="alternate" href="http://txqz.net/blog/2007/01/17/1920"/>
		<content type="xhtml">
			<div xmlns="http://www.w3.org/1999/xhtml">
<p>すでにテーブルにある主キーやユニークキーと同じ値をふくむ行を挿入しようとするときに、こちらの想定する振る舞いをさせる方法のまとめ。まぁ、<a href="http://dev.mysql.com/doc/refman/4.1/ja/insert.html" title="MySQL AB :: MySQL 4.1 リファレンスマニュアル :: 6.4.3 INSERT 構文">リファレンスマニュアルの INSERT 構文のところ</a>を見れば話は終わりなのだけれども。</p>
<table>
<thead><tr><th>ID (主キー)</th><th>値</th></tr></thead>
<tbody>
<tr><th>1</th><td>100</td></tr>
<tr><th>2</th><td>800</td></tr>
<tr><th>3</th><td>400</td></tr>
</tbody>
</table>
<dl>
<dt>間違って主キーと同じ値を含む行を挿入しようとしたらエラーを出して教えてほしいとき</dt>
<dd><p><code class="mysql">INSERT INTO test VALUES(1,100);</code> とすると、<samp>ERROR 1062 (23000): Duplicate entry '1' for key 1</samp> というエラーが出る。</p></dd>
<dt>間違って主キーと同じ値を含む行を挿入しようとしても無視して処理を続行してほしいとき</dt>
<dd><p><code class="mysql">INSERT IGNORE INTO test VALUES(1,100);</code> とすると、<samp>Query OK, 0 rows affected (0.00 sec)</samp> と表示される。テーブルは変更されていない。</p></dd>
<dt>主キーと同じ値を含む行がないなら新しく挿入し、あるならその行を更新してほしいとき</dt>
<dd><p><code class="mysql">REPLACE INTO test VALUES(1,500);</code> とすると、<samp>Query OK, 2 rows affected (0.00 sec)</samp> と表示される。削除してから挿入するので影響された行は2になる。テーブルは;</p>
<table>
<thead><tr><th>ID (主キー)</th><th>値</th></tr></thead>
<tbody>
<tr><th>1</th><td>500</td></tr>
<tr><th>2</th><td>800</td></tr>
<tr><th>3</th><td>400</td></tr>
</tbody>
</table>
<p><code class="mysql">REPLACE INTO test VALUES(4,200);</code> とすると、<samp>Query OK, 1 row affected (0.00 sec)</samp> と表示される。主キーが4である行はないので、そのまま挿入されて、影響された行は1になる。テーブルは;</p>
<table>
<thead><tr><th>ID (主キー)</th><th>値</th></tr></thead>
<tbody>
<tr><th>1</th><td>500</td></tr>
<tr><th>2</th><td>800</td></tr>
<tr><th>3</th><td>400</td></tr>
<tr><th>4</th><td>200</td></tr>
</tbody>
</table>
</dd>
<dt>主キーと同じ値を含む行がないなら新しく挿入し、あるならその行を元のデータを使って更新してほしいとき</dt>
<dd><p><code class="mysql">INSERT INTO test VALUES(1,100) ON DUPLICATE KEY UPDATE value = value + 100;</code> とすると、<samp>Query OK, 2 rows affected (0.00 sec)</samp> と表示される。削除してから挿入するので影響された行は2になる。テーブルは;</p>
<table>
<thead><tr><th>ID (主キー)</th><th>値</th></tr></thead>
<tbody>
<tr><th>1</th><td>600</td></tr>
<tr><th>2</th><td>800</td></tr>
<tr><th>3</th><td>400</td></tr>
<tr><th>4</th><td>200</td></tr>
</tbody>
</table>
</dd>
</dl>
<p>最後の構文は無駄な IF 文を除去するのに最適なのでぜひおさえておきたい。</p>
			</div>
		</content>
		<category term="MySQL"/>
		<trackback:ping>http://txqz.net/blog/2007/01/17/1920/tb</trackback:ping>
		<trackback:about>http://bittorrentlove.blog58.fc2.com/blog-entry-260.html</trackback:about>
		<published>2007-01-17T19:20:00+09:00</published>
		<updated>2007-08-22T12:08:20+09:00</updated>
	</entry>
	<entry>
		<title>MySQLでTF-IDFの計算、あと2つのベクトルの内積の計算</title>
		<id>tag:txqz.net,2006-12-19:/blog/2006/12/19/2347</id>
		<link rel="alternate" href="http://txqz.net/blog/2006/12/19/2347"/>
		<content type="xhtml">
			<div xmlns="http://www.w3.org/1999/xhtml">
<p>本文を形態素分解し、必要な品詞をtfテーブルとdfテーブルに入れる。分析対象となる文書群すべてについてこの処理を行い、各形態素のTF-IDF値を求めて文書をベクトル化する。他の文書ベクトルと内積を比較し、小さい順に「似ている記事」を求めたい (クラスタリングとかは別途)。</p>
<p>HarmanによるTF値の正規化とSparok JonesによるDF値の正規化をする場合のTF-IDF値の計算式は以下のようになる (<a href="http://www.r.dl.itc.u-tokyo.ac.jp/~nakagawa/infoDB/ir-vector.pdf">参考文献</a>):</p>
<pre><code class="math">tfidf(i,j) = log2(freq(i,j) + 1) / log2(NoT) * (log2(N / Dfreq(i)) + 1)</code></pre>
<h3>HarmanによるTFの正規化</h3>
<pre><code class="math">tf(i,j) = log2(freq(i,j) + 1) / log2(NoT)</code></pre>
<dl>
<dt>tf(i,j)</dt><dd>文書jにおける単語iのTF値</dd>
<dt>freq(i,j)</dt><dd>文書jにおける単語iの登場回数</dd>
<dt>NoT</dt><dd>文書j中のタームの種類数(num of terms)</dd>
</dl>
<h3>Sparck JonesによるDFの正規化</h3>
<pre><code class="math">idf(i) = log2(N / Dfreq(i)) + 1</code></pre>
<dl>
<dt>idf(i)</dt><dd>単語iのDF値</dd>
<dt>N</dt><dd>文書集合中の文書総数</dd>
<dt>Dfreq(i)</dt><dd>単語iが登場する文書数</dd>
</dl>
<h3>MySQL での表現</h3>
<dl>
<dt>TF値の分子</dt><dd><pre><code class="sql">SELECT log2(times + 1) FROM tf WHERE item='j' AND tag='i';</code></pre></dd>
<dt>TF値の分母</dt><dd><pre><code class="sql">SELECT log2(count(tag)) FROM tf WHERE item='j' GROUP BY item;</code></pre></dd>
<dt>IDF値のlogの分子</dt><dd><pre><code class="sql">SELECT count(id) FROM article;</code></pre></dd>
<dt>IDF値のlogの分母</dt><dd><pre><code class="sq;">SELECT times FROM df WHERE tag = 'i';</code></pre></dd>
</dl>
<p>全部くっつけると:</p>
<pre><code class="sql">SELECT item, tag, log2(tf.times + 1) / log2(total) * (log2(n / df.times) + 1) AS tfidf
  FROM tf
    LEFT JOIN df USING(tag)
    LEFT JOIN (SELECT item, count(tag) total FROM tf GROUP BY item) AS a USING(item)
    CROSS JOIN (SELECT count(id) AS n FROM items) AS b
  WHERE item="<var>j</var>";</code></pre>
<p>実際はユーザ変数を使った方がSQLが短くなっていいと思う。</p>
<pre><code class="sql">SELECT @total := count(tag) FROM tf WHERE item = "<var>j</var>";
SELECT @n := count(id) FROM items;
SELECT item, tag, log2(tf.times + 1) / log2(@total) * (log2(@n / df.times) + 1) AS tfidf
  FROM tf LEFT JOIN df USING(tag)
  WHERE item="<var>j</var>";</code></pre>
<p>これで記事の各形態素のTFIDF値が求められたので、tfidfテーブルに保管しておく。</p>
<h3>内積を求めて近い記事を出す</h3>
<p>とりあえず各記事の上位100単語くらいを使うことにする。</p>
<pre><code class="sql">INSERT INTO tfidf
  SELECT item, tag, log2(tf.times + 1) / log2(@total) * (log2(@n / df.times) + 1) AS tfidf
    FROM tf LEFT JOIN df USING(tag)
    WHERE item="<var>j</var>"
    ORDER BY tfidf DESC
    LIMIT 100;</code></pre>
<p>ある文書wがn次元のベクトルで表せる (w = {w<sub>1</sub> w<sub>2</sub> ... w<sub>n</sub>})とき、文書wとxの内積は</p>
<pre><code class="math">Σ(w<sub>i</sub> * v<sub>i</sub>) / √(Σ(w<sub>i</sub><sup>2</sup>) * Σ(v<sub>i</sub><sup>2</sup>))</code></pre>
<p>MySQLで書くと</p>
<pre><code class="sql">CREATE TEMPORARY TABLE inp
  SELECT self.tag, self.tfidf self, target.tfidf target
    FROM tfidf self
      LEFT JOIN (SELECT tag,tfidf FROM tfidf WHERE item='<var>v</var>') target USING(tag)
    WHERE self.item = '<var>w</var>';
SELECT sum(self * target) / sqrt(sum(pow(self,2))*sum(pow(target,2))) inp FROM inp</code></pre>
<h3>実際にやってみる</h3>
<p>実際にニュー速各板のスレでやってみた。だいたい同じニュースの続きのスレだと0.6以上の高い値に、似たようなネタの異なるニュースの場合は0.3～0.4くらいになった。以下はそれらの例。カッコ内が内積</p>
<h4>高い値 …… 同じニュースの次スレ、前スレ</h4>
<dl>
<dt>【経済】 「格差是正のため、正社員の待遇を非正規社員水準に合わせる」…経済財政諮問会議・八代氏★５</dt>
<dd><ul>
<li>【経済】 「格差是正のため、正社員の待遇を非正規社員水準に合わせる」…経済財政諮問会議・八代氏★４ (0.76865722990833)</li>
<li>【経済】 「格差是正のため、正社員の待遇を非正規社員水準に合わせる」…経済財政諮問会議・八代氏★３ (0.72848890331971)</li>
<li>【経済】「格差是正のため正社員待遇を非正規社員水準へ」…経済財政諮問会議メンバー・八代尚宏氏★２ (0.61619675121174)</li>
<li>【経済】「格差是正のため正社員待遇を非正規社員水準へ」…経済財政諮問会議メンバー・八代尚宏氏 (0.63664490692797)</li>
</ul></dd>
<dt>【芸能】森本レオが石原真理子の処女を奪ったことを認める「それでもやっぱりマリコがんばれ」</dt>
<dd><ul>
<li>【芸能】石原真理子「17歳で森本レオに処女奪われた」…週刊誌に暴露、「宣伝か」の声も★２ (0.82246383998552)</li>
<li>【芸能】石原真理子「17歳で森本レオに処女奪われた」…週刊誌に暴露、「宣伝か」の声も[12/18] (0.79417204039799)</li>
</ul></dd>
<dt>◆自治議論★64◆</dt>
<dd><ul>
<li>愛の説教部屋166(地獄キャンペーン実施中)（　ﾟдﾟ) (0.67651278868912)</li>
<li>◆自治議論★63◆ (0.81182145799799)</li>
<li>◆自治議論★62◆ (0.79929338782244)</li>
</ul></dd>
</dl>
<h4>中くらいの値 …… 似たようなネタだが異なるニュースのスレ</h4>
<dl>
<dt>【MLB】多田野、アスレチックスと再契約 春季キャンプでメジャー復帰目指す★３</dt>
<dd><ul>
<li>【社会】 ＮＨＫ職員（男）、電車で大学生（１８０cm・１２０kgの男子）に痴漢→逮捕…東京★３ (0.4629403483907)</li>
<li>【社会】 ＮＨＫ職員（男）、電車で大学生（男子）に痴漢→逮捕…東京★２ (0.41509734340642)</li>
</ul></dd>
<dt>【大阪】コリアNGOセンター事務局長「公立校で民族教育は不要との意見が出かねない…外国籍の子供に愛国心強調しないで」［12/18］</dt>
<dd><ul>
<li>【論説】 「"日教組が、教育荒廃の元凶"というのは言いがかりだ」…東京新聞★２ (0.35593573724151)</li>
<li>【日韓】 ［特派員コラム］韓国は日本を追い越すことができる?潜在力も意欲も韓国が上[12/18] (0.3069814685099)</li>
<li>【論説】 「愛国心、"格差"はぐらかす為か？ 学生らは愛国心強要に"日本社会の悪化"を感じている」…毎日新聞★３ (0.39403589906126)</li>
</ul></dd>
<dt>【フィギュアスケート】高橋・安藤・浅田・村主ら日本勢に謎の症状･･･体調不良者が続出★３</dt>
<dd><ul>
<li>【北海道】カキ「風評被害」に悲鳴、取扱額４０％減　ノロウイルス食中毒、今季の感染例ゼロなのに★２ (0.41831300355656)</li>
</ul></dd>
</dl>
<p>今回やってみて、同じニュースのスレッドは★１だろうが★８だろうが同じようなことを延々と話しているのではないかと感じた。何スレも立つような息の長いニュースについて、スレッドごとの単語の登場の仕方とか共起の仕方を見ていくと、ニュー速民のニュースへの態度を表せたりするかも。面白いのはフィギュアスケート選手の体調不良の記事とカキの風評被害の記事が関連付けられたこと。フィギュアスケートの記事中に「カキ」への言及がなくても、ニュー速民による噂話の可視化によって、実は関係あるかもしれない2つの記事が結びついた。なんか集合知かも～。</p>
<ins class="ps" datetime="2007-06-02T19:43:05+09:00" id="PS20070602194305">
<p>このとき使ったのと似たような手法を<a href="http://sangi.in/kouho">候補者ブログクローラ</a>でも使っています。</p>
</ins>
			</div>
		</content>
		<category term="MySQL"/>
		<category term="SQL"/>
		<category term="TF-IDF"/>
		<category term="ベクトル空間"/>
		<category term="内積"/>
		<category term="自然言語処理"/>
		<trackback:ping>http://txqz.net/blog/2006/12/19/2347/tb</trackback:ping>
		<published>2007-06-02T19:43:05+09:00</published>
		<updated>2007-12-04T18:55:11+09:00</updated>
	</entry>
	<entry>
		<title>MySQLノウハウ</title>
		<id>tag:txqz.net,2006-12-13:/blog/2006/12/13/0943</id>
		<link rel="alternate" href="http://txqz.net/blog/2006/12/13/0943"/>
		<content type="xhtml">
			<div xmlns="http://www.w3.org/1999/xhtml">
<p>いろいろな本からメモってきたメモのメモ。出典を書いておくのを忘れた。思い出し次第補完するかも。</p>
<ul>
<li>deleteのコストは高いので、無効化を示すフィールドを作ってupdateすべき</li>
<li>slow query logに要注意</li>
<li>多くのエントリでほとんどのフィールドが同じ値を持つ場合はインデックスの効果が小さい
<ul><li>→複合インデックスの効果が大きい</li></ul></li>
<li>複合インデックスは指定の順番が大切。AとBという指定の場合、A単独でもインデックスの効果がある。逆は真でない。</li>
<li>インデックスが使われる場面は
<ul><li>フィールド値を定数と比較するとき (where name = 'hogehoge')</li>
<li>フィールド値でJOINするとき (where a.name = b.name)</li>
<li>フィールド値の範囲を求めるとき (&lt;,&gt;,between)</li>
<li>LIKE句が文字列から始まるとき (where name like 'hoge%')</li>
<li>min(),max()</li>
<li>文字列のプレフィクスを基にしたorder by,groupy by</li>
<li>WHERE句の全てのフィールドがインデックスの一部の場合はDBをまったく参照せずに結果が返ってくる</li></ul></li>
<li>インデックスが使われない場面は
<ul><li>LIKE句がワイルドカードから始まるとき (where name like '%hoge')</li>
<li>DB全体を読んだほうが早いとMySQLが判断したとき</li>
<li>ほかでインデックスを使ったとき</li></ul></li>
<li>遅いクエリをexplain selectで解剖
<ul><li>using filesort と using temporary が現れないように</li>
<li>using index や where used が現れるとうれしい。where used で type:ALLの場合は適切なインデックスをつけるように</li></ul></li>
<li>left join や straight join と using の組み合わせは where より早い
<ul><li>left join on と natural join + where だと後者のほうが早いのでそうしていたけれども、usingを一回試してみよう</li>
<li>usingは二つのカラム名が同じでないと使えないみたい → usingとonは同じみたい</li></ul></li>
<li>where in はかなり早い</li>
<li>インデックスが適用されないので having はなるべく使わないように</li>
<li>なるべくシンプルなSQL文を心がける</li>
<li>update,insert,deleteの最適化
<ul><li>multi-row insertをなるべく使うようにする
<ul><li>JDBC経由だとどうすればいいんだろう</li></ul></li></ul></li>
<li>select high_priority や update low_priority を使ってみる</li>
<li>replace のほうが delete → insert より早い</li>
<li>load data infile, load_file()</li>
<li>limit句を使って転送量の削減</li>
<li>joinに使うフィールドは完全に同じ形式にする
<ul><li>varchar(64)のフィールドとvarchar(32)のフィールドをJOINさせないように。できればchar()のほうがよりよい</li></ul></li>
<li>select ... procedire analyse() で最大必要フィールド長を分析</li>
<li>不必要に長いchar,varcharを避ける</li>
<li>enumの利点
<ul><li>エントリごとに1バイトのみ必要</li>
<li>高速化</li>
<li>想定外のデータが入らない</li></ul></li>
<li>NULLが入らないフィールドには NOT NULL をつけておく</li>
<li>varcharよりもなるべくcharを使う。varcharは別テーブルに追いやって全フィールドの固定長化を図る</li>
<li>key_buffer_size_indexをRAMの4分の1くらいにあげよう</li>
<li>table_cacheをなるべく多く</li>
<li>スワップはダメゼッタイ</li>
<li>NFSを避ける</li>
<li>多くのハードディスクに分散させる</li>
<li>ハードウェアの優先順位は RAM &gt; HDD &gt; CPU &gt; ネットワーク
<ul><li>スレーブには鬼のようにメモリを積んでおきたい</li></ul></li>
<li>選択性の高いインデックスだと効果がある
<ul><li>PRIMARY KEYは選択性最強。膨大な量のデータがあっても1秒程度で</li>
<li>性別などは選択性の低いインデックス。あまり効果がない。WHEREでつなげるなら後回しに</li>
<li>WHERE句でよく一緒に使われる列をインデックスに指定する</li>
<li>WHERE句に単独で使われることもある列は、インデックスの最初の列に指定する</li>
<li>選択性の優れている列から順に指定する</li>
<li>選択する列も結合インデックスに含めるとさらに高速化が期待できる → DBにまったくアクセスせずに結果を返せる
<ul><li>参照が早くなる反面更新が遅くなるので注意</li></ul></li></ul></li>
<li>NULLはインデックスに含まれない。NULLの検索をしたいならデフォルト値の設定を検討すべき</li>
<li>!=,&lt;&gt;の使用に注意。in,orなどの置き換えを考える</li>
<li>WHERE句の条件に関数を使うとインデックスが使用されない。意味的に等価なSQL文への置き換えを考える</li>
<li>order by 句にインデックスが指定されている場合、インデックス内ですでにデータがソートされているので高速(not nullの場合)</li>
<li>distinctを使うと遅くなる。本当に必要なときだけ使う</li>
<li>unionも重複行を削除する処理を行うので処理が遅くなる。union allを使うように</li>
<li>group by句を使う場合集計するカラムだけでなく(sum()などの集計関数を使って)集計されるカラムにもインデックスをつけておくと早い</li>
</ul>
			</div>
		</content>
		<category term="MySQL"/>
		<trackback:ping>http://txqz.net/blog/2006/12/13/0943/tb</trackback:ping>
		<trackback:about>http://highsense.jp/article/47613243.html</trackback:about>
		<published>2006-12-13T09:43:00+09:00</published>
		<updated>2007-07-12T14:43:17+09:00</updated>
	</entry>
	<entry>
		<title>Repairテーブルまだ終わらない</title>
		<id>tag:txqz.net,2006-11-23:/blog/2006/11/23/0000</id>
		<link rel="alternate" href="http://txqz.net/blog/2006/11/23/0000"/>
		<content type="xhtml">
			<div xmlns="http://www.w3.org/1999/xhtml">
<p>138000秒(38時間+)経ってもまだ終わらない。</p>
			</div>
		</content>
		<category term="MySQL"/>
		<trackback:ping>http://txqz.net/blog/2006/11/23/0000/tb</trackback:ping>
		<published>2006-11-23T00:00:00+09:00</published>
		<updated>2007-04-08T17:37:33+09:00</updated>
	</entry>
	<entry>
		<title>updateが終わらなくてkillしてもCommandがkilledのまま</title>
		<id>tag:txqz.net,2006-11-22:/blog/2006/11/22/0959</id>
		<link rel="alternate" href="http://txqz.net/blog/2006/11/22/0959"/>
		<content type="xhtml">
			<div xmlns="http://www.w3.org/1999/xhtml">
<p>なんか電車の中からsunameri経由でkurageにログインしてMySQLの調子を観測するのがたるいのでなにかソリューションを考え中、ていうかsunameriにPHPMyAdminとか入れればいいのか。んー</p>
<p>で、このあいだのニュース板クローラに続き実況板クローラも、updateがぜんぜん終わらなくてkillしてもCommandがkilledのまま終わらなくてrepair table したらそれが30000秒経っても終わらないという状態になってしまったので、ていうか実況板クローラで遊ぶよりも卒論とかにリソースを多く注入すべきだと思ったので12月が終わるまで休止させるかも。そんなこと言いつつ適当なタイミングで思ったより早く復活するかもしれないので未来のことはぜんぜんわからないし、そりゃ鬼だってバカウケするよなぁと思った。あとJavaScriptは意外と面白い。</p>
			</div>
		</content>
		<category term="MySQL"/>
		<category term="tv.oshira.se"/>
		<category term="クローラ"/>
		<trackback:ping>http://txqz.net/blog/2006/11/22/0959/tb</trackback:ping>
		<published>2006-11-22T09:59:00+09:00</published>
		<updated>2007-04-08T17:39:04+09:00</updated>
	</entry>
	<entry>
		<title>大学祭とMySQLのレプリケーション</title>
		<id>tag:txqz.net,2006-11-11:/blog/2006/11/11/2202</id>
		<link rel="alternate" href="http://txqz.net/blog/2006/11/11/2202"/>
		<content type="xhtml">
			<div xmlns="http://www.w3.org/1999/xhtml">
<p>大学祭。ミナミにいたらひーろんが来たので、彼と相合い傘（はーと←ぉぇ）佐世保バーガーがおいしかった。午後から行列になったそうだし、午前の早いうちに味わっておいてよかった。</p>
<p>戻ってきてからcronプロセスの監視。なんかエラーがでてたとこがあったのでパーサを少し書き直した。バージョンの古いのと新しいのがごちゃごちゃしていて、直したと思ったのがそれが実運用に入っていなかったりする。あとですっきりさせないといけない。jarファイルにまとめるとか。</p>
<p>そのあとはMySQLのレプリケーションに手を出す。なんか、レプリケーションまで行くとWindowsにmsiインストーラを使ってクリックだけしてMySQLをインストールしていた頃に比べると隔世の感が出てきた気がする。レベルうｐみたいな</p>
<p>お手本を参考に作業を進める。17時から始めて19時頃にはいったんうまくいった。マスタでinsertしたデータがスレーブでselectできたときにはある種のカタルシス、エクスタシーを覚えた。xtc。</p>
<p>が、ある操作をすると存在するはずのファイルがないと言われる。マスタからスレーブにデータを移すときに一部のデータを移動し損ねたかと思った私は、ふたたびマスタのファイルを圧縮しスレーブに移動させた。そしたらなんということでしょう、スレーブとマスタが協調しなくなってしまったのです。</p>
<p>後から思えば、ファイルは存在しなかったのではない。root権限になっていてmysqlユーザからは見えなかっただけなのだ。そこでchown -R mysql.mysqlできるだけの機転や経験があれば私は2時間を現状回復に費やさずにすんだのに。</p>
<p>ともあれ、終バスの15分前にはなんとか現状回復に成功。爆発1秒前に時限爆弾を止められた映画の主人公の気持ちを疑似体験できた。きょうが土曜日で終電に接続するバスの時間が13分遅くなっていたのも幸い。めずらしくうまくいった。</p>
			</div>
		</content>
		<category term="MySQL"/>
		<category term="レプリケーション"/>
		<category term="佐世保バーガー"/>
		<category term="大学祭"/>
		<category term="静岡大学"/>
		<trackback:ping>http://txqz.net/blog/2006/11/11/2202/tb</trackback:ping>
		<published>2006-11-11T22:02:00+09:00</published>
		<updated>2007-04-08T17:39:42+09:00</updated>
	</entry>
	<entry>
		<title>where 句に関数を使うとインデックスが使われない罠</title>
		<id>tag:txqz.net,2006-11-08:/blog/2006/11/08/1006</id>
		<link rel="alternate" href="http://txqz.net/blog/2006/11/08/1006"/>
		<content type="xhtml">
			<div xmlns="http://www.w3.org/1999/xhtml">
<p><a href="http://dev.mysql.com/doc/refman/4.1/ja/date-and-time-functions.html" title="MySQL AB :: MySQL 4.1 リファレンスマニュアル :: 6.3.4 日付と時刻関数">MySQL 4.1 リファレンスマニュアル の 「日付と時刻関数」</a>には、<q title="MySQL AB :: MySQL 4.1 リファレンスマニュアル :: 6.3.4 日付と時刻関数" cite="http://dev.mysql.com/doc/refman/4.1/ja/date-and-time-functions.html">このクエリでは、過去 30 日以内の日付の <code class="literal">date_col</code> 値を持つすべてのレコードが選択されます</q>として <code class="mysql">SELECT something FROM tbl_name WHERE TO_DAYS(NOW()) - TO_DAYS(date_col) &lt;= 30;</code> という SQL 文が紹介されているけれども、これって INDEX が使われないわけで。</p>
<p>30日前の日付が他で分かるなら、それを BETWEEN で指定してあげれば、type が range なインデックスが使われる。だから、たとえば <code class="mysql">SELECT something FROM tbl_name WHERE date_col BETWEEN DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -30 DAY),'%Y-%m-%d 00:00:00') AND DATE_FORMAT(NOW(),'%Y-%m-%d 23:59:59')</code> とすればインデックスを使いつつ過去30日以内レコードを選択できるかなと思った。まぁ、データの量によっては無理してインデックスを使う必要もないわけなのだけれども。</p>
			</div>
		</content>
		<category term="MySQL"/>
		<category term="インデックス"/>
		<trackback:ping>http://txqz.net/blog/2006/11/08/1006/tb</trackback:ping>
		<published>2006-11-08T10:06:00+09:00</published>
		<updated>2007-04-08T17:35:58+09:00</updated>
	</entry>
	<entry>
		<title>SennaのSnippet関数を使うためのSQL文を生成したい</title>
		<id>tag:txqz.net,2006-10-17:/blog/2006/10/17/2115</id>
		<link rel="alternate" href="http://txqz.net/blog/2006/10/17/2115"/>
		<content type="xhtml">
			<div xmlns="http://www.w3.org/1999/xhtml">
<p><a href="http://qwik.jp/senna/old_mysql_binding_docs.html#418ed1514ab16a435dfff0e09f6d0161" title="Senna 組み込み型全文検索エンジン - 旧ブラジル版MySQLバインディングのドキュメント置き場">Sennaのsnippet udf</a>を使うSQLは:</p>
<pre><code class="sql">SELECT snippet(文書, snippetの長さの最大バイト数, snippetの最大個数, 文書の文字コード, htmlエンコーディングの有無, snippetの開始タグ, snippetの終了タグ, 単語1, 単語1の前につけられるタグ, 単語1の後につけられるタグ, 単語2, 単語2の前につけられるタグ, 単語2の後につけられるタグ, ...);</code></pre>
<p>となっている。単語の数に応じて引数の数が変化するので、PreparedStatementを使えない。</p>
<p>簡単のためにフレーズ検索は考えないことにする。検索窓とかからスペース区切りの検索フレーズを受け取ったら、snippet関数の第8引数以降を何とかして生成したい。</p>
<p>against()の中はPreparedStatementを使えるので、prepareするSQL文は以下のようになるかなー (AND検索する場合):</p>
<pre><code class="sql">SELECT title, snippet(body,256,2,'utf8',1,'','',<var>あとで追加する部分</var>) AS snip FROM items WHERE MATCH(title,body) AGAINST(? in boolean mode) order by MATCH(title,body) AGAINST(? in boolean mode);</code></pre>
<p>で、空白区切りのクエリを受け取ったら:</p>
<pre><code class="php">$queries = explode(' ',$q);
$snip = "";
$against = "";
foreach($queries as $idx => $each){
    $snip .= ','.mysql_real_escape_string($each).",'&lt;span class=\"query q$idx\"&gt;','&lt;/span&gt;'";
    $against .= '+'.$each;
}
$sql = "SELECT title, snippet(body,256,2,'utf8',1,'',''$snip) AS snip FROM items WHERE MATCH(title,body) AGAINST(? in boolean mode) order by MATCH(title,body) AGAINST(? in boolean mode)";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ss", $against, $against);</code></pre>
<p>って感じ? 定石はどんななんだろ。</p>
			</div>
		</content>
		<category term="MySQL"/>
		<category term="PHP"/>
		<category term="Senna"/>
		<category term="Snippet"/>
		<category term="検索エンジン"/>
		<trackback:ping>http://txqz.net/blog/2006/10/17/2115/tb</trackback:ping>
		<published>2007-07-28T18:53:56+09:00</published>
		<updated>2007-07-28T18:53:56+09:00</updated>
	</entry>
	<entry>
		<title>CentOSにMeCab+Senna+MySQLを入れる</title>
		<id>tag:txqz.net,2006-08-27:/blog/2006/08/27/0253</id>
		<link rel="alternate" href="http://txqz.net/blog/2006/08/27/0253"/>
		<content type="xhtml">
			<div xmlns="http://www.w3.org/1999/xhtml">
<p>日本語で全文検索をするために MySQL に Senna を組み込むメモ。少し前は個人ニュースサイトめぐり→日記書きに数時間を要していた私が次に目をつけたのがRSSめぐり→はてブ登録なのだがそれでも依然として数時間かかる。そんなあなた (私) に朗報です。私の代わりにブックマークしてくれるエージェントを作ればいいのです。そしてそのエージェントが今日よく使われていた単語やタグとか出してくれて、1次ソースAについてBさんとCさんとDさんがそれぞれの立場でこんなことを書いているとかいうのが俯瞰できて、さらにあとから検索とかができればいいのです。いまホッテントリを見なくてもいいのです。あとで検索できればいいのです。Googleが拾ってくる量は莫大過ぎるし個人で把握できる範囲にも限度がある。ユーザの皆さんが勝手に重み付けしてくれるならそれを使ったらいい。先週の週刊東洋経済を読んで悟った。いやなんか変だ。むにむに</p>
<p>まずは MeCab のインストール。<a href="http://mecab.sourceforge.jp/" title="MeCab: Yet Another Part-of-Speech and Morphological Analyzer">配布元</a>に詳しいドキュメントあり。<a href="http://qwik.jp/senna/install.html" title="Senna 組み込み型全文検索エンジン - インストール方法">Senna のインストール方法</a>には、mecab を configure するときに --prefix=/usr をつけていないぽかったのでつけずにやったら起動できなかった。悔い改めてやり直した。</p>
<pre>$ cd mecab-0.93
$ ./configure --enable-utf8-only --enable-mutex --prefix=/usr
$ make
$ make check
$ sudo make install</pre>
<p>続けて辞書をインストールして実行してみる。</p>
<pre>$ cd mecab-ipadic-2.7.0-20060707
$ ./configure --with-charset=utf8 --prefix=/usr
$ make
$ sudo make install
$ mecab</pre>
<p>テストしてみましょう。</p>
<pre>諏訪を新宿並みに有名にしたかった
諏訪    名詞,固有名詞,人名,姓,*,*,諏訪,スワ,スワ
を      助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
新宿    名詞,固有名詞,地域,一般,*,*,新宿,シンジュク,シンジュク
並み    名詞,接尾,一般,*,*,*,並み,ナミ,ナミ
に      助詞,格助詞,一般,*,*,*,に,ニ,ニ
有名    名詞,形容動詞語幹,*,*,*,*,有名,ユウメイ,ユーメイ
に      助詞,格助詞,一般,*,*,*,に,ニ,ニ
し      動詞,自立,*,*,サ変・スル,連用形,する,シ,シ
たかっ  助動詞,*,*,*,特殊・タイ,連用タ接続,たい,タカッ,タカッ
た      助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
EOS</pre>
<p>成功。続けて Senna を入れる。参考書は前出の<a href="http://qwik.jp/senna/install.html" title="Senna 組み込み型全文検索エンジン - インストール方法">Senna のインストール方法</a>。</p>
<pre>$ cd senna
$ ./configure --prefix=/usr
$ make
$ sudo make install
$ sudo mkdir /var/senna
$ sudo emacs /var/senna/senna.conf</pre>
<p>で、senna.conf に、</p>
<pre>DEFAULT_ENCODING utf8</pre>
<p>続けて MySQL 。<a href="http://qwik.jp/senna/mysql_configure.html" title="Senna 組み込み型全文検索エンジン - MySQLのconfigureオプション">MySQLのconfigureオプション</a>や、<a href="http://blog.nomadscafe.jp/archives/000407.html" title="Sennaのインストール : blog.nomadscafe.jp">Sennaのインストール</a>などを参考にした。</p>
<pre>$ cd mysql-5,0,24
$ patch -p1 &lt; ../senna/bindings/mysql/mysql-5.0.24.senna.diff
$ patch -p1 &lt; ../senna/bindings/mysql/mysql-5.0.24.senna.2ind.diff
$ libtoolize -c -f
$ aclocal-1.9
$ autoheader
$ automake-1.9 -c -a -i
$ autoconf
$ touch sql/sql_yacc.yy
$ CFLAGS="-O3 -mtune=pentium4" \
&gt; CXX=gcc \
&gt; CXXFLAGS="-O3 -mtune=pentium4 -felide-constructors -fno-exceptions -fno-rtti" \
&gt; ./configure --prefix=/usr \
&gt; --with-charset=utf8 \
&gt; --with-extra-charsets=complex \
&gt; --with-senna \
&gt; --with-big-tables \
&gt; --with-readline \
&gt; --enable-thread-safe-client \
&gt; --enable-local-infile \
&gt; --enable-assembler \
&gt; --with-client-idflags=-all-static \
&gt; --with-mysql-idflags=-all-static \
&gt; --with-mysql-user=mysql \
&gt; --localstatedir=/var/lib/mysql \
&gt; --with-unix-socket-path=/var/lib/mysql/mysql.sock
$ make
$ sudo make install
$ sudo ./scripts/mysql_install_db</pre>
<p>エラーが出たので</p>
<pre>$ su -
# ./scripts/mysql_install_db
# chown -R mysql.mysql /var/lib/mysql
# chmod 755 /var/lib/mysql
# cp support-files/my-medium.cnf /etc/my.cnf
# cp /usr/local/src/mysql-5.0.24/support-files/mysql.server /etc/rc.d/init.d/mysqld
# chmod 755 /etc/rc.d/init.d/mysqld
# chkconfig --add mysqld
# chkconfig --level 345 mysqld on
# service mysqld start</pre>
<p>そして</p>
<pre>$ mysqladmin -u root password oniichandoite,soitsukorosenai
$ mysql -uroot -p
mysql&gt; create database sennatest;
mysql&gt; grant all on sennatest.* to gonbei@localhost identified by '[tamurayukari]ttedarenanoyo,sakurananoyo,a.ta.shi!';
$ quit;
$ mysql -ugonbei -p
mysql&gt; use sennatest;
mysql&gt; create table articles(
    -&gt; id int unsigned not null auto_increment primary key,
    -&gt; body text,
    -&gt; fulltext(body)
    -&gt; );
Query OK, 0 rows affected (0.06 sec)

mysql&gt; insert into articles values
    -&gt; (null,"郵貯民営化は重要な問題だと思う "),
    -&gt; (null,"スローライフを志向するiPodの強み - CNET Japan"),
    -&gt; (null,"HTML, CSS, Photoshopを同時に学べるサンプル付きデザイン記事：Goodpic"),
    -&gt; (null,"jazzanovaの日記 - 現在顧問弁護士に相談中であり、対応についても検討中"),
    -&gt; (null,"mixi非公式ニュースサイト - mixiの問題人物Kusakabe氏、強制退会に？"),
    -&gt; (null,"むだづかいにっき♂：ネット上で議論を仕掛ける事について"),
    -&gt; (null,"はてな perl ハッカーの方々にお聞きします。近頃ますます良い感じなperlですが、どのような開発環境で開発していますでしょうか。"),
    -&gt; (null,"シナトラ千代子 - 投げ銭が飛び交うなかでダイアリーに立てこもる、という意味。"),
    -&gt; (null,"Going My Way: Skypeの会話をPodcast用に録音する場合の設定方法"),
    -&gt; (null,"Kusakabeさんがmixiの一部？を賑わしている。彼にmixi強制退会が言い渡されたのだ。"),
    -&gt; (null,"Ringo's Weblog: googleと競合しない方法2 "),
    -&gt; (null,"Moleskin Diary - 投げ銭よりたれ銭"),
    -&gt; (null,"第38回　海外メディアが伝えた小泉・郵政解散劇の評判 - nikkeibp.jp - 立花隆の「メディア ソシオ-ポリティクス」"),
    -&gt; (null,"ほその日記 - フォームが変更された事を知る"),
    -&gt; (null,"総選挙はてなと公職選挙法:北海道に住む国家公務員日記 "),
    -&gt; (null,"はてな、政党を株式に見立てて総選挙結果を予測 - CNET Japan");
Query OK, 16 rows affected (1.48 sec)
Records: 16 Duplicates: 0 Warnings: 0

mysql&gt; select * from articles where match (body) against ('はてな');
+----+----------------------------------------------------------------------------------------------------------------------------+
| id | body                                                                                                                       |
+----+----------------------------------------------------------------------------------------------------------------------------+
|  7 | はてな perl ハッカーの方々にお聞きします。近頃ますます良い感じなperlですが、どのような開発環境で開発していますでしょうか。 |
| 15 | 総選挙はてなと公職選挙法:北海道に住む国家公務員日記                                                                        |
| 16 | はてな、政党を株式に見立てて総選挙結果を予測 - CNET Japan                                                                  |
+----+----------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)</pre>
<p>すばらしい。じゃあ次は PHP からアクセスしてみよう。PHP は apt-getで入れた。</p>
<pre><samp>Warning: mysql_connect(): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (13) in /var/www/html/test.php on line 4</samp></pre>
<p>うまくいかない。ちゃんと/var/lib/mysql には mysql.sock があるんだけどなー。</p>
<p><code class="php">mysql_connect("localhost",$user,$pass);</code> を <code class="php">mysql_connect("<em>localhost.localdomain</em>",$user,$pass);</code> に変えたらうまくいった。しかし <code class="mysql">where match() against()</code> を含んでいる SQL 文を投げると mysql_fetch_object()が false を返す。普通に <code class="mysql">select * from articles;</code> とすると日本語の部分が全部 "?" になる。MySQL も Apache も UTF-8 を使う設定になっているはずなのになぜだー。</p>
<p>ぐぐったら<a href="http://q.hatena.ne.jp/1146357712" title="人力検索はてな - mysql-5.0.20を使いたいのですが、文字化けで困っております。日本語の文字が?になってしまいます。 WIN2000+AN HTTP+mysql+perl5.8で使いたいと思っております。 文字化け..">はてなQに似たような悩みの人がいた</a>。これを参考に、my.cnf の client セクションに default-character-set=utf8 を、 mysqld セクションに init-connect=SET NAMES utf8 を追加。mysqld を restart。直った。素敵。今回は以上。</p>
<ins class="ps" datetime="2007-06-21T20:44:20+09:00" id="PS20070621204420">
<p>この記事のメモは<a href="http://qwik.jp/tritonn/" title="Tritonnプロジェクト ～ MySQL+Sennaによる全文検索 ～ - Tritonnプロジェクト">Tritonn</a>が始まる前に書かれた古いものです。Tritonnを使った場合のインストール方法については<a href="/blog/2007/06/07/2026" title="Sennaとかのアレの続き Tritonn編">Sennaとかのアレの続き Tritonn編</a>をどうぞ。</p>
</ins>
			</div>
		</content>
		<category term="CentOS"/>
		<category term="Linux"/>
		<category term="MeCab"/>
		<category term="MySQL"/>
		<category term="PHP"/>
		<category term="Senna"/>
		<category term="インストール"/>
		<category term="メモ"/>
		<category term="文字コード"/>
		<trackback:ping>http://txqz.net/blog/2006/08/27/0253/tb</trackback:ping>
		<trackback:about>http://d.hatena.ne.jp/tetsuyasato/20060827/1156648145</trackback:about>
		<published>2006-08-27T02:53:00+09:00</published>
		<updated>2007-07-28T18:58:11+09:00</updated>
	</entry>
</feed>