基于开发者空间GaussDB云数据库的PLpgSQL实践二

1 概述

1.1 案例介绍

PL/pgSQL是一种程序语言,叫做过程化SQL语言(Procedural Language/Postgres SQL),PL/pgSQL是PostgreSQL数据库对SQL语句的扩展。在普通SQL语句的使用上增加了编译语言的特点,所以PL/pgSQL就是把数据操作和查询语句组织在PL/pgSQL代码的过程性单元中,通过逻辑判断、循环等操作实现复杂的功能或者计算的程序语言。

本案例选择Gauss数据库作为示例,直观地展示 PL/pgSQL在商业应用开发与过程化编程能力和实际应用开发中为开发者带来的便利。

通过实际操作,让大家深入了解如何利用 PL/pgSQL 开发并部署一个函数功能模块。在这个过程中,大家将学习到从函数创建、数据批量读取到SQL程序编写以及与触发器集成等一系列关键步骤,从而掌握 PL/pgSQL 的基本使用方法,体验其在应用开发中的优势。

1.2 适用对象

  • 企业
  • 个人开发者
  • 高校学生

1.3 案例时间

本案例总时长预计60分钟。

1.4 案例流程

说明:

  1. 领取开发者空间云数据库GaussDB生态版;
  2. 在空间开发桌面终端进入GaussDB的DAS工具;
  3. 进行数据库的图形化SQL界面,进行GaussDB之PL/pgSQL的操作;

1.5 资源总览

资源名称规格单价(元)时长(分钟)
开发者空间-云数据库GaussDB鲲鹏通用计算增强型 kc1 | 2vCPUs | 4G | HCE2.0免费60

最新案例动态,请查阅 《基于开发者空间GaussDB云数据库的PLpgSQL实践二》。小伙伴快来领取华为开发者空间,进入云主机桌面版实操吧!

2 OpenGauss数据库PL/pgSQL实践

2.1 领取开发者空间GaussDB数据库

免费领取GaussDB在线试用版(2025年 06月 21日 - 2025年 12月 31日)。地址如下:
https://developer.huaweicloud.com/signup/75dae31d0eb04cdcab822c76d35eb9a1
有1000个名额,数量有限,速度为王。

领取后,按提示即可创建数据库,如下:

登录数据库

输入创建数据库时的密码,点击测试连接,通过后点击登录

创建测试数据库,点击确定

查看数据库兼容类型

执行sql:

3 PL/PGSQL实践

PLPGSQL是数据库的编程语言。相当于在数据库中用SQL语言进行逻辑处理与代码开发。可以把业务系统中封装的功能模块下沉到数据库端实现,以达到减轻业务系统的逻辑压力、降低架构复杂度和简化业务系统实现难度。

PLPGSQL是一种块结构型语言,例如匿名块,存储过程和函数体的完整文本必须是块。块定义如下:

[ <<标签>> ]
[ 声明
变量;
变量 := 赋值 ]
BEGIN
    SQL QUERY
END [ 标签];

PLPGSQL定义的功能模块(存储过程和函数)可以互相嵌套。例如SQL块中嵌套子SQL块,存储过程引用PLPGSQL定义的其他函数和模块功能。

3.1 游标之数据批量处理

游标用于对 SQL 查询的结果集做批处理读取场景。当SQL查询的结果集数据量过大,一次性读取会导致内存不够缓存。所以出现了游标的功能,用COUSOR对大数据量的结果集中分批处理(例如一次取1000条数据),直到循环多次,批量把结果集里的数据读取完毕。

3.1.1 声明游标变量

PL/pgSQL语法中,所有游标都必须通过游标变量去访问,游标变量是特殊的数据类型 refcursor。游标变量的声明语法如下:

name [ [ NO ] SCROLL ] CURSOR [ ( arguments ) ] FOR query;

当关键字 SCROLL 被指定,则游标可以向后滚动处理数据,如果指定了NO SCROLL,则游标向后读取数据将被拒绝。如果没有指定 SCROLL,游标是否自动向后读取数据取决了SQL查询。arguments 如果被指定,则其以逗号分隔以对应 query 查询数据类型的列表,arguments的字段名称将替换掉 query查询中对应的字段名。示例如下:

DECLARE
    curs1 refcursor;
    curs2 CURSOR FOR SELECT * FROM tenk1;
    curs3 CURSOR (key integer) FOR SELECT * FROM tenk1 WHERE unique1 = key;

这3个游标变量都属于 refcursor 数据类型,但curs1能被用于任何Query,curs2已经被绑定了一个完整的query查询,最后的curs3游标绑定了一个参数化的Query(当curs3被打开时,key用一个整形数值替代)。curs1由于没有绑定于任何指定的Query,所以可称之为末绑定的游标变量curs1。

当游标Query使用FOR UPDATE / SHARE(互斥锁 / 共享锁)时,则不能指定关键字SCROLL。对于Query涉及volatile函数时,则推荐使用NO SCROLL。SCROLL的实现场景是游标对Query输出结果集多次读取以保证读取结果的一致性(多次读取的数据内容保持不变),而Query里的volatile函数无法保证这一点。

3.1.2 打开游标

语法1:

OPEN unbound_cursorvar [ [ NO ] SCROLL ] FOR query;

该语法操作是打开游标去执行游标绑定的Query查询。游标必须已经声明的refcursor变量,Query查询必须是SELECT,或返回元组的其他内容(例如EXPLAIN)。

示例如下:

OPEN curs1 FOR SELECT * FROM foo WHERE key = mykey;

语法2:

OPEN unbound_cursorvar [ [ NO ] SCROLL ] FOR EXECUTE query_string 
[ USING expression [, ... ] ];

该游标变量被打开并指定的Query查询去执行。

OPEN curs1 FOR EXECUTE format('SELECT * FROM %I WHERE col1 = $1',tabname) USING keyvalue;

此用例中,通过Query中函数format()写入表名,col1的值是通过USING插入的,因此这不需要引用。

语法3:

OPEN bound_cursorvar [ ( [ argument_name := ] argument_value [, ...] ) ];

这种方法是游标在声明时,已经绑定了动态SQL,而在打开游标时,需要对该游标适配具体的参数值,使其在动态SQL中被替换掉。这种方法下,SQL计划一直保存在缓存中,直到游标执行结束。这种方法不能指定关键字SCROLL和NO SCROLL,因为游标的滚动行为已经确定。该语法3与语法2的EXECUTE关键字并不等价。

游标的参数值用两种方法传递:位置和命名。用参数位置表示时,所有参数都是按顺序指定的。在参数命名法中,使用 := 指定每个参数的名称,将其与参数表达式分开。

如下示例:

OPEN curs2;
OPEN curs3(42);
OPEN curs3(key := 42);

下面示例中的curs4,效果与上面curs3一样。

DECLARE
    key integer;
    curs4 CURSOR FOR SELECT * FROM tenk1 WHERE unique1 = key;
BEGIN
    key := 42;
    OPEN curs4;
