TOP

データベースsqlite3 その10

ROWID・時間に関する処理

特徴的な仕様

sqliteデータベースにおいて、抑えておきたい機能について検証してみたいと思います。

ROWID

ROWID は INTEGER PRIMARY KEY の別名

デフォルトでは、SQLite のすべてのレコードには、通常「 rowid 」と呼ばれる特別な列があり、テーブル内のその行を一意に識別するのに使用されます。

WITHOUT ROWID でパフォーマンスを向上させる

デフォルトではテーブルを生成し、データを入れるたびごとに ROWID が作られ、その分、容量が増えてしまう。これを抑制する為に、CREATE TABLE 文の末尾に WITHOUT ROWID をつけることにより、これを抑制できる。

  • SQLiteバージョン 3.8.2 (2013-12-06) 以降が対象である。
  • primary key を持つテーブルにのみ適用される。この時、下記に示す integer primary key を持つテーブルでは、rowid のエイリアスにならない。
  • AUTOINCREMENT は WITHOUT ROWID テーブルでは機能しません。
  • sqlite3関数において、WITHOUT ROWID テーブルではrowidを返す関数は機能しません。
  • without rowid を適用したテーブルの primary key列は not null 制約を持たなければならない。

integer primary key に autoincrement 制約をつける利点

create table ステートメントにおいて、integer primary key 制約をつけるとデータを追加するたびにrowid の値が一つずつカウントアップする。これは、autoincrement 制約をつけたのと同様な動作です。

但し、データを削除した場合に、微妙に動作に違いがあります。値に null 指定の場合、一旦削除された key (rowid と等価)の利用が max に達した場合、increment 制約がある場合は、再利用できません。increment 制約が無い場合は、空きがある任意の値が利用されます。但し、null ではなく、削除した値そのものを指定した場合は、問題なく両者において利用できます。このことから、「integer primary key autoincrement」 制約指定により、削除された key(rowid と等価) を再利用不可にする制約として定義することが出来ます。

rowid が max になりそうも無い場合は、動作に変りがないので、increment 制約をつけなくても支障が在りません。

そのことを、sqlite3 コマンドプロンプトにおいて、以下のように確かめてみました。

「integer primary key not null」 ケース

「integer primary key autoincrement not null 」ケース

日付情報の取得

sqlit3 において、日付情報を示すデータは以下に示す関数により、NUMERIC アフィニティとして受け取られます。出力されるデータ形式により、integer または text として格納処理されるので、他のリレーショナルデータベースのような 日付・時間型はありません。

時間を扱う関数

  1. date(timestring, modifier, modifier, …) # YYYY-mm-dd形式
  2. time(timestring, modifier, modifier, …) # HH:MM:SS形式
  3. datetime(timestring, modifier, modifier, …) # YYYY-mm-dd HH:MM:SS
  4. julianday(timestring, modifier, modifier, …) # ユリウス日紀元前4714年11月24日(予言グレゴリオ暦)のグリニッジ正午からの日数を返す
  5. strftime(format, timestring, modifier, modifier, …) # 指定した日時を指定した書式で返す
  6. unixepoch(time-value, modifier, modifier, …)

SQLiteで格納される日付時刻にはタイムゾーンの概念はなく、すべての日付時刻をUTCとして保持する。したがって、上記の関数を使用する場合は、modiferに時差を指定する必要があります。

修飾子 modifier

  • NNN days
  • NNN hours
  • NNN minutes
  • NNN.NNNN seconds
  • NNN months
  • NNN years
  • start of month
  • start of year
  • start of day
  • weekday N
  • unixepoch
  • julianday
  • auto
  • localtime
  • utc

date() を使ったケース

datetime() を使ったケース

  1. 任意の日付を指定する場合は、上記時間を扱う関数に合わせて指定する必要があります。
  2. .mode column で2次元表形式で画面表示にかえる。
  3. unixepoch は世界標準時を1970年1月1日0時0分0秒からの経過秒を数値で表したものです。

julianday() を使ったケース

strftime() を使ったケース

フォーマット

%dday of month: 00
%ffractional seconds: SS.SSS
%Hhour: 00-24
%jday of year: 001-366
%JJulian day number (fractional)
%mmonth: 01-12
%Mminute: 00-59
%sseconds since 1970-01-01
%Sseconds: 00-59
%wday of week 0-6 with Sunday==0
%Wweek of year: 00-53
%Yyear: 0000-9999
%%%

