Galapagos Tech Blog

株式会社ガラパゴスのメンバーによる技術ブログです。

SQLの条件分岐について検証してみた

はじめまして、AIR Design for Marketing 事業部 バックエンドエンジニアの大田です。 好きな言語はC#、好きなRDBMSPostgreSQLです :)

前置き

RDBから何かしらのリストを取得する際にユーザが指定した任意の条件で絞り込むというのは一般的なユースケースだと思いますが、それを実現するクエリの構築方法はいくつかあるかと思います。 私がパッと思いつくのは以下のような方法です。

※ コードはイメージです。動作確認などはしていません 🙏🏼

パターン1: 条件毎にクエリを分割し、文字列結合で WHERE 句を組み立てる (以下 "文字列結合")

conditions = list("TRUE")
params = dict()

if name != "":
  conditions.append("name LIKE '%' || :name || '%'")
  params.append(name)

if email != "":
  conditions.append("email = :email")
  params.append(email)

condition = " AND ".join(conditions)
query = "SELECT * FROM account WHERE " + condition
result = connection.execute(text(query), params)

パターン2: クエリビルダを使って WHERE 句を組み立てる (以下 "クエリビルダ")

query = account.select()

if name != "":
  query = query.where(account.c.name.like("%" + name + "%")

if email != "":
  query = query.where(account.c.email == email)

result = connection.execute(query)

パターン3: WHERE 区の中で条件分岐を使う (以下 "条件分岐")

query = """
SELECT * FROM account
WHERE
  (:name = '' OR name LIKE '%' || :name || '%')
  AND (:email = '' OR email = :email)
"""

params = dict(name=name, email=email)
result = connection.execute(text(query), params)

私はSQLインジェクションのリスクを減らすという観点から クエリビルダ もしくは 条件分岐 を使っているのですが、JOIN するテーブルが多くなるようなケースでは比較的見通しが良くなるという理由で 条件分岐 を採用することが多いです。

この 条件分岐 ですが、他の選択肢と異なる点として「ユーザが指定していない条件に関する式もクエリに含まれる」ことが挙げられると思います。 これによって、他の選択肢では考慮する必要のない「評価しなくていい式も評価してしまっているかも?」という懸念が(私の中で)出てきました 🤔

そうは言っても RDBMS は賢いので問題ないとは思いつつ、せっかくなので評価しなくていい式が無視されているかどうか検証してみることにしました。

検証結果

先に書いてしまいますが、単純な条件分岐を用いれば評価しなくていい式は無視されます 😃

RDBMS は賢いです。これからも信頼して使っていきましょう。

検証

今回は PostgreSQL 14.5 を使用します。

pg_sleep 関数

pg_sleep 関数は名前の通り指定した秒数 sleep してくれる関数で、クエリの結果が返ってくる時間が指定した秒数かかっているかどうかによって式が評価されているかどうか分かるという、検証に際して難しいことを考えなくていいありがたい選択肢です 😃

(余談ですが、この関数は PostgreSQL ドキュメント内の Date/Time Functions and Operators ページに載っています。そこかぁ 🤔)

実行時間

(クエリの実行時間を見るために、 \timing on (doc)を使っています。)

postgres=# SELECT 1 WHERE TRUE OR (SELECT pg_sleep(1)) IS NOT NULL;
 ?column? 
----------
        1
(1 row)

Time: 4.354 ms

期待通り、1秒経たずに結果が返ってきていますね 😄

postgres=# SELECT 1 WHERE FALSE OR (SELECT pg_sleep(1)) IS NOT NULL;
 ?column? 
----------
        1
(1 row)

Time: 1015.302 ms (00:01.015)

こちらは pg_sleep(1) が評価された結果、1秒以上経ってから結果が返ってきていますね。

実行計画

念の為、実行計画も見ておきましょう。

postgres=# EXPLAIN SELECT 1 WHERE TRUE OR (SELECT pg_sleep(1)) IS NOT NULL;
                QUERY PLAN                
------------------------------------------
 Result  (cost=0.00..0.01 rows=1 width=4)
(1 row)

postgres=# EXPLAIN SELECT 1 WHERE FALSE OR (SELECT pg_sleep(1)) IS NOT NULL;
                    QUERY PLAN                    
--------------------------------------------------
 Result  (cost=0.01..0.02 rows=1 width=4)
   One-Time Filter: ($0 IS NOT NULL)
   InitPlan 1 (returns $0)
     ->  Result  (cost=0.00..0.01 rows=1 width=4)
(4 rows)

FALSE OR ... では右辺が評価されているのがわかりますね 👀

単純な条件分岐であれば、しっかりと短絡処理が効いてくれているようです 😃

最後に

ガラパゴスでは一緒に働く仲間を募集しています!

ガラパゴスでの働き方に関する私のインタビュー記事もありますので、もし良ければ読んでみてください 😃

note.com

ご興味のある方はぜひご応募いただけますと嬉しいです。

aws-vault を使ってマルチアカウント環境でも快適にコンソール画面へログイン

お疲れ様です、TECH石浪です。

みなさんAWSはマルチアカウントにしていますか?

株式会社ガラパゴスも僕がJOINした頃と比べると社員数が3倍を超え、入社当時は1アカウントで開発本番両方をVPCで区切って扱っていましたが、今ではプロダクト毎+本番/開発と各環境ごとにAWS アカウントを用意する正気な開発になりました。

さて、そうなるとAWSアカウントの切り替えどうやろうってなりますよね、自分はずっとBASTIONユーザーからスイッチロールするJumpアカウントの手法を使っていました。

参考記事: マルチアカウントな AWS環境のマネジメントコンソールへのアクセス方法をまとめてみた | DevelopersIO

しかしこの切り替え、環境が増えるとちょっと、いやかなり面倒なんですよね。 そこでaws-vaultを使ってみたところ、めっちゃよかったので紹介したいと思います。

続きを読む

Jetpack Composeの状態管理クラスを調べてみた

ガラパゴスに入社して2ヶ月弱、AIR Design for Apps事業部 Androidチームの務台です。

私は現在JetpackComposeとMVVMアーキテクチャを用いたプロジェクトに携わっていますが、ふと疑問が沸きました。

ViewModelでの状態管理って、どのクラス使うのが良いんだ?

というわけで調べてみました。

結論

StateLiveDataFlowRxJava2のいずれかを使用することができるが、 ViewModelでの状態管理には基本的にはFlowを用いると良い。

ただし、Composable内でクローズな状態管理をする、XMLのレイアウトからJetpackComposeに移行する等、 他のものを使用した方が良いケースもある。

調べたこと

以下、状態管理クラスについての調査結果になります。

使用可能なクラス

ViewのComposable関数からViewModelの状態を監視して値を更新していくためには、ViewでState<T>として取得できるクラスである必要があります。 これはAndroid Developersガイドによると

  • State
  • LiveDate
  • Flow
  • RxJava2

のいずれかが使用可能です。

この中でStateLiveDataAndroidに依存したライブラリであり、ライフサイクルに関する問題はライブラリが良きに計らってくれるので、実装者は意識する必要がなくなります。

FlowRxJava2はKotlin(Java)のライブラリで、本来は非同期処理を行うためのものですが、JetpackComposeの拡張関数により状態管理にも使用することができるようになりました。 これによって、ViewModelやModelでの状態管理を一貫してFlowまたはRxJava2を使用することができます。

各クラスの調査

状態管理に使用可能な4種類のクラスについてそれぞれ使い方や特徴等を調べてみました。

State

状態管理に使用することができるものの中で最もシンプルなクラスで、mutableStateOf()インスタンスを作成することができます。

Stateを利用した状態管理の例:

// ViewModel.kt

val messageState = mutableStateOf<String>("")

fun onChangeMessage(newValue: String) {
    messageState.value = newValue
}


// View.kt

val receivedMessageState by viewModel.messageState

TextField(
    value = receivedMessageState,
    onValueChange = viewModel::onChangeMessage,
)

onChangedMessageMutableState.valueが変更されると、その値を使用しているComposableへ通知が飛び、値が更新されます。

かなりシンプルに使うことができる分、非同期処理や複数の状態を関連させる等の複雑な動きが絡んでくると管理が難しくなりそうです。

また、View側でrememberと組み合わせて、Composableの外部に公開しない、ステートフルな状態を管理する用途に使用することもできます。

rememberと併用した例:

// View.kt

val messageState = remember { mutableStateOf<String>("") }

TextField(
    value = viewModel.messageState.value,
    onValueChange = {
        messageState.value = it
    },
)

LiveData

監視可能なデータホルダクラスで、ライフサイクルに応じた監視が可能であるため、余計な心配事を減らすことができるクラスです。

ViewModel側にLiveDataを置き、View側でobserveAsState()を呼ぶことで、ViewModelでの変更を監視してComposableへ通知を送ることができます。

LiveDataを利用した状態管理の例:

// ViewModel.kt

private val _messageState = MutableLiveData<String>("")
val messageState: LiveData<String> = _messageState

fun onChangeMessage(newValue: String) {
    _messageState.value = newValue
}


// View.kt

val receivedMessageState by viewModel.messageState.observeAsState("")

TextField(
    value = receivedMessageState,
    onValueChange = viewModel::onChangeMessage,
)

比較的簡単に使用することができるため、とりあえず動かしてみたいという場合や、 DataBindingを用いた既存プロジェクトをJetpackComposeへ移行する場合 に使用することができるかと思います。

Flow

Flowを用いた状態管理にはStateFlowを用いることができます。

coroutineの一種であるFlowを状態管理の用途に絞ったものがStateFlowで、 suspend関数とは異なり複数の値を順次出力することができます。

ViewModel側にStateFlowを置き、View側でcollectAsState()を呼ぶことで、ViewModel側での変更をComposableへ通知することができます。

StateFlowを用いた状態管理の例:

// ViewModel.kt

private val _messageState = MutableStateFlow<String>("")
val messageState: StateFlow<String> = _messageState

fun onChangeMessage(newValue: String) {
    viewModelScope.launch {
        _messageState.value = newValue
    }
}


// View.kt

val receivedMessageState by viewModel.messageState.collectAsState()

TextField(
    value = receivedMessageState,
    onValueChange = viewModel::onChangeMessage,
)

今回調査した中では最も後発のクラスで、LiveDataFlowを組み合わせたような使い方が可能です。

また、以下のようにcombineを用いて関連する状態をまとめて監視することもできます。

combineを用いて二つのメッセージの合計文字数を管理する例:

// ViewModel.kt

private val _firstMessageState = MutableStateFlow<String>("")
val firstMessageState: StateFlow<String> = _firstMessageState

private val _secondMessageState = MutableStateFlow<String>("")
val secondMessageState: StateFlow<String> = _secondMessageState

val messageCountState: StateFlow<Int> = combine(
    _firstMessageState,
    _secondMessageState,
) { firstMessage, secondMessage ->
    firstMessage.count() + secondMessage.count()
}.stateIn(viewModelScope, SharingStarted.Eagerly, 0)

fun onChangeFirstMessage(newValue: String) {
    viewModelScope.launch {
        _firstMessageState.value = newValue
     }
}

fun onChangeSecondMessage(newValue: String) {
    viewModelScope.launch {
        _secondMessageState.value = newValue
    }
}


// View.kt

val receivedFirstMessageState by viewModel.firstMessageState.collectAsState()
val receivedSecondMessageState by viewModel.secondMessageState.collectAsState()
val receivedCountState by viewModel.messageCountState.collectAsState()

TextField(
    value = receivedFirstMessageState,
    onValueChange = viewModel::onChangeFirstMessage,
)
TextField(
    value = receivedSecondMessageState,
    onValueChange = viewModel::onChangeSecondMessage,
)
Text(text = receivedCountState.toString())

非同期によるデータの取得からFlowを使うことで、一貫してFlowのみで状態管理を行うことができるため、 ViewModelでの状態管理にはこれを利用するのが良いのではないかと思います。

RxJava2

Reactive ExtensoionsJavaで実装したライブラリで、これに拡張機能を追加したRxKotlinというライブラリも存在します。

Reactive Extensionsとは、Observable<T>Observer<T>インターフェースを用いて非同期通信を行うことでデータを受け渡そう、という考えみたいですが、調べても理解が難しかったためあまり触れないでおきますごめんなさいゆるして

RxJava2では様々なユースケースで非同期処理を行うことができる機能が多く備わっているようです。

RxJava2を用いた状態管理の例:

// ViewModel.kt

private val _messageState: PublishSubject<String> = PublishSubject.create()
val messageState = _messageState

fun onChangeMessage(newValue: String) {
    viewModelScope.launch {
        _messageState.onNext(newValue)
    }
}


// View.kt

val receivedMessageState by viewModel.messageState.subscribeAsState("")

TextField(
    value = receivedMessageState,
    onValueChange = viewModel::onChangeMessage,
)

上記のように状態管理に使用することはできますが、多機能故に学習コストが高くなってしまい、現在では似たようなことを簡単にできるFlowがあることから、新しく採用する場面はほぼ無いのではないかと思います。

まとめ

JetpackComposeでの状態管理には基本的にFlowを使うのが良いのではないかと思いますが、 他のクラスも以下の表のように、状況によっては使用することがありそうです。

クラス名 用途
State シンプルなViewModelでの状態管理、rememberと併用してクローズな状態管理
LiveData 既存プロジェクトのJetpackComposeへの移行
Flow ViewModelでの状態管理、データソースの非同期での取得
RxJava2 データソースの非同期での取得

最後に

弊社ではいっしょに働くアプリエンジニアを募集しています!
ご興味のある方はぜひご応募いただけますと嬉しいです。

Androidエンジニア募集中!: www.wantedly.com
リードエンジニア(AndroidiOS問わず)も募集中!:www.wantedly.com