MySQL ビュー/ストアド プロシージャ/トリガー

        この章では、MySQL ビュー、ストアド プロシージャ、トリガーなどのいくつかのストレージ オブジェクトを紹介することを目的としており、ビューとは何か、ストアド プロシージャとは何か、トリガーとは何かを紹介します。また、ビュー、ストアド プロシージャ、トリガーを作成、クエリ、変更、削除するにはどうすればよいでしょうか? そして、対応する例を通してその活用方法をさらに紹介し、理解を深めていきます。

1. 見る

ビューとは何ですか? 

        View(ビュー)は仮想テーブルです。ビュー内のデータは実際にはデータベースに存在しません。行と列のデータはカスタム ビューのクエリで使用されるテーブルから取得され、ビューの使用時に動的に生成されます。

        平たく言えば、ビューはクエリの SQL ロジックのみを保存し、クエリ結果は保存しません。したがって、ビューを作成するときの主な作業は、この SQL クエリ ステートメントの作成になります。

これを言うのは少し青白く、まだよく理解していないと思います。ビューを作成、変更、削除することでさらに説明しましょう。

ビューを作成する

-- [ WITH [ CASCADED | LOCAL ] CHECK OPTION ] 是视图检查选项, 后面会讲到
CREATE [OR REPLACE] [force] VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH [ CASCADED | LOCAL ] CHECK OPTION ] [with read only]
-- 示例:创建一个查询tb_user表的视图user_v_1, 查询字段是id,name
create or replace view user_v_1 as select id,name from tb_user where id <= 10;

注: ここでのビューは仮想テーブルであり、実際のテーブルではなく、select ステートメントの論理カプセル化です

②クエリ  ビュー

-- 查看创建视图语句
SHOW CREATE VIEW 视图名称; 
-- 查看视图数据
SELECT * FROM 视图名称 ...... ;

ビューを変更する

-- 方式一:和创建视图语句一致, 但这里必须使用 [OR REPLACE] 
CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH [ CASCADED | LOCAL ] CHECK OPTION ] 
-- 方式二
ALTER VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]

④ビューの削除 

DROP VIEW [IF EXISTS] 视图名称 [,视图名称] ...

チェックオプションの表示

        try to check オプションを紹介する前に、try to check? を使用する必要がある理由を示す例を見てみましょう。

-- 为tb_user表创建一个视图
create or replace view user_v_1 as select id,name from tb_user where id <= 10; 
-- 查询该视图的数据(id<=10)
select * from user_v_1; 
-- 向视图插入两条数据
insert into user_v_1 values(6,'Tom'); 
insert into user_v_1 values(17,'Tom22');
-- 查询tb_user表的数据, 这两条数据都能成功的在 tb_user 表中查看到
select * from tb_user;
-- 再次查询该视图的数据, 能看到id=6的数据, 不能看到id=17的数据
select * from user_v_1; 

ビューで id=17 のデータが見つからないのはなぜですか?

        ビューの作成時に指定された条件は id<=10 であり、id 17 のデータは適格ではないため、クエリは実行されませんが、このデータは実際にベース テーブルに正常に挿入されています。ビューを定義するときに条件を指定すると、データの挿入、変更、削除を行うときに、条件を満たした場合のみ動作し、そうでない場合は動作できないのでしょうか。答えは「はい」です。これには、ビューの検査オプションの助けが必要です。

解決:

       ビューを作成するステートメントの最後に[WITH [ CASCADED | LOCAL ] CHECK OPTION ]チェック オプションを追加すると、MySQL は挿入、更新、削除など、ビューを通じて変更されている各行をチェックします。ビューの定義に準拠するようにします。定義が満たされていない場合、次のエラーが報告され、操作は失敗します。

        さらに、MySQL では、別のビューに基づいてビューを作成でき、依存するビュー内のルールの一貫性もチェックされます。チェックの範囲を決定するために、mysql にはCASCADEDLOCALの 2 つのオプションが用意されています。デフォルト値は CASCADED です。次に、2 つのオプションについて説明します。

① CASCADED(カスケード)

        たとえば、v2 ビューの作成時にチェック オプションがカスケードとして指定されているが、v1 ビューの作成時にチェック オプションが指定されていない場合、v2 ビューは v1 ビューに基づきます。次に、v2 をチェックすると、v2 の限定されたスコープがチェックされるだけでなく、v1 に関連付けられたビュー v1 の限定されたスコープもカスケードされます。

