1対多のテーブルをJOINし、駆動テーブルにLIMITを付ける

あけましておめでとうございます。
2022年も、オンラインコンサルタントをよろしくお願いいたします。

さて、弊社の開発の中で、1対多のテーブルをJOINし駆動テーブルの方でLIMITをしたいということがありました。かなり苦戦したので、ここに解決方法を記載しておきます。
イメージは下記です。

<イメージ>
とあるSNSのユーザーと、そのフォロワー情報を一覧で取ってくる。
【要件】
・ユーザーとフォロワーのid、名前、ログインID、パスワードを取得する
・取得するユーザー数(フォロワーの数は関係なし)の上限を100件とする

・usersテーブル

idnamelogin_idpassword
1
SNS太郎snstarou123456
2SNS次郎snsjirou123456
3SNS三郎snssaburou123456
4SNS四郎snssirou123456

・followersテーブル

followee_idfollower_id
12
13
24

LIMITなしで情報を取得しようとすると、下記のようなSQLになります。

SELECT
    followee.id, followee.name, followee.login_id, followee.password,
    follower.id as follower_id, follower.name as follower_name,
    follower.login_id as follower_login_id, follower.password as follower_password
FROM
    users AS followee
LEFT JOIN
    followers
ON
    followee.id = followers.followee_id
LEFT JOIN
    users AS follower
ON
    follower.id = followers.follower_id

この結果に対して、取得するfolloweeの数を制限したいのですが、今回のレコード数が「フォローフォロワー関係の数」となり、followeeのLIMITをすることができません。。。

今回は、GROUP_CONCATを使った方法で解決しました。
下記のSQLです。

SELECT
    followee.id, followee.name, followee.login_id, followee.password,
    GROUP_CONCAT(COALESCE(follower.id, 'NULL') ORDER BY follower.id) AS follower_ids,
    GROUP_CONCAT(COALESCE(follower.name, 'NULL') ORDER BY follower.id) AS follower_names,
    GROUP_CONCAT(COALESCE(follower.login_id, 'NULL') ORDER BY follower.id) AS follower_login_ids,
    GROUP_CONCAT(COALESCE(follower.password, 'NULL') ORDER BY follower.id) AS follower_passwords
FROM
    users AS followee
LEFT JOIN
    followers
ON
    followee.id = followers.followee_id
LEFT JOIN
    users AS follower
ON
    follower.id = followers.follower_id
GROUP BY followee.id
ORDER BY followee.id ASC
LIMIT 100

これにて、ユーザーとフォロワーの情報を取得し、LIMITを付けることもできました。

あとがき

今回、SNSの例を使ったんですが、実際にはフォロワーの数が膨大になるので今回の解決方法は現実的でないですね。。。
1つのデータに対するJOIN数が少ない場合は、今回の方法が役に立つかと思います。
もっと良い方法があるという場合は、コメントいただけると嬉しいです。

PHPStorm 自分が一つ前にいたブランチ名を取得する

大した話じゃなくって恐縮なんですが💦。

プルリクとかを見るたびに、しょっちゅうブランチを切り替えていると、自分がどのブランチにいたのかわからなくなりませんか?

例えば

①自分の作業 hogehoge ブランチ
②プルリクが来た相手のブランチ someone ブランチ

となっていて、someoneブランチに切り替えてプルリクをレビューした後に、いざ自分の作業に戻るとき

「あれ?なんてブランチにいたんだっけ…。」

ってよくなります。

ローカルのブランチも50個ぐらいずらっと並んでいて、目視で探すことができません。hogehogeブランチとかパンチの聞いた名前ならいいんですが、大体忘れがちな名前をつけてしまっています。

自分が今いるブランチを見るなら、

git log

とかでできるんですけどね。

自分が前にいたブランチを見る方法がわかりませんでした。

今も、めちゃいい方法とは思いませんが、下記の方法でやってます。

(もっといい方法があれば教えてください。m(_ _)m)

私はGitのクライアントはPHPStormを使ってますので、それでのやり方を紹介します。

①左下のGitタブをクリック
②Logタグをクリック
③下記のBranchというところをAllにします。そうすると、全員のコミットの歴史が見れますのでそこから自分のコミットを見ると、ブランチ名が書いてあります。