すべて、text ストレージタイプで格納される

日時の比較

トリガー文の実行を中断させる[raise][select case when][on conflict]

RAISE();  トリガー文の実行を中断
RAISE(ROLLBACK/ABORT/FAIL, message);  
  指定されたON CONFLICT処理が実行され、メッセージ文が出力され、現在のクエリが終了します。
RAISE(IGNORE);
  実行しているトリガーが破棄されます。トリガー処理を除いたプログラムは継続されます。
CASE WHEN ELSE を使った条件式の構造
SELECT
  CASE
    WHEN condition THEN expression
    WHEN condition THEN expression
    ELSE expression
  END
FROM table name;
ON CONFLICT処理 制約違反が発生した場合の競合解決方式を指定
ROLLBACK
  実行しているトランザクションをロールバックする。
ABORT
  デフォルトの処理。実行しているsql文は実行前に戻りますが、その前に実行されたsql文の結果は破棄されません。
FAIL 
  実行しているsql文は中断されます。その前に実行されたsql文の結果は破棄されません。UNIQE、NOT NULL、CHECKに対する制約違反にのみ有効。外部キー制約違反の場合は、ABORT として処理されます。

sqlite は日付情報をサニダイズして使用する必要がある

sqlite において、日時データを yyyy-mm-dd で格納される事(julianday unixepoch を除く)を前提としております。ストレージタイプでは text 格納なのですが、フォーマットされた状態で格納する必要があります。このことをチェックする仕組みは sqlite では用意されていないので、自力で作らなければいけないようです。

日付情報テーブルを定義し、正しく格納する為の確認作業をする

日付情報は text として格納されてしまう為、数字以外の文字列もすべて格納できてしまう。date() 関数を使用すると、0000-00-00 形式以外の文字列は null として格納される。

date() 関数の挙動について確認してみる

date() 関数を使えば、挿入データに対して、ある程度はサニダイズできる。但し、日にちは 01~31、月は 01~12 の範囲に関しての有効定義であり、2、4、6、9、11月末日の考慮はされていない。うるう年も考慮に入れていない。

日付入力をチェックする仕組みを考える

データの入力処理では、日付をチェックする必要があります。各種プログラミング言語において、サニダイズコードを作成することも考えられます。ここでは、あくまでも、端末上で、以下のような処理を考えることにします。

  • date() 関数を使って入力データが正しくフォーマットされているかチェックする。
  • 入力データが正しくセットされる為に、トリガー処理を使い、不正データは格納しない。
  • トリガー処理において、月末日とうるう年のチェックを行う。

尚、うるう年とは、通常、2月末日が28日であるのに対し、29日である年のことであります。うるう年を考慮したサニダイズトリガー処理を以下のように考えてみました。

  1. date() 関数で入力データがnullを返す場合は、差し戻す。
  2. strftime 関数を使って、日を示す数字が 31 であれば、
  3. 月を示す数字が 02 または 04 または 06 または 09 または 11であれば差し戻す。
  4. 日を示す数字が 30 であれば、
  5. 月を示す数字が 02 であれば差し戻す。
  6. 日を示す数字が 29 の場合は、
  7. 月を示す数字が 02 であれば、
  8. 年を示す数字が 4で割り切れなければ、差し戻す。
  9. 年を示す数字が 100 で割り切れ、400 で割り切れなければ、差し戻す。
CASE WHEN ELSE を使った条件式の構造
SELECT
  CASE
    WHEN condition THEN expression
    WHEN condition THEN expression
    ELSE expression
  END
FROM table name;

年月日格納サニダイズをするトリガーコード

トリガー処理を検証してみる

sqlite_master テーブル

sqlite_masterテーブルは、データベースのスキーマ情報を一括して管理する特殊なテーブルです。データベースにテーブルが生成されると、作られます。

よって、sqlite_を接頭辞にするテーブル名は、予約されているので生成できずエラーになる。

テーブル情報を表示してみる

sqlite_masterテーブルはsqlite_schemaテーブルのエイリアスです。

sqlite_masterテーブルのフィールド

type

table index view trigger のいずれかのテキスト文字

name

生成されたtable index view triggerの名前

tbl_name

nameフィールドに関連付けられたtable index view triggerの名前

rootpage

B木アルゴリズムで格納されたsqlite_schemaテーブル情報のページ番号を示しています。ルートは0またはnullとなります。

sql

sqlステートメントのテキスト表示

利用サンプル

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください