シーケンス値を取得する

MySQLでシーケンスオブジェクト作ろうとする度に「あ、そういえば無かったわ」ってなる。

「あれ?どう書くんだっけ?」とか「値のリセットどうするんだっけ?」ってなることが多いのでまとめる。プロジェクト初期にこの辺は実装しちゃうんで、次のプロジェクトを始める時に忘れてるのが原因。

1. PostgreSQL

> -- シーケンスの作成:簡易
> CREATE SEQUENCE seq_hoge;

> -- シーケンスの作成:詳細
> CREATE SEQUENCE seq_fuga
>  START 30000
>  INCREMENT BY 1
>  MAXVALUE 99999
>  NO CYCLE
>  CACHE 1;

> -- 値設定(これだとnextval()は2が戻る。1から始めたい時は0をセットする)
> SELECT setval('seq_hoge', 1);

> -- 値取得 & カウントアップ
> SELECT nextval('seq_hoge');

> -- 現在値取得(セッション内で先にsetval/nextvalしないとエラーになる)
> SELECT currval('seq_hoge');

> -- 現在値取得(いつでも取得できる)
> SELECT last_value from seq_hoge;

> -- シーケンス一覧
> \ds
                  リレーション一覧
  スキーマ  |      名前      |          |  所有者
-----------+---------------+------------+-----------
 hoge_test | seq_hoge      | シーケンス  | hoge_test
 hoge_test | seq_fuga      | シーケンス  | hoge_test

> -- シーケンスの詳細
> \d seq_hoge
            シーケンス "hoge_test.seq_fuga"
 タイプ |  開始  | 最小 |  最大  | 増分 | 循環? | キャッシュ
--------+-------+------+-------+------+--------+------------
 bigint | 30000 |    1 | 99999 |    1 | no     |          1

重要

  • 運用中はsetval()しない。ダメ、ゼッタイ。
  • currval()やlast_valueは最新値ではない。他セッションで更新されている可能性がある。
  • currval()やlast_valueは「今どれくらいシーケンスが進んでるのかな〜?」みたいな参考値。(それなら対象カラムのmax()でいいから実運用ではあんまり使わない)
  • 主キーなどで使用する値はnextval()を使用する。
  • 「シーケンス値 = DB登録順」とは限らない。実装方法やユーザ操作によっては「シーケンス値取得とDB登録の間隔が(もの凄く)空く」こともある。

2. MySQL

MySQLにはシーケンスオブジェクトはありません。ガーン!!

Ver8になっても実装しないってことは「シーケンスオブジェクトなんて必要ないゼ!!」ってポリシーなんでしょうね。便利だと思うんだけどなー

MySQLではシーケンス値が欲しい場合はAUTO_INCREMENTを使います。(PostgreSQLにも同様のSERIAL型があるよ)

ただ、AUTO_INCREMENTはテーブルごとに1つだけという制限があります。それから整数型(ひょっとしたら他の数値型もOK?)のみです。

> -- AUTO_INCREMENTを指定してテーブル作成
> CREATE TABLE hoge_t (
>  id   INT AUTO_INCREMENT,
>  name TEXT,
>  PRIMARY KEY (id)
> );

> -- データ登録
> INSERT INTO hoge_t (name) VALUE ('ほげ');

> -- データ取得
> SELECT * FROM hoge_t;
+----+--------------------+
| id | name               |
+----+--------------------+
|  1 | ほげ               |
+----+--------------------+
1 row in set (0.00 sec)

> -- AUTO_INCREMENTの初期値あり
> CREATE TABLE hoge_t (
>  id   INT AUTO_INCREMENT,
>  name TEXT,
>  PRIMARY KEY (id)
> )
> AUTO_INCREMENT = 2000;

> -- AUTO_INCREMENT値変更
> ALTER TABLE hoge_t AUTO_INCREMENT = 3000;

3. Oracle

> -- シーケンスの作成:簡易
> CREATE SEQUENCE seq_hoge;

> -- シーケンスの作成:詳細
> CREATE SEQUENCE seq_fuga
>  START WITH 30000
>  INCREMENT BY 1
>  MAXVALUE 99999
>  NO CYCLE
>  CACHE 1;

> -- 値設定(実は素直に設定できない)
>
> -- 方法1 ... INCREMENTを書き換えて、値取得して、INCREMENTを戻す。
> ALTER SEQUENCE seq_fuga INCREMENT BY 9999 NOCACHE;
> SELECT seq_fuga.NEXTVAL FROM DUAL;
> ALTER SEQUENCE seq_fuga  INCREMENT BY 1;
>
> -- 方法2 ... STARTを変えて作り直す。(他のパラメタがある場合は忘れずセット)
> CREATE OR REPLACE SEQUENCE seq_fuga START WITH 100;
>
> -- 方法3 ... 最近はコレでできるらしい。やったことない。
> ALTER SEQUENCE seq_fuga RESTART WITH 100;

> -- 値取得 & カウントアップ
> SELECT seq_fuga.NEXTVAL FROM DUAL;

> -- 現在値取得
> SELECT seq_fuga.CURRVAL FROM DUAL;

値設定方法3はココで発見しました。最新の実行環境を持ってないので試せてません。そもそもOracle10までしか使ったことないしー

RESTART WITHってできるのかな? マニュアルだと書けないっぽいんだけど。RESTART START WITHが正しい?

値設定方法2はあんまり好きじゃないです。シーケンスが何番から始めたってのは気軽に上書きしない方がいいような気がするのですよ。「じゃあ具体的に何がダメなの?」って言われると「あーうー…特に思いつきません」だけど。

Oracleでシーケンス値変更するのって地味に面倒くさいですよね。

4. おまけ

シーケンスはこのサンプルみたいな関数経由で使用することが多いです。

固定長文字列にして、必要に応じて末尾に類推困難のための文字を追加してます。末尾の類推困難文字は要件によって個数を増減させます。サンプルは2個付けてます

phpとPostgreSQLです。

/**
 * 主キーを生成
 * @return  string
 */
public static function getNewHogeCd()
{
    return self::getSeq('seq_hoge_cd', '0000000'). self::getRandChar(). self::getRandChar();
}

/**
 * シーケンス値取得とフォーマット
 * @param   string  $seqName    シーケンス名称
 * @param   string  $fmt        フォーマット(PostgreSQL:to_char関数準拠)
 * @return  string              シーケンス値
 */
private static function getSeq($seqName, $fmt)
{
    return DB::select(
        "SELECT TRIM(TO_CHAR(NEXTVAL(:seqname), :fmt)) AS seqval;",
        ['seqname' => $seqName, 'fmt' => $fmt]
    )[0]->seqval;
}

/**
 * 類推困難化用の付加文字
 * @param   string  $src    ソース文字
 * @return  string          末尾文字
 */
private static function getRandChar($src='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ')
{
    return substr($src, mt_rand(0, (strlen($src)-1)) , 1);
}