【问题标题】:PostgreSQL Mathematical FunctionPostgreSQL 数学函数
【发布时间】:2013-05-09 10:39:57
【问题描述】:

我有一个表aps_sections,其中包含许多整数字段(例如bare_width 和worn_width)。我还有多个查找表(例如aps_bare_width 和aps_worn_width),其中包含一个 ID 列和一个 WEIGHTING 列。 ID 记录在aps_sections 表的上述列中。我需要对aps_sections 表中的列的加权求和(其中加权值来自查找表)。我已经使用下面的SELECT 语句成功地管理了这个。

SELECT aps_sections.ogc_fid,
       ( aps_bare_width.weighting
       + aps_worn_width.weighting
       + aps_gradient.weighting
       + aps_braiding.weighting
       + aps_pigeon.weighting
       + aps_depth.weighting
       + aps_standing_water.weighting
       + aps_running_water.weighting
       + aps_roughness.weighting
       + aps_surface.weighting
       + aps_dynamic.weighting
       + aps_ex_cond.weighting
       + aps_promotion.weighting
       + aps_level_of_use.weighting) AS calc
FROM row_access.aps_sections,
     row_access.aps_bare_width,
     row_access.aps_worn_width,
     row_access.aps_gradient,
     row_access.aps_braiding,
     row_access.aps_pigeon,
     row_access.aps_depth,
     row_access.aps_standing_water,
     row_access.aps_running_water,
     row_access.aps_roughness,
     row_access.aps_surface,
     row_access.aps_dynamic,
     row_access.aps_ex_cond,
     row_access.aps_promotion,
     row_access.aps_level_of_use
WHERE aps_bare_width.fid = aps_sections.bare_width
AND   aps_worn_width.fid = aps_sections.worn_width
AND   aps_gradient.fid = aps_sections.gradient
AND   aps_braiding.fid = aps_sections.braiding
AND   aps_pigeon.fid = aps_sections.pigeon
AND   aps_depth.fid = aps_sections.depth
AND   aps_standing_water.fid = aps_sections.standing_water
AND   aps_running_water.fid = aps_sections.running_water
AND   aps_roughness.fid = aps_sections.roughness
AND   aps_surface.fid = aps_sections.surface
AND   aps_dynamic.fid = aps_sections.dynamic
AND   aps_ex_cond.fid = aps_sections.ex_cond
AND   aps_promotion.fid = aps_sections.promotion
AND   aps_level_of_use.fid = aps_sections.level_of_use

我现在需要做的是创建一个函数,将计算结果添加到aps_sections 表的physical_sn_priority 列。到目前为止,我的理解是我的函数应该类似于:

CREATE OR REPLACE FUNCTION row_access.aps_weightings()
  RETURNS trigger AS
$BODY$
    BEGIN
    NEW.physical_sn_priority := ;
       RETURN NEW;
    END;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;
ALTER FUNCTION public.update_km()
  OWNER TO postgres;

但我不知道在NEW.physical_sn_priority := 后面放什么。我是 SQL 和 PostgreSQL 的初学者,因此我将不胜感激!

【问题讨论】:

  • 很抱歉这些言论让大家厌烦了,但是像这样的问题需要声明 Postgres 的版本。它是哪一个?您还需要解释触发器的来源。您要更新单行、不同的行还是所有行?任何添加的列都可以为 NULL 还是都定义为 NOT NULL?是否保证参照完整性(所有这些关系上的外键)?以及所涉及列的数据类型是什么?

标签: sql function postgresql triggers mathematical-expressions


【解决方案1】:

虽然 Erwin(一如既往)正确地认为某个版本会有所帮助,但我认为使用 PL/pgSQL 的 SELECT ... INTO construction 将是最简单的答案。 (不与SELECT INTO 相同,其工作方式类似于INSERT 或CREATE TABLE。)

SELECT ( aps_bare_width.weighting
   + /* obvious deletia */
   + aps_level_of_use.weighting)
INTO NEW.physical_sn_priority
FROM row_access.aps_bare_width,
 /* snip */,
 row_access.aps_level_of_use
