1
1

More than 3 years have passed since last update.

[PHP×PostgreSQL]DBのレコードをCSV出力する

Last updated at Posted at 2021-02-14

PHPで、DBから取り出したレコードをCSV出力するサンプルコードです。

環境

  • OS: Amazon Linux2
  • PHP: 7.4.11
  • PostgreSQL: 11.5

※今回は、Amazon Linux2でLNPP環境を構築する | Qiitaで構築した、LNPP環境を使用しています。

ディレクトリ構成と処理概要

/product/share
     |
    ccc
     |----dbCsv.php → 処理プログラム
     |
     |----config.ini → 設定ファイル(PostgreSQLへの接続情報を記載)
     |      
     | 
     |----csv                   
            |----data_[yyyymm].csv → 出力CSV(yyyymm形式:年月ごとに出力, 文字コード:SJIS)              
                      :
                      :

DBから、前月1ヶ月間のデータ(アンケート結果)を取得し、文字コード:SJISでcsv出力する内容になります。

テーブルデータの準備

まず、PostgreSQLにログインし、スキーマとテーブルを作成します。

-- ホスト: 127.0.0.1, ポート番号: 5432のデータベース: postgresにロール名: postgresでログイン
psql -h 127.0.0.1 -p 5432 -U postgres -d postgres
-- スキーマ: myschemaを作成
create schema myschema;

-- スキーマ: myschemaに会員情報テーブル: membersを作成
create table myschema.members (
  member_id int not null
  , name varchar(255) not null
  , sex char(1) not null
  , age smallint not null
  , created_by varchar(255)
  , created_at date
  , update_by varchar(255)
  , update_at date
  , constraint members_PKC primary key (member_id)
) ;

-- スキーマ: myschemaにアンケート結果テーブル: questionnaire_resultsを作成
create table myschema.questionnaire_results (
  member_id int
  , questionnaire_id bigint
  , choice1 varchar(255)
  , choice2 varchar(255)
  , choice3 varchar(255)
  , created_by varchar(255)
  , created_at date
  , updated_by varchar(255)
  , updated_at date
  , constraint questionnaire_results_PKC primary key (member_id,questionnaire_id)
  , foreign key (member_id) references myschema.members(member_id)
) ;

テーブルの作成まで、完了したら、対象のスキーマに、対象のテーブルが存在しているか、確認しておきましょう。

-- スキーマへのパスを変更
SET search_path TO myschema;
-- 現在のスキーマを確認
select current_schema;
-- 現在のスキーマに存在するテーブル一覧を表示
\dt;

作成したテーブルにデータを投入します。
アンケート結果テーブル: questionnaire_resultsからは、先月1ヶ月分(本記事の執筆が2021年2月なので、2021年1月分)のデータを取得するため、更新日時:updated_atに、境界値を意識した値を入れます。

-- 会員情報テーブル: membersにデータを投入
insert into myschema.members values (1,'相川健太','m',21,'webuser','2020/12/28 12:03:45','webuser','2020/12/28 12:03:45');
insert into myschema.members values (2,'井上和子','f',18,'webuser','2020/12/30 11:43:54','webuser','2020/12/30 11:43:54');
insert into myschema.members values (3,'宇野拓人','m',32,'webuser','2021/01/09 13:26:11','webuser','2021/01/09 13:26:11');
insert into myschema.members values (4,'榎本翼','m',28,'webuser','2021/01/10 09:33:57','webuser','2021/01/10 09:33:57');
insert into myschema.members values (5,'恩田栞','f',26,'webuser','2021/01/10 16:54:05','webuser','2021/01/10 16:54:05');

-- アンケート結果テーブル: questionnaire_resultsにデータを投入
insert into myschema.questionnaire_results values (2,1001,'伊達巻','田作り','昆布巻き','webuser','2020/12/31 23:59:59','webuser','2020/12/31 23:59:59');
insert into myschema.questionnaire_results values (1,1001,'お雑煮','数の子',null,'webuser','2021/01/01 00:00:00','webuser','2021/01/01 00:00:00');
insert into myschema.questionnaire_results values (5,1001,'栗きんとん','伊達巻','黒豆','webuser','2021/01/15 16:37:42','webuser','2021/01/15 16:37:42');
insert into myschema.questionnaire_results values (3,1001,'海老','お雑煮','黒豆','webuser','2021/01/31 23:59:59','webuser','2021/01/31 23:59:59');
insert into myschema.questionnaire_results values (4,1001,'田作り','昆布巻き','伊達巻','webuser','2021/02/01 00:00:00','webuser','2021/02/01 00:00:00');

コードと解説

まず、設定ファイル、config.iniです。
こちらには、DBの接続設定の記載をしています。

config.ini
[db]
;ホスト
host = '127.0.0.1';
;ポート番号
port = '5432';
;DB名
name = 'postgres';
;ユーザー名
user = 'postgres';
;パスワード
pass = 'postgres';