3.1.3 使用游标

通过上面语法打开游标后,用 FETCH 语句来操作它。当PL/pgSQL函数要批量返回数据时,可以让函数返回refcursor值,调用函数对游标进行操作。refcursor变量值只能引用打开的游标,直到事务结束时被隐匿的关闭。

3.1.3.1 FETCH

语法如下:

FETCH [ direction { FROM | IN } ] cursor INTO target;

FETCH从游标中检索下一行(在指示的方向上)到目标中,该目标可能是行变量、记录变量或逗号分隔的简单变量列表,就像SELECT INTO一样。如果没有合适的行,则将目标设置为NULL(s)。与SELECT INTO一样,可以检查特殊变量,以查看是否获取了行。如果没有获得行,则光标定位在最后一行之后或第一行之前,具体取决于移动方向。

方向子句可以是SQL FETCH命令中允许的任何变体,但可以读取多行的变体除外;例如,它可以是NEXT、PRIOR、First、Last、ABSOLUTE count、RELATION count、Forward或backward。省略方向与指定NEXT相同。在使用计数的表单中,计数可以是任何整数值表达式(与SQL FETCH命令不同,SQL FETCH命令只允许使用整数常量)。需要向后移动的方向值很可能会失败,除非游标是用SCROLL选项声明或打开的。

游标必须是引用打开的游标入口的refcursor变量的名称。示例如下:

FETCH curs1 INTO rowvar;
FETCH curs2 INTO foo, bar, baz;
FETCH LAST FROM curs3 INTO x, y;
FETCH RELATIVE -2 FROM curs4 INTO x;
3.1.3.2 MOVE

语法如下:

MOVE [ direction { FROM | IN } ] cursor;

关键字MOVE在不检索任何数据的情况下重新定位游标。MOVE的原理与FETCH类似,其不同在于MOVE只重新定位光标,而不返回移动到的行。而SELECT INTO 通过指定变量FOUND能够检测其移动的位置是否在数据集中,避免发生错误。如果没有指定的drection,则游标会移到数据集最后一行的下一行位置,或者第一行的前面,其取决于指定的drection。

MOVE curs1;
MOVE LAST FROM curs3;
MOVE RELATIVE -2 FROM curs4;
MOVE FORWARD 2 FROM curs4;
3.1.3.3 UPDATE/DELETE WHERE CURRENT OF

语法如下:

UPDATE table SET ... WHERE CURRENT OF cursor;
DELETE FROM table WHERE CURRENT OF cursor;

当游标位于表的某一元组时,则使用该语法,游标指定的元组可以被修改更新或者删除元组。如果要限制游标查询,则应该使用FOR UPDATE。

UPDATE foo SET dataval = myval WHERE CURRENT OF curs1;
3.1.3.4 关闭游标

语法如下:

CLOSE cursor;

对打开的游标执行关闭操作,可以理解为游标资源的释放。但该操作应该在事务结束之前,对游标进行释放。如果在事务结束时没有关闭游标,则结束事务时也会自动对游标进行关闭操作。

示例如下:

CLOSE curs1;
3.1.4 通过游标批量处理结果集

PL/pgSQL函数返回一个游标时,在处理大数据集时,返回多行或者多列比较高效。

下面示例中,游标名称被调用者指定的用法:

DROP TABLE IF EXISTS test CASCADE;
CREATE TABLE test (col text);
INSERT INTO test VALUES ('123');

CREATE FUNCTION reffunc(refcursor) RETURNS refcursor AS '
BEGIN
    OPEN $1 FOR SELECT col FROM test;
    RETURN $1;
END;
' LANGUAGE plpgsql;
BEGIN;
SELECT reffunc('funccursor');
FETCH ALL IN funccursor;
COMMIT;

游标名称自动生成的用例:

-- 此处自行创建用例表test
DROP TABLE IF EXISTS test;
CREATE TABLE test(col int);
CREATE OR REPLACE FUNCTION reffunc2() RETURNS refcursor AS $$
DECLARE
    ref_temp refcursor;
BEGIN
    OPEN ref_temp FOR SELECT col FROM test;
    RETURN ref_temp;
END;
$$ LANGUAGE plpgsql;
-- need to be in a transaction to use cursors.
BEGIN;
SELECT reffunc2();
FETCH ALL IN "<unnamed portal 3>";
COMMIT;

FETCH ALL IN 后面的内容根据实际内容而改变。

下面用例展示,从函数中返回多个游标的用法:(注:根据Function实际,创建前需要建表table_1和table_2)

-- 此处简单创建用例表table_1, table_2
create table table_1(id int);
create table table_2(id int); 
CREATE FUNCTION myfunc(refcursor, refcursor) RETURNS SETOF refcursor AS $$
BEGIN
    OPEN $1 FOR SELECT * FROM table_1;
    RETURN NEXT $1;
    OPEN $2 FOR SELECT * FROM table_2;
    RETURN NEXT $2;
END;
$$ LANGUAGE plpgsql;
-- need to be in a transaction to use cursors.
BEGIN;

SELECT * FROM myfunc('a', 'b');

FETCH ALL FROM a;
FETCH ALL FROM b;
COMMIT;
3.1.4.1 使用LOOP循环体从游标中获取结果集

使用FOR关键字的LOOP循环体,用迭代变量获取游标返回的元组数据,循环处理游标中的数据结果。语法如下:

[ <<label>> ]
FOR recordvar IN bound_cursorvar [ ( [ argument_name := ] argument_value [, ...] ) ] LOOP
    statements
END LOOP [ label ];

这里使用的游标必须已经绑定到Query,并且不能是已经被打开的游标变量。FOR语句会自动打开游标,并在循环退出时自动关闭游标。当FORLOOP使用带参数的游标时,则必须使用表达式的实际参数值。其方式与OPEN方式相同。

3.2 自定义函数实现

PL/pgSQL中自定义函数非常常见。通常用CREATE FUNCTIONA表示该操作。函数必须要有返回值RETURNS。其它与存储过程内容一致。但当函数没有要返回的结果时,可以只写

RETURN;表示返回NULL。

自定义函数其他章节已经有过多介绍和示例,此处不再过多说明。

示例:(函数get_score_point根据输入的vscore成绩分数返回对应的绩点)

CREATE or REPLACE FUNCTION get_score_point(vscore INT)
RETURNS DECIMAL
as $$
DECLARE
BEGIN
  RETURN (
  CASE
    WHEN vscore <= 59 THEN
      0
    WHEN vscore <= 69 THEN
      0.1
    WHEN vscore <= 79 THEN
      0.2
    WHEN vscore <= 89 THEN
      0.3
    ELSE
      0.4
  END);
END; $$ LANGUAGE PLPGSQL;

示例:(函数add_mask根据SQL查询对部分字段脱敏,调用dbe_output.print_line输出元组数据)