④隣のUserというところでも絞り込みができますので、ここで「me」を選ぶと自分のコミットの歴史だけ見せてくれます。コミットログがめちゃ並んでて自分のコミットがわからない場合は使ってください。

PowerAutomateでTeamsにアダプティブカードを投稿する

PowerAutomate使ってますか?
なんか、めちゃくちゃ便利ですなやつです。officeやTeams周りの面倒な手作業を自動化してくれます。
最近、個人的に波が来ていていろんな作業を自動化することに役立てています。


0. アダプティブカードとは?

正直なところ、「アダプティブカード」と聞いてピンと来る人は少ないと思います。
ざっくりアダプティブカードとは、見た目がいい感じのカードです。少しざっくり過ぎたかもしれませんが、なんでもできるのでこういう言い方が良いような気がします。
応答の結果を表示したり、ユーザーに入力してもらうフォームを用意出来たり、なんでもできます。今回、作成したアダプティブカードのサンプルはこんな感じです↓


1. アダプティブカードを作ろう

Teamsに投稿する場合、ターゲットのバージョンを1.2にする必要があります。
(画面右上)

ターゲットを1.2にすることだけ忘れなければ、基本的に問題はありません。
あとは、左側のアイテムを選んでカードに追加していくだけです。

※入力を受け付ける場合「id」を設定する必要があります。


2. Teamsにアダプティブカードを投稿する

  • 1. アダプティブカードの内容をコピーしておく
  • 2. PowerAutomateのアダプティブカードを送信可能な処理を選択
  • 3. 1でコピーしたアダプティブカードを、メッセージに張り付ける

これでアダプティブカードをTeamsに投稿できます!

アダプティブカードに入力欄を追加している場合、「アダプティブカードを投稿して応答を待機する」を設定
それ以降の処理で、動的なコンテンツとしてアダプティブカードで入力した内容を動的なコンテンツとして利用することができます。


オリジナルのカードを投稿できると、なんだかうれしい気持ちになりますよね😊

ユニットテストメモ(概要や注意点など)

ユニットテストをつくってみて、注意点など忘れそうなのでメモにまとめました。
どこまでテストをつくるかは難しいですが、必要な箇所をチームメンバーと確認して漏れがないようにカバーしておくべきかと思います。

〇ユニットテストについて
範囲を絞った状態の自動化されたテストをつくることでコード上の欠陥をみつけることができます。
・1つのテストに1つのアサーションをして、どこの部分がダメかわかるようにする。(テストの失敗の原因を分かりやすくする)

OCでは、UnitTestsとIntegratedTestsの2つの自動化されたテストがあります。

<UnitTests>
メソッドのテストのみを行う。
→クラスのメソッドに引数を渡して期待通りの処理をするかなど

・基本的には、getterやsetter以外のpublicメソッドは全てテストをつくる。

・privateメソッドのテストについては、重要なものであればテストつくったほうがよい。

・別クラスなどからメソッドが呼び出されている場合は、そのメソッド自体と呼び出し元のメソッドの両方ともテストをつくる。
例)以下の場合であれば、saveDriverStatusもinsertStatusDBもupdateLastDriverStatusもテストを作る。

public function saveDriverStatus($status){

 $isSuccess = $this->insertStatusDB($status);

 if($isSuccess){

  $this->updateLastDriverStatus($status);

 }

}

<IntegratedTests>
大きな機能そのものを一連の流れを通してテストします。
→1つの大きな機能を最初から最後まで実行して問題なく機能するか(シナリオテストも含む?)
→複数のメソッドまたはクラスを組み合わせても問題ないかなども確認する
→範囲の大きい他ファイルを実行するようなテストにしてしまうとどこで欠陥が発生しているか分かりづらいので、範囲を作成した機能部分のみにするなど対象範囲を広げすぎないようなテストにする

・機能の設定でON/OFFなどの条件があるものは、それぞれの条件でのテストをつくる
例)「配送先が近くにある場合でも停滞検知するか」という設定項目がある場合→設定ONでのテストと設定OFFでのテストをそれぞれ作成