処理本体です。
サンプルなので、読みやすさ重視で、DB周りの処理もDaoに分割せず、同じファイルに書いています。

dbCsv.php
<?php
// 設定ファイルの読み込み
$conf = parse_ini_file('config.ini', true);
// 先月初日
$last_month_start = date('Y-m-d H:i:s', mktime(0, 0, 0, date('m')-1, 1, date('Y'))); 
// 先月末日
$last_month_end = date('Y-m-d H:i:s', mktime(23, 59, 59, date('m'), 0, date('Y'))); 
// 先月(年月のみ)
$last_month = date('Ym', mktime(0, 0, 0, date('m'), 0, date('Y'))); 
// csvファイル名
$filename = '/product/share/ccc/csv/data_'.$last_month.'.csv';

try{

    // csvファイルが存在しない(処理未実施の)場合のみ、処理を実施
    if(!file_exists($filename)){
        $dbh = new PDO('pgsql:host='.$conf['db']['host'].';port='.$conf['db']['port'].';dbname='.$conf['db']['name'].';user='.$conf['db']['user'].';password='.$conf['db']['pass']);
        // 先月1ヶ月分のアンケート結果を取得
        $stmt = $dbh->prepare("SELECT m.member_id as member_id, m.sex as sex, m.age as age, q.choice1 as choice1, q.choice2 as choice2, q.choice3 as choice3 
                                FROM myschema.members m INNER JOIN myschema.questionnaire_results q 
                                ON m.member_id = q.member_id 
                                WHERE q.updated_at BETWEEN :last_month_start AND :last_month_end");

        $stmt->bindValue(':last_month_start', $last_month_start);
        $stmt->bindValue(':last_month_end', $last_month_end);
        $stmt->execute();
        $result = $stmt->fetchAll();
        // csvヘッダ
        $csv = 'member_id,sex,age,choice1,choice2,choice3'.PHP_EOL;
        // csvボディ
        foreach($result as $row){
            $csv .= $row['member_id'].','.$row['sex'].','.$row['age'].','.$row['choice1'].','.$row['choice2'].','.$row['choice3'].PHP_EOL;
        }
        // csv出力(SJIS)
        $fp = fopen($filename, 'w');
        fwrite($fp, mb_convert_encoding($csv,"SJIS"));
        fclose($fp);
    }else{
        echo "すでにファイルが存在しています\n";
    }

}catch(PDOException $e){
    echo "接続に失敗しました\n";
    echo $e;
}finally{
    // 接続をクローズ
    $dbh = null;
}

(1)月初と月末の取得
PHPで月末日を取得する | Qiitaがとても参考になりました。
2月のような短い月でも想定通りの結果となりました。

(2) PDOによるPostgreSQL接続
PDO_PGSQL DSN | PHPマニュアルを参考に記述します。

(3) SQLの実行
今回は、値のバインドを行うので、queryではなく、prepare→値のバインド→executeの流れで処理を実行します。
値のバインドを行うには、bindValueと、bindParamの2種類の方法がありますが、エラーがある場合にその場で判定してくれるなど、メリットが大きいbindValueを用いました。
詳しくは、
bindParam()とbindValue()の違い | Qiita
辺りをご参照ください。

(4) 実行結果の取得
今回は、複数行を取得するので、先頭の1行のみ取得するfetchではなく、fetchAllを使います。

(5) CSV出力
PHPには、fputcsvという行を CSV 形式にフォーマットし、ファイルポインタに書き込んでくれる関数があるのですが、fetchAllで取得したレコードは、例えば以下のように、値に重複が出てしまいます。

fetchAllで取得したレコード(一部抜粋)
array(12) {
  ["member_id"]=>
  int(1)
  [0]=>
  int(1)
  ["sex"]=>
  string(1) "m"
  [1]=>
  string(1) "m"
  ["age"]=>
  int(21)
  [2]=>
  int(21)
  ["choice1"]=>
  string(9) "お雑煮"
  [3]=>
  string(9) "お雑煮"
  ["choice2"]=>
  string(9) "数の子"
  [4]=>
  string(9) "数の子"
  ["choice3"]=>
  NULL
  [5]=>
  NULL
}

そのため、今回は必要な値だけ別途変数に詰め込んで、その後出力することにしました。
PHP_EOLは、改行を表していて、環境に応じて、自動で改行コードを切り替えてくれる定数です。
今回は、Linux環境なので、改行コードは、LFとなります。
fopenを使って対象のファイルを開き、fwriteを使って書き込み処理を行い、fcloseで、ファイルを閉じています。
SJIS形式にしたいのですが、設定の関係上、そのままだと、UTF-8で保存されてしまうため、mb_convert_encodingを使って変換しています。

終わりに

何気に、前月の範囲指定がいちばん難しかったです。
日付や範囲指定は永遠の課題だなぁ・・。

参考

テーブルデータの準備

コードと解説

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