CREATE OR REPLACE FUNCTION add_mask(id1 TEXT, id2 TEXT) RETURNS TEXT AS $$
DECLARE
    var1 TEXT;
    var2 int;
    cursor c1 is select case when id::text = id2 then '***' else id::text end as id, ranking from (select id, dense_rank() over(order by sum_point desc) as ranking from 
(select id,get_score_point(math)+get_score_point(art)+get_score_point(phy) as sum_point from stu)) where id::text = id1;
BEGIN
    open c1;
    loop fetch c1 into var1, var2;
    exit when c1%notfound;
        return dbe_output.print_line('学号是:'||var1||', 排名是:'||var2);
    end loop;
    close c1;
END;
$$ LANGUAGE PLPGSQL;

该用例中使用了dbe_output.print_line函数包,是GaussDB的兼容Oracle的商业特性,因为dbe_output高级包属于Oracle兼容性功能。

3.3 触发器实现

TRIGGER

当数据库中表数据被改变或者发生数据库事件(events)时,可定义触发器函数,触发对应的操作。触发器函数与自定义函数类似,不同的是触发器函数返回的是触发器return trigger。

3.3.1 数据变更触发器

一般触发器函数是没有参数且返回触发器类型,但是触发器函数有自己的默认参数。其默认参数有12个,这里主要介绍常用的两个:NEW和OLD,其都是record数据类型。NEW参数主要用于行级操作的INSERT/UPDATE触发器。OLD参数主要用于行级操作的UPDATE/DELETE触发器。

下面示例,当表的一个元组插入或更新时,当前用户名和时间则会被写入元组last_user和last_date字段。并检查employee表的name字段是否被给定和salary的数据是否正确。

CREATE TABLE emp (
    empname           text,
    salary            integer,
    last_date         timestamp,
    last_user         text
);

CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
    BEGIN
        -- Check that empname and salary are given
        IF NEW.empname IS NULL THEN
            RAISE EXCEPTION 'empname cannot be null';
        END IF;
        IF NEW.salary IS NULL THEN
            RAISE EXCEPTION '% cannot have null salary', NEW.empname;
        END IF;

        -- Who works for us when they must pay for it?
        IF NEW.salary < 0 THEN
            RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
        END IF;

        -- Remember who changed the payroll when
        NEW.last_date := current_timestamp;
        NEW.last_user := current_user;
        RETURN NEW;
    END;
$emp_stamp$ LANGUAGE plpgsql;

CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
    FOR EACH ROW EXECUTE PROCEDURE emp_stamp();

下面示例表示,当表emp元组的任何insert,update或者delete时,其记录都将被写到emp_audit表中。当时的时间和用户名也一并被写入。

CREATE TABLE emp (
    empname           text NOT NULL,
    salary            integer
);

CREATE TABLE emp_audit(
    operation         char(1)   NOT NULL,
    stamp             timestamp NOT NULL,
    userid            text      NOT NULL,
    empname           text      NOT NULL,
    salary            integer
);

CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS $emp_audit$
    BEGIN
        --
        -- Create a row in emp_audit to reflect the operation performed on emp,
        -- making use of the special variable TG_OP to work out the operation.
        --
        IF (TG_OP = 'DELETE') THEN
            INSERT INTO emp_audit SELECT 'D', now(), current_user, OLD.*;
        ELSIF (TG_OP = 'UPDATE') THEN
            INSERT INTO emp_audit SELECT 'U', now(), current_user, NEW.*;
        ELSIF (TG_OP = 'INSERT') THEN
            INSERT INTO emp_audit SELECT 'I', now(), current_user, NEW.*;
        END IF;
        RETURN NULL; -- result is ignored since this is an AFTER trigger
    END;
$emp_audit$ LANGUAGE plpgsql;

CREATE TRIGGER emp_audit
AFTER INSERT OR UPDATE OR DELETE ON emp
    FOR EACH ROW EXECUTE PROCEDURE process_emp_audit();

示例:

CREATE TABLE emp (
    empname           text PRIMARY KEY,
    salary            integer
);

CREATE TABLE emp_audit(
    operation         char(1)   NOT NULL,
    userid            text      NOT NULL,
    empname           text      NOT NULL,
    salary            integer,
    stamp             timestamp NOT NULL
);

CREATE VIEW emp_view AS
    SELECT e.empname,
           e.salary,
           max(ea.stamp) AS last_updated
      FROM emp e
      LEFT JOIN emp_audit ea ON ea.empname = e.empname
     GROUP BY 1, 2;

CREATE OR REPLACE FUNCTION update_emp_view() RETURNS TRIGGER AS $$
    BEGIN
        --
        -- Perform the required operation on emp, and create a row in emp_audit
        -- to reflect the change made to emp.
        --
        IF (TG_OP = 'DELETE') THEN
            DELETE FROM emp WHERE empname = OLD.empname;
            IF NOT FOUND THEN RETURN NULL; END IF;

            OLD.last_updated = now();
            INSERT INTO emp_audit VALUES('D', current_user, OLD.*);
            RETURN OLD;
        ELSIF (TG_OP = 'UPDATE') THEN
            UPDATE emp SET salary = NEW.salary WHERE empname = OLD.empname;
            IF NOT FOUND THEN RETURN NULL; END IF;

            NEW.last_updated = now();
            INSERT INTO emp_audit VALUES('U', current_user, NEW.*);
            RETURN NEW;
        ELSIF (TG_OP = 'INSERT') THEN
            INSERT INTO emp VALUES(NEW.empname, NEW.salary);

            NEW.last_updated = now();
            INSERT INTO emp_audit VALUES('I', current_user, NEW.*);
            RETURN NEW;
        END IF;
    END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER emp_audit
INSTEAD OF INSERT OR UPDATE OR DELETE ON emp_view
    FOR EACH ROW EXECUTE PROCEDURE update_emp_view();

示例:

--
-- Main tables - time dimension and sales fact.
--
CREATE TABLE time_dimension (
    time_key                    integer NOT NULL,
    day_of_week                 integer NOT NULL,
    day_of_month                integer NOT NULL,
    month                       integer NOT NULL,
    quarter                     integer NOT NULL,
    year                        integer NOT NULL
);
CREATE UNIQUE INDEX time_dimension_key ON time_dimension(time_key);

CREATE TABLE sales_fact (
    time_key                    integer NOT NULL,
    product_key                 integer NOT NULL,
    store_key                   integer NOT NULL,
    amount_sold                 numeric(12,2) NOT NULL,
    units_sold                  integer NOT NULL,
    amount_cost                 numeric(12,2) NOT NULL
);
CREATE INDEX sales_fact_time ON sales_fact(time_key);

--
-- Summary table - sales by time.
--
CREATE TABLE sales_summary_bytime (
    time_key                    integer NOT NULL,
    amount_sold                 numeric(15,2) NOT NULL,
    units_sold                  numeric(12) NOT NULL,
    amount_cost                 numeric(15,2) NOT NULL
);
CREATE UNIQUE INDEX sales_summary_bytime_key ON sales_summary_bytime(time_key);