②LOCAL(ローカル)

        たとえば、v2 ビューは v1 ビューに基づいており、v2 ビューの作成時にチェック オプションがローカルとして指定されます (1) v1 ビューの作成時にチェック オプションが指定されていない場合、v2 のみがチェックされますv2 の限定された範囲のチェックを実行すると、v2 の関連ビュー v1 はチェックされません (2) v1 ビューの作成時にチェック オプションが指定されている場合、v2 の限定された範囲をチェックすると、その限定された範囲はチェックされませんv1 のものも同時にチェックされます。(MySQL 8.0が有効です)

CASCADED と LOCAL の主な違いは、
        どちらもビューを再帰的に検索することです。違いは、
                CASCADED は前のビューにチェック オプションがあるかどうかに関係なく、限られた範囲で実行するのに対し、
                LOCAL は前のビューにチェック オプションがあるかどうかをチェックすることです。 , ある場合は限定された範囲で実施されますが、ない場合は実施されません。

ビューのデータは気軽に更新できない!

         ビューを更新(データの挿入、削除、更新など)するには、ビュー内の行と基になるテーブル内の行の間に 1 対 1 の関係が必要です。ビューに次のいずれかが含まれている場合、ビューは更新できません。

  • 集計関数またはウィンドウ関数 (SUM()、MIN()、MAX()、COUNT() など)
  • 明確な
  • グループ化
  • 持っている
  • UNION または UNION ALL 

上記のいずれかを使用すると、ビューの行データまたはフィールドとベース テーブルの行データまたはフィールドの間に 1 対 1 の対応がなく、更新操作は失敗します。

ビューについて、最後にその機能を見てみましょう。

  • Simplicity
       View は、ユーザーのデータの理解を簡素化するだけでなく、操作も簡素化します。頻繁に使用するクエリをビューとして定義できるため、ユーザーは後続の操作のすべての条件を毎回指定する必要がありません。
  • セキュリティ
       データベースは認証できますが、データベースの特定の行および特定の列に対しては認証できません。ビューを通じて、ユーザーは表示できるデータのみをクエリおよび変更できます。
  • データの独立性
       ビューは、ユーザーがベース テーブルの構造の変更による影響を防ぐのに役立ちます。つまり、ベース テーブルのフィールド名が変更された場合でも、プログラムのニーズを満たすビューを作成するときに、エイリアスを使用してフィールド名の変更をシールドできます。

2 番目に、ストアド プロシージャ

ストアド プロシージャとは何ですか?

        ストアド プロシージャは、事前にコンパイルされてデータベースに保存されている SQL ステートメントの集合です。ストアド プロシージャを呼び出すと、アプリケーション開発者の多くの作業が簡素化され、データベースとアプリケーション サーバー間のデータ転送が削減され、効率が向上します。データ処理の良好です。一言でまとめると、ストアド プロシージャはコードをカプセル化し、データベースの SQL 言語レベルで再利用します。 

 ストアド プロシージャの特徴は何ですか?

  • カプセル化と再利用: 特定のビジネス SQL をストアド プロシージャにカプセル化し、必要に応じて直接呼び出すことができます。
  • パラメータを受け取り、データを返すことができます: ストアド プロシージャでは、パラメータを渡し、戻り値を受け取ることができます。
  • ネットワークの相互作用を減らし、効率を向上させる: 複数の SQL が関係する場合、各実行はネットワーク送信になります。また、ストアド プロシージャにカプセル化されている場合は、ネットワークとの対話は 1 回だけで済みます。

同様に、ビューの作成、呼び出し、表示、削除によってさらに進みます (次の 4 つの手順で、最初のボックスは構文であり、残りのボックスまたは図は例です)。

① ストアドプロシージャを作成する 

CREATE PROCEDURE 存储过程名称 ([ 参数列表 ]) 
BEGIN
    -- SQL语句 
END ;
-- 示例:创建一个存储过程p1, 用来封装查询tb_user表数据总量的SQL语句
create procedure p1()
begin
	select count(*) from tb_user;
end;

② ストアドプロシージャを呼び出す

