「この商品を買った人はこんな商品も買っています」を実装したい

この商品を買った人はこんな商品も買っています をやりたい。割とマジで。
テンション・リダクション効果があるらしい。Amazonの商品レコメンドやYoutubeの動画

yahoo shopping: この商品を見た人は、こんな商品にも興味を持っています
楽天: この商品を見ている人はこちらもチェック
amazon: この商品に関連する商品, よく一緒に購入されている商品

アルゴリズム
L 協調フィルタリング -> 好みが近い人が好きな商品をオススメする

{ “Aさん”:[ “商品A”, “商品B”}, “Bさん”: [“商品C”, “商品G”, “商品H”], … }
レコメンド対象との共起数をとり、共起数が高い順にオススメとして表示する
お客さんごとに、「Aさんの買った商品の集合」と「そのお客さんの買った商品の集合」の積集合の長さを取る

### ロジックのイメージ
自分の買った商品 a b c
Aさんの買った商品 b c d
Bさんの買った商品 d
Cさんの買った商品 f g
Dさんの買った商品 a e

自分の買った商品と近いのはAさん -> dをレコメンド
次に自分の買った商品と近いのはDさん -> 次にeをレコメンド
まだ購入してない時は売れ筋商品をレコメンド

こんな感じ?

### 配列の持ち方
一緒に買われている商品を配列に持って、カウントしていく

こういうデータがあるとする。
山田 a
山田 b
山田 c
田中 b
田中 f
佐藤 a
佐藤 g
後藤 e
後藤 f

一緒に買われている商品なので、同じ人をforループで回して、商品ごとに一緒に買われている商品をkey(商品)とvalue(一緒に買われている類似度)で追加していく
a => [b =>1, c=>1]
b=>[a=>1, c=>1]
c=>[a=>1, b=>1]
b=>[a=>1, c=>1, f=>1]
f=>[b=>1]
a=>[b=>1, c=>1, g=>1]
e=>[f=>1]
f=>[b=>1, e=>1]

つまり、outputを見ると、まず商品ごとの変数が必要

初期データ
$data => [ $山田[a, b, c], $田中[b, f], $佐藤[a, g], $後藤[e, f]]


$山田の中でforeach, $田中の中でforeach, $佐藤の中でforeach

商品一覧の配列から各商品ごとの一緒に買われている商品の連想配列をkey順にソートして出力

これ、別にpythonじゃなくても何の言語でもできそうやな
まず、注文データから初期データを作って、それからforeachをして、次に出力
とりあえず作ってみよう 頭使うからチョコレート食べながらコーディングしたい

[Laravel8.x] debugログの見方とerrorログの設定

$ less storage/logs/laravel.log

ログに書き込まれるレベル
emergency, alert, critical, error, warning, notice, info, debug

[2020-12-25 11:25:08] laravel.EMERGENCY: Unable to create configured logger. Using emergency logger. {“exception”:”[object] (InvalidArgumentException(code: 0): Log [] is not defined. at /home/vagrant/dev/testApp/vendor/laravel/framework/src/Illuminate/Log/LogManager.php:192)

[2020-12-26 11:55:12] local.ERROR: syntax error, unexpected ‘Route’ (T_STRING), expecting ‘)’ {“exception”:”[object] (ParseError(code: 0): syntax error, unexpected ‘Route’ (T_STRING), expecting ‘)’ at /home/vagrant/dev/testApp/routes/web.php:73)

ログの出力はconfigs/logging.phpに書かれている