WHERE aps_bare_width.fid = NEW.bare_width
    AND /* snip */
    aps_level_of_use.fid = NEW.level_of_use;

RETURN NEW;

根据文档,INTO 可以出现在该行的其他几个位置;我觉得这很容易理解。

[编辑]

虽然这可行,但经过反思,我认为应该修改架构。

CREATE TYPE weighted_item_t AS ENUM ('bare_width', /* ... */, 'level_of_use');
CREATE TABLE current_weights(item_type weighted_item_t, fid int, current_weight float);
     /* key and checks omitted */
     /* note, if item_type can be deduced from fid, we don't even need the enum */
CREATE TABLE sections_items(section_id int /* FK into aps_sections */,
     item_type weighted_item_t, fid int);

现在查询将分解为简单的总和。您需要在aps_sections 之前将记录插入section_items,这可以在有或没有存储过程的事务中使用延迟约束来完成,具体取决于您获取数据的方式以及您对其格式的控制程度。 如果(这不清楚,因为它不会在更新时改变)你想要非规范化的总数,你可以得到它

SELECT SUM(current_weight) INTO NEW.physical_sn_priority
FROM section_items NATURAL JOIN current_weights
WHERE NEW.section_id=section_items.section_id;

如果在未来某个日期添加额外的加权特征,效果会更好。

【讨论】:

    【解决方案2】:

    简化测试用例

    您应该以较少的噪音提出您的问题。这个较短的查询可以很好地完成工作:

    SELECT aps_sections.ogc_fid,
           ( aps_bare_width.weighting
           + aps_worn_width.weighting
           + aps_gradient.weighting) AS calc
    FROM row_access.aps_sections,
         row_access.aps_bare_width,
         row_access.aps_worn_width,
         row_access.aps_gradient,
    WHERE aps_bare_width.fid = aps_sections.bare_width
    AND   aps_worn_width.fid = aps_sections.worn_width
    AND   aps_gradient.fid = aps_sections.gradient;
    

    回答

    作为@Andrew already advised,最好的方法是简化您的架构。

    如果由于某种原因这不可能,这里有一个替代方法来简化添加许多列(可能是也可能不是NULL),创建这个小巧而强大的函数:

    CREATE OR REPLACE FUNCTION f_sum(ANYARRAY)
      RETURNS numeric LANGUAGE sql AS
    'SELECT sum(i)::numeric FROM unnest($1) i';
    

    呼叫:

    SELECT f_sum('{2,NULL,7}'::int[])
    
    • 它采用polymorphic 数组类型并返回numeric。适用于可以由sum() 求和的任何数字类型。如果需要,投射结果。
    • 简化了对大量列求和的语法。
    • NULL 值不会破坏您的计算,因为聚合函数 sum() 会忽略这些。

    在触发函数中可以这样使用:

    NEW.physical_sn_priority := f_sum(ARRAY [
        COALESCE(physical_sn_priority, 0)     -- add to original value
       ,(SELECT weighting FROM aps_bare_width x WHERE x.fid = NEW.bare_width)
       ,(SELECT weighting FROM aps_worn_width x WHERE x.fid = NEW.worn_width)
       ,(SELECT weighting FROM aps_gradient   x WHERE x.fid = NEW.gradient)
        ...
       ])
    

    由于您的所有联接表仅适用于查找单个字段,并且彼此完全独立,因此您也可以使用单独的子查询。我也是这样做的,因为我们不知道是否有任何子查询可能返回 NULL,在这种情况下,您的原始查询或 Andrew 的版本将导致没有行/没有分配。

    分配给NEW 真的只有在aps_sections 表上的BEFORE 触发器中才有意义。此代码适用于BEFORE INSERT(在表中还找不到该行!)以及BEFORE UPDATE。您想使用来自NEW 的当前值,而不是表中的旧行版本。

    【讨论】:

      猜你喜欢
      • 2022-06-24
      • 1970-01-01
      • 2020-05-02
      • 1970-01-01
      • 2021-08-26
      • 2017-09-29
      • 2022-01-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多