CALL 名称 ([ 参数 ]);

③ ストアドプロシージャを参照する

-- 查询某个存储过程的定义
SHOW CREATE PROCEDURE 存储过程名称 ;
-- 查询指定数据库的存储过程及状态信息 
SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'xxx';
-- 示例:查询存储过程p1的定义
show create procedure p1;
-- 示例:查询itcast数据库的存储过程及状态信息 
select * from information_schema.ROUTINES where ROUTINE_SCHEMA = 'itcast';

④ ストアドプロシージャを削除する

DROP PROCEDURE [ IF EXISTS ] 存储过程名称;

知らせ:

        コマンドラインでストアドプロシージャを作成するSQLを実行する場合、SQL文の終了文字をキーワード区切り文字で指定する必要があります。

        デフォルトでは、コマンド ラインは ; で終わり、クエリと削除を実行する SQL ステートメントも ; で終わるため、コマンド ラインで実行プロセスを作成するステートメントが途中で終了するのを防ぐために、コマンド ラインのターミネータは変更される。

上記のストアド プロシージャを作成する場合は、単純なクエリ ステートメントのみを使用しますが、複雑なビジネス システムで複雑なストアド プロシージャを設計する場合は、多くの文法構造を関与させる必要があります。ストアド プロシージャの文法構造を分析してみましょう。

ストアド プロシージャの文法構造 --- 変数

        MySQL には、システム変数、ユーザー定義変数、ローカル変数の 3 種類の変数があります。

1. システム変数

        システム変数は、ユーザー定義ではなく MySQL サーバーによって提供され、サーバー レベルに属します。グローバル変数(GLOBAL)とセッション変数(SESSION)に分かれます。

  • グローバル変数 (GLOBAL): すべてのセッションのグローバル変数。
  • セッション変数 (SESSION): セッション変数は 1 つのセッションに固有であり、別のセッション ウィンドウでは有効になりません。

SESSION / GLOBAL が指定されていない場合、デフォルトは SESSION セッション変数です。

① システム変数を表示する

-- 查看所有系统变量
SHOW [ SESSION | GLOBAL ] VARIABLES;
-- 可以通过LIKE模糊匹配方 式查找变量
SHOW [ SESSION | GLOBAL ] VARIABLES LIKE '......';
-- 查看指定变量的值 123
SELECT @@[SESSION.|GLOBAL.]系统变量名;
-- 示例:
show session variables ;       -- 查看当前会话的系统变量  
show global variables ;        -- 查看全局的系统变量

show variables like 'auto%';   -- 查看当前会话以'auto'开头的系统变量

select @@session.autocommit;    -- 查看当前会话的autocommit变量的值
select @@global.autocommit;    -- 查看全局的autocommit变量的值

② システム変数を設定する

SET [SESSION|GLOBAL] 系统变量名 = 值 ; 
SET @@[SESSION.|GLOBAL.]系统变量名 = 值 ;

2. ユーザー定義変数

        ユーザー定義変数は、ユーザーが必要に応じて定義する変数であり、事前に宣言する必要がなく、使用時に「@変数名」で直接使用できます。そのスコープは現在の接続です。(注: @@ システム変数は 2 つあり、次に紹介するローカル変数はありません)

① ユーザー定義変数に値を代入する

-- 方式一
SET @var_name = expr [, @var_name = expr] ... ; 
SET @var_name := expr [, @var_name := expr] ... ;
-- 赋值时,可以使用 = ,也可以使用 :=

-- 方式二
-- 查询的同时为其赋值
SELECT @var_name := expr [, @var_name := expr] ... ; 
-- 将某表的某个字段的数据赋值给变量
SELECT 字段名 INTO @var_name FROM 表名;
-- 示例:
set @myname = 'itcast';      -- 设置自定义变量myname的值为itcast
set @mygender := '男',@myhobby := 'java';

select @mycolor := 'red';    -- 设置自定义变量mycolor的值为red,并使用
select count(*) into @mycount from tb_user;    -- 从tb_user表的总数据量并赋值给mycount变量

② ユーザー定義変数を使用する

SELECT @var_name ;

注:
        ユーザー定義変数は宣言や初期化の必要がなく、直接代入することができますが、代入せずに直接使用した場合、取得される値は NULL になります。

