rhanda | 元銀行員Web系エンジニアの日記

実務未経験からWeb系受託開発企業に転職したひよっこエンジニアが覚えたことや日々の感情を残すブログ

request specのファイル名は複数形・単数形どちらにすべきか

request specを新たに追加するとき、これまであまり意識せずにファイル名を/複数形_spec.rb(ex. /users_spec.rb)としていたが、既存のrequest specで単数リソースの場合には/単数形_spec.rbとしているものもあることに気づいてどうすべきか迷ったことがありました。

という相談をしたところ、

rspec-railsについてくるジェネレータは複数形で作るようになっている(pluralizeしている)ので、複数形で問題なさそう」
https://github.com/rspec/rspec-rails/blob/v6.0.1/lib/generators/rspec/request/request_generator.rb#L13

 target_path('requests', "#{name.underscore.pluralize}_spec.rb")


とコメントを頂いて、根拠の探し方が勉強になりました。

『プログラミングTypeScript』読みました

構成

1章 イントロダクション
2章 TypeScript:全体像
3章 型について
4章 関数
5章 クラスとインターフェース
6章 高度な型
7章 エラー処理
8章 非同期プログラミングと並行、並列処理
9章 フロントエンドとバックエンドのフレームワーク
10章 名前空間とモジュール
11章 JavaScriptとの相互運用
12章 TypeScriptのビルドと実行
13章 終わりに

感想

TypeScriptの概要や用語、型が安全になるとどう有益なのかが説明されていて勉強になりました。
全体としては詳細な説明やコードの例が割合多く掲載されていて、今後書くときにリファレンスとして使うことも多そうだと感じました。

メモ

  • TypeScriptは、よくあるミスをチェックすることでプログラムをより安全にする。
  • ここでいう安全とは型安全性のことを指し、「型を使ってプログラムが不正なことをしないように防ぐこと」を意味する。
  • TSC(TypeScriptコンパイラー) は、バイトコードではなく JavaScriptコードへコンパイルする
  • TypeScriptは、構文に関するエラーと型に関するエラーをコンパイル時にスローする。実際にはそれらのエラーはコードエディターの中で入力するとすぐに表示される。
  • 実行時ではなくコンパイル時にエラーが出るため、ミスに気づいて修正するフィードバックのループがスピードアップする
  • 全てのものの型がわかっている時に最も効果を発揮するが、全てわかっている必要はない
    • しかしコードベース移行の真っ最中とかでなければ100%を目指すべき
  • 型が何であるかを明示的にTypeScriptに伝えるにはアノテーションを使う。アノテーションは「値: 型」という形式をとる。
let a: number = 1
let b: string = 'hello'
let c: boolean[] = [true, false] 

エイリアス

  • 変数宣言(let, const, var)を使って値の別名(エイリアス)となる変数を宣言できるのと同様に、ある方を指し示す型エイリアスを宣言することができる。
type Age = number

type Person = {
  name: string
  age: Age
}
// Ageはただのnumberだが、これによってPersonという形状の定義が理解しやすくなるメリットがある。

タプル

  • 配列のサブタイプ(派生型)
  • タプルは固定調の配列を型付けするための特別な方法で、その配列の各インデックスでの値は特定の既知の型を持つ。
  • 他の多くの型と違い、タプルを宣言する時には明示的に型付けする必要がある。それはJavaScriptにおけるタプルと配列の構文は同じであり(いずれも角括弧を使う)、TypeScriptには角括弧から配列の方を推論するためのルールが既に存在するため。
// 名前・名字・生まれ年のタプル 
let b: [string, string, number] = ['taro', 'yamada', 1995]
  • タプルは省略可能な要素や、可変長の要素もサポートしている。

呼び出しシグネチャ

  • 関数の型についての構文、型シグネチャとも呼ばれる。
  • 型レベルのコードだけを含み、値はなし。

(a: number, b: number) ⇒ number