--
-- Function and trigger to amend summarized column(s) on UPDATE, INSERT, DELETE.
--
CREATE OR REPLACE FUNCTION maint_sales_summary_bytime() RETURNS TRIGGER
AS $maint_sales_summary_bytime$
    DECLARE
        delta_time_key          integer;
        delta_amount_sold       numeric(15,2);
        delta_units_sold        numeric(12);
        delta_amount_cost       numeric(15,2);
    BEGIN

        -- Work out the increment/decrement amount(s).
        IF (TG_OP = 'DELETE') THEN

            delta_time_key = OLD.time_key;
            delta_amount_sold = -1 * OLD.amount_sold;
            delta_units_sold = -1 * OLD.units_sold;
            delta_amount_cost = -1 * OLD.amount_cost;

        ELSIF (TG_OP = 'UPDATE') THEN

            -- forbid updates that change the time_key -
            -- (probably not too onerous, as DELETE + INSERT is how most
            -- changes will be made).
            IF ( OLD.time_key != NEW.time_key) THEN
                RAISE EXCEPTION 'Update of time_key : % -> % not allowed',
                                                      OLD.time_key, NEW.time_key;
            END IF;

            delta_time_key = OLD.time_key;
            delta_amount_sold = NEW.amount_sold - OLD.amount_sold;
            delta_units_sold = NEW.units_sold - OLD.units_sold;
            delta_amount_cost = NEW.amount_cost - OLD.amount_cost;

        ELSIF (TG_OP = 'INSERT') THEN

            delta_time_key = NEW.time_key;
            delta_amount_sold = NEW.amount_sold;
            delta_units_sold = NEW.units_sold;
            delta_amount_cost = NEW.amount_cost;

        END IF;


        -- Insert or update the summary row with the new values.
        <<insert_update>>
        LOOP
            UPDATE sales_summary_bytime
                SET amount_sold = amount_sold + delta_amount_sold,
                    units_sold = units_sold + delta_units_sold,
                    amount_cost = amount_cost + delta_amount_cost
                WHERE time_key = delta_time_key;

            EXIT insert_update WHEN found;

            BEGIN
                INSERT INTO sales_summary_bytime (
                            time_key,
                            amount_sold,
                            units_sold,
                            amount_cost)
                    VALUES (
                            delta_time_key,
                            delta_amount_sold,
                            delta_units_sold,
                            delta_amount_cost
                           );

                EXIT insert_update;

            EXCEPTION
                WHEN UNIQUE_VIOLATION THEN
                    -- do nothing
            END;
        END LOOP insert_update;

        RETURN NULL;

    END;
$maint_sales_summary_bytime$ LANGUAGE plpgsql;

CREATE TRIGGER maint_sales_summary_bytime
AFTER INSERT OR UPDATE OR DELETE ON sales_fact
    FOR EACH ROW EXECUTE PROCEDURE maint_sales_summary_bytime();
INSERT INTO sales_fact VALUES(1,1,1,10,3,15);
INSERT INTO sales_fact VALUES(1,2,1,20,5,35);
INSERT INTO sales_fact VALUES(2,2,1,40,15,135);
INSERT INTO sales_fact VALUES(2,3,1,10,1,13);
SELECT * FROM sales_summary_bytime;
DELETE FROM sales_fact WHERE product_key = 1;
SELECT * FROM sales_summary_bytime;
UPDATE sales_fact SET units_sold = units_sold * 2;
SELECT * FROM sales_summary_bytime;

示例:(下面示例中new_table和old_table需要替换)

CREATE TABLE emp (
    empname           text NOT NULL,
    salary            integer
);

CREATE TABLE emp_audit(
    operation         char(1)   NOT NULL,
    stamp             timestamp NOT NULL,
    userid            text      NOT NULL,
    empname           text      NOT NULL,
    salary            integer
);

CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS $emp_audit$
    BEGIN
        --
        -- Create rows in emp_audit to reflect the operations performed on emp,
        -- making use of the special variable TG_OP to work out the operation.
        --
        IF (TG_OP = 'DELETE') THEN
            INSERT INTO emp_audit
                SELECT 'D', now(), current_user, o.* FROM old_table o;
        ELSIF (TG_OP = 'UPDATE') THEN
            INSERT INTO emp_audit
                SELECT 'U', now(), current_user, n.* FROM new_table n;
        ELSIF (TG_OP = 'INSERT') THEN
            INSERT INTO emp_audit
                SELECT 'I', now(), current_user, n.* FROM new_table n;
        END IF;
        RETURN NULL; -- result is ignored since this is an AFTER trigger
    END;
$emp_audit$ LANGUAGE plpgsql;

CREATE TRIGGER emp_audit_ins
    AFTER INSERT ON emp
    REFERENCING NEW TABLE AS new_table
    FOR EACH STATEMENT EXECUTE PROCEDURE process_emp_audit();
CREATE TRIGGER emp_audit_upd
    AFTER UPDATE ON emp
    REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
    FOR EACH STATEMENT EXECUTE PROCEDURE process_emp_audit();
CREATE TRIGGER emp_audit_del
    AFTER DELETE ON emp
    REFERENCING OLD TABLE AS old_table
FOR EACH STATEMENT EXECUTE PROCEDURE process_emp_audit();
3.3.2 事件触发器

事件触发器即当有event发生时,触发对应的操作,该类触发器函数的默认参数有两个:TG_EVENT text和TG_TAG text。示例如下:

注: 事件触发器在OpenGauss上支持,GaussDB集中式并不支持事件触发器!

CREATE OR REPLACE FUNCTION snitch() RETURNS event_trigger AS $$
BEGIN
    RAISE NOTICE 'snitch: % %', tg_event, tg_tag;
END;
$$ LANGUAGE plpgsql;

CREATE EVENT TRIGGER snitch ON ddl_command_start EXECUTE PROCEDURE snitch();

下面是OpenGauss的示例。

f0b4d01654a3bbb44a89f829198b99a4.png

3.4 打印输出说明

3.4.1 存储过程打印输出结果集

存储过程没有Return变量,因此存储过程结束时也没有Return语句。此场景下,如果要提前结束其运行过程,则使用没有表达式的Return语句。如果该存储过程有Output输出参数,则其会返回最终结果。

PL/pgSQL的函数,存储过程,Do匿名块都可以内部调用存储过程。Output参数与Call调用的处理方式不同。存储过程的每个Out 或者 InOut 参数必须对应Call语句中的一个变量,存储过程返回的任何值都将由输出参数返回结果。

CREATE PROCEDURE triple(INOUT x int)
LANGUAGE plpgsql
AS $$
BEGIN
    x := x * 3;
END;
$$;

DO $$
DECLARE myvar int := 5;
BEGIN
  CALL triple(myvar);
  RAISE NOTICE 'myvar = %', myvar;  -- prints 15
END;
$$;

存储过程中输出参数对应的变量可以是简单数值,也可以是复合数值,但目前Output 参数不能是数组元素。

注意 raise 多用于调试 :

RAISE LEVEL … LEVEL 有6种错误级别: debug, log, info, notice, warning, exception(默认)