3. ローカル変数

        ローカル変数とは、ローカルに定義され、必要に応じて有効になる変数で、アクセスする前にDECLARE文が必要です。ストアド プロシージャ内のローカル変数および入力パラメータとして使用できます。ローカル変数のスコープは、ローカル変数が宣言されている BEGIN ... END ブロックです

CREATE PROCEDURE 表名()
BEGIN
		-- 声明一个局部变量
		DECLARE 变量名 变量类型 [DEFAULT ... ] ;
        -- 变量类型就是数据库字段类型:INT、BIGINT、CHAR、VARCHAR、DATE、TIME等。
        -- DEFAULT 是局部变量的默认值,选用
		
		-- 为局部变量赋值
		SET 变量名 = 值 ; 
		SET 变量名 := 值 ; 
		SELECT 字段名 INTO 变量名 FROM 表名 ... ;
END
-- 示例:
create procedure p2() 
begin
    declare stu_count int default 0;              -- 声明局部变量stu_count,值默认为0
    select count(*) into stu_count from student;  -- 为局部变量stu_count赋值
    select stu_count;                             -- 使用/输出局部变量stu_count
end; 

call p2();    -- 调用存储过程

ストアド プロシージャの文法構造 --- if 判定

        if が条件判断に使用される場合、具体的な文法構造は次のとおりです。

IF 条件1 THEN 
    ..... 
ELSEIF 条件2 THEN     -- 可选 
    ..... 
ELSE                  -- 可选 
    ..... 
END IF;

ストアド プロシージャの文法構造 --- パラメータ

CREATE PROCEDURE 存储过程名称 ([ IN/OUT/INOUT 参数名 参数类型 ]) 
BEGIN
        -- SQL语句 
END ;

例を見てみましょう:

-- 示例:根据传入参数score,判定当前分数对应的分数等级,并返回。
-- score >= 85分,等级为优秀。
-- score >= 60分 且 score < 85分,等级为及格。
-- score < 60分,等级为不及格。
create procedure p4(in score int, out result varchar(10))
begin
    if score >= 85 then 
        set result := '优秀'; 
    elseif score >= 60 then
        set result := '及格'; 
    else
        set result := '不及格'; 
    end if; 
end;

-- 定义用户变量 @result来接收返回的数据, 用户变量可以不用声明 
call p4(18, @result); 
select @result;

ストアド プロシージャの文法構造 --- case

        case はフロー制御関数に似ており、次の 2 つの構文形式があります。 

文法 1:

-- 含义: 当case_value的值为 when_value1时,执行statement_list1,当值为 when_value2时, 执行statement_list2, 否则就执行 statement_list 
CASE case_value 
    WHEN when_value1 THEN statement_list1 
    [ WHEN when_value2 THEN statement_list2]
     ... 
    [ ELSE statement_list ] 
END CASE;

文法 2:

-- 含义: 当条件search_condition1成立时,执行statement_list1,当条件search_condition2成 立时,执行statement_list2, 否则就执行 statement_list 
CASE
    WHEN search_condition1 THEN statement_list1 
    [WHEN search_condition2 THEN statement_list2] 
    ... 
    [ELSE statement_list] 
END CASE;

知らせ:

        判定条件が複数ある場合は、and または or を使用して複数の条件を接続できます。

ストアド プロシージャの文法構造 --- ループ 

        MySQL には、while、repeat、loop という 3 つの主なタイプのループがあります。以下では、それらを 1 つずつ紹介します。

一方

        while ループは条件付きループ制御ステートメントです。条件が満たされた後、ループ本体内の SQL ステートメントを実行します。具体的な構文は次のとおりです。

-- 先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑 
WHILE 条件 DO 
    SQL逻辑... 
END WHILE;

繰り返す 

        repeat は条件付きループ制御ステートメントであり、until で宣言された条件が満たされると、ループは終了します。具体的な構文は次のとおりです。

-- 先执行一次逻辑,然后判定UNTIL条件是否满足,如果满足,则退出。如果不满足,则继续下一次循环 
REPEAT
    SQL逻辑... 
    UNTIL 条件 
END REPEAT;

ループ 

        LOOP は単純なループを実装します。SQL ロジックにループを終了する条件を追加しない場合、これを使用して単純な無限ループを実装できます。

