ラベル mysql の投稿を表示しています。 すべての投稿を表示
ラベル mysql の投稿を表示しています。 すべての投稿を表示

2016年8月2日火曜日

「履歴情報つきinteger試作」をMySQLで

nuko_yokohamaさんの記事「履歴情報つきinteger試作」が面白い.PostgreSQLのJSON機能を用いてSQLだけで履歴情報を管理している.こうした機能を考えていたので試してみたくなった.JSON機能についてはMySQLでもバージョン5.7からサポートしているらしい.せっかくなのでMySQLのJSON機能の勉強もかねてMySQLでの実装を試みてみた.

関数定義

「履歴情報つきinteger試作」では色々と関数を定義しているが,ここでは最低限の「create_tt_int」「add_tt_int」「value(実際にはgetValueByUnixTimeとgetValueByNum)」の実装を試みた.

履歴つきinteger作成

履歴つきinteger作成関数.時間はUNIX_TIMESTAMPで表現する.

DROP FUNCTION IF EXISTS create_tt_int;
delimiter //
CREATE FUNCTION create_tt_int(value integer)
 RETURNS JSON DETERMINISTIC
BEGIN
  DECLARE t INT UNSIGNED;
  set t = UNIX_TIMESTAMP();
 RETURN (SELECT JSON_OBJECT('header',JSON_OBJECT('nums',1,'newest',t,'oldest',t),'values',JSON_ARRAY(JSON_OBJECT('value',value,'timestamp',t,'num',1))));
END//
delimiter ;

履歴つきinteger更新

履歴つきinteger更新関数.JSON_ARRAYのマージに手間取った.JSON_MERGEでよかった.

DROP FUNCTION IF EXISTS add_tt_int; delimiter // CREATE FUNCTION add_tt_int(src json, value integer) RETURNS JSON DETERMINISTIC BEGIN DECLARE t INT UNSIGNED; SET t = UNIX_TIMESTAMP(); RETURN (SELECT JSON_OBJECT("header",JSON_OBJECT("nums",JSON_EXTRACT(src,"$.header.nums")+1,"oldest",JSON_EXTRACT(src,"$.header.oldest"),"newest",t),"values",JSON_MERGE(JSON_ARRAY(JSON_OBJECT("value",value,"num",JSON_EXTRACT(src,"$.header.nums")+1,"timestamp",t)),JSON_EXTRACT(src,"$.values")))); END// delimiter ;


UnixTimeを指定して値を取得

UnixTimeを指定して値を取得.

DROP FUNCTION IF EXISTS getValueByUnixTime;
delimiter //
CREATE FUNCTION getValueByUnixTime(src json,t INT UNSIGNED)
 RETURNS JSON DETERMINISTIC
BEGIN
 DECLARE i int;
 DECLARE x int;
 DECLARE t2 JSON;
 DECLARE value JSON;
 IF src->'$.header.oldest' > t THEN RETURN JSON_OBJECT();
 ELSEIF src->'$.header.newest' < t THEN RETURN JSON_EXTRACT(src,CONCAT("$.values[0].value"));
 END IF;
 SET x = src->'$.header.nums';
 SET i = 0;
 set value = JSON_OBJECT();
 WHILE i < x DO
  SET t2 = JSON_EXTRACT(src,CONCAT("$.values[",i,"].timestamp"));
  IF t2<=t THEN RETURN JSON_EXTRACT(src,CONCAT("$.values[",i,"].value"));
  END IF;
  SET i = i + 1;
 END WHILE;
 RETURN value;
END//
delimiter ;


Numを指定して値を取得

履歴番号Numを指定して値を取得.

DROP FUNCTION IF EXISTS getValueByNum;
delimiter //
CREATE FUNCTION getValueByNum(src json,num INT UNSIGNED)
 RETURNS JSON DETERMINISTIC
BEGIN
 DECLARE i int;
 DECLARE x int;
 DECLARE num2 INT UNSIGNED;
 DECLARE value JSON;
 SET x = src->'$.header.nums';
 IF (num < 0 ) THEN RETURN JSON_OBJECT();
 ELSEIF (num > x) THEN RETURN JSON_OBJECT();
 END IF; 
 SET i = 0;
 set value = JSON_OBJECT();
 WHILE i < x DO
  SET num2 = JSON_EXTRACT(src,CONCAT("$.values[",i,"].num"));
  IF (num=num2) THEN RETURN JSON_EXTRACT(src,CONCAT("$.values[",i,"].value"));
  END IF;
  SET i = i + 1;
 END WHILE;
 RETURN value;
END//
delimiter ;

使い方

テーブルを作成,履歴つきintegerを作成.2回ほど更新.

DROP TABLE test2;
CREATE TABLE test2 (id INT UNSIGNED, data JSON);
INSERT INTO test2 VALUES (1, create_tt_int(100));
INSERT INTO test2 VALUES (2, create_tt_int(300));
SELECT SLEEP(60);
UPDATE test2 SET data = add_tt_int(data, 200) WHERE id = 1;
UPDATE test2 SET data = add_tt_int(data, 500) WHERE id = 2;
SELECT SLEEP(60);
UPDATE test2 SET data = add_tt_int(data, 300) WHERE id = 1;
UPDATE test2 SET data = add_tt_int(data, 600) WHERE id = 2;
SELECT * FROM test2;