// function greet(name: string)
type Greet = (name: string) => string

オーバーロードされた関数

  • 複数の呼び出しシグネチャを持つ関数
  • JavaScriptは動的な言語であるため、特定の関数を呼び出すために複数の方法が存在するのがよくあるパターンで、出力される方が入力される引数の方によって変わることもある
  • TypeScriptはこのダイナミズムを静的な型システムを使って具現化する
  • ブラウザのDOM APIの表現にはオーバーロードが使われがち
type Reserve = {
  (from: Date, to: Date, destination: string): Reservation
  (from: Date, destination: string): Reservation
}

let reserve: Reserve = (
  from: Date,
  toOrDestination: Date | string,
  destination?: string
) => {
  // ...
}

インターフェース - 型エイリアスと同様に、型に名前をつけるための方法

E2Eテストで意識したいこと

  • beforeでブラウザ操作しない。
    • 一連の操作自体がテスト対象なのでscenarioにすべて書く
    • これはrequest specでも同じで、テスト対象のリクエストをbeforeには書かない
  • コストが非常に高い(遅い、壊れると直すのに時間がかかる)ので、最もありうるユースケースとなる正常系の操作だけをテストする
    • それ以外はrequest specでテストする
    • 場合によって異常系のテストも許容できることもあるが、404 が出るだけのテスト等はrequest specで十分

『オブジェクト指向でなぜつくるのか 知っておきたいOOP、設計、アジャイル開発の基礎知識』読みました

構成

第1章 オブジェクト指向はソフトウエア開発を楽にする技術
第2章 オブジェクト指向と現実世界は似て非なるもの
第3章 OOP を理解する近道はプログラミング言語の歴史にあり
第4章 OOP は無駄を省いて
第5章 メモリの仕組みの理解はプログラマのたしなみ
第6章 OOP がもたらしたソフトウエアとアイデアの再利用
第7章 汎用の整理術に化けたオブジェクト指向
第8章 UML は形のないソフトウエアを見る道具
第9章 現実世界とソフトウエアのギャップを埋めるモデリング
第10章 擬人化して役割分担させるオブジェクト指向設計
第11章 オブジェクト指向から生まれたアジャイル開発
第12章 オブジェクト指向を使いこなそう
補章 関数型言語でなぜつくるのか

メモ

歴史

  • 黎明期には機械語でプログラムを書いていた
    • 1940年代、プログラマは自身が機械語を使って1行1行コードを書いていた
    • 当時のコンピュータは巨大で、コンピュータの前にそれを収納する建物の建設から始めたという話があるほどだったため、機械語で地道に書いても間に合った
  • アセンブリ言語の登場
    • 無機質な機械語を人間が理解しやすい記号に置き換えて表現し、プログラミングの効率向上を試みた
    • しかし命令を少し間違えただけでもプログラムが暴走してしまう等の問題があった
  • 高級言語の発明
    • 命令を一つ一つ記述するのではなく、より人間にわかりやすい形式で表現
    • 1957年に FORTMAN、1960年に COBOL が登場
    • プログラミングの生産性や品質が向上も、それ以上にコンピュータの普及と発展の進行が爆発的であったため、生産性向上ニーズは収まらず
      • 1960年代後半には NATO の国際会議で「20世紀末には世界の総人口がプログラマになっても、増大するソフトウエアへの需要に追いつかない」というソフトウエア危機が宣言されたほど

以上のような経緯で、ソフトウエア危機に対応するため、さまざまなアイディアや新しい言語が提案された。

