RDBでHas Manyな関係のレコードを取得するときはクエリ分割したいよねって話だったんだけど、
なんかキャッチーっぽい感じに直した。
前提
対象
SELECT
S.id,
S.name,
T.name AS tag_name,
T.id AS tag_id
FROM Subjects_Tags AS S_T
LEFT JOIN Subjects AS S ON S_T.subject_id = S.id
LEFT JOIN Tags AS T ON S_T.tag_id = T.id
WHERE
S_T.subject_id IN ( /* id埋め込み */ )
ORDER BY
S.id, T.id
;
$records = $db->query(/* 省略 */);
<?php $lastSubjectId = null; ?>
<div>
<?php foreach($records as $record): ?>
<?php if ($lastSubjectId === null): ?>
<h3>name:<?php echo $record['name'] ?></h3>
<h4>Tags</h4>
<ul>
<?php elseif ($lastSubjectId !== $record['id']): ?>
</ul>
</div>
<div>
<h3>name:<?php echo $record['name'] ?></h3>
<h4>Tags</h4>
<ul>
<?php endif; ?>
<li><?php echo $record['tag_name'] ?></li>
<?php endforeach; ?>
<?php if ($lastSubjectId !== null): ?>
</ul>
<?php endif; ?>
</div>
倒す
Query結果のデータ構造を整理する
foreachの中でタグが完結していない。if elseも相まって読みにくい。適切なデータ構造になっていないせいだ。
$records = $db->query(/* 省略 */);
// [id => record] の構造に変換
$subjects = [];
foreach($records as $record){
if(isset($subjects[$record['id']])){
continue;
}
$record['tags'] = [];
unset($record['tag_id']);
unset($record['tag_name']);
$subjects[$record['id']] = $record;
}
foreach($records as $record) {
$subject_id = $record['id'];
$record['id'] = $record['tag_id'];
$record['name'] = $record['tag_name'];
unset($record['tag_id']);
unset($record['tag_name']);
$subjects[$subject_id]['tags'][] = $record;
}
<?php foreach($subjects as $subject): ?>
<div>
<h3>name:<?php echo $subject['name'] ?></h3>
<h4>Tags</h4>
<ul>
<?php foreach($subject['tags'] as $tag): ?>
<li><?php echo $tag['name'] ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endforeach; ?>
Queryを分割する
都度わざわざ本来の名前に戻したり、側面に合わせてunset
しなくてはならない。レコードがSubjectの側面とTagの側面を同時に持っているせいだ。
SELECT *
FROM Subjects
WHERE id IN ( /* id埋め込み */ )
;
SELECT S_T.subject_id, Tags.*
FROM Subjects_Tags as S_T
LEFT JOIN Tags ON S_T.tag_id = Tags.id
WHERE S_T.subject_id IN ( /* id埋め込み */ )
;
$s_records = $db->query(/* 省略 */);
// [id => record] の構造に変換
$subjects = [];
foreach($s_records as $record){
$record['tags'] = [];
$subjects[$record['id']] = $record;
}
$t_records = $db->query(/* 省略 */);
foreach($t_records as $record) {
if (empty($subjects[$record['subject_id']])) {
// Updateが挟まれなければ滅多にcontinueされない
continue;
}
$subjects[$record['subject_id']]['tags'][] = $record;
}
成果
- 構造化データを利用することで、テンプレート部分の可読性が向上した。
- SQLからカラムの別名を排除した。
リスク
- クエリ分割によって、ひとかたまりのデータ塊ではなくなった。分割前とくらべてデータが不安定になる。
- クエリ分割した分のオーバーヘッドが発生する。