LoginSignup
1
0

More than 5 years have passed since last update.

倒せウンコード in PHP: MySQLから取ったレコード結果をそのまま使うやつ

Last updated at Posted at 2017-06-19

RDBでHas Manyな関係のレコードを取得するときはクエリ分割したいよねって話だったんだけど、
なんかキャッチーっぽい感じに直した。

前提

  • 次のテーブル構造での話

    • Subjects --(1:n)-+ Subjects_Tags +-(n:1)-- Tags
    • capture.PNG
  • PHPにてSelect結果は(連想)配列で得られるものとする。

対象

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からカラムの別名を排除した。

リスク

  • クエリ分割によって、ひとかたまりのデータ塊ではなくなった。分割前とくらべてデータが不安定になる。
  • クエリ分割した分のオーバーヘッドが発生する。
1
0
11

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
1
0