2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SQLが10倍速くなる!医療統計系アプリの“重すぎるクエリ”を救った5つのパフォーマンス最適化術

Posted at

はじめに

日々の業務で、アプリケーションが「遅い」「応答しない」といった問題に直面することは少なくない。
特に医療統計系アプリケーションにおいては、数十万〜数百万件の医療データを扱う大規模クエリが存在し、レスポンスが数十秒以上かかるケースもあった。

本記事では、実際の現場で遭遇した「重すぎるSQLクエリ」の最適化事例をベースに、“再設計不要で即効性がある”5つのパフォーマンス改善Tipsを紹介する。


アプリケーション構成(背景)

  • DBMS:SQL Server 2019
  • クエリ対象:複雑なCTE + サブクエリ + CROSS JOINを含むデータ集計
  • データ量:年間3000万件超(複数医療機関 × 日別記録)
  • アプリ層:C#(ASP.NET Blazor)からSQLを実行し、可視化・グラフ出力

BEFORE:遅すぎたクエリの例(実行時間:35秒)

WITH cte1 AS (
    SELECT ...
    FROM D_患者
    JOIN D_入院情報 ...
    WHERE ...
),
cte2 AS (
    SELECT ...
    FROM cte1
    JOIN D_診療 ...
    WHERE ...
)
SELECT ...
FROM cte2
CROSS JOIN D_病棟
WHERE ...
  • CROSS JOINによる全組み合わせ爆発
  • 3階層のネスト+大量データ
  • カラム関数使用によるINDEX無効化

✅ 効果のあった最適化Tips 5選


不要なCROSS JOINを排除 or INNER JOINにリファクタ

Before

FROM cte2
CROSS JOIN D_病棟

After

FROM cte2
JOIN D_病棟 ON cte2.hp_code = D_病棟.hp_code
  • 無条件結合は想定外に行数を爆発させ、ソート・グループ処理のボトルネックに
  • 「病棟ごとに絞ったレコードのみ対象にすればよい」=INNER JOINで限定的に処理

📈 実行時間:35秒 → 9秒(約74%削減)


CTE内での集計とJOINの順序を見直す

-- ✖ NG:JOIN後にGROUP BY(不要な行までJOIN対象)
WITH base AS (
  SELECT ...
  FROM D_診療 a
  JOIN D_入院 b ON ...
)
SELECT dept, COUNT(*) FROM base GROUP BY dept
-- ◎ OK:GROUP BY先行、JOIN対象を事前に絞る
WITH filtered AS (
  SELECT dept, patient_id
  FROM D_診療
  WHERE act_date BETWEEN '2025-01-01' AND '2025-01-31'
  GROUP BY dept, patient_id
)
SELECT ...
FROM filtered
JOIN D_入院 ON ...
  • JOIN対象の件数を減らすだけで劇的に高速化
  • 実行計画上の“行数見積り”が小さくなり、オプティマイザが賢く動く

📈 実行時間:9秒 → 3.5秒


関数をWHERE句から追い出す(INDEX殺しの排除)

-- ✖ NG
WHERE CONVERT(VARCHAR, act_date, 112) = '20250101'

-- ◎ OK
WHERE act_date = '2025-01-01'
  • SQL Serverではカラムに関数を使うとインデックスが無効化
  • 型変換・TRIM・CASTなども基本NG(事前にデータを整える設計が必要)

📈 2倍以上の高速化に繋がるケースあり


フィルタ対象をINではなくJOIN/EXISTSで書き換え

-- ✖ NOT IN(NULLを含むとすべて除外)
WHERE dept_code NOT IN (SELECT dept_code FROM M_閉鎖病棟)

-- ◎ NOT EXISTSを使う
WHERE NOT EXISTS (
  SELECT 1 FROM M_閉鎖病棟 b 
  WHERE b.dept_code = a.dept_code
)
  • サブクエリ内にNULLがあるとIN/NOT INは破綻する
  • EXISTSの方が安定性・パフォーマンスともに優れることが多い

読み取り専用処理では WITH (NOLOCK) を使い分ける

-- 読み取りブロックを回避(SELECT専用処理限定)
FROM D_患者 WITH (NOLOCK)
  • 大規模な読み取り時に共有ロックによる待機やデッドロックを回避
  • ただしトランザクション中の不整合読み込みには注意

📈 複数ユーザーの同時参照時に待機時間を削減


AFTER:最適化されたクエリ(実行時間:35秒 → 2.8秒)

  • 不要なCROSS JOINを削除
  • CTE設計を見直し、GROUP BY順序を改善
  • WHERE句の最適化+関数除去
  • EXISTS+NOLOCK活用

💡最終的に12倍高速化達成。


おわりに

アプリケーションのパフォーマンスは、フロントやバックエンドだけでなく、SQLクエリの設計によって根本的に改善できることが多い
特に業務系や統計系アプリでは、クエリ1つで待機時間が数十秒変わるのは日常茶飯事。

今回紹介したTipsは、すべて「今すぐ現場で使える」ものばかり。
もしこの記事が役に立ったら、いいねやストック・コメントで応援してもらえると嬉しい。


💬 コメント歓迎!

  • 実務でも使っているTipsありますか?
  • 他にもこんな改善法あるよ!というのもぜひ教えてください!
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?