• 締切済み

SQLで連続したカラムが何個あるかを求める方法

お世話になっております。 PHPとmysqlでサイトを作っておりまして、そのログを分析しようとしています。 あるテーブルにユーザー名とログイン日付が入ったテーブルが有ったとして、その中で連続○日ログインしていた人は○人という結果を取りたいと考えているのですがこれをSQLで処理する方法はありますでしょうか? 今のところ、PHPで全データを取った後にPHP側で処理するしかないかと思っているのですが、件数が多いため可能であればmysql側で処理したいと思っています。 日付はUNIXTIMEに直すなどすればINTEGERに変換出来るとは思っているのですが、日付型(難しいようであればINT型の連続値でも問題ありません)でSQLで処理出来る方法はあるのでしょうか? ○○結合をすれば良いなど手がかりなど教えていただけると助かります。 ■テーブル username   login_date ほげ      2014/5/1 ほげ      2014/5/2 ほげ      2014/5/3 ほげ      2014/5/4 ふーばー   2014/5/2 ふーばー   2014/5/3 あああ     2014/5/1 あああ     2014/5/3 あああ     2014/5/4 ■ほしい結果 4日連続   1名 2日連続   2名

みんなの回答

  • yambejp
  • ベストアンサー率51% (3827/7415)
回答No.4

見直したら一部非効率なところがあったのでちょっと改善するとともに ちょっとだけ解説します //元データ insert into tbl values('ほげ','2014-5-1'),('ほげ','2014-5-2'),('ほげ','2014-5-3'),('ほげ','2014-5-4'),('ふーばー','2014-5-2'),('ふーばー','2014-5-3'),('あああ','2014-5-1'),('あああ','2014-5-3'),('あああ','2014-5-4'),('いいい','2014-5-1'),('ううう','2014-5-10'),('ううう','2014-5-11'),('ううう','2014-5-13'),('ううう','2014-5-14'); ※インデックスを貼ってあります //第一段階 select t1.username ,t1.login_date as d1 ,t2.login_date as d2 ,datediff(t2.login_date,t1.login_date)+1 as diff from tbl as t1 left join tbl as t2 on t1.username=t2.username and t1.login_date <= t2.login_date inner join tbl as t3 on t3.login_date between t1.login_date and t2.login_date and t1.username=t3.username group by username,d1,d2 having count(*)=diff これでusername,login_dateごとに、何日連続しているか得られます。 たとえば「あああ」さんは あああ,2014-05-01,2014-05-01,1 あああ,2014-05-03,2014-05-03,1 あああ,2014-05-03,2014-05-04,2 あああ,2014-05-04,2014-05-04,1 となり、5/1から5/1まで1連続、5/3から5/3まで1連続、5/3から5/4まで2連続、5/4から5/4まで1連続であることがわかります。 考え方次第ですが厳密にいえば1連続データは連続ではないので抽出しないでもいいですが、 これをしておかないと1回しかログインしていない人や、毎日ログインしない人がヒットしません。 すこし無駄なような気がしますが1連続はとっておいて損はないでしょう 今回ほしいのはusernameごとの最大連続日数なので //第二段階 select username,max(diff) as renzoku from ( select t1.username ,t1.login_date as d1 ,t2.login_date as d2 ,datediff(t2.login_date,t1.login_date)+1 as diff from tbl as t1 left join tbl as t2 on t1.username=t2.username and t1.login_date <= t2.login_date inner join tbl as t3 on t3.login_date between t1.login_date and t2.login_date and t1.username=t3.username group by username,d1,d2 having count(*)=diff ) as t4 group by username とすると、結果 username,renzoku あああ,2 いいい,1 ううう,2 ふーばー,2 ほげ,4 となります。元データをみてもらうとわかりますが 「ううう」さんは2連続ログインが2回ありますが、「最大」は2連続となるので 上記データがただしいとわかると思います。 #1さんの抽出だと2連続が2回は4連続だとカウントされてしまいます 最終的に、連続日数ごとの件数が知りたいので //第三段階 select renzoku,count(*) as count from ( select username,max(diff) as renzoku from ( select t1.username ,t1.login_date as d1 ,t2.login_date as d2 ,datediff(t2.login_date,t1.login_date)+1 as diff from tbl as t1 left join tbl as t2 on t1.username=t2.username and t1.login_date <= t2.login_date inner join tbl as t3 on t3.login_date between t1.login_date and t2.login_date and t1.username=t3.username group by username,d1,d2 having count(*)=diff ) as t4 group by username ) as t5 group by renzoku で、命題の解を得ることができます なお、処理的には第一段階ではインデックスがきいていますが 第二、第三段階は結果にたいする集計のためインデックスが利用されません。 ただ、第一段階でほぼデータ抽出が完了しているため、 さほど負荷が問題になることはないと思います