進化の方向は保守性と再利用性重視へ

  • 高級言語までの進化では「コンピュータにやらせたい仕事をいかに簡単に、かつ人間に親しみやすい方法で表現するか」を追求してきたが、それだけではソフトウエア危機を救えなかった。
  • 次の構造化言語(PascalC言語 etc.)への進化は「保守性を向上させるための進化
  • 背景は、プログラムの寿命が最初の想定よりも長くなったこと。
  • コンピュータ登場当初は、毎回ゼロからプログラムを作り直していた。
  • しかしソフトウエアに対する要求がどんどん膨らみ、プログラムの規模が大きくなってくると毎回全てを作り直すのでは間に合わなくなった。
  • そのため、出来上がったプログラムに修正を加えながら対応するケースが増加した。

構造化言語では解決できない問題を打破したのが OOP(Object-Oriented Programming)だった

OOP への進化まとめ

  • 高級言語
    • 命令の高級化による表現力向上およびサブルーチンによる重複ロジック排除を実現した
  • 〜構造化言語
    • プログラムを保守するための機能を強化し、サブルーチンの独立性を高める仕組みなどが導入された
  • OOP
    • 品質向上のために制約をつけて複雑さを避ける機能を用意した。加えて部品化や再利用の機能を大幅強化した

OOP は決して従来のプログラミング技術を置き換えるものではなく、以前のものを基盤として欠点を補い、品質が高く、保守しやすく、再利用しやすいソフトウエアを作るために考案されたものである。

デザインパターン

優れた設計のアイディアを、後から再利用できるように名前をつけて文書化したもの。

再利用部品群とデザインパターンの、発展の流れ

OOP によって汎用性の高いソフトウエア部品群の再利用と、アイディアやノウハウの再利用の2つの再利用技術がもたらされた。

  1. OOP を利用して再利用部品群が作られる
  2. 再利用部品群に共通して現れる設計のアイディアを抽出したデザインパターンが登場
  3. 再利用部品群を作るために、デザインパターンを利用

優れたソフトウエアを手早く作るためのアジャイル宣言

  • ウォーターフォール開発プロセスの限界に対応するため、XP やスクラムなど軽量な反復型開発手法がたくさん登場した。そしてこれらをひっくるめて「アジャイル開発手法」と名づけて推進しようとする動きが起きた。
    • アジャイルagile)」は機敏・敏捷を意味する言葉
    • 無駄な作業を省き、優れたソフトウエアを手早く作ることを主眼に置くためにその名がつけられた
  • 2001年にこれらの開発手法の提唱者や推進者たちが、共同でアジャイルソフトウエア開発宣言をまとめた。

アジャイル開発

アジャイル開発はオブジェクト指向から生まれた

このように、アジャイル開発はオブジェクト指向に直接関係ないとはいえ、実際には結びつきがある

『ファシリテーションの教科書』読みました

構成

chapter01 ファシリテーション − 変革リーダーのコアスキル
chapter02 議論の大きな骨格をつかむ
chapter03 参加者の状況を把握する
chapter04 「論点」を広く洗い出し、絞り、深める
chapter05 合意形成・問題解決のステップでファシリテーションを実践する
chapter06 発言を引き出し、理解する
chapter07 発言を深く理解する
chapter08 議論を方向づけ、結論づける
chapter09 対立をマネジメントする
chapter10 感情に働きかける
chapter11 ファシリテーションは「合気道

メモ

本書は、課長や部長など上司の立場の人間が、部下たちに意欲的に会議へ参加してもらいたいといった場合における、リーダーとしてのスキルを説明している。

目指す像:ファシリテーター型リーダー

  • ファシリテーションは単なる会議進行の技法ではなく、リーダーの必須スキル
  • メンバーが考えるべき重要な「問い」を提示し、メンバーの思考投入量を最大化し、知恵と意欲を引き出す



ファシリテーションとは

メンバーの腹落ち感を作るための核となる、コミュニケーションの営み

「腹落ち」とは

「目的と理由」を深く理解し、「具体的なあるべき姿」を自ら描き、「ワクワク感や当事者意識」を持てるレベルで納得すること。

  • 経営層など上位の方針や目的を、理由や背景とともに具体的に理解する
    • 上位の目的を理解して初めて、現場で具体的判断ができる
  • そのために自分のあるべき姿を具体的にする
  • 自分がやりたいと思えるようにする

