Linuxを日常的に使う実験ブログ

HaskellでDBを操作しよう Beamチュートリアル Part 2

 2023-12-30

 2024-01-24

 プログラミング

今回はBeamチュートリアルの第二回目です。最初から読みたい方はBeamチュートリアル Part 1をご覧ください。今回は基本的なデータの取得を学んでいきます。

データの準備

まずはこのチュートリアルで使うデータを用意しましょう。Github上のmigrations/0002\_insert\_data.sqlにデータ挿入のSQLが用意してあります。以下のようにコマンドを実行してデータを挿入しましょう。

psql -U <username> beam < migrations/0002_insert_data.sql

Beamが生成するSQLの確認方法

データの操作をするにあたり、まずはBeamが生成するSQLを確認する方法を見ておきましょう。Beamを実行するには各バックエンドをインポートする必要があります。今回はPostgreSQL用のbeam-postgresを用いて説明します。もし他のバックエンドに興味がある場合はhackageで探して下さい。

さて、beam-postgresでBeamを実行するにはrunBeamPostgres関数を使います。もし生成されたSQLを確認したいときにはrunBeamPostgresDebugを使います。

runBeamPostgresDebug :: (String -> IO ()) -> Connection -> Pg a -> IO a

runBeamPostgresDebugの第一引数にputStrLnを適用することで実行時にSQLを出力できます。今回のサンプルでもいくつかの箇所でrunBeamPostgresDebugを使用したコードを書いています。

単純なクエリ

まず単純にテーブルの全レコードリストを全フィールドで取得することを考えます。今回は全開データの挿入の項でインプットしたCategoryデータを読み込んでみましょう。

sample01 :: Connection -> IO ()
sample01 c = do
  li <- runBeamPostgres c $ runSelectReturningList $ select query
  print li
  where
    query = all_ (users blogDatabase)

最も単純なクエリですね。Beamで構築されるSQLとは異なりますが、意味としては以下のSQLに相当します。

SELECT * FROM users;

尚、本チュートリアルで紹介するコードはgithub上にあり、これをクローンすることですぐに実行してみることができます。Config.hsのデータベース接続情報は適宜書き換えて下さい。

実行例

git clone https://github.com/kurocode25/beam-tutorial.git
cd beam-tutorial
stack run -- sample01

任意のフィールドを取得

場合によってはすべてのフィールドが必要ではないかも知れません。必要なフィールドのみを取得したい場合について考えます。なんと先程と同じように全フィールドを取得した後にreturnもしくはpureで必要な項目のタプルを返すだけす。

sample02 :: Connection -> IO ()
sample02 c = do
  li <- runBeamPostgresDebug putStrLn c $ runSelectReturningList $ select $ do
    ca <- all_ (categories blogDatabase)
    return (categorySlug ca, categoryName ca)
  print li

条件指定によるデータ取得

データベースを扱う上で欠かせないのが条件によるデータ取得です。ここではSQLのWHER句に相当する使い方を見ていきます。userNameが’Kuro’のユーザーを取得してみましょう。

sample03 :: Connection -> IO ()
sample03 c = do
  li <- runBeamPostgresDebug putStrLn c $ runSelectReturningList $ select $ do 
    filter_ (\u -> userName u ==. "Kuro") $ all_ (users blogDatabase)
  print li

guard_とfilter_

データ取得の際にSQLのwhere句に相当する条件によるフィルタリングをするにはguard_関数を使う場合とfilter_関数を使う場合があります。いずれも同様に条件づけを行うことができます。

sample03をguard_を使って書くと以下のようになります。

sample04 :: Connection -> IO ()
sample04 c = do
  li <- runBeamPostgresDebug putStrLn c $ runSelectReturningList $ select $ do
    u <- all_ (users blogDatabase)
    guard_ (userName u ==. "Kuro")
    return u
  print li

guard_に関してはテーブルの結合のところで再度詳しく見てみたいと思います。

Limit/Offset句

APIでページネーションを行いたい場合などLIMIT/OFFSETを使いたい場合も多いと思います。そのような場合は以下のように書きます。

sample05 :: Connection -> IO ()
sample05 c = do
  let limitNum = 3 :: Integer
  let offsetNum = 1 :: Integer
  li <- runBeamPostgres c $ runSelectReturningList $ select $ do
    limit_ limitNum $ offset_ offsetNum $ all_ (categories blogDatabase)
  print li

次のOrder句の部分でも触れますが、関数を適用する順番に気をつけてください。limit_関数を一番外側で適用することで選んだデータから最終的に個数を絞ることになります。

OrderBy句

データを取得する際に並び替えたい場合はorderBy_関数を使い、昇順、降順の順はasc_desc_関数を使うことができます。

sample06 :: Connection -> IO ()
sample06 c = do
  let limitNum = 3 :: Integer
  let offsetNum = 1 :: Integer
  li <- runBeamPostgres c $ runSelectReturningList $ select $ do
    limit_ limitNum $ offset_ offsetNum $
      orderBy_ (desc_ . categoryName) $ all_ (categories blogDatabase)
  print li

もし並び順の条件を複数つけたい場合はタプルにします。

sample07 :: Connection -> IO ()
sample07 c = do
  let limitNum = 3 :: Integer
  let offsetNum = 1 :: Integer
  li <- runBeamPostgres c $ runSelectReturningList $ select $ do
    limit_ limitNum $ offset_ offsetNum $
      orderBy_ (\ca -> (asc_ (categoryName ca) , desc_ (categorySlug ca))) $ all_ (categories blogDatabase)
  print li

Distinctサポート

重複した結果を集約して結果を返すSQLのDISTINCTをBeamで行ってみましょう。nub_関数を使うとSQLでDISTINCTを使うことができます。

sample08 c = do
  li <- runBeamPostgresDebug putStrLn c $ runSelectReturningList $ select $ do
    nub_ $ all_ (posts_tags blogDatabase)
  print li

まとめ

ここまででBeamを使った基本的なデータベースの操作を見てきました。できるだけ重複なく様々な書き方を紹介しながらBeamに親しめるように構成を考えたつもりですが、まだ紹介しきれなかった部分も多くあります。一度ユーザーガイドに目を通して見て下さい。

この記事との出会いが読者の方のHaskellライフを充実させるものであれば良いなと思っています。次回はテーブルの結合について学んでいきます。