'channels' => [
        'stack' => [
            'driver' => 'stack',
            'channels' => ['single'],
            'ignore_exceptions' => false,
        ],

        'single' => [
            'driver' => 'single',
            'path' => storage_path('logs/laravel.log'),
            'level' => env('LOG_LEVEL', 'debug'),
        ],

-> stackのsingleつまり、’LOG_LEVEL’, ‘debug’がlogs/laravel.logに出力されている
-> ログレベル:デバッグ(詳細なデバッグ情報を出力)

エラーのみを出力するファイルを作成する

 'stack' => [
            'driver' => 'stack',
            'channels' => ['single','errors'],
            'ignore_exceptions' => false,
        ],

        'single' => [
            'driver' => 'single',
            'path' => storage_path('logs/laravel.log'),
            'level' => env('LOG_LEVEL', 'debug'),
        ],
        'errors' => [
            'driver' => 'daily',  // 日付ごとにファイルを出力
            'path' => storage_path('logs/errors.log'), // errors-YYYY-MM-DD.log
            'level' => 'error',
            'days' => 28, // ログを保有する日数:28日
        ],

どういう運用をしたいかによると思うが、errorログは最低限出力しておきたい。

[AWS EC2] apache logの見方

ssh ec2-user@${public ip} -i ~/.ssh/***.pem
cat /etc/httpd/conf.d/custom.conf

# アクセスログ
<IfModule log_config_module>
    CustomLog "/var/www/log/access_log" combined
</IfModule>

$ cd /var/www/log/
$ ls
access_log error_log

$ sudo less /var/log/httpd/error_log
$ sudo less /var/log/httpd/access_log

### アクセスログの見方
$ cat /etc/httpd/conf/httpd.conf
LogFormat “%h %l %u %t \”%r\” %>s %b \”%{Referer}i\” \”%{User-Agent}i\” %I %O” combinedio

%h リモートホストのIPアドレス
%l 接続元のユーザー名
%u リモートユーザ
%t アクセスされた日時
\”%r\” アクセスされたファイル
%>s ステータスコード
%b リソースに対する転送量
\”%{Referer}i\” アクセス元のURL
\”%{User-Agent}i\” なんのOSでどのブラウザからアクセスしてきたか

なるほど。

[MySQL8.0.22] 機種依存文字の保存

update users set name=”アイウエオ、カキクケコ” where id=3;

| 3 | NULL | アイウエオ、カキクケコ |

update users set name=”①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳:㍉㍍㌔㌘㌧㌦㍑㌫㌢:㍻㍼㍽㍾:ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ” where id=3;
| 3 | NULL | ①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳:㍉㍍㌔㌘㌧㌦㍑㌫㌢:㍻㍼㍽㍾:

update users set name=”〃 仝 ゝ ゞ 々 〆 ヾ ― ‐ / 〇 ヽ _  ̄ ¨ ` ´ ゜ ゛ \ § ^ ≫ ¬ ⇒ ⇔ ∀ ∃ ∠ ⊥ ⌒ ∂ ∇ ≡ ∨ ≪ † √ ∽ ∝ ∵ ∫ ∬ Å ‰ ♯ ♭ ♪ ‡ ~ ′ ≒ × ∥ ∧ | … ± ÷ ≠ ≦ ≧ ∞ ∴ ♂ ♀ ∪ ‥ ° ⊃ ⊂ ⊇ ∩ ⊆ ∋ ∈ 〓 〒 ※ ″” where id=3;
| 3 | NULL | 〃 仝 ゝ ゞ 々 〆 ヾ ― ‐ / 〇 ヽ _  ̄ ¨ ` ´ ゜ ゛ \ § ^ ≫ ¬ ⇒ ⇔ ∀ ∃ ∠ ⊥ ⌒ ∂ ∇ ≡ ∨ ≪ † √ ∽ ∝ ∵ ∫ ∬ Å ‰ ♯ ♭ ♪ ‡ ~ ′ ≒ × ∥ ∧ | … ± ÷ ≠ ≦ ≧ ∞ ∴ ♂ ♀ ∪ ‥ ° ⊃ ⊂ ⊇ ∩ ⊆ ∋ ∈ 〓 〒 ※ ″

全部いけるんか。すげえ。
続いて、PDO

$name = "〃 仝 ゝ ゞ 々 〆 ヾ ― ‐ / 〇 ヽ _  ̄ ¨ ` ´ ゜ ゛ \ § ^ ≫ ¬ ⇒ ⇔ ∀ ∃ ∠ ⊥ ⌒ ∂ ∇ ≡ ∨ ≪ † √ ∽ ∝ ∵ ∫ ∬ Å ‰ ♯ ♭ ♪ ‡ ~ ′ ≒ × ∥ ∧ | … ± ÷ ≠ ≦ ≧ ∞ ∴ ♂ ♀ ∪ ‥ ° ⊃ ⊂ ⊇ ∩ ⊆ ∋ ∈ 〓 〒 ※ ″";
$name = mb_convert_encoding($name, "UTF-8", "utf-8");
$stmt = $pdo->prepare("INSERT INTO test1(name) VALUES(:name)");
$stmt->bindParam(':name', $name,PDO::PARAM_STR);
$stmt->execute();

mysql> select * from test1;
+—-+————————————————————————————————————————————————————————————————————————————————————————————————————————-+
| id | name |
+—-+————————————————————————————————————————————————————————————————————————————————————————————————————————-+
| 1 | test |
| 2 | アイウエオ、カキクケコ |
| 3 | ①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳:㍉㍍㌔㌘㌧㌦㍑㌫㌢:㍻㍼㍽㍾:ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ |
| 4 | 〃 仝 ゝ ゞ 々 〆 ヾ ― ‐ / 〇 ヽ _  ̄ ¨ ` ´ ゜ ゛ \ § ^ ≫ ¬ ⇒ ⇔ ∀ ∃ ∠ ⊥ ⌒ ∂ ∇ ≡ ∨ ≪ † √ ∽ ∝ ∵ ∫ ∬ Å ‰ ♯ ♭ ♪ ‡ ~ ′ ≒ × ∥ ∧ | … ± ÷ ≠ ≦ ≧ ∞ ∴ ♂ ♀ ∪ ‥ ° ⊃ ⊂ ⊇ ∩ ⊆ ∋ ∈ 〓 〒 ※ ″ |
+—-+————————————————————————————————————————————————————————————————————————————————————————————————————————-+

ちょっと整理する必要があるな。

機種依存文字のHTMLの表示

半角文字、機種依存文字、記号のHTML表示

<p>アイウエオ、カキクケコ</p><br>
		<p>①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳:㍉㍍㌔㌘㌧㌦㍑㌫㌢:㍻㍼㍽㍾:ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ</p><br>
		<p>〃 仝 ゝ ゞ 々 〆 ヾ ― ‐ / 〇 ヽ _  ̄ ¨ ` ´ ゜ ゛ \ § ^ ≫ ¬ ⇒ ⇔ ∀ ∃ ∠ ⊥ ⌒ ∂ ∇ ≡ ∨ ≪ † √ ∽ ∝ ∵ ∫ ∬ Å ‰ ♯ ♭ ♪ ‡ ~ ′ ≒ × ∥ ∧ | … ± ÷ ≠ ≦ ≧ ∞ ∴ ♂ ♀ ∪ ‥ ° ⊃ ⊂ ⊇ ∩ ⊆ ∋ ∈ 〓 〒 ※ ″</p><br>

表示される
続いて、mysqlでテストする

[Laravel8.x] usersテーブルの値がNULLかのMiddlewareを作る

ここではemailが登録されているかの判定をし、登録されていなければ登録画面へ飛ばす
is_nullで判定する。

$ php artisan make:middleware RegisteredEmail
app/Http/Kernel.php

    protected $routeMiddleware = [
        // 省略
        'RegisteredEmail'=>\App\Http\Middleware\RegisteredEmail::class,
    ];

app/Http/Middleware/RegisteredEmail.php

use Illuminate\Support\Facades\Auth;
    public function handle(Request $request, Closure $next)
    {
       $user = Auth::user();
        if(is_null($user->email)){
            return redirect()->intended('/auth');
        }
        return $next($request);
    }

route

Route::group(['middleware' => ['auth','RegisteredEmail']], function(){
	Route::get('/email_test', [AdminController::class, 'emailTest']);
});

なるほど、登録されてなければ登録して、って処理は多いと思う

[MySQL8.0.22] Oracleのデータ型(NUMBERとDATE)を挿入する

OracleのNUMBER(16,4)とDATEをMySQLに挿入したい。

取り敢えずOracle側のテストデータとして以下を用意する。
2021/01/07 10:00:00
1500.0000

mysqlのCreate table

create table oracle_test(
	id int unsigned auto_increment primary key,
	price4 decimal(10,4),
	price2 decimal(10,2),
	datetime_at datetime,
	timestamp_at timestamp
);

mysql> describe oracle_test;
+————–+—————+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+————–+—————+——+—–+———+—————-+
| id | int unsigned | NO | PRI | NULL | auto_increment |
| price4 | decimal(10,4) | YES | | NULL | |
| price2 | decimal(10,2) | YES | | NULL | |
| datetime_at | datetime | YES | | NULL | |
| timestamp_at | timestamp | YES | | NULL | |
+————–+—————+——+—–+———+—————-+
5 rows in set (0.00 sec)

insert into oracle_test(price4) value("1500.0000");
insert into oracle_test(price2) value("1500.0000");
insert into oracle_test(datetime_at) value("2021/01/07 10:00:00");
insert into oracle_test(timestamp_at) value("2021/01/07 10:00:00");

mysql> select * from oracle_test;
+—-+———–+———+———————+———————+
| id | price4 | price2 | datetime_at | timestamp_at |
+—-+———–+———+———————+———————+
| 1 | 1500.0000 | NULL | NULL | NULL |
| 2 | NULL | 1500.00 | NULL | NULL |
| 3 | NULL | NULL | 2021-01-07 10:00:00 | NULL |
| 4 | NULL | NULL | NULL | 2021-01-07 10:00:00 |
+—-+———–+———+———————+———————+
4 rows in set (0.00 sec)

insert into oracle_test(price2) value(“1500.0050”);
| 5 | NULL | 1500.01 | NULL | NULL |

なるほど、mysql側で勝手に変換してくれんのか。
凄いけど、型づけ言語の考え方からいくと、型が完全に一致してないのに入るのは気持ち悪いな。
まあ、取り敢えず良しとしよう。

[Oracle] NUMBER型、DATE型をmysqlに挿入する

### NUMBER型(oracle)とDecimal型(mysql)
NUMBER型
L NUMBER(精度,位取り)単位で、精度:38桁、位取り:-84~127桁のデータを格納
Decimal型
L DECIMAL(精度,位取り)単位で、精度:65桁、位取り:0~30桁

-> 位取りが「負」の場合、あるいは「31以上」が指定されていた場合に桁あふれが発生する可能性あり

### OracleのDATE型とMySQLのtimestamp型、datetime型
DATE型
 L 年月日・時分秒単位で、西暦前4712年1月1日0時0分0秒~西暦9999年12月31日23時59分59秒の期間のデータを格納
date型
 L 年月日・時分秒・マイクロ秒単位で、西暦前1000年1月1日0時0分0.000001秒~西暦9999年12月31日23時59分59.999999秒の期間のデータを格納
-> 西暦前1000年1月1日0時0分0秒より古いデータの有無、及びナノ秒単位のデータの有無に注意する必要あり
datetime型
 L ‘1000-01-01 00:00:00’ ~ ‘9999-12-31 23:59:59’
timestamp型
L ‘1970-01-01 00:00:01’ UTC ~ ‘2038-01-19 03:14:07’ UTC

なんやと、データ連携って、データベースがOracleとMySQLだと全然単純な話じゃないんだな。
参ったぜ。

[PHP7.4.16] CSVからKeyとValueの連想配列を作る

まず、CSVにデータがある

このデータを元に連想配列を作る

$data = [];
$fp = fopen("Book.csv", "r");
while(! feof($fp)){
	$csv = fgets($fp);
	$csv = trim($csv,'"');
	$csv = mb_convert_encoding($csv, "UTF-8", "utf-8");
	$csv = str_replace('"','',$csv);
	$array = explode(",",$csv);
	$data[$array[0]] = $array[1];
}

echo "<pre>";
var_dump($data);
echo "</pre>";

まあOKっぽい。。