LOOP は、次の 2 つのステートメントで使用できます。

  • LEAVE: ループを終了するためにループとともに使用されます。
  • ITERATE: ループ内で使用する必要があり、その機能は、現在のループの残りのステートメントをスキップし、次のループに直接入ることです。
[begin_label:] LOOP 
    SQL逻辑... 
    LEAVE label; -- 退出指定标记的循环体 
    ITERATE label; -- 直接进入下一次循环
END LOOP [end_label];
-- 上述语法中出现的 begin_label,end_label,label 指的都是我们所自定义的标记。

ストアド プロシージャの文法構造 --- カーソル カーソル

        カーソル (CURSOR) は、クエリ結果セットを格納するために使用されるデータ型であり、ストアド プロシージャや関数を循環するために使用できます。カーソルの使用には、カーソルの OPEN、FETCH、および CLOSE の宣言が含まれます。その構文は次のとおりです。

①宣言 カーソル

DECLARE 游标名称 CURSOR FOR 查询语句 ;
-- 其和声明局部变量相似, 是不过这里的数据类型为 cursor,且后面带有查询语句

② カーソルを開く

OPEN 游标名称 ;

③ カーソルレコードを取得する

FETCH 游标名称 INTO 变量 [, 变量 ] ;

④ カーソルを閉じる

CLOSE 游标名称 ;

ストアド プロシージャの文法構造 - 条件ハンドラー

        条件ハンドラー (Handler) を使用して、フロー制御構造の実行中に問題が発生した場合に、対応する処理ステップを定義できます。具体的な構文は次のとおりです。

DECLARE handler_action HANDLER FOR condition_value [, condition_value] ... statement ; 

handler_action 的取值: 
    CONTINUE: 继续执行当前程序 
    EXIT: 终止执行当前程序 
condition_value 的取值: 
    SQLSTATE sqlstate_value: 状态码,如 02000 
    SQLWARNING: 所有以01开头的SQLSTATE代码的简写 
    NOT FOUND: 所有以02开头的SQLSTATE代码的简写 
    SQLEXCEPTION: 所有没有被SQLWARNING 或 NOT FOUND捕获的SQLSTATE代码的简写

例を通して、カーソルと条件ハンドラーの具体的な使用法を見てみましょう。

-- 示例: 根据传入的参数uage,来查询用户表tb_user中,所有的用户年龄小于等于uage的用户姓名(name)和专业(profession),
-- 并将用户的姓名和专业插入到所创建的一张新表(id,name,profession)中。

create procedure p(in uage int) 
begin
    declare uname varchar(100); 
    declare upro varchar(100); 
    -- A. 声明游标, 存储查询结果集
    declare u_cursor cursor for select name,profession from tb_user where age <= uage;
    -- B. 声明条件处理程序 : 当SQL语句执行抛出的状态码为02000时,将关闭游标u_cursor,并退出 
    declare exit handler for SQLSTATE '02000' close u_cursor; 

    -- C. 准备: 创建表结构
    drop table if exists tb_user_pro; 
    create table if not exists tb_user_pro( 
        id int primary key auto_increment, 
        name varchar(100), 
        profession varchar(100) 
    );

    -- D. 开启游标
    open u_cursor; 

    while true do 
        -- E. 获取游标中的记录
        fetch u_cursor into uname,upro; 
        -- F. 插入数据到新表中
        insert into tb_user_pro values (null, uname, upro); 
    end while; 

    -- G. 关闭游标
    close u_cursor; 
end; 

-- 调用执行过程
call p(30);

上記の場合、条件ハンドラーが宣言されていない場合、while ループ条件が true で終了条件がないため、実行プロセスが呼び出されたときに「02000」エラーが報告されます (null ポインター例外と同様)。呼び出し結果は引き続き成功する可能性があります。つまり、要件は正常​​に完了できます。

この時点で、ストアド プロシージャに関する知識は学習しました。次に、ストアド プロシージャに似たストアド関数を見てみましょう。

ストアド関数 

         ストアド関数は戻り値を持つストアド プロシージャであり、ストアド関数のパラメータは IN 型のみにすることができます。具体的な構文は次のとおりです。