議論をする理由

アクション(行動)に向けた合意の形成

  • 決定内容の合理性向上
    • 考え方をぶつけて、優れたアイディアを生み出す
  • 決定プロセスの納得性を高める
    • 様々なトレードオフがある中で何故そうしなければならないのか。腹落ち感

出発点と到達点を明確化する

  • 何を中心に議論すべきなのか
  • 各論点にどの程度時間をかけるのか
  • 結果それぞれの発言をどう位置づけ、どう反応すべきかを判断する

会議を行う理由が明確になると、腹落ち感も向上する。

ファシリテーターは演出家

  • なかなか発言しづらい人、もしくは立場上不利な意見を言わざるを得ない人を把握しておいて、その人が発言できる流れを考える。
「私はB部長の意見をこう受け取ったのですが、実際に実行されるAさんからするとそれで大丈夫ですか?」

(もしAさんが反論を述べてそれにB部長が反発を覚えても、いざとなればファシリテーターのまとめ方のせいにできる振り方)

  • 放っておいても話す人はむしろ逆を考える。

メンバー全員が議論に参加している状態を作り、誰も「聞くだけのその他大勢」にならないように努力する。

論点とは

  • その主張や意見は、どういった問いに答えているのか
  • どんなポイントについて考えた結果、導かれたものなのか

ということ。

論点の洗い出し

議論されるテーマ自体から、その問いの答えを出すにあたって判断を左右するポイントを洗い出す。 階層化ができたら、議論すべき論点とそうでない論点の峻別し、絞り込む。 議論の最初のポイントは、その論点についての議論は議論のゴールに至る上で、合意形成の必要があるかどうか。

# 論点の階層化例)

我がチームは〇〇選手を獲得すべきか?
- 獲得したい選手か?
    - 能力は高いか
        - テクニックはあるか
        - 体力はあるか
    - 人気はあるか
        - ファンサービスを行なっているか
- 我がチームは獲得できるのか?
    - 希望年俸は払えるのか
    - 在京チーム志望ではないか

論点の把握とコントロールが難しい

参加者が論点を明確に意識し、正しく共有した状態で議論が進むことはそう多くない。 ファシリテーターはそんな状況下で適切に進行することが求められる。
(💭とても共感した)

何が問題かを洗い出す。

最初は why ではなく where

  • 人がどんどん辞めている。
  • それってそもそも問題なのか
  • 即時補充できないことが問題なのか
  • 長期間勤めてくれないことが問題なのか

ファシリテーターが問題について当事者意識を強く持ち、多くの論点や切り口、仮説を持つ必要。 答えはメンバーに出して貰えば良いので、ファシリテーターが全て用意する必要は無い。

where がでたら why

  • いろんな視点、立場からたくさん意見を出してもらう
  • そこからどれが最も影響が大きいか、有ると無いとで差が出るか、と絞り込んでいく。
  • ファシリテーターは、議論で出てくる意見に漏れや偏りがないかを判断するスピードを上げるために、そのテーマに関する因果関係を推定した上で、仮説を持っておく必要がある。

アクションを選択するフェーズ

  • まずオプション(出された選択肢)を評価する基準について合意する必要がある (ex. 効果の大きさ、スピード、工数、コストなど何をもって決めるか)

  • いつ、誰が、何をするのか」を具体化し認識を揃える。 (💭 当たり前にも感じられるが、欠如しがちらしい)

  • アウトプット(行動)イメージを明確に

調整の仕方を決めておく

どれだけ具体的にプランニングしても、作業に着手すると遅延や調整すべき点は出る。 要協議・決定事項が生じた際に、「誰がいつどのように決めるのか」を合意しておく

