読者です 読者をやめる 読者になる 読者になる

Portal:siro

ダイレクトマーケティングブログ

同村チェッカーについて技術的な話4

さて技術的な話その4です。
バックナンバーはこちら。
同村チェッカーについて技術的な話 - Portal:siro
同村チェッカーについて技術的な話2 - Portal:siro
同村チェッカーについて技術的な話3 - Portal:siro

今回からは自動挿入の話に入ります。
今までは、エピローグURLの解析に重点置いてましたが、
そもそもエピローグURLの入力はどうしてるの?というところから。

最初の実装ではエピローグのURLを僕が手動で入力していたんですが、
そんな面倒くさいこと逐一やってられないので自動化しました。

何をやっているのか、先に概要だけ説明しますと、

  1. 現在どの村まで登録済かDBにアクセスして確認
  2. 終了済みの村一覧(例:http://crazy-crazy.sakura.ne.jp/crazy/sow.cgi?cmd=oldlog )から未登録の村番号を抽出
  3. 抽出した村のエピローグURLを特定・解析・登録

やってることはこれだけです。というわけでまずは抽出までの部分をご説明します。

<?php
// それぞれ最新村のIDを抽出
// 但し、IDにgapが生じている場合は、gap以降の村を取り直す。
$db_conn = db_conn();
if($db_conn !== false){
	$first_vil;	
	$result = mysql_query("SELECT dv1.vil_server,MIN(dv1.vil_no+1) as vil_no FROM douson_vil as dv1
	 WHERE (dv1.vil_no+1) NOT IN (SELECT dv2.vil_no FROM douson_vil as dv2 WHERE dv1.vil_server = dv2.vil_server)
	 GROUP BY dv1.vil_server");
	while($row = mysql_fetch_assoc($result)){
		$first_vil[$row['vil_server']] = $row['vil_no'];
	}
	// 村一覧のURL等の配列を生成(現行稼動州のみ)
	$array_giji;
	// 標準
	// 2011/12/03 標準鯖長期停止のためコメントアウト
	/*
	$array_giji[] = array(
		'url' => "http://utage.sytes.net/wolf/sow.cgi?cmd=oldlog",
		'server' => "Wolf",
		'first_vil' => $first_vil['標準']);
	*/
	// 陰謀
	$array_giji[] = array(
		'url' => "http://cabala.halfmoon.jp/cafe/sow.cgi?cmd=oldlog",
		'server' => "Cafe",
		'first_vil' => $first_vil['陰謀(陰謀の苑・Cabala Cafe)']);
	// 大乱闘AS
	// 2012/01/21 大乱闘AS鯖長期停止のためコメントアウト
	/*
	$array_giji[] = array(
		'url' => "http://jinro.jksy.org/~nanakorobi/sow.cgi?cmd=oldlog",
		'server' => "AS",
		'first_vil' => $first_vil['大乱闘AS']);
	*/
	// RPBPr
	$array_giji[] = array(
		'url' => "http://perjury.rulez.jp/sow.cgi?cmd=oldlog",
		'server' => "RPBPr",
		'first_vil' => $first_vil['RPBPr']);
	// RPBx
	$array_giji[] = array(
		'url' => "http://xebec.x0.to/xebec/sow.cgi?cmd=oldlog",
		'server' => "RPBx",
		'first_vil' => $first_vil['RPBx']);
	// RPBc
	$array_giji[] = array(
		'url' => "http://crazy-crazy.sakura.ne.jp/crazy/sow.cgi?cmd=oldlog",
		'server' => "RPBc",
		'first_vil' => $first_vil['RPBc']);
	// 似顔絵人狼
	// 2011/12/03 似顔絵鯖長期停止のためコメントアウト
	/*
	$array_giji[] = array(
		'url' => "http://utage.sytes.net/pan/sow.cgi?cmd=oldlog",
		'server' => "Pan",
		'first_vil' => $first_vil['似顔絵人狼']);
	*/

	foreach($array_giji as $giji){
		insert_vil_all($giji['url'],$giji['server'],$giji['first_vil'],999);
	}
	echo "auto insert done.";

}
?>

まずこちらが、1日に1回くらいの頻度で手動実行しているphpです。
え?そこ手動なの?って感じではありますが、echoとかガンガン吐いてるのを見てわかるように、実行結果は一応目で見て確認しています。
もちろんcron書けば1日に1回くらいの自動実行など容易にできるのですが、エラーが見てわかるようにとか、色々な理由で現在も手動実行を続けています。


番号抽出の部分はここ。見づらいのでSQL部分だけ抽出します。

SELECT dv1.vil_server,MIN(dv1.vil_no+1) as vil_no 
FROM douson_vil as dv1 
WHERE (dv1.vil_no+1) NOT IN 
	(SELECT dv2.vil_no FROM douson_vil as dv2 WHERE dv1.vil_server = dv2.vil_server)
GROUP BY dv1.vil_server

未登録の村なら、単純にvil_no(村番号)のmaxを取ってくればいいというわけでもなく、
建った順と村が終了する順が一致しなかったりして、gapが生じたりすることが稀によくあります。
なのでここでは「追加済みのもっとも大きな村番号+1の値を州単位で取得、ただしgapが生じている場合はgapが生じている村番号」を取得しています。
と言っても解りづらいのでもう少し説明します。


FROMとGROUP BYについては割愛。ここでエイリアスdv1を付け、検索結果をサーバー単位で纏めているだけです。
次にWHERE句。副問い合わせごとまいります。

WHERE (dv1.vil_no+1) NOT IN 
	(SELECT dv2.vil_no FROM douson_vil as dv2 WHERE dv1.vil_server = dv2.vil_server)

例を見ながら解説していきます。
こんな感じで、4と6の間にgapが生じた状態で村が登録されているとします。

10
9
8
7
6
4	// 5がない
3
2
1

村番号を1つ抽出してきます。無論、別サーバの村番号と比較しても意味がないので、ちゃんと副問い合わせ内でvil_serverを条件に含めておきます。
つまり書き下すと、

WHERE (dv1.vil_no+1) NOT IN 
	(1,2,3,4,6,7,8,9,10)

となります。

dv1.vil_noも当然(1,2,3,4,6,7,8,9,10)なわけです。
これらをNOT INで比較していきます*1
dv1.vil_no=1の時、dv1.vil_no+1=2→NOT INの中に2は「ある」ので抽出結果はfalse。
dv1.vil_no=2の時、dv1.vil_no+1=3→NOT INの中に3は「ある」ので抽出結果はfalse。
dv1.vil_no=3の時、dv1.vil_no+1=4→NOT INの中に4は「ある」ので抽出結果はfalse。
dv1.vil_no=4の時、dv1.vil_no+1=5→NOT INの中に5は「ない」ので抽出結果はtrue。
……ちょっと省略します。
dv1.vil_no=9の時、dv1.vil_no+1=10→NOT INの中に10は「ある」ので抽出結果はfalse。
dv1.vil_no=10の時、dv1.vil_no+1=11→NOT INの中に11は「ない」ので抽出結果はtrue。

つまり、わかりやすく書き下すとこうなります(GROUP BY省略)

SELECT dv1.vil_server,MIN(dv1.vil_no+1) as vil_no 
FROM douson_vil as dv1 
WHERE dv1.vil_no+1 = 5 OR dv1.vil_no+1 = 11

これの「MIN」(今まで最大最大言ってましたが必要なのはMINなのです!実際ヤヤコシイ!)を取得するとつまりこの例の場合は「5」が抽出されることになります。
5番の村はありません、ここから登録すればオッケー☆となります。

ちなみにgapが無い場合は、上の例で言うと11のみが抽出されることになりますので、11村から登録すればいいというわけです。オッケー☆

複数gapが発生してる場合はどーすんのってのがありますが、その時も最小から取ります。
先述したとおり、既に登録されている村の場合はskipする機能がありますので、問題なく。
それに基本的に1日1回動かしているので、複数gapが発生することもそもそも稀です。

あとは、array_gijiに各サーバ毎の連想配列を作り、
その配列を実際に解析する関数へと投げています。
実際難しくない。


解析の関数はまた後日解説します。bye☆

*1:よく考えたらNOT EXISTSでもよいのですが今回はまあいいです