CREATE FUNCTION 存储函数名称 ([ 参数列表 ]) 
RETURNS 返回类型 [characteristic ...] 
BEGIN
    -- SQL语句 
    RETURN ...; 
END ;

特性の説明 (必須):

  • DETERMINISTIC: 同じ入力パラメータは常に同じ結果を生成します
  • NO SQL : SQL ステートメントが含まれません。
  • READS SQL DATA: データを読み取るステートメントは含まれますが、データを書き込むステートメントは含まれません。

ストアド関数について詳しく学ぶために例を見てみましょう。

-- 计算从1累加到n的值,n为传入的参数值。
create function fun1(n int) 
returns int deterministic 
begin
    declare total int default 0; 

    while n>0 do 
        set total := total + n; 
        set n := n - 1; 
    end while; 

    return total; 
end; 

select fun1(50);

3. トリガー

        トリガーはテーブルに関連するデータベース オブジェクトであり、挿入/更新/削除の前 (BEFORE) または後 (AFTER) にトリガーで定義された一連の SQL ステートメントをトリガーして実行します。トリガーのこの機能は、アプリケーションがデータの整合性、ログ記録、データ検証、およびデータベース側でのその他の操作を保証するのに役立ちます。

        他のデータベースと同様に、トリガー内の変更されたレコードの内容を参照するには、別名 OLD および NEW を使用します。現在、トリガーは行レベルのトリガーのみをサポートしており、ステートメントレベルのトリガーはサポートしていません

① トリガーを作成する

CREATE TRIGGER trigger_name 
BEFORE/AFTER INSERT/UPDATE/DELETE ON tbl_name FOR EACH ROW -- 行级触发器
BEGIN
    trigger_stmt ; 
END;

② トリガーを表示する

SHOW TRIGGERS ;

③ トリガーの削除

DROP TRIGGER [schema_name.]trigger_name ; -- 如果没有指定 schema_name,默认为当前数据库 。

例を通してトリガーの使用方法を見てみましょう。

        要件: トリガーを通じて tb_user テーブルのデータ変更ログを記録し、追加、変更、削除を含めて変更ログをログ テーブル user_logs に挿入します。

a. まずログテーブル user_logs を作成します。

create table user_logs( 
    id int(11) not null auto_increment, 
    operation varchar(20) not null comment '操作类型, insert/update/delete', 
    operate_time datetime not null comment '操作时间', 
    operate_id int(11) not null comment '操作的ID', 
    operate_params varchar(500) comment '操作参数', 
    primary key(`id`) 
)engine=innodb default charset=utf8;

b. データ挿入のトリガーを定義する

create trigger tb_user_insert_trigger
after insert on tb_user for each row
begin
		insert into user_logs(id, operation, operate_time, operate_id, operate_params)
		values(null, 'insert', now(), new.id, 
            concat('插入的数据内容为: id=', new.id, ',name=', new.name, ',phone=', new.phone, ',email=', new.email, ',profession=', new.profession));
end;

テスト挿入データ:

c. データを変更するトリガーを定義する

create trigger tb_user_update_trigger
after update on tb_user for each row
begin
		insert into user_logs(id, operation, operate_time, operate_id, operate_params)
		values(null, 'update', now(), new.id, 
		    concat('更新之前的数据: id=', old.id, ',name=', old.name, ',phone=', old.phone, ',email=', old.email, ',profession=', old.profession, 
		        ' | 更新之后的数据: id=', new.id, ',name=', new.name, ',phone=', new.phone, ',email=', new.email, ',profession=', new.profession));
end;

テスト更新データ:

知らせ:

        update tb_user set age = 18 where id <= 5 ステートメントを使用すると、現在のトリガーが行レベルをサポートしているため、5 個のデータが更新され、この時点で 5 個のログ データが user_logs テーブルに挿入されます。トリガー

d. データを削除するトリガーを定義する

create trigger tb_user_delete_trigger
after delete on tb_user for each row
begin
		insert into user_logs(id, operation, operate_time, operate_id, operate_params)
		values(null, 'update', now(), old.id, 
		    concat('删除之前的数据: id=', old.id, ',name=', old.name, ',phone=', old.phone, ',email=', old.email, ',profession=', old.profession));
end;

データの削除をテストします。

おすすめ

転載: blog.csdn.net/weixin_52850476/article/details/124686228