発言を引き出す

  • なぜこの議論の場が設けられているのかを伝える
  • なぜこのメンバーが呼ばれているのかを伝える
  • 「〜〜な立場からの意見が欲しい」と振ってみる
  • 賛成に傾いているときは反対の意見を聞く

など。 ファシリテーターが以上を意識することから生まれる「本当に聴きたいという姿勢」 人間は自分の話を聞いてくれる・理解しようとしてくれる人の方が話をしたいと感じるもの

発言者の発言目的を理解する

発言には論理的目的だけでなく心理的・感情的側面の目的もある。 (自分の存在をアピールしたい、喜びや悲しみ・怒りを理解されたいなど) ここに目を向けることで、真意を掴みやすくなる

相手の論理を完成させることを考える。

  • 例えば「アメリカと日本の消費者の嗜好は似ている」という前提の上で 「このサービスはアメリカで成功したから日本でも成功する」という論理であっても、 発言者がその前提を当たり前だと思っていて説明を飛ばしてしまうことも多い。
  • 前提を飛ばしているかもしれないということがわかっていると、 「それだけでそう言えるでしょうか?状況が異なれば同様に成功するとは限りません」 といった正しい結論を導く問いかけができる。
  • 対立する意見があったときに、相手の主張が理解できないのは何故か合意形成のために何の確認が必要なのか論理のどの部分に合意が必要なのかといった点を具体的にして、議論が進むよう促すことが必要。

ファシリテーターはこのような話の聞き方が必要。 揚げ足を取るとか批判をするのではなく、正しい意思決定のためにあるべき論理を追求したいという態度。


議論を方向づける


ファシリテーターが議論すべきと思っている論点が話題に出ると、反射的に自分の意見を述べたりして、メンバーの発言機会を奪ってしまうことがある。 活発に発言している状態であれば、ファシリテーターは参加者が自由に発言できる空気を保つためにSTAYが良さそう。

①広げる

話の広げ方のワーディングに注意
言葉選びを誤ると、メンバーの思考を意図しない方向へ誘導してしまう

  • 論点を広げる(論点を示した上で、他に論点があるか?)
  • 意見を広げる(論点を示した上で、反対もしくは別の主張を求める)
  • 根拠を広げる(論点と結論を示した上で、別の理由づけを求める)
  • 情報を広げる(前の情報を位置付けた上で、5W1H などの軸を使って、具体的情報を引き出す)

②深める

  • どの論点には合意ができていて、何について対立があるのかを整理し、考えるべきことを絞り込んでいく作業。
  • 議論をする目的をメンバーと共有し、討論の勝ち負けでなく、本来の目的に沿った姿勢でメンバーが議論に臨めるように働きかける。

③止める

議論を効率的に進めて、メンバーの集中力を維持するために以下を実施

  • 今議論すべきでない論点(現状持ちうる情報では十分な検討ができない、上位の者に相談確認が必要など)を切り上げて本来議論すべき論点へ戻す。
  • メンバーが「止められた」ことへに負の感情を抱かないよう細心の配慮をする。
  • 説得ではなく問いかける。
「XX という論点について 〜〜 という意見ですね」
「XX という論点について結論を出すためには、〜〜 という論点について考える必要がありそうですね。」
「では 〇〇 という論点についてはどう思いますか」

  などと自然な修正を試みる。

  • なかなか動かない場合は「今ここで 〜〜 という論点の結論を出すべきと思われる理由は何でしょうか」など。

その議論を止める必要性を相手に気づいてもらうというスタンス。 今議論すべきでない論点への対応は、まず発言者の意見を論点に転換し、明確にすることからスタート。

④まとめる・結論づける

行動につなげる。決まったことと決まっていないことを峻別して示し、そして誰がいつまでに何をするのかを明確に表現して、メンバーに確認する。

議論がまとまらなかった時

  • どんな論点に関してどこまで結論が出たのか
  • 合意と行動に至るために残された課題は何か

