kintone でSQLを使う（その３）サブクエリー

[kintone でSQLを使う](http://qiita.com/rex0220/items/4d0c757344e8ac144f8d) の続き。
サブクエリーを試してみました。

# 部品と最新の会社査定を JOIN して、表示

## 部品表の集計結果

![2017-02-27_17h27_56.png](https://qiita-image-store.s3.amazonaws.com/0/100572/0c909db5-0e4c-f250-df5b-45ec57544e72.png)

## 原材料の調達先

![2017-02-27_17h28_20.png](https://qiita-image-store.s3.amazonaws.com/0/100572/c1281c1b-e8fe-f01e-4a0a-e3ce21e01d2e.png)



# 必要ライブラリ

- [alasql.min.js](http://alasql.org/) : JavaScript で SQL が使えます。
- [handsontable](https://handsontable.com/?_ga=1.73131861.714310381.1484906350) : グリッド表示用

# 集計コード

処理概要

- 原材料アプリのレコードを取得
- 調達先の会社名を取得
- 調達先の会社名で査察情報のレコードを取得
- json 構造を簡単化
- SQL で集計（サブクエリー）

クエリーの処理

- サブクエリー：会社の査察年月日を降順に並べて、その先頭（最新日付）の日付・評価を取得
- 最新の査察年月日・評価と原材料を会社名で JOIN して、原材料の名前順に表示

製品の工程情報があれば、工程順に原材料を並べたいところ。


join の後にサブクエリーを書けなかったので、少し配列の順番に注意

```
////////////////////////////////////////////////////////////////////////////
// kintone JavaScript: Summary of kintone records by alasql.
//  2017.02.27 by rex0220
////////////////////////////////////////////////////////////////////////////
(function() {
    "use strict";

    // 一覧表示
    kintone.events.on("app.record.detail.show", function(event) {

        var record = event.record;

        var obj = {};
        var appId1 = 749;
        var q1 = '製品名 = "' + record['製品名'].value + '" and 削除フラグ not in ("削除")';

        // 原材料取得
        fetchRecords(appId1, q1, ['製品名', '原材料名', '会社名']).then(function(records) {
            obj.rs1 = convertToRows(records);
            console.log('rs1', JSON.stringify(obj.rs1));

            var companyNames = {};
            obj.rs1.map(function(row) {
                companyNames['"' + row['会社名'] + '"'] = 1;
            });

            var strNames = Object.keys(companyNames).join(',');

            console.log('会社名', strNames);

            if (!strNames)
                return event;

            // 査察情報取得
            var appId2 = 748;
            var q2 = '会社名 in (' + strNames + ') and 削除フラグ not in ("削除")';
            // var q1 = '削除フラグ not in ("削除")';
            return fetchRecords(appId2, q2, ['会社名', '査察年月日', '査察の評価']);

        }).then(function(records) {
            obj.rs2 = convertToRows(records);
            console.log('rs2', JSON.stringify(obj.rs2));

            // var result0 = alasql(
            //     "SELECT [会社名], FIRST([査察年月日]) as [査察年月日], FIRST([査察の評価]) as [査察の評価]\
            //     FROM (SELECT * FROM ? ORDER BY [会社名], [査察年月日] DESC) \
            //     GROUP BY [会社名]", [obj.rs2]);
            // console.log('r0', result0);

            var result1 = alasql(
                "SELECT  a.[製品名], a.[原材料名], a.[会社名], b.[査察年月日], b.[査察の評価]\
                FROM ( \
                    SELECT [会社名], FIRST([査察年月日]) as [査察年月日], FIRST([査察の評価]) as [査察の評価]\
                    FROM (SELECT * FROM ? ORDER BY [会社名], [査察年月日] DESC) \
                    GROUP BY [会社名] ) as b \
                    join ? as a on a.[会社名] = b.[会社名]\
                ORDER BY a.[製品名], a.[原材料名], a.[会社名]", [obj.rs2, obj.rs1]);
            console.log('r0a', result1);

            // grid 表示
            var grid = kintone.app.record.getSpaceElement('xp-grid');
            new Handsontable(grid, { 
                data: result1, 
                colWidths: [150, 150, 150, 100, 100],
                colHeaders: ['製品名', '原材料名', '会社名', '査察年月日', '査察の評価'], 
                columns: [
                    { data: '製品名' },
                    { data: '原材料名' },
                    { data: '会社名' },
                    { data: '査察年月日' },
                    { data: '査察の評価' },
                ],
                readOnly: true 
            });

        });

        return event;
    });


    // get records
    function fetchRecords(appId, opt_query, opt_fields, opt_offset, opt_limit, opt_records) {
        var query = opt_query || '';
        var offset = opt_offset || 0;
        var limit = opt_limit || 500;
        var allRecords = opt_records || [];
        var params = {app: appId, query: query + ' limit ' + limit + ' offset ' + offset };
        if (opt_fields) params.fields = opt_fields;
        return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params).then(function(resp) {
            allRecords = allRecords.concat(resp.records);
            if (resp.records.length === limit) {
                return fetchRecords(appId, query, opt_fields, offset + limit, limit, allRecords);
            }
            return allRecords;
        });
    }

    // records convert to table rows 
    function convertToRows(records) {
        var rows = records.map(function(record){
            var keys = Object.keys(record);
            var row = {};
            keys.map(function(key){
                row[key] = record[key].type === 'NUMBER' ? Number(record[key].value) : record[key].value;
            });
            return row;
        });
        return rows;
    }

})();
```

# 部品表、部品調達、査察情報アプリに分かれている場合

処理概要

３本のアプリを読み込んで、SQL で組み立ててあげるだけですので、基本は同じです。
無理に３つの配列を JOIN しなくても、２本の JOIN 結果が配列に作って、さらに JOIN してもkintone で扱うデータ件数であれば処理時間はそんなに変わりません。


```
////////////////////////////////////////////////////////////////////////////
// kintone JavaScript: Summary of kintone records by alasql.
//  2017.02.28 by rex0220
////////////////////////////////////////////////////////////////////////////
(function() {
    "use strict";

    // 一覧表示
    kintone.events.on("app.record.detail.show", function(event) {

        var record = event.record;

        var obj = {};
        var appId1 = 749;
        var q1 = '製品名 = "' + record['製品名'].value + '" and 削除フラグ not in ("削除")';

        // 原材料取得
        fetchRecords(appId1, q1, ['製品名', '原材料名']).then(function(records) {
            obj.rs1 = convertToRows(records);
            console.log('rs1', JSON.stringify(obj.rs1));

            var partNames = {};
            obj.rs1.map(function(row) {
                partNames['"' + row['原材料名'] + '"'] = 1;
            });
            var strNames = Object.keys(partNames).join(',');
            console.log('原材料名', strNames);

            // 部品調達情報取得
            var appId2 = 751;
            var q2 = '原材料名 in (' + strNames + ') and 削除フラグ not in ("削除")';
            // var q1 = '削除フラグ not in ("削除")';
            return fetchRecords(appId2, q2, ['原材料名', '会社名']);

        }).then(function(records) {
            obj.rs2 = convertToRows(records);
            console.log('rs2', JSON.stringify(obj.rs2));

            var companyNames = {};
            obj.rs2.map(function(row) {
                companyNames['"' + row['会社名'] + '"'] = 1;
            });

            var strNames = Object.keys(companyNames).join(',');

            console.log('会社名', strNames);

            // 査察情報取得
            var appId3 = 748;
            var q3 = '会社名 in (' + strNames + ') and 削除フラグ not in ("削除")';
            // var q1 = '削除フラグ not in ("削除")';
            return fetchRecords(appId3, q3, ['会社名', '査察年月日', '査察の評価']);

        }).then(function(records) {
            obj.rs3 = convertToRows(records);
            console.log('rs3', JSON.stringify(obj.rs3));

            var result1 = alasql(
                "SELECT  a.[製品名], a.[原材料名], c.[会社名], c.[査察年月日], c.[査察の評価]\
                FROM ( \
                    SELECT [会社名], FIRST([査察年月日]) as [査察年月日], FIRST([査察の評価]) as [査察の評価]\
                    FROM (SELECT * FROM ? ORDER BY [会社名], [査察年月日] DESC) \
                    GROUP BY [会社名] ) as c \
                JOIN ? as b ON b.[会社名] = c.[会社名]\
                JOIN ? as a ON a.[原材料名] = b.[原材料名]\
                ORDER BY a.[製品名], a.[原材料名], c.[会社名]", [obj.rs3, obj.rs2, obj.rs1]);
            console.log('r0a', result1);

            // grid 表示
            var grid = kintone.app.record.getSpaceElement('xp-grid');
            new Handsontable(grid, { 
                data: result1, 
                colWidths: [150, 150, 150, 100, 100],
                colHeaders: ['製品名', '原材料名', '会社名', '査察年月日', '査察の評価'], 
                columns: [
                    { data: '製品名' },
                    { data: '原材料名' },
                    { data: '会社名' },
                    { data: '査察年月日' },
                    { data: '査察の評価' },
                ],
                readOnly: true 
            });

        });

        return event;
    });


    // get records
    function fetchRecords(appId, opt_query, opt_fields, opt_offset, opt_limit, opt_records) {
        var query = opt_query || '';
        var offset = opt_offset || 0;
        var limit = opt_limit || 500;
        var allRecords = opt_records || [];
        var params = {app: appId, query: query + ' limit ' + limit + ' offset ' + offset };
        if (opt_fields) params.fields = opt_fields;
        return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params).then(function(resp) {
            allRecords = allRecords.concat(resp.records);
            if (resp.records.length === limit) {
                return fetchRecords(appId, query, opt_fields, offset + limit, limit, allRecords);
            }
            return allRecords;
        });
    }

    // records convert to table rows 
    function convertToRows(records) {
        var rows = records.map(function(record){
            var keys = Object.keys(record);
            var row = {};
            keys.map(function(key){
                row[key] = record[key].type === 'NUMBER' ? Number(record[key].value) : record[key].value;
            });
            return row;
        });
        return rows;
    }

})();
```

# 半製品の登録と所要量展開

半製品と所要量展開は、kintone で扱うのは向かないと思いますが、お試しということで。

- 原材料に、半製品を追加します。
- 半製品の原材料を登録します。

![2017-03-01_00h02_29.png](https://qiita-image-store.s3.amazonaws.com/0/100572/e3f8037e-fde1-fa43-5d68-fca46332a3de.png)


処理としては、半製品の原材料を求める処理を追加します。
半製品の階層が多い場合は、半製品の原材料を求める処理を繰り返します。


```
////////////////////////////////////////////////////////////////////////////
// kintone JavaScript: Summary of kintone records by alasql.
//  2017.02.28 by rex0220
////////////////////////////////////////////////////////////////////////////
(function() {
    "use strict";

    // 一覧表示
    kintone.events.on("app.record.detail.show", function(event) {

        var record = event.record;

        var obj = {};
        var appId1 = 749;
        var q1 = '製品名 = "' + record['製品名'].value + '" and 削除フラグ not in ("削除")';

        // 原材料取得
        fetchRecords(appId1, q1, ['製品名', '原材料名']).then(function(records) {
            obj.rs1 = convertToRows(records);
            console.log('rs1', JSON.stringify(obj.rs1));

            var partNames = {};
            obj.rs1.map(function(row) {
                partNames['"' + row['原材料名'] + '"'] = 1;
            });
            var strNames = Object.keys(partNames).join(',');
            console.log('原材料名', strNames);

            // 半製品原材料取得
            var appId1 = 749;
            var q1a = '製品名 in (' + strNames + ') and 削除フラグ not in ("削除")';
            // var q1 = '削除フラグ not in ("削除")';
            return fetchRecords(appId1, q1a, ['製品名', '原材料名']);
        }).then(function(records) {
            obj.rs1a = convertToRows(records);
            console.log('rs1a', JSON.stringify(obj.rs1a));

            obj.rs1 = obj.rs1.concat(obj.rs1a);
            console.log('rs1', JSON.stringify(obj.rs1));

            var partNames = {};
            obj.rs1.map(function(row) {
                partNames['"' + row['原材料名'] + '"'] = 1;
            });
            var strNames = Object.keys(partNames).join(',');
            console.log('原材料名', strNames);


            // 部品調達情報取得
            var appId2 = 751;
            var q2 = '原材料名 in (' + strNames + ') and 削除フラグ not in ("削除")';
            // var q1 = '削除フラグ not in ("削除")';
            return fetchRecords(appId2, q2, ['原材料名', '会社名']);

        }).then(function(records) {
            obj.rs2 = convertToRows(records);
            console.log('rs2', JSON.stringify(obj.rs2));

            var companyNames = {};
            obj.rs2.map(function(row) {
                companyNames['"' + row['会社名'] + '"'] = 1;
            });

            var strNames = Object.keys(companyNames).join(',');

            console.log('会社名', strNames);

            // 査察情報取得
            var appId3 = 748;
            var q3 = '会社名 in (' + strNames + ') and 削除フラグ not in ("削除")';
            // var q1 = '削除フラグ not in ("削除")';
            return fetchRecords(appId3, q3, ['会社名', '査察年月日', '査察の評価']);

        }).then(function(records) {
            obj.rs3 = convertToRows(records);
            console.log('rs3', JSON.stringify(obj.rs3));

            var result1 = alasql(
                "SELECT  a.[製品名], a.[原材料名], c.[会社名], c.[査察年月日], c.[査察の評価]\
                FROM ( \
                    SELECT [会社名], FIRST([査察年月日]) as [査察年月日], FIRST([査察の評価]) as [査察の評価]\
                    FROM (SELECT * FROM ? ORDER BY [会社名], [査察年月日] DESC) \
                    GROUP BY [会社名] ) as c \
                JOIN ? as b ON b.[会社名] = c.[会社名]\
                JOIN ? as a ON a.[原材料名] = b.[原材料名]\
                ORDER BY a.[製品名], a.[原材料名], c.[会社名]", [obj.rs3, obj.rs2, obj.rs1]);
            console.log('r0a', result1);

            // grid 表示
            var grid = kintone.app.record.getSpaceElement('xp-grid');
            new Handsontable(grid, { 
                data: result1, 
                colWidths: [150, 150, 150, 100, 100],
                colHeaders: ['製品名', '原材料名', '会社名', '査察年月日', '査察の評価'], 
                columns: [
                    { data: '製品名' },
                    { data: '原材料名' },
                    { data: '会社名' },
                    { data: '査察年月日' },
                    { data: '査察の評価' },
                ],
                readOnly: true 
            });

        });

        return event;
    });


    // get records
    function fetchRecords(appId, opt_query, opt_fields, opt_offset, opt_limit, opt_records) {
        var query = opt_query || '';
        var offset = opt_offset || 0;
        var limit = opt_limit || 500;
        var allRecords = opt_records || [];
        var params = {app: appId, query: query + ' limit ' + limit + ' offset ' + offset };
        if (opt_fields) params.fields = opt_fields;
        return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params).then(function(resp) {
            allRecords = allRecords.concat(resp.records);
            if (resp.records.length === limit) {
                return fetchRecords(appId, query, opt_fields, offset + limit, limit, allRecords);
            }
            return allRecords;
        });
    }

    // records convert to table rows 
    function convertToRows(records) {
        var rows = records.map(function(record){
            var keys = Object.keys(record);
            var row = {};
            keys.map(function(key){
                row[key] = record[key].type === 'NUMBER' ? Number(record[key].value) : record[key].value;
            });
            return row;
        });
        return rows;
    }

})();
```




# あとがき

GROUP BY で MAX(日付) をすると出来ない。
文字列比較になるためか？