Hasura "Running Total" 计算列

Hasura "Running Total" Computed Column

我想在 Hasura 中创建一个计算列,其中 returns 一组另一个 table 并包括该集合的 运行 总列。例如:

table_a

id           product_id
-----------  ----------  
1            "1"      
2            "2"      
2            "3"      


table_b

id           product_id  qty   created_at
-----------  ----------  ---   ----------
1            "1"         6     01/01/20
2            "2"         4     01/02/20
3            "3"         2     01/02/20
4            "3"         2     01/02/20 
5            "1"         4     01/03/20
6            "2"         6     01/03/20

所需的 GQL 响应:

{
  "data": {
    "table_a": [
      {
        "id": 1,
        "product_id": "1",
        "computed_table_b_rows": [
           {
              id: 1,
              product_id: "1",
              qty: 6,
              created_at: 01/01/20,
              running_total: 6,
           },
           {
              id: 5,
              product_id: "1",
              qty: 4,
              created_at: 01/03/20,
              running_total: 10,
           },
        ]
      }
    ]
  }
}

这是我目前没有的方法:

CREATE FUNCTION filter_table_b(table_a_row table_a)
RETURNS SETOF table_b AS $$
  SELECT *,
  SUM (qty) OVER (PARTITION BY product_id ORDER BY created_at) as running_total
  FROM table_b
  WHERE product_id = table_a_row.product_id
$$ LANGUAGE sql STABLE;

似乎 SETOF 应该是 table 的子集,并且不能有新列。例如。我试过了:

CREATE FUNCTION getfoo(int) RETURNS SETOF table_a AS $$
    SELECT *, 'asdf' as extra FROM table_a WHERE id = ;
$$ LANGUAGE SQL;

导致 Hasura 报告的错误:

SQL Execution Failed
postgres-error: return type mismatch in function declared to return table_a

{
    "path": "$.args[0].args",
    "error": "query execution failed",
    "internal": {
        "arguments": [],
        "error": {
            "status_code": "42P13",
            "exec_status": "FatalError",
            "message": "return type mismatch in function declared to return table_a",
            "description": "Final statement returns too many columns.",
            "hint": ""
        },
        "prepared": false,
        "statement": "CREATE FUNCTION getfoo(int) RETURNS SETOF table_a AS $$\n    SELECT *, 'asdf' as extra FROM table_a WHERE id = ;\n$$ LANGUAGE SQL;"
    },
    "code": "postgres-error"
}

但是还有其他一些选择:

  1. 将 "running total" 列添加到 table_b 本身(通过更改函数定义。详见下文。
  2. 如果这不够高效,另一种选择是使用总和和分区在 table_b 上创建一个包含 running_total 列的视图。然后创建从 table_a 到 table_b_view.
  3. 的关系
  4. 在 table_b 上创建一个触发器,当添加新行时它会计算 running_total 列并存储它,因为该数据似乎是静态的,因为它基于历史数据。

下面详细介绍了选项 1,因为它最接近原始实现(使用函数):

CREATE OR REPLACE FUNCTION public.table_b_running_total(table_b_row table_b)
 RETURNS bigint
 LANGUAGE sql
 STABLE
AS $function$
  SELECT SUM (qty)
  FROM table_b
  WHERE product_id = table_b_row.product_id
  AND created_at <= table_b_row.created_at
  LIMIT 1
$function$

有了这个,我能够得到想要的结果——虽然想要的 graphql 响应不完全匹配。

查询:

query MyQuery {
  table_a(where:{ id: { _eq: 1 }}) {
    id
    product_id
    table_bs {
      id
      product_id
      qty
      created_at
      table_b_running_total
    }
  }
}

回复:

{
  "data": {
    "table_a": [
      {
        "id": 1,
        "product_id": "1",
        "table_bs": [
          {
            "id": 1,
            "product_id": "1",
            "qty": 6,
            "created_at": "2020-01-01T00:00:00+00:00",
            "table_b_running_total": 6
          },
          {
            "id": 5,
            "product_id": "1",
            "qty": 4,
            "created_at": "2020-01-03T00:00:00+00:00",
            "table_b_running_total": 10
          }
        ]
      }
    ]
  }
}