RETURN

return 字段 || ‘,’ || 字段;

语法如下:

RAISE [ level ] 'format' [, expression [, ... ]] [ USING option = expression [, ... ] ];
RAISE [ level ] condition_name [ USING option = expression [, ... ] ];
RAISE [ level ] SQLSTATE 'sqlstate' [ USING option = expression [, ... ] ];
RAISE [ level ] USING option = expression [, ... ];
RAISE ;

例如:

RAISE NOTICE 'Calling cs_create_job(%)', v_job_id;

变量v_job_id将会替换掉%。

RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id;

示例:

-- 编写add_mask(id1,id2)函数,当id1是当前查询用户时,显示正常ID,如果不是则显示为-- id2
    create or replace function add_mask(id1 text, id2 text)
    returns text as $$
    declare
        var1 text;
    begin
        select current_user into var1;
        if var1 = id1 then
            return 'current user is ' || id1;
        else
            return 'current user is ' || id2;
        end if;
    end;
    $$ language plpgsql;
3.4.2 函数输出结果集
3.4.2.1 RETURN语句

自定义函数可以声明返回任意数据集,可通过RETURN NEXT,RETURN QUERY,RETURNS TABLE,RETURNS SETOF。

语法如下:

RETURN expression;

函数用RETURN语句终止运行并返回结果给调用者。这种形式用于不返回结果集合的PL/pgSQL函数。

在返回标量类型的函数中,表达式的结果将自动转换为函数的返回类型。但要返回复合(行)值,则必须编写一个表达式精确地返回所请求的列集,该方法需要显式构造。

如果函数声明了参数输出值,那么函数 return 后不跟表达式,将返回当前值给输出参数变量。如果将函数声明为返回 void 类型,则可以使用 return 语句提前退出函数,return后面不能写表达式。

函数的返回值不能为undefined。如果PL/pgSQL控制块执行到函数的顶层块的末尾而未命中return语句,则将发生运行报错。但是,此限制不适用于带有输出参数的函数和返回void的函数。在这些情况下,如果顶级块完成,则自动执行return语句。

例如:

-- functions returning a scalar type
RETURN 1 + 2;
RETURN scalar_var;

-- functions returning a composite type
RETURN composite_type_var;
RETURN (1, 2, 'three'::text);  -- must cast columns to correct types
3.4.2.2 RETURN NEXT和RETURN QUERY

语法如下:

RETURN NEXT expression;
RETURN QUERY query;
RETURN QUERY EXECUTE command-string [ USING expression [, ... ] ];

当PL/pgSQL函数被声明返回SETOF sometype时,则存储过程的实现略有不同。

在该场景下,RETURN NEXT 或 RETURN QUERY 返回的一系列结果集,用 RETURN 不带参数的语句来声明函数执行结束。RETURN NEXT 既可以表示标题数据类型,也可以表示复合数据类型。而对于复合数据类型,RETURN NEXT 将返回结果整个表的结果集。RETURN QUERY 是将QUERY的执行结果集返回给函数结果集。在函数的单个返回结果集中,RETURN NEXT 和 RETURN QUERY 可以混合使用,该场景下函数的结果集将以串联方式展现。而一个无参数的 RETURN语句则会控制函数的执行结束以达到控制函数执行流程的结果。

RETURN QUERY 有一个 RETURN QUERY EXECUTE 的变量,它用于指向SQL的动态执行结果。参数表达式可通过 WITH 插入到 QERUY 字符串里,其方法与 EXECUTE 方法相同。

如果声明函数时没给Output参数,则在使用Reutrn Next时不应该跟表达式。当有多个Output参数时,函数则应该声明返回SETOF。如下面RETURN NEXT的用例:

CREATE TABLE foo (fooid INT, foosubid INT, fooname TEXT);
INSERT INTO foo VALUES (1, 2, 'three');
INSERT INTO foo VALUES (4, 5, 'six');

CREATE OR REPLACE FUNCTION get_all_foo() RETURNS SETOF foo AS
$BODY$
DECLARE
    r foo%rowtype;
BEGIN
    FOR r IN
        SELECT * FROM foo WHERE fooid > 0
    LOOP
        -- can do some processing here
        RETURN NEXT r; -- return current row of SELECT
    END LOOP;
    RETURN;
END;
$BODY$
LANGUAGE plpgsql;

SELECT * FROM get_all_foo();

RETURN QUERY用例:(需要提前创建flight表,字段至少包含flightid和flightdate)

CREATE FUNCTION get_available_flightid(date) RETURNS SETOF integer AS
$BODY$
BEGIN
    RETURN QUERY SELECT flightid
                   FROM flight
                  WHERE flightdate >= $1
                    AND flightdate < ($1 + 1);

    -- Since execution is not finished, we can check whether rows were returned
    -- and raise exception if not.
    IF NOT FOUND THEN
        RAISE EXCEPTION 'No flight at %.', $1;
    END IF;

    RETURN;
 END;
$BODY$
LANGUAGE plpgsql;

-- Returns available flights or raises exception if there are no
-- available flights.
SELECT * FROM get_available_flightid(CURRENT_DATE);

3.5 PL/PGSQL之事务管理

调用存储过程使用CALL关键字,如同调用匿名块使用DO一样。在PL/pgSQL语法中,可以使用COMMIT和ROLLBACK结束事务。使用COMMIT或者ROLLBACK结束事务后,另一个新事务会自动启动。所以没有单独的START TRANSACTION。这些与PL/pgSQL中的BEGIN和END是不同的。

示例如下:(需要创建表test1(a int),其中a字段至少有奇数和偶数值)

CREATE PROCEDURE transaction_test1()
LANGUAGE plpgsql
AS $$
BEGIN
    FOR i IN 0..9 LOOP
        INSERT INTO test1 (a) VALUES (i);
        IF i % 2 = 0 THEN
            COMMIT;
        ELSE
            ROLLBACK;
        END IF;
    END LOOP;
END;
$$;

CALL transaction_test1();

事务的控制仅受顶层的CALL或DO调用,或者嵌套的CALL或DO调用中可用。如果调用栈为CALL proc1() -> CALL proc2() -> CALL proc3(),那么第二个和第三个过程可以执行事务控制动作。但如果调用栈是CALL proc1() -> SELECT func2() -> CALL proc3(),由于中间有SELECT,那么最后一个过程不能做事务控制。

PL/pgSQL不支持保存点(保存点/回滚到保存点/释放保存点命令)。

在Cursor LOOP中有些特殊注意点:参数如下示例:

CREATE PROCEDURE transaction_test2()
LANGUAGE plpgsql
AS $$
DECLARE
    r RECORD;
BEGIN
    FOR r IN SELECT * FROM test2 ORDER BY x LOOP
        INSERT INTO test1 (a) VALUES (r.x);
        COMMIT;
    END LOOP;
END;
$$;

CALL transaction_test2();

在非只读的游标循环中不允许使用事务操作(例如UPDATE…Returning)。

