FreeGridv1.15.1English

서버 측 그룹핑

datasource와 groupBy를 함께 쓰면 그룹 단계마다 서버에 요청하므로, GROUP BY 쿼리로 그룹과 건수·합계를 돌려주면 됩니다.

실행 예제서버 측 그룹핑

그룹과 건수·합계를 서버에서 받아, 펼칠 때마다 불러옵니다.

이 데모의 코드
import { createGrid, type Row, type ServerParams } from '@freegridjs/core';
import { makeRows } from '../data';
import { select, separator, text, tr, type DemoContext } from '../ui';

export default function mount(ctx: DemoContext) {
  const { lang } = ctx;
  const h = (en: string, ko: string) => tr(lang, { en, ko });
  const all = makeRows(20000);

  /**
   * A pretend server. With groupBy it answers, for the open groups (groupKeys),
   * one row per group with its count and totals — what a GROUP BY query returns.
   */
  const datasource = {
    getRows(p: ServerParams) {
      const by = p.groupBy ?? [];
      const keys = p.groupKeys ?? [];
      let rows = all.filter(r => keys.every((k, i) => r[by[i]] === k));
      let answer: { rows: Row[]; total?: number };
      if (keys.length < by.length) {
        const col = by[keys.length];
        const groups = new Map<unknown, { count: number; q1: number; total: number }>();
        for (const r of rows) {
          const g = groups.get(r[col]) ?? { count: 0, q1: 0, total: 0 };
          g.count++;
          g.q1 += r.q1;
          g.total += r.total;
          groups.set(r[col], g);
        }
        const desc = p.sort.find(s => s.colId === col)?.dir === 'desc';
        answer = { rows: [...groups].sort((a, b) => String(a[0]).localeCompare(String(b[0])) * (desc ? -1 : 1)).map(([v, g]) => ({ [col]: v, ...g })) };
        ctx.log(`getRows(groupKeys: ${JSON.stringify(keys)}) → ${answer.rows.length} ${h('groups', '그룹')}`);
      } else {
        rows = rows.slice(p.start, p.end);
        answer = { rows, total: rows.length };
        ctx.log(`getRows(groupKeys: ${JSON.stringify(keys)}) → ${rows.length} ${h('rows', '행')}`);
      }
      return new Promise<typeof answer>(resolve => setTimeout(() => resolve(answer), 250));
    },
  };

  const grid = createGrid(ctx.host, {
    columns: [
      { field: 'country', header: h('Country', '국가'), width: 160 },
      { field: 'company', header: h('Company', '회사'), width: 160 },
      { field: 'name', header: h('Name', '이름'), width: 160 },
      { field: 'q1', header: 'Q1', type: 'number', format: '#,##0', width: 130, aggFunc: 'sum' },
      { field: 'total', header: h('Total', '합계'), type: 'number', format: '#,##0', width: 140, aggFunc: 'sum' },
    ],
    datasource, getRowId: r => String(r.id), locale: lang,
    groupBy: ['country', 'company'],
  });

  text(ctx, h('20,000 rows stay on the "server". Opening a group asks only for what is inside it; the log shows each request.',
    '2만 행은 "서버"에만 있습니다. 그룹을 펼칠 때 그 안에 든 것만 요청합니다. 아래 기록에서 요청을 볼 수 있습니다.'));
  separator(ctx);
  select(ctx, h('Group by', '그룹 기준'), [['country,company', h('Country → Company', '국가 → 회사')], ['company', h('Company', '회사')], ['', h('None', '없음')]], 'country,company',
    v => grid.setGroupBy(v ? v.split(',') : []));
  return grid;
}

서버 측 그룹핑은 데이터를 서버에 둔 채로 쓰는 행 그룹핑입니다. datasource를 쓰는 그리드에 groupBy를 주면, 그리드는 한 단계씩 요청합니다. 먼저 최상위 그룹을, 그룹을 펼칠 때마다 그 안의 하위 그룹이나 행을 요청합니다. 주문 100만 건을 상태별로 묶어도 GROUP BY 쿼리 한 번과 몇 행이면 되고, 100만 행을 브라우저로 가져오지 않습니다.

요청마다 groupBy와, 위에 펼쳐진 그룹들의 값인 groupKeys가 들어옵니다. 키가 묶는 컬럼 수보다 적으면 그룹마다 한 행씩, 그룹 값과 count, aggFunc가 있는 컬럼의 집계를 컬럼 ID를 키로 돌려주세요. 가장 안쪽 그룹에서는 모든 키로 걸러 낸 행 자체를 일반 서버 요청처럼 블록 단위로 돌려주면 됩니다. 묶는 컬럼으로 정렬하면 그룹 순서가 바뀝니다.

createGrid(el, {
  columns: [
    { field: 'status' },
    { field: 'customer' },
    { field: 'qty', type: 'number', aggFunc: 'sum' },
  ],
  groupBy: ['status'],
  datasource: {
    async getRows({ start, end, sort, filters, groupBy, groupKeys }) {
      if (groupKeys.length < groupBy.length) {
        // a group level: one row per group, with count and totals by column id
        // SELECT status, COUNT(*) AS count, SUM(qty) AS qty FROM orders GROUP BY status
        return { rows: [{ status: 'new', count: 12, qty: 340 }, { status: 'done', count: 80, qty: 2100 }] };
      }
      // inside an open group: the rows, filtered by groupKeys (e.g. status = 'new')
      return api.rows({ start, end, sort, filters, where: { status: groupKeys[0] } });
    },
  },
});

사용자는 setGroupBy()나 그룹 패널로 묶는 기준을 바꿀 수 있고, 바꿀 때마다 최상위 단계부터 다시 요청합니다. Java, Node.js, Python, PHP, C#, Go용 서버 연동 함수가 이 요청을 안전한 SQL로 처리합니다.

getRows의 그룹 값

이름타입설명
groupBystring[]묶는 컬럼 ID 목록(순서대로).
groupKeysunknown[]이 요청 위에 펼쳐진 그룹들의 값. 최상위에서는 빈 배열.
group rows{ [groupCol], count, [colId]: total }groupKeys.length < groupBy.length인 동안에는 groupBy[groupKeys.length] 값마다 한 행을 count와 컬럼 ID별 집계와 함께 돌려줍니다.
leaf rows{ rows, total? }가장 안쪽 그룹을 펼쳤을 때: 모든 그룹 값에 맞는 [start, end) 행.

이 페이지에 대한 질문

서버에서 그룹핑할 수 있나요?

네. datasource와 groupBy를 함께 쓰면 getRows가 groupBy와 groupKeys를 받으므로, GROUP BY 쿼리로 그룹마다 건수와 합계를 한 행씩 돌려주면 됩니다.