プロパティにオブジェクトを持つオブジェクトをクローンする

つい最近の開発でハマったポイントだったので、こちらに記載しておきます。

とある変数にオブジェクトを格納した後、その変数を他の変数に代入したい。そんなときがあると思います。
しかしただ代入するだけだと、下記のような挙動をし、思っていたのとは違う結果を生みます。

<?php
class classA {
    public $name = 'classA';
    public $info = '1つ目のクラス';
}

$a = new classA();
$b = $a;
$b->info .= ' 変更をしました';

echo $a->name . ' ' . $a->info . PHP_EOL; // classA 1つ目のクラス 変更をしました
echo $b->name . ' ' . $b->info . PHP_EOL; // classA 1つ目のクラス 変更をしました

(かなり細かい話になるので、ここは飛ばしても良いかもしれません。)
この現象の理由は、$aに入っているのは「オブジェクト」ではなく、「オブジェクトが入っているメモリへのアドレス」だからです。
$bにメモリへのアドレスが代入され、$b->infoの変更は、アドレス先にあるオブジェクトを変更するという意味になります。
$aと$bが指しているオブジェクトは同じものになるので、$bの変更が$aにも反映されているのです。

これを防ぐには、下記の「clone」を用います。

<?php
class classA {
    public $name = 'classA';
    public $info = '1つ目のクラス';
}

$a = new classA();
$b = clone $a; // ココがポイント!!!
$b->info .= ' 変更をしました';

echo $a->name . ' ' . $a->info . PHP_EOL; // classA 1つ目のクラス
echo $b->name . ' ' . $b->info . PHP_EOL; // classA 1つ目のクラス 変更をしました

さて、ここから今回の本題になります。
下記のコードは、先ほどのクラスにオブジェクトのプロパティを追加したものです。

<?php
class ParentClass {
    public $name = 'ParentClass';
    public $info = '親クラス';
    public ChildClass $child;

    public function __construct()
    {
        $this->child = new ChildClass();
    }
}

class ChildClass {
    public $name = 'ChildClass';
    public $info = '子クラス';
}

$a = new ParentClass();
$b = clone $a;
$b->info .= ' 変更をしました';
$b->child->info .= ' 変更をしました';

echo $a->name . ' ' . $a->info . ' ' . $a->child->name . ' ' . $a->child->info . PHP_EOL;
// ParentClass 親クラス ChildClass 子クラス 変更をしました
echo $b->name . ' ' . $b->info . ' ' . $b->child->name . ' ' . $b->child->info . PHP_EOL;
// ParentClass 親クラス 変更をしました ChildClass 子クラス 変更をしました

clone後、$bのParentClassとChildClassに変更を加えています。
すると、なんとParentClassの変更は$aに反映していないのに、ChildClassの変更は$bに反映されています。

ちなみに、var_dumpの結果は以下のようになります。(上:$a、下:$b)
ParentClassは$aと$bで違っていますが、中身のChildClassは同じになっています。

object(~~Class)の後の、[]に注目!

ChildClassもcloneしたい場合、下記のようにします。

<?php
class ParentClass {
    public $name = 'ParentClass';
    public $info = '親クラス';
    public ChildClass $child;

    public function __construct()
    {
        $this->child = new ChildClass();
    }

    // ココがポイント!!!
    public function __clone()
    {
        $this->child = clone $this->child;
    }
}

class ChildClass {
    public $name = 'ChildClass';
    public $info = '子クラス';
}

$a = new ParentClass();
$b = clone $a;
$b->info .= ' 変更をしました';
$b->child->info .= ' 変更をしました';

echo $a->name . ' ' . $a->info . ' ' . $a->child->name . ' ' . $a->child->info . PHP_EOL;
// ParentClass 親クラス ChildClass 子クラス
echo $b->name . ' ' . $b->info . ' ' . $b->child->name . ' ' . $b->child->info . PHP_EOL;
// ParentClass 親クラス 変更をしました ChildClass 子クラス 変更をしました

ParentClassに__clone()というメソッドを追加します。これにより、ParentClassのインスタンスがcloneされるときに、$childもクローンしてねという指示を出すことができます。

var_dumpの結果も、以下の通りです。