3.6 常用SQL操作

3.6.1 union all/ union

该语法用于合并两个SELECT查询结果集。union会对两个查询结果集去除重复的数据。而union all不会对结果集去重。

select * from score1 order by chinese limit 10
union 
select * from score2 order by chinese limit 10;

select * from score1 order by chinese limit 10
union all
select * from score2 order by chinese limit 10;
3.6.2 dense() rank()

用于对结果集排序。

dense_rank() over([partition by column] order by column desc)
  • DENSE_RANK函数为各组内值生成连续排序序号,其中,相同的值具有相同序号
CREATE TABLE dense_rank_t1(a int, b int);
INSERT INTO dense_rank_t1 VALUES(1,1),(1,1),(1, 2),(1, 3),(2, 4),(2, 5),(3,6);
SELECT a,b,DENSE_RANK() OVER(PARTITION BY a ORDER BY b) FROM dense_rank_t1;
3.6.3 not in/ not exist

查看两个班级相同的科目, 202201班在score2中不存在的成绩, 要求使用not in(考试时详细确认题目要求, 查看是具体哪些科目成绩)

select chinese from score where chinese not in (select chinese from score2);
3.6.4 merge into

将目标表和源表中的数据针对关联条件进行匹配, 匹配时对目标表进行update更新操作, 不匹配时对目标表进行insert写入操作

MERGE INTO products p USING newproducts np ON (p.product_id = np.product_id)   
    WHEN MATCHED THEN  
        UPDATE SET p.product_name = np.product_name, p.category = np.category WHERE p.product_name != 'play gym'  
    WHEN NOT MATCHED THEN  
        INSERT VALUES (np.product_id, np.product_name, np.category) WHERE np.category = 'books';
3.6.5 首字母大写函数initcap
-- family_name跟first_name用'.'拼接,要求首字母大写
select id, initcap(family_name||'.'||first_name), sexmark, grade from su;
3.6.6 系统视图的权限查询
select t1.*,rolname from (select datname,(aclexplode(datacl)).grantee, (aclexplode(datacl)).privilege_type from pg_database) t1,pg_roles where grantee=pg_roles.oid and rolname='sjh111' and datname not like '%template%';
-- 查看用户user1和数据库的相关权限,题目提示用pg_database和pg_roles,要求显示数据库名、用户名、数据库的权限(一定要背下来,原题,而且不要去格式美化)
SELECT a.datname, b.rolname, string_agg(a.priv_t, ',')
  from (SELECT datname, (aclexplode(COALESCE(datacl, acldefault('d' :: "char", datdba)))).grantee as grantee, (aclexplode(COALESCE(datacl, acldefault('d' :: "char", datdba)))).privilege_type as priv_t
          FROM "pg_database"
         WHERE datname not like '%template%' ) a,
       "pg_roles" b
 WHERE (a.grantee = 0 or a.grantee = b.oid)
   AND b.rolname = 'user1'
 GROUP BY a.datname, b.rolname;
3.6.7 插入一条数据,当主键冲突时将mark改为’F’

语法如下:

INSERT INTO xxx ON DUPLICATE KEY UPDATE expression

示例:

insert into su values(2,'tom','jerry','tom','H',63) on duplicate key update mark='F'

至此,PL/PGSQL的学习告一段落。

4 反馈改进建议

如您在案例实操过程中遇到问题或有改进建议,可以到论坛帖评论区反馈即可,我们会及时响应处理,谢谢!

相关推荐

鸿蒙案例上新啦 | 购物、听歌、健身:15个模板带你一键激活社交娱乐

页面层级如何组织、跨页共享状态如何同步、列表与详情及个人中心如何形成稳定闭环。鸿蒙开发实践中心将这些高频消费与互动场景沉淀为可参考的标准模板,提供从业务架构到界面实现的快捷实践入口。更多实战案例欢迎前往鸿蒙开发实践中心案例中心查看并开启云端体验。

华为云官方博客 338

鸿蒙案例上新啦 | 出门打车、到家上课:25个模板带你一屏打通日常生活

如何合理拆分页面组件、如何建立单一可信的状态源、如何让用户在每一步操作中都获得明确反馈。鸿蒙开发实践中心以标准化的应用与元服务模板为入口,将清晰的业务架构与可复用的代码结构直观呈现。不妨立即挑选一个感兴趣的方向进入云端环境,从页面布局、组件封装、状态管理到路由组织,亲手跑通你的鸿蒙实战项目。

华为云官方博客 325

LakeAgent:DWS数据仓库的第二大脑,告别 “翻译式运维”,迈入智能自治时代

和普通 SQL 改写工具不同,LakeAgent 自动做多方案代价对比,主动规避负优化陷阱:补充分区键过滤实现剪枝,扫描分区由 37 收敛至 1,执行代价从 758.28 降至 113.74;未来的数据仓库,将不止能算、能存,更能看懂自己、诊断自己、理解自己。长久以来,数仓的智能都依附于人:信号需要人解读,瓶颈需要人研判,经验困在个体里,无法随底座永续生长。CPU 高、CPU 告警、CPU 诊断、CPU 使用率过高、CPU 满。语法、错误码、计费、性能调优、最佳实践、故障处置等全场景内容。

华为云官方博客 555

如何使用RDS Supabase后端BaaS能力:文件存储(File Storage)

• product-images(公开桶,允许公共读取):策略名称"Public read for product-images",允许SELECT,目标角色anon,策略定义bucket_id = 'product-images'。上传/删除需另配INSERT/DELETE策略,详见SQL。• product-specs(私有桶,仅认证用户访问自己的文件夹):建议拆为上传、读取、删除三条策略,策略定义均为 bucket_id = 'product-specs' 且仅限本人文件夹(详见下文 SQL)。

华为云官方博客 476

如何使用RDS Supabase后端BaaS能力:实时订阅与推送(Realtime)

Realtime 把数据库的每次变更同步到客户端:表里增、删、改一行,订阅者无需刷新页面或主动请求即可秒级收到。三大能力各司其职:• Postgres Changes — 监听表的 INSERT/UPDATE/DELETE,变化即通知• Broadcast — 客户端之间直接互发,适合瞬时通知、协同编辑• Presence — 跟踪频道里“谁在线”典型场景有实时数据展示(订单、库存、监控指标即时刷新)、即时通讯(消息收发、在线名单、“对方正在输入”)、协同编辑与实时通知。

华为云官方博客 465

AI Agent如何通过MCP连接到华为云RDS Supabase应用平台

Schema元数据列出所有可访问的表(按schema分组)。Schema元数据列出表的列及元数据(类型/默认值/约束)。Schema元数据列出所有约束(PK/FK/UNIQUE/CHECK/EXCLUDE)。可按schema/表/类型过滤。Schema元数据列出所有外键关系。可按schema/表过滤。Schema元数据列出所有索引及定义和大小。可按schema/表过滤。Schema元数据列出已应用的数据库迁移记录。Schema元数据列出已安装的PostgreSQL扩展。