中身は以下のとおり.履歴情報がJSON形式で記録されている.

mysql> select * from test2\G;
*************************** 1. row ***************************
  id: 1
data: {"header": {"nums": 3, "newest": 1470061025, "oldest": 1470060905}, "values": [{"num": 3, "value": 300, "timestamp": 1470061025}, {"num": 2, "value": 200, "timestamp": 1470060965}, {"num": 1, "value": 100, "timestamp": 1470060905}]}
*************************** 2. row ***************************
  id: 2
data: {"header": {"nums": 3, "newest": 1470061025, "oldest": 1470060905}, "values": [{"num": 3, "value": 600, "timestamp": 1470061025}, {"num": 2, "value": 500, "timestamp": 1470060965}, {"num": 1, "value": 300, "timestamp": 1470060905}]}
2 rows in set (0.00 sec)

ERROR: 
No query specified

ここから値を取り出すにはgetValueByUnixTimeを使う.

mysql> SELECT getValueByUnixTime(data,UNIX_TIMESTAMP('2016-08-01 23:15:00')) AS value FROM test2;
+-------+
| value |
+-------+
| {}    |
| {}    |
+-------+
2 rows in set (0.00 sec)

mysql> SELECT getValueByUnixTime(data,UNIX_TIMESTAMP('2016-08-01 23:15:05')) AS value FROM test2;
+-------+
| value |
+-------+
| 100   |
| 300   |
+-------+
2 rows in set (0.00 sec)
mysql> SELECT getValueByUnixTime(data,UNIX_TIMESTAMP('2016-08-01 23:50:00')) AS value FROM test2;
+-------+
| value |
+-------+
| 300   |
| 600   |
+-------+
2 rows in set (0.00 sec)

ちゃんと履歴から値をとってこれている.

2013年3月15日金曜日

MySQLの文字コードセット「utf8」と「utf8mb4」

 MySQLの文字コードセット「utf8」は3バイトまでのサポートで4バイトをサポートするのはMySQLバージョン5.5以降からサポートしている文字コードセット「utf8mb4」であるとのことを今更ながら知りました.

MySQL 5.0 Reference Manual > 10.1.10. Unicode Support
http://dev.mysql.com/doc/refman/5.0/en/charset-unicode.html
MySQL 5.5 Reference Manual > 10.1.10. Unicode Support
http://dev.mysql.com/doc/refman/5.5/en/charset-unicode.html

簡単なコード(PHP)で動作検証してみました.

$conn=mysql_connect('192.168.0.5:3306','admin','password');

if($conn){
    if(mysql_select_db('test2',$conn)){
        $res=mysql_query('SET NAMES utf8',$conn);        
        if(!$res){
            print(mysql_error()."\n");
        }
        $res = mysql_query("DELETE FROM test");
        if(!$res){
            print(mysql_error()."\n");
        }
        $str="𪀚";
        printf("%s %d bytes\n",$str,strlen($str));
        var_dump(bin2hex($str));
        $res = mysql_query("INSERT INTO test (title) values ('".$str."') ",$conn);
        if(!$res){
            print(mysql_error()."\n");
        }
        $str2="田";
        printf("%s %d bytes\n",$str2,strlen($str2));
        var_dump(bin2hex($str2));
        $res = mysql_query("INSERT INTO test (title) values ('".$str2."') ",$conn);
        if(!$res){
            print(mysql_error()."\n");
        }
        $res=mysql_query('SELECT * FROM test',$conn);
        if($res){
            while($row = mysql_fetch_assoc($res)){
                printf("%s\n",$row['title']);
            }
            mysql_free_result($res);
        }else{
            print(mysql_error()."\n");
        }
    }
    mysql_close($conn);
}

コードでは2つの漢字「𪀚」(鳥+戎)と「田」をデータベースに登録し,検索表示しています.

MySQL 5.0.87の場合,
4バイトの文字があってもエラーは表示されません.
無視されるだけです.

$ php test.php
𪀚 4 bytes
string(8) "f0aa809a"
田 3 bytes
string(6) "e794b0"

また,当然「utf8mb4」はサポートしていませんので,使用しようとするとエラーとなります.

$ php mysql.php
Unknown character set: 'utf8mb4'
𪀚 4 bytes
string(8) "f0aa809a"
田 3 bytes
string(6) "e794b0"

MySQL 5.6.10の場合は4バイトの文字「𪀚」をutf8で登録しようとするとエラーとなります.
$ php test.php
𪀚 4 bytes
string(8) "f0aa809a"
Incorrect string value: '\xF0\xAA\x80\x9A' for column 'title' at row 1
田 3 bytes
string(6) "e794b0"
「utf8mb4」を使うと4バイトの文字をちゃんと登録することができます.
$ php test.php
𪀚 4 bytes
string(8) "f0aa809a"
田 3 bytes
string(6) "e794b0"
𪀚
この投稿では文字化けしてるけど.

4バイト文字を「utf8mb4」で登録できる事はわかったけど,5.0から5.5に移行するにはSQLの変更も必要そう.インデックスサイズも変更となるし.

取りあえず,そういう問題があることだけは把握しておこう.