実務のクエリは、ほとんどが文字列との戦い
数値の扱いは簡単です。やっかいなのは文字列のほう ―― 余計な空白が混ざった名前、大文字小文字がバラバラのメールアドレス、ハイフンでつながったID、ほぼ一致しているのに微妙にズレているフリーテキスト項目など、現場では悩みの種が尽きません。SQLite には、こうしたケースをアプリ側のコードに頼らず処理できる、シンプルで実用的な文字列関数がひととおり用意されています。
このページでは、まず押さえておきたい sqlite 文字列関数として、連結・部分文字列の取り出し・検索・置換・トリム・整形を順に見ていきます。
sqlite の文字列連結は || 演算子で行う
SQLite には CONCAT 関数はありません。文字列をつなぐには || 演算子を使います。
数値などほかの型は自動的にテキストに変換されます。ただし注意点として、オペランドのいずれかが NULL だと、式全体が NULL になります。これはSQL標準の挙動ですが、初見でハマる人が多いポイントです。
NULL を含む可能性のあるカラムは COALESCE(col, '') や COALESCE(col, 'default') で包んでおきましょう。そうしないと、値が一つ欠けるだけで文字列全体が NULL になってしまいます。
length・upper・lower で文字数や大文字小文字を扱う
この3つは sqlite 文字列関数の中でも特に出番が多いです。
LENGTH が返すのはバイト数ではなく文字数です。バイト数が必要な場合(まれですが、ストレージ分析などで使う場面はあります)は OCTET_LENGTH を使ってください。なお、UPPER と LOWER はデフォルトでは ASCII の英字しか変換しません。アクセント付き文字はそのまま素通りするので、変換したい場合は ICU 拡張をロードしておく必要があります。
SUBSTR で sqlite の部分文字列を取り出す
SUBSTR(text, start, length) は文字列の一部を切り出す sqlite の部分文字列関数です。インデックスは 1 始まりで、最初の文字は 0 ではなく 1 になる点に注意してください:
いくつか押さえておきたいポイントがあります。
- 第3引数は省略可能です。省略した場合は
startから末尾までが返ります。 startを負の値にすると、文字列の末尾から数えます。startが文字列の長さを超えていてもエラーにはならず、空文字列が返ります。
別のデータベースから来た人の指の記憶に配慮してか、SUBSTRING も同義語として使えます。
INSTR で部分文字列の位置を調べる
INSTR(haystack, needle) は、haystack の中で needle が最初に現れる位置を 1 始まりで返します。見つからなければ 0 が返ります。
上の式は、SQLite で「@ より前の部分をすべて取り出す」ときの定番パターンです。INSTR で区切り位置を探し、SUBSTR で切り出す。この組み合わせは今後何度も書くことになります。注意点として、INSTR は一致する箇所がないと 0 を返すので、切り出す前に必ずチェックしてください。SUBSTR に 0 を渡すと、エラーにはならないものの想定外の結果になります。
REPLACE 関数で部分文字列を置き換える
REPLACE(text, old, new) は、old に一致する箇所を すべて new に置き換えます。
大文字と小文字は区別され、正規表現は使えません。あくまでリテラルな部分文字列だけです。複雑な変換が必要なときは REPLACE を入れ子にしてつなげることもできますが、2〜3段を超えてきたらアプリケーション側で処理した方がいいでしょう。
TRIM・LTRIM・RTRIMの使い方
ユーザー入力には前後に余計な空白が紛れ込みがちです。そんなときは TRIM で取り除けます。
既定では半角スペースを削除します。第2引数を指定すると、削る文字を自分で決められますが、ここで渡した文字列は「部分文字列」ではなく「削除対象の文字集合」として扱われる点に注意してください。たとえば TRIM('xxxhelloxx', 'x') の結果は 'hello' になります。
printf:数値と文字列のフォーマット
小数点以下の桁数を揃えたい、数値をゼロ埋めしたい、16進数で出力したい——そんなときに使えるのが printf(別名 format)です。
書式指定子は C 言語の流儀に従っていて、%d、%s、%f、%x、0 やスペースでのパディングといった指定がそのまま使えます。|| と大量の CAST を組み合わせて文字列を組み立てるより、はるかにスッキリ書けます。
sqlite の like と glob の違い:パターンマッチング
同じパターンマッチングでも、この2つの演算子はまったく別物です。
LIKE は SQL でおなじみのワイルドカード、つまり任意の文字列にマッチする % と1文字にマッチする _ を使います。ASCII 文字については大文字・小文字を区別しません。
GLOB は Unix シェル風のワイルドカードを使います。具体的には、任意の文字列にマッチする *、1 文字にマッチする ?、文字クラスを表す [abc] で、こちらは大文字・小文字を区別します。
使い分けのコツはこうです。人間が普通に思い浮かべる「〜で始まる」「〜を含む」「〜で終わる」のマッチングなら LIKE。大文字小文字を区別したい場合や文字クラスを使いたい場合は GLOB。どちらもインデックスを活用できますが、それはパターンが先頭固定の場合('%foo' ではなく 'foo%')に限られます。先頭にワイルドカードが来ると、フルスキャンになってしまいます。
文字列の分割:SPLIT 関数は存在しない
SQLite には SPLIT_STRING 関数がありません。実用的な回避策は次の2つです。
区切り文字で分割して複数行に展開したい場合、一番きれいな方法は JSON 配列に対して json_each を使うか、再帰 CTE を書くかのどちらかです。これは後の章で改めて扱うので、ここでは「全部の単語をくれ」が SQLite では一行で書けない、ということだけ覚えておいてください。
実践例:名前データのクリーニング
ここまでの内容を組み合わせてみましょう。users テーブルに表示名が雑に入っていて、余計な空白が混じっていたり、大文字小文字がバラバラだったり、"Dr. " や "Mr. " といった敬称を取り除きたい、というよくあるケースを考えます。
式は内側から外側へと読み解きます。まず外側の空白を取り除き、小文字に変換し、敬称を削除して、敬称を消した結果として残った先頭の空白をもう一度トリムする、という流れです。それぞれのステップは単独の関数で、複雑さは積み重ねから生まれているだけです。ネストが3〜4段を超えてきたら、生成カラム(第「Advanced Features」章を参照)を使うか、データ取り込みの段階でクレンジングを済ませるサインだと考えてください。
この章のポイント
- 文字列の連結には
||を使う。NULLが混ざると結果もNULLになるのでCOALESCEで守る。 SUBSTRとINSTRを組み合わせれば、「探して切り出す」用途はほぼカバーできる。REPLACEは指定した部分文字列の出現箇所をすべて置換する。TRIM系の関数は空白だけでなく、任意の文字セットを指定できる。- フォーマット付き出力には
printfが最適。 - 大文字小文字を区別しないSQLワイルドカード検索なら
LIKE、シェル風で大文字小文字を区別したいならGLOB。
次回:数値関数
文字列を一通り扱ったので、次は当然ながら数値の番です。丸め処理、絶対値、整数除算のクセ、そしてSQLiteの新しめのバージョンで追加された数学関数まで見ていきます。続きは次のページで。
よくある質問
SQLiteで文字列を連結するには?
CONCATではなく || 演算子を使います。SQLiteには標準で CONCAT 関数がないので注意してください。たとえば 'Hello, ' || name で2つの文字列をつなげられます。ただし、片方でも NULL が混ざると結果は丸ごと NULL になるので、NULL許容のカラムは COALESCE でラップしておくのが安全です。
SQLiteで部分文字列を取り出すには?
SUBSTR(text, start, length) を使います(SUBSTRING という別名でもOK)。インデックスは1始まりなので、SUBSTR('hello', 1, 3) は 'hel' を返します。start に負の値を渡すと末尾から数え、length は省略可能で、省くと末尾まで全部取ってきます。
SQLiteにSPLIT_STRING関数はある?
ありません。SQLiteには文字列を分割する組み込み関数は用意されていません。多くのケースでは INSTR と SUBSTR を組み合わせれば必要な部分を抜き出せますし、区切り文字で分割したいなら再帰CTEを書く手もあります。頻繁に使うなら、自前のスプリッタを書くより JSON配列にして json_each で展開するほうがシンプルです。
LIKEとGLOBの違いは?
LIKE はASCII範囲では大文字小文字を区別せず、ワイルドカードに % と _ を使います。一方 GLOB は大文字小文字を区別し、Unixシェル風のワイルドカード(*、?、[abc])が使えます。大文字小文字を厳密に扱いたいときや文字クラスを使いたいときは GLOB、見慣れたSQL風のマッチングがしたいなら LIKE という使い分けが定番です。