华为云官方博客 473

华为大数据Flink让每一 CU 算力,跑出开源 5.74 倍的性能

1. 开源Apache Flink 已成为业界流计算事实标准,但在极端重负载场景下存在性能瓶颈。2. 华为大数据Flink通过多项自研深度优化,在执行计划、算子执行、状态存储三个层面构建协同的优化体系。3. Nexmark Benchmark 测试结果:整体性能提升 5.74 倍,极端重负载场景性能领先最高 18.3 倍。4. 100% 兼容 Apache Flink 1.20,业务代码零改造,无缝迁移。

华为云官方博客 380

LakeAgent实战 | 业务分析篇从一句提问到业务分析

本文以“订单金额变化”分析为例,展示LakeAgent如何通过自然语言提问,结合DataArtsStudio的数据资产与专业技能,帮助业务人员快速定位数据、生成SQL、追踪血缘、生成报告。从问题出发,逐步实现数据发现、变化洞察、原因追溯与团队协作,最终形成可复用的分析流程与企业规则,显著提升数据分析效率与一致性。

华为云官方博客 390

华为云RDS Supabase AI应用已开放公测 :30分钟搭建AI应用后端

认证、存储、实时推送、数据库API——4个后端模块,传统开发需要数周。今天,你只需要30分钟。AI创业公司的全栈开发工程师。上周老板说"下周要上线的AI对话应用,后端你来搞"——认证鉴权、数据库接口、文件存储、实时推送,每一个模块都需要数周搭建,而市场不会等你。小张打开华为云RDS Supabase,30分钟搭好认证、数据库API、对象存储,剩下的时间全用来打磨AI交互体验。基于开源Supabase+华为云RDS PostgreSQL全托管能力,为开发者提供。

华为云官方博客 396

如何使用RDS Supabase后端BaaS能力:数据库函数(Database Function)

上一篇介绍了 Database Data API——建表即自动生成增删改查接口,解决单表读写,但聚合统计、跨表事务、字段自动维护等逻辑已超出其能力边界。本篇推进到数据库函数(Database Function):把业务逻辑封装进数据库,前端通过 .rpc() 调用,由触发器自动响应数据变更。上一篇中,小张基于 Data API 完成了产品目录的建表与基础读写。但新需求随之而来:按分类统计产品数与价格区间、上架新品同时写产品表与定价表、产品修改后自动刷新更新时间。

华为云官方博客 464

如何使用RDS Supabase后端BaaS能力:创表即生成API(Database Data API)

认证、存储、实时推送、数据库API——4个后端模块,传统开发要数周。本系列逐个拆解,首篇聚焦数据库API(Database Data API):建表即得 API,无需手写后端。小张,AI创业公司的全栈工程师。老板让他做个产品目录页,展示产品与价格、支持上下架。传统做法要搭后端、写接口、配权限,工期少说1-2周;而在华为云RDS Supabase,建两张表、应用 SDK 直连,几分钟增删改查全跑通。

华为云官方博客 478

从一句话到一个能出去的链接,AI开发者空间做了什么

自行搭建的 Agent、正在使用的开源项目、团队既有的工具,都可以接入,可以通过AI创建或者手动搭建环境;华为云 AI开发者空间——面向开发者的云端一站式开发平台,做的就是把这段路接上:环境免安装,浏览器打开即为完整的 Linux 运行环境,预置的 AI 智能体开箱即用。第一个任务建议选能出结果的:做一个游戏、生成一个页面、创建一台实例,从需求走到可访问的链接,一次就能看清这个环境在做什么。沙箱是一个完整的 Linux 环境,依赖、服务、端口都是真实的,不是模拟。环境释放后,权限随之失效。

华为云官方博客 426

华为云码道配置自定义大模型,四种方式完整实操

案例介绍。

华为云官方博客 624

图数据库:实现穿透式监管的数据基座

穿透式监管标志着国企监管从“管企业”向“管资本”转型过程中的关键一跃。而图数据库正是这一跃的技术支点。从股权穿透到资金链穿透,从供应链穿透到数据穿透,华为GES图数据库正在将“穿透式监管”从理念变为可落地、可操作的实践。无论是天眼查式的全量链路查询,还是某集团财经的税务规则判断,抑或是芯片供应链式的部件风险分析,乃至通过Palantir构建决策知识图谱,其底层逻辑都是一致的——用图的力量,穿透一切形式的“隔离层”,直抵风险本质与责任核心。

华为云官方博客 664

《公共云三十问》正式收官,奔赴企业AI百场景新征程

今年6月底,公共云产业联盟重点推出《公共云三十问》专栏,以每周三问的节奏,连载十周,解读《先进公共云》白皮书,分享国计民生系列案例,旨在凝聚共识、推动落地实践。同时,专栏分析公共云在产业升级、民生福祉、科技创新、全球发展等方面的作用,梳理中国特色公共云发展路径:以普惠云拓展用云广度,以智算云提升用云深度,以自主云夯实用云厚度,以全球云打开用云跨度。这十问从专属云、行业云切入,深入政务、金融、交通、电力、钢铁、化工、油气、制造等国计民生场景,回答公共云“怎么用”。

华为云官方博客 473

开发者小技巧|IAM配置,让码道代码智能体连续30天免登录

案例介绍。

华为云官方博客 645

码道深度对接CodeArts,让需求自动落地为代码

案例介绍。

华为云官方博客 720

【公共云三十问 之三十】拥抱AI,企业如何建设面向智能体的专属云?

企业可分阶段建设覆盖开发、运行、运营及安全的一体化智能体平台——从构建一站式、高低码协同的智能体开发平台,到打造高性能、极致弹性的运行时环境,再到搭建AgentOps驱动的持续运营与运维体系,同时构建端到端、多层次的全链路安全防护体系。在数据湖建设方面,遵循“存量复用、平滑演进、AI导向、分步迭代”原则,分三阶段推进——打造AI多模数据湖核心能力、完成存算分离与统一元数据管理、实现闭环赋能全面支撑智能体规模化落地,在保护既有投资的前提下将传统数据平台转型为AI数据专属供给平台。

华为云官方博客 823

【公共云三十问 之二十九】国计民生之制造:制造云如何重塑数智时代的新质生产力?

平台打通软件交付的完整路径,覆盖需求管理、代码托管、代码检查、编译构建、制品仓库、部署、测试等关键环节,支持大模型能力融入研发全流程,实现从需求到交付的端到端管理。通过构建“研发一朵云”,方案打破地域与组织壁垒,实现跨团队协同开发,有效缩短软件交付周期,助力工业制造企业从“传统研发”走向“敏捷研发”,加速数智化转型进程。当制造云真正成为数据流通的平台、AI创新的底座,公共云的社会价值将从基础设施层向生产场景层持续延伸,为新型工业化建设和制造强国战略注入源源不断的动力,重塑数智时代的中国制造新质生产力。

华为云官方博客 789

告别重复手工迁仓,码道MCP+Skill实现仓库批量迁移