tasukete_goo
質問者

お礼

早速の回答ありがとうございます。 ただ私自身が風邪をひいてしまい、まだ会社に行けておりません。 週明けには行くつもりですので実験させていただきます。 結果出ましたら再度お礼させていただきます。

全文を見る
すると、全ての回答が全文表示されます。
  • mpro-gram
  • ベストアンサー率74% (170/228)
回答No.3

大量データを多重join すると途方もない時間がかかったりするし、usernameとlogin_dateで並べたのを順に見ていけば、一巡で、集計値は出てきそうなのにって思うから、mysql の procedure で実行してみました。 やってることは、php側でやるならこうやるだろうことをプログラムしています。phpとmysqlで大量データのやりとりが減るのが利点。 offset をカウンタ変数で指定しながら1行ずつデータ取得のところ、prepared文が必要なのが苦労しました。prepared文には、@変数でないと、外からデータを入れたり、取り出したり出来ないというのもあって、局所変数と入り交じってます。 実行時間的には、どうかな、それほど大量データでは試してないので、行数と同じ回数select発行するのは、かなり時間掛かる気がしないでもないけど、unique indexがあればなんとかというところ。 -- 元データ、 unique index があること create table `loglog` ( username varchar(100) , login_date date , unique ( username, login_date ) ); -- 集計用 一時テーブル procedure 内で create や truncate すると、権限設定がややこしいので、先に作成しておく create table logseq ( username varchar(10) , seqstart date , sequence int ); -- delimiter 変更 \d # -- 全角空白で字下げしています。一応集計区間の開始だけ指定してみました。 create procedure logsequence(in start date) begin DECLARE seqday, maxline INT DEFAULT 0; DECLARE u1 varchar(10) default ''; select count(*) into maxline from loglog where login_date>=start ; if (maxline >1 ) then  select username, login_date into u1,@d1 from loglog where login_date>=start order by username,login_date limit 1 offset 0;  set seqday = 1, @idx=1;  set @sql =concat( 'select username, login_date, datediff(login_date, @d1) into @u2,@d2,@diffcnt from loglog where login_date>="' , start , '" order by username,login_date limit 1 offset ?' );  prepare tmp_stmt from @sql;  while @idx < maxline DO   execute tmp_stmt using @idx;   if(u1 = @u2) then    if(@diffcnt = seqday) then     set seqday = seqday +1;    else     if(seqday>1) then      insert into logseq values (u1,@d1, seqday );     end if;      set @d1 = @d2 , seqday = 1 ;    end if;   else    if(seqday>1) then     insert into logseq values (u1,@d1, seqday );    end if;    set u1 = @u2 , @d1 = @d2 , seqday = 1 ;   end if;   set @idx = @idx +1 ;  end while; end if; select maxsequence, count(*) as usercount from ( select username , max(sequence) as maxsequence from logseq where seqstart >= start group by username ) as seq group by maxsequence; end # -- delimiter 変更 \d ; truncate logseq; call logsequence('2014-04-01'); -- 一時データのチェック 実連続値の表示 select * from logseq order by username,seqstart; -- 各連続値ごとの集計(usernameは複数回カウントされる) select sequence, count(*) as multicount from logseq group by sequence;

