Luận văn này trình bày các kết quả nghiên cứu về khung nhìn thực truy vấn lồng, để cập nhật gia tăng khung nhìn thực truy vấn lồng trong Hệ quản trị CSDL PostgreSQL và ngôn ngữ C. Qua đó, chúng tôi đã trình bày tổng quan các nghiên cứu liên quan đến khung nhìn thực, các phương pháp để cập nhật gia tăng.
Kết quả của luận văn này, là xây dựng được module hỗ trợ sinh mã tự động các trigger thực hiện cập nhật gia tăng các bảng khung nhìn thực truy vấn lồng đơn giản liên quan đến các truy vấn như INSERT, UPDATE, DELETE. 2. GIỚI HẠN CỦA LUẬN VĂN NÀY
Trong quá trình thực hiện luận văn, dù em đã cố gắng rất nhiều nhưng do hạn chế về thời gian, kiến thức và kinh nghiệm về lĩnh vực này nên luận văn không thể tránh khỏi những thiếu sót, rất mong nhận được những đóng góp chân thành cũng như sự giúp đỡ của quý thầy cô để luận văn tốt nghiệp của em được hoàn thiện hơn.
- Luận văn chỉ dừng lại ở cập nhật gia tăng cho khung nhìn thực truy vấn lồng đơn giản.
- Mới chỉ thử nghiệm ở một tập dữ liệu gồm 3 bảng, chưa có điều kiện để nghiên cứu thử nghiệm ở nhiều tập dữ liệu lớn hơn và phức tạp hơn.
3. ĐỀ XUẤT HƯỚNG NGHIÊN CỨU TIẾP THEO
- Chương trình phát triển cho khung nhìn thực nối ngoài dựa trên thuật toán đã trình bày.
TÀI LIỆU THAM KHẢO
Tài liệu tiếng Việt
[1]Nguyễn Trần Quốc Vinh, Nguyễn Văn Quý(2013) “Nghiên cứu xây dựng và
tích hợp mô-đun viết lại truy vấn hỗ trợ khung nhìn thực trong PostgreSQL”,
Tạp chí Khoa học và Công nghệ - Đại học Đà Nẵng, Số 8(69)-2013.
[2]Nguyễn Trần Quốc Vinh, Nghiên cứu xây dựng chương trình sinh mã tự động các trigger phục vụ cập nhật gia tăng khung nhìn thực, Đề tài NCKH cấp Đại học Đà Nẵng, mã số Đ2012-04-19.
[3]Nguyễn Trần Quốc Vinh, Trần Trọng Nhân (2014), “Nghiên cứu xây dựng mô-đun sinh tự động mã nguồn trigger trong ngôn ngữ C cập nhật gia tăng, đồng bộ các khung nhìn thực trong PostgreSQL”, Kỷ yếu Hội nghị Quốc gia lần thứ VII về Nghiên cứu ứng dụng Công nghệ thông tin, 2014.
[4]Nguyễn Trần Quốc Vinh (2009), Cập nhật không đồng bộ các khung nhìn thực, Tạp chí khoa học và công nghệ các trường đại học kỹ thuật. 74: p. 30 – 33.
[5]Nguyễn Trần Quốc Vinh (2009), “Ứng dụng khung nhìn thực để nâng cao
tốc độ thực thi truy vấn”, Tạp chí khoa học và công nghệ Đại học Đà Nẵng. 1(30): p. 59 - 65.
Tài liệu tiếng Anh
[6] Hoshi M., Prasan R., Sudarshan S., Krithi R., “Materialized View Selection
and Maintenance Using Multi-Query Optimization”, ACM SIGMOD, 2001.
[7] Levy A.Y., Mendelzon A.O., Sagiv Y., “Answering Queries Using Views”,
Proceedings of the 22th International Conference on Very Large Data Bases, 1996.
[8] Gupta A., Mumick I.S., “Maintenance of materialized views: problems, techniques, and applications”, Materialized views, G. Ashish and M. Iderpal Singh, Editors. 1999, MIT Press,tr. 145-157.
[9] Gupta A., Mumick I.S., Subrahmanian V.S., “Maintaining views
incrementally”,SIGMOD Rec., 1993, 22(2), p. 157-166.
[10] Gupta D.K., “Counting solutions to the view maintenance problem”,
[11] Lee K.Y., Kim M.H., “Optimizing the incremental maintenance of multiple join views”, Proceedings of the 8th ACM international workshop on Data warehousing and OLAP 2005, ACM: Bremen, Germany, p. 107-113.
[12] Shmueli O. Maintenance of views. SIGMOD record. 1984. – 14. – tr. 240-255. http://portal.acm.org/citation.cfm?id=602293.
Tài liệu Internet
[13]http://www.postgresql.org/docs/9.3/static/trigger-example.html,04/09/2014.
[14]Jonathan Gardner, http://tech.jonathangardner.net/wiki/PostgreSQL/
Materialized_Views, 04/9/2014.
PHỤ LỤC 1
Ví dụ tạo trigger trên C trong PosgreSQL
Đầu tiên, định nghĩa bảng:
CREATE TABLE ttest (x integer);
Đây là mã nguồn của hàm trigger:
#include "postgres.h"
#include "executor/spi.h" /*Đây là những gì mà cần làm việc với SPI */ #include "commands/trigger.h" /* ... triggers ... */
#include "utils/rel.h" /* ... các mối quan hệ */ #ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC; #endif
extern Datum trigf(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(trigf);
Datum
trigf(PG_FUNCTION_ARGS) {
TriggerData *trigdata = (TriggerData *) fcinfo->context; TupleDesc tupdesc;
HeapTuple rettuple; char *when;
bool checknull = false; bool isnull;
int ret, i;
/* chắc chắn rằng nó được gọi như một trigger ở tất cả */ if (!CALLED_AS_TRIGGER(fcinfo))
elog(ERROR, "trigf: not called by trigger manager"); /* Bộ giá trị trả về người thực thi */
if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) rettuple = trigdata->tg_newtuple;
else
/* kiểm tra giá trị rỗng */ if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event) && TRIGGER_FIRED_BEFORE(trigdata->tg_event)) checknull = true; if (TRIGGER_FIRED_BEFORE(trigdata->tg_event)) when = "before"; else when = "after "; tupdesc = trigdata->tg_relation->rd_att; /* kết nối đến SPI manager */
if ((ret = SPI_connect()) < 0)
elog(ERROR, "trigf (fired %s): SPI_connect returned %d", when, ret); /* lấy số lượng hàng trong bảng */
ret = SPI_exec("SELECT count(*) FROM ttest", 0); if (ret < 0)
elog(ERROR, "trigf (fired %s): SPI_exec returned %d", when, ret); /* count(*) trả về int8, hãy cẩn thận để chuyển đổi */
i = DatumGetInt64 (SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull));
elog (INFO, "trigf (fired %s): there are %d rows in ttest", when, i); SPI_finish();
if (checknull) {
SPI_getbinval(rettuple, tupdesc, 1, &isnull); if (isnull)
rettuple = NULL; }
return PointerGetDatum(rettuple); }
Sau khi đã biên dịch mã nguồn, khai báo hàm và các trigger:
CREATE FUNCTION trigf() RETURNS trigger AS ’filename’
LANGUAGE C;
CREATE TRIGGER tbefore BEFORE INSERT OR UPDATE OR DELETE ON ttest
FOR EACH ROW EXECUTE PROCEDURE trigf();
CREATE TRIGGER tafter AFTER INSERT OR UPDATE OR DELETE ON ttest
FOR EACH ROW EXECUTE PROCEDURE trigf();
Bây giờ, chúng ta có thể kiểm tra phép tính của trigger như sau:
=> INSERT INTO ttest VALUES (NULL);
INFO: trigf (firesd before): there are 0 rows in ttest INSERT 0 0
-- Bỏ qua việc chèn và trigger AFTER không được kích hoạt => SELECT * FROM ttest;
x --- (0 rows)
=> INSERT INTO ttest VALUES (1);
INFO: trigf (fired before): there are 0 rows in ttest INFO: trigf (sau khi kích hoạt): there are 1 rows in ttes^ INSERT 167793 1
vac=> SELECT * FROM ttest; x
--- 1
(1 rows)
=> INSERT INTO ttest SELECT x * 2 FROM ttest;
INFO: (fired after): there are 2 rows in ttest INSERT 167794 1
=> SELECT * FROM ttest; X
--- 1 2
(2 rows)
=> UPDATE ttest SET x = NULL WHERE x = 2;
INFO: (fired before): there are 2 rows in ttest UPDATE 0
=> UPDATE ttest SET x = 4 WHERE x = 2;
INFO: (fired before): there are 2 rows in ttest INFO: (fired after): there are 2 rows in ttest UPDATE 1
vac=> SELECT * FROM ttest; x
--- 1 4
(2 rows)
=> DELETE FROM ttest;
INFO: (fired before): there are 2 rows in ttest INFO: (fired before): there are 1 rows in ttest INFO: (fired after): there are 0 rows in ttest INFO: (fired after): there are 0 rows in ttest DELETE 2
=> SELECT * FROM ttest;
x --- (0 rows)
PHỤ LỤC 2
1. Mã nguồn trigger C sinh bởi chương trình sinh mã tự động
Cho CSDL quản lý sinh viên đơn giản với 3 bảng và các quan hệ 1 – n:
TVG:
SELECT sinh_vien.ma_sv, sinh_vien.ten_sv, sinh_vien.que_quan, lop.ma_lop, lop.ten_lop, khoa.ma_khoa,khoa.ten_khoa
FROM sinh_vien
JOIN lop ON sinh_vien.ma_lop = lop.ma_lop JOIN khoa ON lop.ma_khoa = khoa.ma_khoa
Chạy chương trình sinh mã với truy vấn đầu vào như trên, thu được sản phẩm như sau:
Mã nguồn c cho sự kiện thao tác dữ liệu xảy ra trên bảng chính là bảng sinhvien: #include "ctrigger.h" #ifndef CTRIGGER #define CTRIGGER 1 #include "postgres.h" #include "fmgr.h" #include "executor/spi.h" #include "commands/trigger.h" #include "utils/rel.h" #include "utils/date.h"
#include "utils/numeric.h" #include "datatype/timestamp.h" #include <string.h> #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif /*
MAXIMUM LENGTH OF A QUERY This length is considered to be modified. */
#define MAX_QUERY_LENGTH 2000
/*
MACRO FOR CALLING THE 'SAVED RESULT SET'
The SRS is used for temporary storing a result set (with a different format). USUAL RESULT SET | SAVED RESULT SET
1. Row index: 0 | 0 2. Col index: 1 | 0
3. Col index meaning: The index of column in db table | The order of adding */
#define SAVED_RESULT savedResult /*
MACRO FOR CALLING THE SIZE VARIABLE OF 'SAVED RESULT SET' This macro is intended for internal purpose.
*/
#define RESULT_COUNT resultCount
/*
THE NUMBER OF SAVED RESULT SET'S ROW This macro is intended for internal purpose.
*/
/*
CORE VARIABLES
User's trigger function variables must not be named the same as these. This macro is intended for internal purpose.
*/
#define PREPARE_STATEMENTS TriggerData *trigdata = (TriggerData *) fcinfo->context;\
TupleDesc tupdesc; \ HeapTuple rettuple; \
bool isnull, checkNull = false; \
char query[MAX_QUERY_LENGTH]; \
char** SAVED_RESULT = NULL; \
int32 RESULT_COUNT = 0, ret, i; \
int32 ROW_NUM
/*
TRIGGERED ROW'S DESCRIPTION For internal purpose only.
*/
#define GET_TRIGGERED_ROW_DESC tupdesc = trigdata->tg_relation- >rd_att
/*
PREPARE FOR TRIGGER FUNCTION RETURNING ROW This row is returned in UPDATE queries.
For internal purpose only. */
#define PREPARE_RETURN_NEW_ROW rettuple = trigdata->tg_newtuple
/*
PREPARE FOR TRIGGER FUNCTION RETURNING ROW For internal purpose only.
*/
#define PREPARE_RETURN_TRIGGERED_ROW rettuple = trigdata- >tg_trigtuple
/*
CONNECT TO SPI
Check for connect error by using macro 'RET_ERR' define below. For internal purpose only.
*/
#define CONNECT ret = SPI_connect()
/*
EXECUTE A QUERY
This macro is used for execute a query defined by 'DEFQ' and 'ADDQ'. ! You are allowed to save the result set for only one query,
using the SRS if you want to store the result set and execute another query statement.
*/
#define EXEC ret = SPI_exec(query, 0)
/*
DISCONNECT FROM SPI For internal purpose only. */
#define DISCONNECT SPI_finish()
// Print/show an error message, then stop the process #define ERR(msg) elog(ERROR, msg)
// Print a message in console log #define INF(msg) elog(INFO, msg)
// Print/show an error message, then stop the process if the previous GET (field) command return NULL value
#define NULL_ERR(msg) if (isnull) elog(ERROR, msg)
// Print a message in console log if the previous GET (field) command return NULL value
#define NULL_INF(msg) if (isnull) elog(INFO, msg)
// Print/show an error message, then stop the process if the previous SPI action return a minus value
#define RET_ERR(msg) if (ret < 0) elog(ERROR, msg) // Check if user's trigger function fire before or not a event #define UTRIGGER_FIRED_BEFORE
(TRIGGER_FIRED_BEFORE(trigdata->tg_event))
// Declare a query command with an init string value, only one DEFQ macro statement is used in constructing a query.
#define DEFQ(c) strcpy(query, c)
// Append a string value to current query, many ADDQ macro statement can be used after a DEFQ in constructing a query.
#define ADDQ(c) strcat(query, c)
/*
DEFINING SAVED RESULT SET (TEMPORARILY)
After EXEC a query command, you can use DEFR macro to allocate a space fporary storing the result set.
! Only 1 SRS can be used at a time, or you have to defining a set yourself withoor temut using the provided macros.
*/
#define DEFR(colNum) ROW_NUM = SPI_processed; \
SAVED_RESULT = (char**) SPI_palloc (sizeof(char*) * (ROW_NUM*colNum)); \
RESULT_COUNT = 0
/*
ADDING FIELDS VALUE TO THE SAVED RESULT SET
! Remember the adding order for each value, it's the column index if you get the value out
(using GET_SAVED_RESULT macro). */
#define ADDR(element) SAVED_RESULT[RESULT_COUNT++] = element; /*
GET THE VALUE IN THE SRS String constant are returned.
! Row and column are both 0-indexed. ! This macro should be only used within the
FOR_EACH_SAVED_RESULT_ROW looping macro. */
#define GET_SAVED_RESULT(row, col) SAVED_RESULT[row + col]
/*
LOOP THROUGH EACH ROW IN THE SAVED RESULT SET
! This looping macro is designed to wrap GET_SAVED_RESULT macro. */
#define FOR_EACH_SAVED_RESULT_ROW(intIterator, colNum) for (intIterator = 0; intIterator < ROW_NUM*colNum; intIterator+=colNum)
// Loop through each row in the result set
#define FOR_EACH_RESULT_ROW(intIterator) for (intIterator = 0; intIterator < ROW_NUM; intIterator++)
//--- ---
/*
DECLARING TRIGGER FUNCTION
This macro should be put at the very beginning of a trigger function,
followed by a pair of curly brackets which contains the function body inside. */
#define FUNCTION(funcName) PGDLLEXPORT
PG_FUNCTION_INFO_V1(funcName); \
Datum \
funcName(PG_FUNCTION_ARGS)
/*
TRIGGER FUNCTION ENDING
This macro should be put at the very end of the function body, before the closing curly brackets of the function
(goes with the FUNCTION macro). */
/*#define END SPI_pfree(SAVED_RESULT); \ SPI_finish(); \
if (checkNull) { \
SPI_getbinval(rettuple, tupdesc, 1, &isnull); \ if (isnull) \
rettuple = NULL; } \ return ((Datum) (rettuple))
*/
#define END if (SAVED_RESULT != NULL) SPI_pfree(SAVED_RESULT);\
SPI_finish(); \
if (checkNull) { \
SPI_getbinval(rettuple, tupdesc, 1, &isnull); \ if (isnull) \
return ((Datum) (rettuple)) /*
COMMON REQUIRED PROCEDURES FOR EACH TRIGGER FUNCTION - Defining core variables
- Checking function context
- Geting triggered row description - Preparing returning row
- Check null - Connect to SPI
This macro should be put after the variables declaration part and before the function's main process.
*/
#define REQUIRED_PROCEDURES PREPARE_STATEMENTS; \
if (!CALLED_AS_TRIGGER(fcinfo)) \
ERR("User's trigger function is not called by trigger manager!"); \
GET_TRIGGERED_ROW_DESC; \ if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) PREPARE_RETURN_NEW_ROW; \ else PREPARE_RETURN_TRIGGERED_ROW; \ if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event) \ && TRIGGER_FIRED_BEFORE(trigdata->tg_event)\ checkNull = true; \ if (CONNECT < 0) \
ERR("User's trigger function: SPI_connect returned a minus value")
/*
GROUP OF MACROS FOR CONVERTING VALUES TO STRING (! VARIABLE STRING)
#define INT32_TO_STR(i32, s) sprintf(s, "%d", i32) #define INT64_TO_STR(i64, s) sprintf(s, "%lld", i64) #define FLOAT32_TO_STR(f32, s) sprintf(s, "%f", f32)
// ! String constant, numeric scale: 10
#define NUMERIC_TO_STR(numeric, s) strcpy(s, numeric_out_sci(numeric, 10))
/*
GROUP OF MACROS FOR GETTING VALUE IN THE TRIGGERED ROW */
#define GET_INT32_ON_TRIGGERED_ROW(i32, col) i32 =
DatumGetInt32(SPI_getbinval(trigdata->tg_trigtuple, tupdesc, col, &isnull)) #define GET_INT64_ON_TRIGGERED_ROW(i64, col) i64 =
DatumGetInt64(SPI_getbinval(trigdata->tg_trigtuple, tupdesc, col, &isnull)) #define GET_FLOAT32_ON_TRIGGERED_ROW(f32, col) f32 =
DatumGetFloat4(SPI_getbinval(trigdata->tg_trigtuple, tupdesc, col, &isnull)) #define GET_STR_ON_TRIGGERED_ROW(str, col) str =
SPI_getvalue(trigdata->tg_trigtuple, tupdesc, col)
#define GET_NUMERIC_ON_TRIGGERED_ROW(numeric, col) numeric = DatumGetNumeric(SPI_getbinval(trigdata->tg_trigtuple, tupdesc, col, &isnull))
/*
GROUP OF MACROS FOR GETTING VALUE IN THE NEW UPDATED ROW
*/
#define GET_INT32_ON_NEW_ROW(i32, col) i32 =
DatumGetInt32(SPI_getbinval(trigdata->tg_newtuple, tupdesc, col, &isnull)) #define GET_INT64_ON_NEW_ROW(i64, col) i64 =
#define GET_FLOAT32_ON_NEW_ROW(f32, col) f32 =
DatumGetFloat4(SPI_getbinval(trigdata->tg_newtuple, tupdesc, col, &isnull)) #define GET_STR_ON_NEW_ROW(str, col) str = SPI_getvalue(trigdata- >tg_newtuple, tupdesc, col)
#define GET_NUMERIC_ON_NEW_ROW(numeric, col) numeric =
DatumGetNumeric(SPI_getbinval(trigdata->tg_newtuple, tupdesc, col, &isnull)) /*
GROUP OF MACROS FOR GETTING VALUE IN THE RESULT SET */
#define GET_INT32_ON_RESULT(i32, row, col) i32 =
DatumGetInt32(SPI_getbinval(SPI_tuptable->vals[row], SPI_tuptable->tupdesc, col, &isnull))
#define GET_INT64_ON_RESULT(i64, row, col) i64 =
DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[row], SPI_tuptable->tupdesc, col, &isnull))
#define GET_FLOAT32_ON_RESULT(f32, row, col) f32 =
DatumGetFloat4(SPI_getbinval(SPI_tuptable->vals[row], SPI_tuptable- >tupdesc, col, &isnull))
#define GET_FLOAT64_ON_RESULT(f64, row, col) f64 =
DatumGetFloat8(SPI_getbinval(SPI_tuptable->vals[row], SPI_tuptable- >tupdesc, col, &isnull))
#define GET_NUMERIC_ON_RESULT(numeric, row, col) numeric = DatumGetNumeric(SPI_getbinval(SPI_tuptable->vals[row], SPI_tuptable- >tupdesc, col, &isnull))
#define GET_STR_ON_RESULT(str, row, col) str =
SPI_getvalue(SPI_tuptable->vals[row], SPI_tuptable->tupdesc, col)
// Check if the value in the specified location in the result set is null or not #define NULL_CELL(row, col) SPI_getbinval(SPI_tuptable->vals[row], SPI_tuptable->tupdesc, col, &isnull), isnull
// Check if is there any row in the result set
#define NO_ROW (SPI_tuptable->alloced == SPI_tuptable->free)
// Defining a string constant
#define STRING(varName) char *varName = NULL
/*/ Short hand for comparing string, equal or not
#define STR_EQUAL(str1, str2) (strcmp(str1, str2) == 0)
// Short hand for comparing string, inequal or not
#define STR_IE(str1, str2) (!STR_EQUAL(str1, str2))*/ #define STR_EQUAL(a, b) (a && b && strcmp(a, b) == 0) #define STR_INEQUAL(a, b) (a && b && strcmp(a, b) != 0) #define STR_EQUAL_CI(a, b) (a && b && _strcmpi(a, b) == 0) #define STR_INEQUAL_CI(a, b) (a && b && _strcmpi(a, b) != 0) #endif
#include "ctrigger.c"
#include "ctrigger.h" // header file chua cac macro de tao trigger tren C de dang. #define _CRT_SECURE_NO_WARNINGS 1 //bo qua cac canh bao cua visual studio khi code C
FUNCTION(khoaupdate){ //trigger cho viec update bang khoa int64 makhoacu; //bien lay ma khoa truoc update
char str_makhoacu[10]; //dung convert ma khoa cu ve dang chuoi, dung cho noi' chuoi~ vao query
char str_makhoamoi[10];
char* tenkhoamoi; // ten khoa moi o dang chuoi san, k can bien convert.
REQUIRED_PROCEDURES; //cau lenh khoi tao cho viec xu ly trigger
GET_INT64_ON_TRIGGERED_ROW(makhoacu, 1); //lay makhoa truoc khi update
INT64_TO_STR(makhoacu, str_makhoacu);
GET_INT64_ON_NEW_ROW(makhoamoi, 1); //lay makhoa sau khi update
INT64_TO_STR(makhoamoi, str_makhoamoi);
GET_STR_ON_NEW_ROW(tenkhoamoi, 2); //lay ten khoa sau khi update
/*
Doan nay tuong duong voi thuc thi cau lenh
update mv set ma_khoa=makhoamoi, ten_khoa=tenkhoamoi where ma_khoa=makhoacu
*/
DEFQ("update mv set ma_khoa="); //define query, chuan bi 1 query ADDQ(str_makhoamoi);
ADDQ(", ten_khoa='"); //add query, noi' chuoi~ them vao query (Tham so nhan vao` chi? duoc kieu? chuoi~)
ADDQ(tenkhoamoi);
ADDQ("' where ma_khoa='"); ADDQ(str_makhoacu);
ADDQ("'");
EXEC; //thuc thi query
INF("Done"); //in thong bao
END; //giai phong bo nho, ket thuc trigger an toan }
FUNCTION(khoadelete) { int64 makhoacu; char str_makhoacu[10]; REQUIRED_PROCEDURES; GET_INT64_ON_TRIGGERED_ROW(makhoacu, 1); INT64_TO_STR(makhoacu, str_makhoacu);
DEFQ("delete from mv where ma_khoa="); ADDQ(str_makhoacu); EXEC; INF("Done"); END; } FUNCTION(lopupdate) { int64 malopcu; char str_malopcu[10]; int64 malopmoi; char str_malopmoi[10]; char* tenlopmoi; int64 makhoamoi; char str_makhoamoi[10]; char* tenkhoamoi=""; REQUIRED_PROCEDURES; GET_INT64_ON_TRIGGERED_ROW(malopcu, 1); INT64_TO_STR(malopcu, str_malopcu); GET_INT64_ON_NEW_ROW(malopmoi, 1); INT64_TO_STR(malopmoi, str_malopmoi);
GET_STR_ON_NEW_ROW(tenlopmoi, 2); GET_INT64_ON_NEW_ROW(makhoamoi, 3); INT64_TO_STR(makhoamoi, str_makhoamoi);
//lay ten khoa moi trong truong hop ma khoa thay doi DEFQ("Select ten_khoa from khoa where ma_khoa="); ADDQ(str_makhoamoi);
EXEC;
DEFR(1); //tao result set co 1 cot
FOR_EACH_RESULT_ROW(i) { //ban ghi tra ve chi co 1 row, vong lap chay 1 lan
GET_STR_ON_RESULT(tenkhoamoi, i, 1); }
DEFQ("update mv set ma_lop="); ADDQ(str_malopmoi); ADDQ(", ten_lop='"); ADDQ(tenlopmoi); ADDQ("', ma_khoa="); ADDQ(str_makhoamoi); ADDQ(", ten_khoa='"); ADDQ(tenkhoamoi);
ADDQ("' where ma_lop=");
ADDQ(str_malopcu); EXEC;
INF("Done"); //in thong bao
END; //giai phong bo nho, ket thuc trigger an toan }
FUNCTION(lopdelete) { int64 malopcu;
char str_malopcu[10];
REQUIRED_PROCEDURES;
GET_INT64_ON_TRIGGERED_ROW(malopcu, 1); INT64_TO_STR(malopcu, str_malopcu);
DEFQ("delete from mv where ma_lop=");