を明らかにした上で、「残された課題を解決し、次回合意に至るためにはどうなれば良いのか?」を明確にした「条件付き合意」を形成する

ステップは次の通り

  1. 合意できた論点と結論を確認する
  2. 合意できていない論点を確認する
  3. 合意できていない論点について、さらに論点を細分化し、どの部分は合意できていて、どこが合意てできていないかを確認・共有する
  4. 合意できていない論点について合意するために、どんな条件が揃えば良いのかを考え、確認する
  5. 合意に必要な条件を満たすための検討(上司や特定部門への検討実施、懸念点への対応可否検討、情報収集など調査の実行など)を依頼し、次回もしくは条件が明らかになった時点で報告してもらうよう依頼する

対立のマネジメント

対立が発生する主な理由は、

  1. 触れる情報の違い
  2. 情報の解釈の違い
  3. 判断基準の違い(優先順位。ex. 顧客対応スピードか、生産効率かコストか)

合意可能なレベルまでその業務の目的やあるべき姿を遡り、段階的に合意をつくっていく。 (ex. 営業・生産はそもそも何のためにやっているのか、より上位の方針上、今は何が重要なのかを問いかける)

感情の重要性

  • 発言したいと感じさせる自由かつ創造的雰囲気
  • 多くの意見が多くのメンバーから出て、スピーディに議論が進む活発な空気
  • そして自分が馬の一員として他のメンバーから受け入れられ、自分の発言が価値あるものとして受け取られるという安心感

安心感

  • 相手の目を見て話を聞く
  • 名前を呼ぶ
  • 考え発言するのに時間的余裕を与える
  • 出た発言をまずしっかり受け止め
  • 内容の是非に関わらず、発言への感謝と共感を示す

といった振る舞いを心がける



感想

立場が異なると優先する価値判断が異なるといったような忘れがちな前提の話から、話の振り方の具体例など細かいところまで言語化された説明がなされていて、とてもわかりやすかったです。
大きなポイントとして、ビジネスにおける議論の目的は「行動の決定」であることを再認識できました。

また、自分がメンバーの時にメンバーの役割を果たすようにしたいと思いました。
どういう発言や行動が求められて、自分がこの議論の場に呼ばれているのかを意識したいです。
さらに、ファシリテーターが論点や方向を見失っているかもしれないと感じた時に、それを思い出させるといった、補佐するような役割もできたら尚よさそうだと感じました。
(文中でも、ファシリテーターは「今この議論を続けるべきか」「他の論点に移るべきか」「意見を述べたそうにしている人はいないか」等々非常にいろんなことを考えていて、キャパシティがほとんど残っていないことも多いとあった)
「今って〜〜の話でしたっけ」みたいな確認程度でも、ファシリテーターやメンバーに気づきのキッカケを与えられたりしそうだと思いました。

今回は上司としてのリーダーが参加者である部下をまとめることが主な事例で、今の自分の立場やスタンスとはやや異なる部分もあったので、 ぜひ他のファシリテーションの書籍も読んでみたい。

Rubyの正規表現で ASCII印刷可能文字を表現する


range を使ってできました。

(' '..'~').to_a.join
# => " !\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"


使用できる文字をASCIIの0x20から0x7Eまで(半角空白を含む半角英数記号)に制限したいことがありました。

ググっても目ぼしい情報を見つけられず手で直接書いていたのですが、 抜け漏れがないかどうかの検証も難しく、 なんとか機械的にできないものかと考えていたところ、この書き方を教えてもらってとても便利🥺と思いました。

メタ文字をエスケープして期待通り使用できました。

> ASCII_PRINTABLE_CHARS = (' '..'~').to_a.join