全文を見る
すると、全ての回答が全文表示されます。
  • yambejp
  • ベストアンサー率51% (3827/7415)
回答No.2

#1は要件に合致していないような・・・ かなり効率悪いですがこんな感じでどうでしょう? //準備 create table tbl(username varchar(20),login_date date); insert into tbl values('ほげ','2014-5-1'),('ほげ','2014-5-2'),('ほげ','2014-5-3'),('ほげ','2014-5-4'),('ふーばー','2014-5-2'),('ふーばー','2014-5-3'),('あああ','2014-5-1'),('あああ','2014-5-3'),('あああ','2014-5-4'),('いいい','2014-5-1'),('ううう','2014-5-10'),('ううう','2014-5-11'),('ううう','2014-5-13'),('ううう','2014-5-14'); //集計 select renzoku,count(*) as count from (select username,max(diff) as renzoku from (select username,d1,max(diff) as diff from( select t1.username,d1,d2,diff from (select t1.username ,t1.login_date as d1 ,t2.login_date as d2 ,datediff(t2.login_date,t1.login_date)+1 as diff from tbl as t1 left join tbl as t2 on t1.username=t2.username and t1.login_date <= t2.login_date) as v1 inner join tbl as t1 on login_date between d1 and d2 and v1.username=t1.username group by username,d1,d2 having count(*)=v1.diff ) as t2 group by username,d1 ) as t3 group by username ) as t4 group by renzoku

全文を見る
すると、全ての回答が全文表示されます。
  • yamada_g
  • ベストアンサー率68% (258/374)
回答No.1

回りくどい気がしますが、こんな感じでどうでしょうか。 select consecutive, count(*) from (  select username, count(*) + 1 consecutive  from (   select username, login_date   from テーブル t1   where exists (    select *    from テーブル t2    where t2.username = t1.username    and t2.login_date = t1.login_date + interval 1 day   )  ) t3  group by username ) t4 group by consecutive; MySQLの環境がないため動作確認をしていないので、文法的な修正が必要かもしれません。 また、全角スペースでインデントしているので実行時は削除してください。

全文を見る
すると、全ての回答が全文表示されます。

