FIND_IN_SETでカンマ区切りの文字列から簡単検索
以下のような testtable テーブルについて、
カンマ区切りになっている文字列 strings を検索したい、と思うケースがあります。
あくまでカンマで区切ったひとつひとつをデータとして扱い、例えば「a」ではヒットさせたくないけど「abc」でヒットさせたい、という感じですね。
id | strings |
1 | 1,2,abc,def |
2 | 1,3,abc,ghi, |
3 | ,,, |
検索が発生する時点で既にこの設計は破綻しているのですが、
現実問題として「じゃあテーブル分けましょう!」と簡単にいかないことも多いので
そういうときは、MySQLであれば以下の関数を用いて検索できます。
FIND_IN_SET(検索する値, 検索対象の文字列・カラム);
主な挙動はこんな感じ。
-- SELECT * FROM `testtable` 以下 WHERE FIND_IN_SET('1' , `strings`); -- id:1, id:2 がヒット WHERE FIND_IN_SET(1 , `strings`); -- id:1, id:2 がヒット(数字は数値でもOK!) WHERE FIND_IN_SET(2 , `strings`); -- id:1 のみヒット WHERE FIND_IN_SET('abc', `strings`); -- id:1, id:2 がヒット WHERE FIND_IN_SET('a' , `strings`); -- ヒットしない WHERE FIND_IN_SET('def', `strings`); -- id:1のみヒット WHERE FIND_IN_SET('' , `strings`); -- id:2, id:3 がヒット WHERE FIND_IN_SET(',' , `strings`); -- ヒットしない
うわー便利。
AND検索・OR検索はいつも通り。
WHERE FIND_IN_SET('abc', `strings`) AND FIND_IN_SET('def', `strings`); -- id:1 のみヒット WHERE FIND_IN_SET('def', `strings`) OR FIND_IN_SET('ghi', `strings`); -- id:1, id:2 がヒット
MySQL独自関数はちょっと…という場合は前後にカンマを付けてLIKE検索。
と言いつつこの書き方もMySQL独自なので、文字列連結方法は各環境に応じてもにょもにょしましょう。
アプリ側でINSERT時に前後にカンマ付けるようにしておいても良いですけどね。
-- 「CONCAT(',', `strings`, ',')」でstringsカラムの前後にカンマを付けた文字列になる -- 「LIKE '%,abc,%'」で「,abc,」の部分一致検索 SELECT * FROM `testtable` WHERE CONCAT(',', `strings`, ',') LIKE '%,abc,%';
雑だけど挙動はだいたい同じです。
-- SELECT * FROM `testtable` 以下 WHERE CONCAT(',', `strings`, ',') LIKE '%,1,%' ; -- id:1, id:2 がヒット WHERE CONCAT(',', `strings`, ',') LIKE '%,2,%' ; -- id:1 のみヒット WHERE CONCAT(',', `strings`, ',') LIKE '%,abc,%'; -- id:1, id:2 がヒット WHERE CONCAT(',', `strings`, ',') LIKE '%,a,%' ; -- ヒットしない WHERE CONCAT(',', `strings`, ',') LIKE '%,def,%'; -- id:1のみヒット WHERE CONCAT(',', `strings`, ',') LIKE '%,,%' ; -- id:2, id:3 がヒット WHERE CONCAT(',', `strings`, ',') LIKE '%,,,%' ; -- id:3のみヒット(ここが違う)
ただしいずれにしろインデックスは効きません。効くわけない。
レコード数や実行頻度と相談して、ご利用は計画的に。
テーブルを分けることができないならフリーワード検索を応用した方がまだ良いかと思います。
(こちらはFULLTEXTインデックスを設定してればちゃんと効く)
連想配列で検索したい!!!
(2018-09-13:書いてるとおりには動いてたけど、実用を考えると破綻してたので、まるっと書き直しました)
array_columnを使えないバージョンを使わざるを得ない時用。
使えるならこちらをご参照ください。
今回は以下のような連想配列から検索することを考えます。
<?php $data_list = array( 1 => array('hoge' => "a" , 'fuga' => "A" ), 2 => array('hoge' => "b" , 'fuga' => "B" ), 3 => array('hoge' => "a" , 'fuga' => "C" ), // hogeが1と重複していることに注意 4 => array('hoge' => null , 'fuga' => "D" ), 5 => array('hoge' => false, 'fuga' => "E" ), 6 => array('hoge' => "f" , 'fuga' => null ), 7 => array('hoge' => "g" , 'fuga' => false), );
検索パターンは次の3通り。
これを全部まとめて関数化したい!と思うのですが、一つ問題が。
「検索結果が存在しないこと」と「検索結果がfalseであること」の戻り値を両立するの難しくない?
上記の配列で、「キーが7のfugaの値が欲しい!」となったとき、欲しい結果はもちろんfalseなんですが、
じゃあ「キーが8のfugaの値が欲しい!」と言われたら? 8は存在しないんだから、関数としてはfalseを返すべきじゃないの?
まあ、戻り値をこんな感じにすることもできますが
array( 'not_found_flag' => 1, // 見つからなかったよフラグ 'result' => null, // 検索結果 )
お手軽に検索したいから関数化するというのに、戻り値がいちいち配列なのはちょっとつらい。あとなんかかっこわるい。
連想配列の中身を検索し、キーを返す関数
ということで、そもそもひとつにまとめないパターンを考えてみました。
キーはfalseにならないのでこれなら関数としてもすっきり。
ただし、ここでは「hoge」やら「fuga」やらが存在してないケースまでは考慮してないので
必要に応じて更にバリデートを入れることになります。
あと、比較は厳密に行っています。場合によってはゆるい比較にしてもOK。
<?php function getKey($list, $val, $key) { foreach ($list as $k => $v) { if ($v[$key] === $val) { return $k; } } return false; }
さっきの配列で検索してみるとこんな感じ。
<?php var_dump( getKey($data_list, "a" , "hoge") ); // 1 : 重複してると最初のキーを返す var_dump( getKey($data_list, null , "hoge") ); // 4 : 値がnullの場合もOK var_dump( getKey($data_list, false, "hoge") ); // 5 : 値がfalseの場合もOK var_dump( getKey($data_list, "h" , "hoge") ); // false : 存在しない var_dump( getKey($data_list, "A" , "fuga") ); // 1 : もちろんfugaからも検索可
あとは適宜必要に応じて戻り値がfalseかどうかをチェックすればOK。
検索結果が存在しないということはあり得ない、という場合は見なくていいし。
ということで実際に検索してみましょう。
さっきの検索したいパターンに当てはめると、
1. キーからhogeの値を求める
これはキーが分かっている状態なのでそもそもさっきの関数すら使わない。
<?php // キー「1」の「hoge」を求める:$resは「a」 $res = $data_list[1]['hoge'];
レコードが存在しない可能性があるならこう。
<?php if (isset($data_list[1]['hoge'])) { // 検索結果は $data_list[1]['hoge'] } else { // 検索結果なし }
2. hogeからキーの値を求める
これはつまりさっきの関数の戻り値。
<?php // hogeが「a」のキーを求める:$resは「1」 $res = getKey($data_list, "a" , "hoge");
レコードが存在しない可能性があるならこう。
<?php $res = getKey($data_list, "a" , "hoge"); if ($res !== false) { // 検索結果は $res } else { // 検索結果なし }
3. hogeからfugaの値を求める
1と2の合わせ技。
<?php // hogeが「a」の「fuga」を求める:$resは「A」 $res = $data_list[getKey($data_list, "a" , "hoge")]['fuga'];
レコードが存在しない可能性があるならこう。
<?php $key = getKey($data_list, "a" , "hoge"); if ($key !== false) { // 検索結果は $data_list[$key]['fuga']; } else { // 検索結果なし }
falseのデータとかそもそもレコードが存在しないとかありえないから一発で済ませたい!!!
という場合はこちら。
hogeの値「a」を元に、キーの値「1」を求める
ほい。
<?php // $resは「1」 $res = key(array_filter(array_map(function($l){return $l['hoge'] === "a";}, $data_list)));
いきなりややこしくなりました。
ちょっと分解しましょう。
<?php // 配列[hoge]が「a」ならtrueを返す無名関数 function($l){return $l['hoge'] === "a";} // …をarray_mapにぶちこむ array_map(function($l){return $l['hoge'] === "a";}, $data_list); // その結果はtrueかfalseでできた一次元配列 // array(1 => true, 2=> false, 3=> true) // になるので、それをtrueでフィルタリングする array_filter(array_map(function($l){return $l['hoge'] === "a";}, $data_list)); // trueだけになった配列 // array(1 => true, 3=> true) // の、現在の…っつーか何もしてないのでつまりは先頭のキーを取得 key(array_filter(array_map(function($l){return $l['hoge'] === "a";}, $data_list))); // 完成 $res = key(array_filter(array_map(function($l){return $l['hoge'] === "a";}, $data_list)));
ちなみに存在しない値で検索しようとした場合はNULLになります。
ただ、キーがNULLになることはあり得ないので(NULLに設定しようとすると勝手に空文字になります)
NULLなら検索結果なしと判断してOK。
hogeの値「a」を元に、fugaの値「A」を求める
上記2つの組み合わせ。
<?php // $resは「A」 $res = $data_list[key(array_filter(array_map(function($l){return $l['hoge'] === "a";}, $data_list)))]['fuga'];
これも存在しない場合はNoticeが出ます。
WinSCPからアクティブモード接続
接続設定のところから 設定>設定
左メニュー「接続」を選び、「パッシブモード」のチェックを外す
以上。
背景色に応じた文字色に変える / ランダム色を生成する
まずはサンプルから。
押すたびに背景色がランダムで変わり、文字色が白か黒の適した色に変わります。
ここをクリック
コードはこんな感じ。
<div id="colordiv" onclick="changeColor();"> ここをクリック </div> <script> /* 与えられた背景色に適した文字色を返す */ function getStrColor (bg){ // 色をRGBに分割 var r = parseInt(bg.substr(1, 2), 16); var g = parseInt(bg.substr(3, 2), 16); var b = parseInt(bg.substr(5, 2), 16); // 適した色を返す return ((((r * 299) + (g * 587) + (b * 114)) / 1000 ) < 128) ? "#FFFFFF" : "#000000" ; } /* ランダムな背景色とそれに適した文字色を求め、それを設定する */ function changeColor(){ // ランダムな色を生成(背景色) var bgcolor = "#" + Math.round(Math.random() * 255).toString(16) + Math.round(Math.random() * 255).toString(16) + Math.round(Math.random() * 255).toString(16); // 生成した背景色に適した文字色を求める var strcolor = getStrColor(bgcolor); // 設定する $("#colordiv").css("background", bgcolor); $("#colordiv").css("color", strcolor); } </script>
ほんとはCSSだけで文字色求める方法を探してたのですが無理そうな気がする。
複数の文字列データをカンマ区切りにするけど5データごとに改行する
関数化した。
<?php function str_chunk($list, $size) { return implode("\n", array_map(function($l){return implode(",", $l);}, array_chunk($list, $size))); }
…。
読みにくいのは置いといて使い方と結果はこんな感じ。
<?php $data_list = array("a","b","c","d","e","f","g","h","j","i","k","l","m",); echo str_chunk($data_list, 5);
a,b,c,d,e f,g,h,j,i k,l,m
やったね。めでたしめでたし。
念のため、カンマと改行文字は引数に渡せるようにしてみる
<? function str_chunk($list, $size, $del1 = ",", $del2 = "\n") { return implode($del2, array_map(function($l, $d){return implode($d, $l);}, array_chunk($list, $size), array_fill(0, count(array_chunk($list, $size)), $del1))); }
さすがにアホではないだろうか?
動いたけど。
array_mapは便利だけど、array(1,2,3)のそれぞれに2を掛けたい、というときに
array(1,2,3)とarray(2,2,2)とかいう無駄引数を渡さなきゃいけない。
(そのための array_fill(0, count(array_chunk($list, $size)), $del1) というクソ表記)
じゃあもうarray_mapとか投げ捨ててforeachでよくない?
<?php function str_chunk($list, $size, $del1 = ",", $del2 = "\n") { $chunk_list = array_chunk($list, $size); $str_list = array(); foreach ($chunk_list as $k => $v) { $str_list[] = implode($del1, $v); } return implode($del2, $str_list); }
読みやすくなった。
ついでに速度検証
array_map版(区切り文字指定できる方)とforeach版の速度比較。
データはサンプルコードにあるa~mの13文字、
10万回実行を5セットした平均値。
array_map版:296.5834
foreach版:269.9892
単位はミリ秒。
まあ誤差レベルだけどarray_mapの方が遅い。
div要素をきれいに並べるのは難しい(floatを使って配置)
これのfloat版。
今回も以下の3つを並べてみます。
<div class="div1">div要素1</div> <div class="div2">div要素2</div> <div class="div3">div要素3</div>
縦一列に並べる(何もしない)
前回と同じなので省略。そのままでいい。
横一列に並べる
全部に「float: left;」を設定。
<style> .div1{ float: left; } .div2{ float: left; } .div3{ float: left; } </style>
これの問題点は、そのままにしておくとその下の要素にも影響すること。
(重なって表示されるとか、その次の要素も回り込んで表示されるとか)
横並びにした次の要素に「clear: left;」を設定すると回避できます。
↑のサンプルの直後には
<div style="clear: left;"></div>
と空divを入れています。
セル結合したテーブルっぽい感じのやつ(その1)
ということで、今回はdiv2とdiv3に「float: left;」を設定。
displayでなんとかする方式と違って、widthをしっかり入れてやらないと崩れます。
<style> .div1{ width: 10em; } .div2{ width: 5em; float: left; } .div3{ width: 5em; float: left; } </style>
セル結合したテーブルっぽい感じのやつ(その2)
こちらはdiv2とdiv3を親divでくくり、div1と親divを横並びにします。
<div class="div1">div要素1</div> <div class="div_parent"> <div class="div2">div要素2</div> <div class="div3">div要素3</div> </div>
<style> .div_parent{ float: left; } .div1{ height: 10em; float: left; } .div2{ height: 5em; } .div3{ height: 5em; } </style>
div要素をきれいに並べるのは難しい(displayを使って配置)
いろんな方法はあるっぽいけど、とりあえず調べて動かしてみたやつを残しておく。
ブラウザによっては動かないとかあるかも。
displayでなくfloatでやるときはこっち。
以下の3つのdivをいろんな並べ方してみます。
(3つと言いつつ、パターンによって親divが発生したりします)
<div class="div1">div要素1</div> <div class="div2">div要素2</div> <div class="div3">div要素3</div>
長くなるので、colorやらbackgroundやらその他諸々は省略しています。
縦一列に並べる(何もしない)
<style> .div1{ width: 5em; } .div2{ width: 5em; } .div3{ width: 5em; } </style>
とりあえずwidthが揃っていればきれいなんじゃないですかね(なげやり)
横一列に並べる
まず親divを作ります。
<div class="div_parent"> <div class="div1">div要素1</div> <div class="div2">div要素2</div> <div class="div3">div要素3</div> </div>
そして親divに「display: table;」、
横一列したい子divに「display: table-cell;」を追加。
heightは長い要素に合わせて勝手に調節してくれてるっぽい。
<style> .div_parent{ display: table; } .div1{ display: table-cell; } .div2{ display: table-cell; } .div3{ display: table-cell; } </style>
セル結合したテーブルっぽい感じのやつ(その1)
(語彙力がない)
さっきのを応用して…
<div class="div1">div要素1</div> <div class="div_parent"> <div class="div2">div要素2</div> <div class="div3">div要素3</div> </div>
<style> .div_parent{ display: table; } .div1{ width: 10em; } .div2{ width: 5em; display: table-cell; } .div3{ width: 5em; display: table-cell; } </style>
セル結合したテーブルっぽい感じのやつ(その2)
さっきのを反時計回りに90度傾けた感じのやつ。
<div class="div_parent"> <div class="div1">div要素1</div> <div class="div_child"> <div class="div2">div要素2</div> <div class="div3">div要素3</div> </div> </div>
<style> .div_parent{ display: table; } .div_div_child{ display: table-cell; } .div1{ display: table-cell; } .div2{ } .div3{ } </style>
↓なんかめっちゃズレてるな…ローカルだときれいに並んでるんだけども…。(このブログ自体のCSSが影響してるっぽい)
jQueryで属性とか値とかを書き換える&取得するいろいろ
value値
<input id="sample1" type="hidden" value=""> <script> // value値を「test」に書き換える $("#sample1").val("test"); // value値を取得する(↑で書き換えているので「test」となる) console.log( $("#sample1").val() ); <script>
テキスト
<span id="sample2">sample2</span> <script> // テキストを「sample2_update」に書き換える $("#sample2").text("sample2_update"); // テキストを取得する(↑で書き換えているので「sample2_update」となる) console.log( $("#sample2").text() ); <script>
スタイル
(クラスの追加削除で対応した方がよいと思いますが…)
<span id="sample3">sample3</span> <script> // CSSに「color: #F00;」を追加する $("#sample3").css("color", "#F00"); //$("#sample3").attr("style", "color: #F00;"); // こっちの方法でもOK // style属性を取得する console.log( $("#sample3").attr("style") ); <script>
チェックボックス
<input id="sample4" type="checkbox"> <script> // チェックをONにする(外したい場合はfalseにする) $("#sample4").prop("checked", true); // チェック有無を取得する(true/false) console.log( $("#sample4").prop("checked") ); <script>
ラジオボタン
<input name="sample5" type="radio" value="1"> <input name="sample5" type="radio" value="2"> <script> // valueが1であるものを選択状態にする $("[name=sample5]").val(["1"]); // 選択されているもののvalue値を取得する console.log( $("[name=sample5]").val() ); <script>
セレクトボックス
<select id="sample6"> <option value="0">---</option> <option value="1">aaa</option> </select> <script> // valueが1であるものを選択状態にする $("#sample6").val("1"); // 選択されているもののvalue値を取得する console.log( $("#sample6").val() ); <script>
jQueryでclassをもちゃもちゃする
基本編(add, remove, toggle, has)
追加
$(要素).addClass(class名);
削除
$(要素).removeClass(class名);
あれば削除、なければ追加
$(要素).toggleClass(class名);
あるかどうかを確認
// あればtrue、なければfalse
$(要素).hasClass(class名);
実装サンプル
<style> #testdiv { padding: 5px; } .back-red{ background: #F00; } </style> <div id="testdiv" class=""> <input type="button" onclick="addBackRed();" value="背景を赤くする(add)"> <input type="button" onclick="removeBackRed();" value="背景を赤くするのをやめる(remove)"> <input type="button" onclick="toggleBackRed();" value="背景の色を切り替える(toggle)"> <input type="button" onclick="hasBackRed();" value="背景の色が赤いかどうか確認する(has)"> </div> <script> function addBackRed(){ $("#testdiv").addClass("back-red"); } function removeBackRed(){ $("#testdiv").removeClass("back-red"); } function toggleBackRed(){ $("#testdiv").toggleClass("back-red"); } function hasBackRed(){ if ($("#testdiv").hasClass("back-red")){ alert("赤いです"); } else{ alert("赤くないです"); } } </script>
応用編?
基本的に、addとremoveとtoggleとhasさえあればなんとかなりそうなので、あとはその組み合わせ。
というか厄介なのはhas。
「複数のclassをすべて持っているかどうか確認したい」
「複数のclassのどれかを持っているかどうか確認したい」
「複数のclassがすべて一致する(過不足がない)かどうか確認したい」
という時のための関数を作ったので置いとく。
複数のclassをすべて持っているかどうか確認したい
function hasAllClass(obj, c){ var c_list = c.split(" "); var retflg = true; $(c_list).each(function(i, e){ if (!obj.hasClass(e)){ retflg = false; } }); return retflg; }
使用例
<span id="testspan" class="a b c">にゃーん</span> <script> console.log( hasAllClass($("#testspan"), "a b c") ); // true(完全に一致してるのでOK) console.log( hasAllClass($("#testspan"), "b c a") ); // true(順番が変わってもOK) console.log( hasAllClass($("#testspan"), "a b" ) ); // true(足りないのはOK) console.log( hasAllClass($("#testspan"), "a b d") ); // false(余分なのはNG) </script>
複数のclassのどれかを持っているかどうか確認したい
function hasEitherClass(obj, c){ var c_list = c.split(" "); var retflg = false; $(c_list).each(function(i, e){ if (obj.hasClass(e)){ retflg = true; return false; } }); return retflg; }
使用例
<span id="testspan" class="a b c">にゃーん</span> <script> console.log( hasEitherClass($("#testspan"), "a b c") ); // true(完全に一致してるのでOK) console.log( hasEitherClass($("#testspan"), "b a" ) ); // true(順番違いや足りないのはOK) console.log( hasEitherClass($("#testspan"), "a d" ) ); // true(存在しないものが混じっててもOK) console.log( hasEitherClass($("#testspan"), "d e" ) ); // false(存在しないものしかないのはNG) </script>
複数のclassがすべて一致する(過不足がない)かどうか確認したい
function hasOnlyClass(obj, c){ var cmp_c_list = c.split(" ").sort(); var tag_c_list = obj.attr("class").split(" ").sort(); return cmp_c_list.toString() == tag_c_list.toString(); }
使用例
<span id="testspan" class="a b c">にゃーん</span> <script> console.log( hasOnlyClass($("#testspan"), "a b c") ); // true(完全に一致してるのでOK) console.log( hasOnlyClass($("#testspan"), "b a c") ); // true(順番が変わってもOK!) console.log( hasOnlyClass($("#testspan"), "a b" ) ); // false(足りないのでNG) console.log( hasOnlyClass($("#testspan"), "a b c d") ); // false(余分なのでNG) </script>
FireFoxでJSをワンクリックで動かせるようにする
ブックマークのURL部分にJSを仕込むだけなので、
「普通のブックマークをアドレスバー付近からワンクリックでアクセスできるようにする」でもある。
もっと言えば「ブックマークツールバーを使う」でしかない。
手順1.目的のJSをブックマークに登録する
Ctrl+Bとかでブックマークを開き、
「ブックマークツールバー」を右クリックで「新しいブックマーク」します。
適当な名前を付けて、URLのところにJSを仕込みます。
保存すると、まずは普通のブックマークになりました。
ブックマークを既に作ってある場合は
「ブックマークツールバー」へドラッグ&ドロップしてください。
以上!
こんな感じになります。
ちなみに
今回はこれを仕込むために設置しました。
君はHTML5の contentEditable 属性を知っているか | Tips Note by TAM
javascript:(function(b){b.contentEditable=!b.isContentEditable;})(document.body);
これで画面仕様書を作るのが楽になったぞ。