集計集約操作におけるROLLUP,GROUPING SETS,CUBEをTypescriptで実現するやり方
はじめに
こんにちわ。nap5です。
集計集約操作におけるROLLUP,GROUPING SETS,CUBEをTypescriptで実現するやり方の紹介です。
SQL操作でよく出てくるGroupBYの亜種になります。
サンプルデータ
このデータをもとにTidyjsで集計集約操作をしていきます。
type Sale = {
city: string;
product: string;
amount: number;
};
const sales: Sale[] = [
{ city: "Tokyo", product: "Apple", amount: 10 },
{ city: "Tokyo", product: "Banana", amount: 20 },
{ city: "Osaka", product: "Apple", amount: 15 },
{ city: "Osaka", product: "Banana", amount: 5 },
];
Group by rollup
期待される結果の集計は次のようになります。明細単位の集約(semiTotal)は一件にするため、Sumしています。集約できれば、Maxでも、Minでもいいかとは思います。
全都市、全商品の合計:50
東京の合計:30
大阪の合計:20
東京のリンゴの合計:10
東京のバナナの合計:20
大阪のリンゴの合計:15
大阪のバナナの合計:5
import { describe, test, expect } from "vitest";
import { bind, bindTo, map } from "fp-ts/lib/Identity";
import { pipe } from "fp-ts/lib/function";
import { groupBy, summarize, tidy, sum } from "@tidyjs/tidy";
describe("グルーピング", () => {
type Sale = {
city: string;
product: string;
amount: number;
};
const sales: Sale[] = [
{ city: "Tokyo", product: "Apple", amount: 10 },
{ city: "Tokyo", product: "Banana", amount: 20 },
{ city: "Osaka", product: "Apple", amount: 15 },
{ city: "Osaka", product: "Banana", amount: 5 },
];
// @see https://docs.snowflake.com/ja/sql-reference/constructs/group-by-rollup
test("GROUP BY ROLLUP", () => {
const outputData = pipe(
sales,
bindTo("input"),
bind("semiTotal", ({ input }) =>
tidy(
input,
groupBy(
["city", "product"],
[
summarize({
amount: sum("amount"),
}),
]
)
)
),
bind("subTotal", ({ input }) =>
tidy(
input,
groupBy(
["city"],
[
summarize({
product: (d) => "All Products",
amount: sum("amount"),
}),
]
)
)
),
bind("total", ({ input }) =>
tidy(
input,
summarize({
city: (d) => "All Cities",
product: (d) => "All Products",
amount: sum("amount"),
})
)
),
bind("output", ({ total, subTotal, semiTotal }) => ({
summary: total.concat(subTotal).concat(semiTotal),
detail: {
total,
subTotal,
semiTotal,
},
})),
map(({ output }) => output.summary)
);
expect(outputData).toStrictEqual([
{
city: "All Cities",
product: "All Products",
amount: 50,
},
{
city: "Tokyo",
product: "All Products",
amount: 30,
},
{
city: "Osaka",
product: "All Products",
amount: 20,
},
{
city: "Tokyo",
product: "Apple",
amount: 10,
},
{
city: "Tokyo",
product: "Banana",
amount: 20,
},
{
city: "Osaka",
product: "Apple",
amount: 15,
},
{
city: "Osaka",
product: "Banana",
amount: 5,
},
]);
});
});
Group by grouping sets
期待される結果の集計は次のようになります。明細単位の集約(semiTotal)は一件にするため、Sumしています。集約できれば、Maxでも、Minでもいいかとは思います。
東京の全商品の合計:30
大阪の全商品の合計:20
全都市のリンゴの合計:25
全都市のバナナの合計:25
東京のリンゴの合計:10
東京のバナナの合計:20
大阪のリンゴの合計:15
大阪のバナナの合計:5
import { describe, test, expect } from "vitest";
import { bind, bindTo, map } from "fp-ts/lib/Identity";
import { pipe } from "fp-ts/lib/function";
import { groupBy, summarize, tidy, sum } from "@tidyjs/tidy";
describe("グルーピング", () => {
type Sale = {
city: string;
product: string;
amount: number;
};
const sales: Sale[] = [
{ city: "Tokyo", product: "Apple", amount: 10 },
{ city: "Tokyo", product: "Banana", amount: 20 },
{ city: "Osaka", product: "Apple", amount: 15 },
{ city: "Osaka", product: "Banana", amount: 5 },
];
// @see https://docs.snowflake.com/ja/sql-reference/constructs/group-by-grouping-sets
test("GROUP BY GROUPING SETS", () => {
const outputData = pipe(
sales,
bindTo("input"),
bind("semiTotal", ({ input }) =>
tidy(
input,
groupBy(
["city", "product"],
[
summarize({
amount: sum("amount"),
}),
]
)
)
),
bind("subTotal1", ({ input }) =>
tidy(
input,
groupBy(
["city"],
[
summarize({
product: (d) => "All Products",
amount: sum("amount"),
}),
]
)
)
),
bind("subTotal2", ({ input }) =>
tidy(
input,
groupBy(
["product"],
[
summarize({
city: (d) => "All Cities",
amount: sum("amount"),
}),
]
)
)
),
bind("output", ({ subTotal1, subTotal2, semiTotal }) => ({
summary: subTotal1.concat(subTotal2).concat(semiTotal),
detail: {
subTotal1,
subTotal2,
semiTotal,
},
})),
map(({ output }) => output.summary)
);
expect(outputData).toStrictEqual([
{
city: "Tokyo",
product: "All Products",
amount: 30,
},
{
city: "Osaka",
product: "All Products",
amount: 20,
},
{
product: "Apple",
city: "All Cities",
amount: 25,
},
{
product: "Banana",
city: "All Cities",
amount: 25,
},
{
city: "Tokyo",
product: "Apple",
amount: 10,
},
{
city: "Tokyo",
product: "Banana",
amount: 20,
},
{
city: "Osaka",
product: "Apple",
amount: 15,
},
{
city: "Osaka",
product: "Banana",
amount: 5,
},
]);
});
});
Group by cube
期待される結果の集計は次のようになります。明細単位の集約(semiTotal)は一件にするため、Sumしています。集約できれば、Maxでも、Minでもいいかとは思います。
全都市、全商品の合計:50
東京の全商品の合計:30
大阪の全商品の合計:20
全都市のリンゴの合計:25
全都市のバナナの合計:25
東京のリンゴの合計:10
東京のバナナの合計:20
大阪のリンゴの合計:15
大阪のバナナの合計:5
おわりに
Tidyjs便利です。簡単ですが、以上です。
この記事が気に入ったらサポートをしてみませんか?