関連するQ&A

  • 難しいSQL

    別の掲示板に質問したのですが、難易度が高いのかしばらく回答が得られなかったのでこちらに質問してみます。(別の掲示板の方は質問を取り消しました。)ちょっと急いでいるのでどんなものでも構いません。SQLに詳しい方、ご教示お願い致します。環境は、MySQL5.1.33(MacOSX版)を使用しています。 以下のようなデータベースがあります。マスター系となるユーザテーブルと、それを参照するトランザクション系の買い物テーブルです。このデータベースから、「※連続して買い物をしたユーザをSELECTしたい」のですがそのようなことは可能でしょうか?可能であるとすると、どのようなSQLを書けばよろしいでしょうか? ※「連続して」とは、日付をまたがって、という意味です。 例)2009/06/12、2009/06/13 (1)ユーザテーブル mysql> desc user; userid, int(11), primary key username varchar(15) (2)買い物テーブル mysql> desc purchase; purchaseid, int(11), primary key userid int(11) purchaseid date 今、買い物テーブルに以下のようにデータがある場合 mysql> select * from purchase; 1, 3, 2004-01-02 6, 1, 2004-04-03 5, 2, 2008-04-05 4, 2, 2000-01-19 3, 5, 2004-01-01 2, 1, 2001-11-10 9, 5, 2003-12-31 8, 1, 2007-04-05 7, 3, 2004-01-03 mysql> select userid from ... とすると3, 5を抜き出すようにしたいです。(3は2004/01/02, 2004/01/03だから。5は2003/12/31, 2004/01/01だから。) このselect文の書き方を教えて頂きたいです。お手数をおかけしますがよろしくおねがいします。

  • SQLでグループ化して降順表示がうまくいきません

    個人的にMysql、php環境でサイトを作っております。 そこで、素人質問で申し訳ありませんが、SQLでお伺いしたい点があります。 例えば下のようなテーブルがあります。 ▽果物テーブル 果物ID | 果物名 | 売れた日付 1 | リンゴ | 2008/11/1 2 | みかん | 2008/11/2 3 | みかん | 2008/11/3 4 | リンゴ | 2008/11/4 これを、 『最近売れた』果物順に、『グループ化』して並べ替えたいと思い、 下記のようなsqlを組みました。 $sql = "SELECT 果物名, FROM 果物テーブル GROUP BY 果物名 ORDER BY 売れた日付 DESC"; 意図としてはリンゴ、みかんの順に並べ替えたいのですが、 みかん、リンゴの順に表示されてしまいます。 想像では、グループ化したとき、古い日付の方が残ってしまい、 | リンゴ | 2008/11/1 | みかん | 2008/11/2 この部分を対象にして日付を降順にしてしまうからだと思うのですが、 これをうまく解決する方法が分かりません。 MSアクセスなどですと先に二重にクエリをかけるなどすることで 「先に降順で並べ替えたクエリを出し、その後グループ化させる」 など処理が簡単なのですが、phpではどうするのかいまひとつ分からず・・・ 申し訳ありませんがよろしくお願いします。

    • ベストアンサー
    • PHP
  • SQLの結果が返ってこない

    PHP+mysqlで以下のようなSQL文で処理を行ったのですがデータが返ってきません。 $sql = 'SELECT * FROM books WHERE id=3'; $recordSet = mysql_query($sql); if(mysql_fetch_assoc($recordSet)){ while ($table = mysql_fetch_assoc($recordSet)) {        処理     } } mysqlの画面で SELECT * FROM books WHERE id=3 を入力してみるとしっかりと結果が返ってきます。 ちなみに1行目を、違うテーブルの $sql = 'SELECT * FROM podcast WHERE code=3 ORDER BY dcdate DESC LIMIT 0,3'; にしてみると、データが表示されます。 2日間かけてずっと試行錯誤してみたのですが、完全に行き詰ってしまったので、何か考えられる原因はありませんでしょうか。 よろしくお願いします。

    • ベストアンサー
    • PHP
  • 複雑なSQLですが、教えてください。

    Windows 2000 MySQL でデータベースプログラムを作成しています。 次のようなテーブルから求めたいSQL結果が下のようになっていますが、 なかなか難しいですが、一発のSQLで出来るようにしたいです。 どなたかSQL達人さん教えてください。 テーブル名:ITEMのレコード ----------------------------- iCode  iPrice  iWeight ----------------------------- 1     50     5 2     60     6 3     70     7 4     50     7 4     80     8 5     90     9 6     100    10 7     100    10 7     110    11 ... 求めたいSQL結果 ----------------------------------------------- iCode iPrice  1  2  3  4  5  6  7 -----------------------------------------------  50   5        7  60      6  70        7  80           8  90              9  100               10  10  110                   11 ---------------------------------------------

    • ベストアンサー
    • MySQL
  • php上でSQL文を実行した結果と、phpMyAdminで実行した結果が違う

    ☆実行環境 php5.3 MySQL4.0 $sql="SELECT * FROM テーブル名 WHERE フィールド名 like '".$変数."%'" php上で上記のようなSQL文を実行させようとしています。 テーブルには該当する行がいくつかあるのですが、ページ上では1行も表示してくれません。 mysql_num_rowsの返り値を見ると0になっています。 SQL文の組み立てがおかしいのかと思い、phpMyAdmin上でSQL文を実行すると、 きちんと欲しい内容が表示されます。 とするとphpでのSQL文の書き方がおかしいのだと思います。 どこかおかしいのかご教授ください。

    • ベストアンサー
    • PHP
  • 簡単なif文でちゃんと分岐しません

    今参考書を見ながらやっていますがうまくいきません。 postからusernameとpasswordの値を受け取って処理する スクリプトなんですが、うまく作動してくれません。 ■問題点 分岐点が4箇所ありますが、 パスワードだけ間違っても、すべて■1■ ログイン処理状態になります。 どうしてもパスワード再設定の画面に切り替わりません。 入力されたデータはデータベースにきちんと入ります。 3日間これで悩んでいて、ついに投稿してみました。 こんなこともわからない私ですが なんとかお願い致します。 ----ここから---- <?php session_start(); $username = $_POST["username"]; $sql = "SELECT * FROM `art_users` WHERE `user_name` = '%{$username}%'"; $query = mysql_query($sql) or die("データ追加エラー" . mysql_error()); $record = mysql_fetch_array($query, MYSQL_ASSOC); if ( $_POST["mode"] == "login" && mysql_num_rows($query) > 0 && $_POST["password"] == $record["user_password"] ) { $_SESSION["userid"] = $record["user_id"]; header("Location: kaukau_top.php"); } ?> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>ログイン処理</title> </head> <body> <?php // ■1■ ログイン処理状態 if ( $_POST["mode"] == "login" ) { // 新規登録:クエリ実行結果が0行=既存ユーザー名と一致しない if ( mysql_num_rows($query) == 0 ) { echo '新規ユーザー登録<br>'; echo '<form action="kaukau_login.php" method="POST">'; echo '<input type="hidden" name="mode" value="register">'; echo '<input type="submit" value="登録">'; echo '</form>'; } // パスワードミス:クエリ実行結果のパスワードと入力されたパスワードが一致しない elseif ( $_POST["password"] != $record["user_password"] ) { echo 'パスワードが違います。<br>'; echo 'ひみつの質問に答えると、パスワードを変更できます。<br>'; echo '<form action="kaukau_login.php" method="POST">'; echo '<input type="hidden" name="username" value="' . $username . '">'; echo '<input type="hidden" name="mode" value="resetpassword">'; echo '<input type="submit" value="送信">'; echo '</form>'; } } // ■2■ パスワード再設定状態 elseif ( $_POST["mode"] == "resetpassword" ) { if( $record["user_answer"] == $_POST["answer"] ) { echo 'パスワード再設定<br>'; echo '<form action="kaukau_login.php" method="POST">'; echo '<input type="hidden" name="username" value="' . $username . '">'; echo '<input type="hidden" name="mode" value="modifypassword">'; echo '<input type="submit" value="登録">'; echo '</form>'; } else{ echo "ひみつの答えが違います"; } } // ■3■ 新規ユーザー登録状態 elseif ( $_POST["mode"] == "register" ) { if( $_POST["password"] == $_POST["confirm"] ) { $sql = "INSERT INTO users (user_name, user_password, user_question, user_answer) VALUES ("; $sql .= " '" . $_POST["username"] ."',"; $sql .= " '" . $_POST["password"] ."',"; $sql .= " '" . $_POST["question"] ."',"; $sql .= " '" . $_POST["answer"] ."')"; $result = mysql_query($sql); echo '登録しました。'; } else { echo 'パスワードを再確認してください。'; } } // ■4■ パスワード変更状態 elseif ( $_POST["mode"] == "modifypassword" ) { if ( $_POST["password"] == $_POST["confirm"] ) { $sql = "UPDATE users"; $sql .= " SET user_password = '" . $_POST["password"] . "'"; $sql .= " WHERE user_name = '" . $_POST["username"] . "'"; mysql_query( $db, $sql ); echo '登録しました。'; } else { echo 'パスワードを再確認してください。'; } } ?> </body> </html>

    • 締切済み
    • PHP
  • PHPでSQLの中にSQLという記述はできますか?

    PHPでログイン画面で入力されたIDからユーザーNOを割り出し、商品テーブルにあるユーザーNOが一致する商品だけを絞り出すSQLを組みたいです。 ユーザーテーブル ユーザーNO、ユーザーID、パスワード 商品テーブル 商品名、ユーザーNO、数量 商品テーブルのsqlのwhereの中に、もうひとつsplをいれるようなことできますか? 例えばユーザーIDがyamadaの検索結果でたユーザーnoで商品テーブルのユーザーnoが一致するものを表示です よろしくお願いします

    • ベストアンサー
    • MySQL
  • SQLでできること

    SQLだけでできることと、できないことって、一言で言い表せるでしょうか? データベースからデータを取り出すとき、どこまでできて、どこからはphpなど他の言語で処理したらいいのか、とても気になるんです。 もしかして、何でもできるのかな? というのも、今やりたいと思っていることが、 (環境はMySQL+PHP使用で、ブラウザで表示させます) テーブル A(人物のリスト) id|name ------- 1 | aaa 2 | bbb 3 | ccc 4 | ddd テーブル B(人物と成績のリスト) name| point ------------- aaa | 10 bbb | 15 bbb | 10 aaa | 20 bbb | 25 ccc | 30 のようなテーブルがあったとき、 aaaのpointの合計 = 30 bbbのpointの合計 = 50 cccのpointの合計 = 30 dddのpointの合計 = 0 ← テーブルBに載ってない人もいる という人物全員の結果の集計をするには、どうすると最適でしょう? 私がすぐ思いつくのは、テーブルAのデータを配列に読み込んで、それを元に次はテーブルBから、人物別の行を取り出すSQL文を作って、pointを集計するという手順です。他に手段はあるでしょうか?

    • ベストアンサー
    • MySQL
  • ログインページ

    PHPとMySQLで ログインするときに、ログインIDとパスワード(md5処理)を 入力する普通のログインページをつくったのですが 照合するときに例えば ログインIDがphp パスワードが178だとログインできず ログインIDがabe パスワードがabeだとログインできる ログインIDがパスワード一致の場合だけ何故かログインできて しまうのですが、自分で書いていてなんなんですが 以下のプログラムではそういう風になってしまうんでしょうか。 <?php if($_POST["keep_login"] != ""){ session_set_cookie_params(365*25*3600); }else{ session_set_cookie_params(0); } session_start(); require("config.php"); if($_POST["passwd"]==""){ $_POST["passwd"]=time(); } if($_POST["action"]=="login"){ $login_id = mysql_real_escape_string($_POST["login_id"]); $sql="select * from users where login_id= '$login_id' and state='0'"; $result=mysql_query($sql); $users=mysql_fetch_array($result); if($users["passwd"]==md5($_POST["passwd"])){ $_SESSION["login_id"]=$_POST["login_id"]; $_SESSION["auth_code"]=md5($magic_code.$_POST["login_id"]); $_SESSION["name_kanji"]=$users["name_kanji"]; $sql="update users set login_date = '".date('Y-m-d H:i:s')."' where login_id= '".mysql_real_escape_string($_POST['login_id'])."' and state='0'"; $result=mysql_query($sql); if($_GET["redirect"] !=""){ header("Location:".$_GET["redirect"]); exit; }else{ header("Location:".$site_url); exit; } }else{ $message='<br><br><font color="red">ログインできませんでした</font><br>'; } } ?>

    • 締切済み
    • PHP
  • phpでのsql文で変数を使う

    POSTで受け取った文字列を,テーブル名として新規テーブルを作成したいのですが,sql文発行の際の変数の記述方法がわかりません。 ネットや本に書いてある方法は一通りためしたのですが,失敗しましたとなります。 ご教授下さい。 php 5.2.3 apache2.2.4 mysql 5.0.45 <?php require_once("../dbini.php"); $con = mysql_connect($DBSERVER, $DBUSER, $DBPASS); $selectdb = mysql_select_db($DBNAME, $con); $name = $_POST['name']; $sql = "create table $name ( qno int(3), qcontents text(500))"; $rst = mysql_query($sql,$con); if($rst){ echo $name . "を作成しました。"; }else{ echo "データベースの作成に失敗しました。"; } ?>

    • ベストアンサー
    • PHP