案例介绍。

华为云官方博客 790

【公共云三十问 之二十八】国计民生之油气:智算云如何叩响数智时代的“地心蓝金”?

西南油气田以“向地球深部要资源”为战略方向,联合以华为云Stack为代表的公共云服务商共同打造统一算力底座,建设西南区域数据湖与通用技术平台,构建“勘探开发工程技术、天然气生产过程管控、天然气产运储销”等三个一体化协同应用场景,支撑油气勘探、开发生产、管输储运等全业务链的数智化转型。当智算云真正成为数据流通的平台、AI创新的底座,油气田得以将算力效能、数据价值与智能技术深度融入勘探开发全链条,将“看不见的算力”转化为“摸得着的储量”,以智能化手段持续降低勘探风险、缩短发现周期、提高采收效率,确保生产安全。

华为云官方博客 613

【公共云三十问 之二十七】国计民生之化工:数智云如何重塑传统化工新范式?

在架构方面,方案构建云边协同的统一体系,涵盖AI协同、数据协同与运营协同三大维度——总部集中训练大模型,边缘节点负责推理执行,云边持续迭代,实现“边用边学”的智能闭环,模型平均精度提升20%以上。此外,方案提供一站式企业级工业物联网平台、数据湖与数据治理平台,覆盖工业数据分析“采、存、算、管、用”全流程,打通生产现场与管理系统间的数据壁垒,有效消除数据孤岛。层面,方案充分发挥软硬协同优势,实现计算、存储、网络与云平台的深度协同,训练线性度超95%,为大模型训练推理提供强劲算力支撑。▲化工“数智”新模式。

华为云官方博客 663

【公共云三十问 之二十六】国计民生之钢铁:数智云如何淬炼中国钢铁新力量?

同时,构建云边端协同的统一架构,支撑集团总部与各生产基地两级协同发展——总部集中训练大模型,边缘节点负责推理执行,云边持续迭代,实现训推一体化的资源调度与高效协同,为钢铁行业的数智化转型构建了坚实的算力底座。层面,方案构建覆盖“工业数采→边缘IT/OT融合→统一数据湖→数据治理平台→数据应用”全链路的一站式数据治理与流通平台,打通生产现场与管理系统间的数据壁垒,让数据“采得到、流得动、用得好”,加速数据价值从生产现场向经营决策层流动释放。湘潭钢铁集团的钢铁数智云,是钢铁工业智能云建设的标杆案例。

华为云官方博客 526

LakeWatch——MRS 智能运维Agent

从资源池、队列、作业等多个维度,对集群CPU与内存资源进行精细化分析与对比,直观呈现不同时间段的空闲、满载等使用状态,识别资源浪费,指导治理优化,为扩缩容决策提供数据支撑。对HDFS存储进行多维度深度分析(目录、用户、用户组等),涵盖使用量、增量、文件大小分布及数据热度等指标,帮助快速识别增长较快的目录、长期未访问的文件,统计目录文件数量与小文件占比,评估HDFS总存储量与扩缩容需求,为存储治理与资源规划提供全面数据支撑。Agent主动巡检,识别异常趋势并提前预警,让运维从"事后处理"转向"事前预防"。

华为云官方博客 515

华为云专题文档发布:丰富的AI案例、选型指导和概念解读

发布 “云服务选型”、“云计算概念”专题,提供云服务选型指导,助您选择匹配业务的云服务,解读云计算相关概念,开启云上学习之路。开发者工具和文档专区上线,提供从学习、实践到反馈的全链路支持。帮助中心新增100余篇AI案例。

华为云官方博客 286

一个人,也能用AI做点“星东西”

华为云星耀创想者大赛-2026华为云OPC创意大赛等你来~

华为云官方博客 290

从需求到可执行工作项:MCP+Skill 让智能体规划真正落地

回顾整个体验,以前让智能体帮我拆需求,它是洋洋洒洒给我列了一大篇 Markdown,看着挺像那么回事,但最后还是得我一条条往 CodeArts Req 里搬,搬完再手动挂父子关系、排日期,拆需求五分钟,搬运两小时。现在好了,MCP 给智能体接上了"手",Skill 给它装上了"项目经理的脑子",一句话下去,34 个工作项连带排期、层级关系全给我建好了,甘特图一拉,齐刷刷都在两周内,节省了多少工作量啊,美滋滋。本次实操在码道代码智能体基础上,构建了一套"需求→可执行工作项"的自动化规划方案。

华为云官方博客 576

华为云码道「码力续航计划」更新!

✅上新GLM‑5.3‑Flash,高速响应,代码生成、工程解析、Agent调试高效输出。新增GLM‑5.3‑Flash福利模型,每日解锁1000万免费Tokens。● 本次活动共发放总量价值100万元Tokens,发完即止!把耗算力的编码重活,交给华为云码道代码智能体🚀🚀。● 每日额度准时刷新,当日清零、次日满血重置。✅复杂算法、大项目解析、批量代码生成随便跑。✅不用抠额度、不用算消耗,放开调试大胆迭代。✅无需额外操作,登录即送,直接切换模型使用。● 把算力交给码道,让撸码不再内耗👏🏻。

华为云官方博客 687

【公共云三十问 之二十四】国计民生之交通:交通云如何铺就数智通衢新坦途?

交通行业作为支撑实体经济的关键基础设施,涵盖公路、铁路、机场、港口、地铁等众多子领域,其中,公路网络作为综合交通运输体系的基础骨架,承担着全国超过70%的客货运量,其数字化、智能化转型的需求尤为迫切。方面,提供Agent开发框架、知识库管理、RAG工程、意图识别、静态应用编排及模型评测等能力,支持客户将行业知识、业务流程与AI能力深度融合,快速构建面向收费稽核、路网调度、应急处置等场景的智能应用。开发并实现落地应用,全面覆盖建设、管理、养护、运营、服务、安全、办公七大业务领域。

华为云官方博客 461

【公共云三十问 之二十五】国计民生之电力:电力云如何点亮数智电力新引擎?

方案构建了覆盖数据获取、质量评估到标准化处理的全链路能力。基于公共云服务商智算底座及气象大模型,通过时空图神经网络重新建模产流机制,中国华电建成超50TB多源融合数据集,突破时空建模与机理融合等五大关键技术,短期径流预测精度较传统机理模型提升5个百分点以上,实现15天中期径流预测精度超85%,推理效率达秒级。基于气象大模型的基础架构与预训练能力,客户可直接在大模型之上开展增量训练与微调,充分利用场站所在区域的高精度历史数据(如3公里、1公里分辨率),针对不同场站和区域的地形地貌与气候特征进行模型调优。

华为云官方博客 381
上一篇: 基于开发者空间,实现仓颉 – C跨语言编程控制台小游戏
下一篇: 基于开发者空间GaussDB云数据库的PLpgSQL实践一
华为云开发者联盟
华为云开发者联盟 企业官方账号 企业官方账号
博客等级 码龄10年 72万+粉丝 5985原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值