> /\A[#{Regexp.escape(ASCII_PRINTABLE_CHARS)}]+\z/
# => /\A[\ !"\#\$%&'\(\)\*\+,\-\.\/0123456789:;<=>\?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]\^_`abcdefghijklmnopqrstuvwxyz\{\|\}~]+\z/


感想

文字コードを使っているんだもんなあと後になって強く感じました。

("0x20".to_i(16).chr.."0x7E".to_i(16).chr).to_a.join
=> " !\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"




『SQL実践入門 高速でわかりやすいクエリの書き方』読みました

構成

1章 DBMSアーキテクチャ 〜この世にただ飯はあるか
2章 SQLの基礎 〜母国語を話すがごとく
3章 SQLにおける条件分岐 〜文から式へ
4章 集約とカット 〜集合の世界
5章 ループ 〜手続型の呪縛
6章 結合 〜結合を制するものはSQLを制す
7章 サブクエリ 〜困難は分割すべきか
8章 SQLにおける順序 〜甦る手続型
9章 更新とデータモデル 〜盲目のスーパーソルジャー
10章 インデックスを使いこなす 〜秀才の弱点

メモ

DBMS

  • 多くのトレードオフのバランスを取るために努力を重ねているミドルウェア
    • ディスクへの同期処理をすれば整合性・耐障害性は向上するが、パフォーマンスが低下
    • パフォーマンスを追求するとその逆。など
  • SQLの実行時間の大半は、I/Oに費やされる
    • I/Oコストが膨らむのを回避すべき
    • <- UNION を使った表現はデメリットが大きい。など
      • SELECT文を複数回発行したり、テーブルへの余分なアクセスを発生させて、ストレージのI/Oコストも消費
      • (UNION のメリットが勝るシーンも存在)
      • IN や CASE をうまく使うとテーブルへのスキャンを減らせる可能性

SQL の基礎

  • SQLの特徴は、行の集合単位でひとまとめにして記述できること
    • これが最もよく現れるのが GROUP BY句 と HAVING句
    • GROUP BYについてパフォーマンス上の注意点は、集約に使われるのがソートであれハッシュであれ、メモリを多く使用する演算であり、十分なワーキングメモリが確保できないと、スワップが発生してストレージ上のファイルが使用されることになり、大幅な遅延が発生すること
  • GROUP BY はカットと集約の2つの機能から成り立っている
  • GROUP BY と CASE 式を組み合わせると、強力な表現力

ウインドウ関数

  • 集約機能を省いた GROUP BY句
  • カットを行った後、その対象の集約をしないため、出力結果の行数が入力となるテーブルの行数と変わらない

ぐるぐる系とガツン系

  • SQL にはループ処理が無い
    • RDB考案者のCodd「関係操作では、関係全体をまとめて操作の対象とする。目的は繰り返し(ループ)を無くすこと」
    • 要はSQL は最初に考えられた時から「ループを無くそう」という発想で作られた言語だった
  • ぐるぐる系
    • ループに依存したコードのこと
      • ループが無くて困ったユーザーが、1レコードずつアクセスする細かいSQLをループで回し、ビジネスロジックはホスト言語側で実装する。という手段で生み出されることが多い。
      • ex. 1行ずつレコードにアクセスするSELECT文をループさせる、1行ずつテーブルを更新する。etc
  • ガツン系
    • 複数行を一度に処理するSQL
  • 基本ガツン系の方が良さそうだが、ここも両方にメリデメあってトレードオフ
    • ガツン系、パフォーマンスは良いが複雑になって保守性低下など
    • ぐるぐる系、基本SQLのパース回数や並列分散のしづらさなどから、パフォーマンス劣る
      • 実行計画安定、処理時間見積りやすい等の利点も

結合

  • クロス結合
    • 全ての結合の母体。2つのテーブルのレコードから、可能な全ての組み合わせ網羅する演算。
  • 内部結合
    • クロス結合の結果の一部、つまり部分集合。
  • 外部結合
    • マスタ側のテーブルにのみ存在するキーがあった場合、それを削除せず結果に保存するよう動作する。その行はクロス結合の結果のどの行とも一致せず、外部にはみ出している。←ことから外部結合
結合はSQLの性能問題の火薬庫
  • SQLで結合演算を行う時には、内部で選択されるアルゴリズムを基準に結合を考えることになる
  • オプティマイザが選択可能な結合アルゴリズムには大きく3種類
  • 基本は Nested Loops というアルゴリズム
  • 結合対象となるテーブルを「駆動表」、もう一方のテーブルを「内部表」と呼ぶ。
  • Nested Loopsの性能を改善するキーワード「駆動表に小さなテーブルを選ぶ」
    • ↑ の前提として「内部表の結合キーの列にインデックスが存在すること」
    • 内部表の結合キー列にインデックスがあれば、そのインデックスを辿ることによって、DBMSは駆動表の1行に対して内部表を馬鹿正直にループする必要がなくなる。
    • いわば内部表のループをある程度スキップできる。
  • 結合キーが内部表に対して一意な場合、内部表のループを完全にスキップできる。
    • (等値結合であれば、内部表のアクセス対象行を必ず1行に限定できる。)
  • こうして考えてみると、「駆動表を小さく」という格言は、裏返して「内部表を大きく」という風に解釈するとわかりやすい。
    • <- 内部表が大きいほどに、インデックスによるループのスキップ効果が大きくなるから。
結合を減らす
  • オプティマイザが失敗することもある。
  • RDBの原則として、実行計画は統計情報からオプティマイザが自動的に組み立てることになっている。
  • それが最適なものにならないこともしばしば
  • 典型例は、データ量の増加等によって統計情報が変化し、ある一定の閾値を超えたところでオプティマイザが実行計画を変化させることによって起こるもの
  • そしてこれを最も引き起こしやすい演算が結合。
  • 従ってSQLの性能変動リスクを抑えるには、「なるべく結合を回避する」ことが重要な戦略になる
サブクエリ
  • サブクエリの計算コストが上乗せされることや、その計算結果のI/Oコストがかかること、インデックスなどの情報がないことから最適化を受けられないなどの問題点がある
  • シンプルな実行計画ほど性能が安定する

上記の通り、結合を使うことは、変動リスクを抱え込むこと。ウインドウ関数などで代替してパフォーマンスを改善できる可能性がある。

インデックス

  • インデックス利用が有効かの判断に用いられる、2指標
    • カーディナリティ(ばらつき具合)の高さ
      • 最も高いのは、全て値が異なる一意キーの列
      • 最も低いのは、値が一種類のみしか存在しない列
    • 選択率の低さ
      • すなわち少ない行に絞り込めること(5~10%が目安らしい)
      • 例)100件のレコードを持つテーブルにおいて、一意キーに対して等号指定すれば必ず1件に絞り込める時、この条件の選択率は1%
  • 選択率が極めて高い場合にインデックスを使うと、テーブルフルスキャンより遅くなることもある
  • インデックスが使用できないパターンも存在
    • 中間一致・後方一致のLIKE述語、IS NULL述語を使っている etc

インデックスが使用できない場合の対処

  • 一つに、UI設計による対処
  • 入力フォームを多めに用意して、ユーザーが自由度高く入力条件を組み合わせられるようにし、選択率の高い検索条件を許容するよう実装。
    • -> 例えば店舗IDで検索するときには受付日も入力必須とすれば、絞り込みがかなり効くようになる。
  • したがって、どんな検索条件があり得るかを、ユーザに対する業務要件やユーザインタフェース等も勘案して考える必要がある。



感想

トレードオフの話が多く「〜〜場面ではXXを優先してOO対処をする」のような考え方にたくさん触れることができ、こういう言語化をできるようになっていきたいと感じました。

また、UIにおいて検索時の必須フォームを作ることで検索時の絞り込みが効くようにし、インデックスを使って良いSQLが発行されるようにする話について、「そっち側からののアプローチもあるのか」と非常に面白いと思いました。