- vừa được xem lúc

[Database] Bài 7 - Một Số Procedure Khác & Khái Niệm View

0 0 22

Người đăng: Semi Art

Theo Viblo Asia

Trước khi nói về khái niệm mới, thì như dự kiến từ cuối bài viết trước là chúng ta sẽ thực hiện nốt procedure truy xuất các bản ghi article bằng category-id và những procedure cơ bản thuộc nhóm procedure/article. Tuy nhiên đối với các procedure cơ bản như insert, select-by-id, update, delete-by-id, thì hầu hết là chúng ta có thể copy/paste code xử lý từ nhóm procedure/category và chỉnh sửa lại đôi chút không đáng kể. Do đó ở đây mình nghĩ chúng ta chỉ cần làm nốt ví dụ về thao tác select-by-category-id của nhóm procedure/article thôi. 😄

Một chút lưu ý về hiệu năng xử lý

Thủ tục select-by-category-id ở đây có logic xử lý tổng quan là chúng ta sẽ lọc ra những bản ghi articlecategory-id khớp với tham số cung cấp. Mặc dù chỉ là một procedure đơn giản nhưng phương thức mà chúng ta tiến hành cũng sẽ có một vài điểm đáng để đặt một chút suy nghĩ.

Để lọc ra các bản ghi phù hợp như đã nói, chúng ta có thể tiến thành theo hai cách -

  • Đọc tất cả các bản ghi article và chuyển thành một mảng chứa các object dữ liệu trong môi trường phần mềm; Sau đó tiến hành lặp qua mảng này để lọc ra những bản ghi phù hợp với category-id được cung cấp.
  • Đọc một bản ghi article đầu tiên và chuyển thành một object dữ liệu trong môi trường phần mềm; Sau đó tiến hành kiểm tra ngay category-id để xem phù hợp không. Nếu phù hợp thì bổ sung vào mảng kết quả; Và cứ thế lặp lại tiến trình xử lý như vậy với tất cả các bản ghi còn lại.

Ở đây chúng ta thấy rõ ràng là trong trường hợp đầu tiên, sẽ có ít nhất một khoảnh khắc nào đó mà bộ nhớ máy tính sẽ phải phân bổ để lưu trữ 1001 object dữ liệu mô tả các bản ghi article. Việc lưu trữ sẽ phải duy trì cho đến khi thao tác lọc các bài viết phù hợp được thực hiện xong và procedure kết thúc thì phần bộ nhớ lưu các object không phù hợp mới được giải phóng.

Trong khi đó đối với cách thức thứ hai, khi một object dữ liệu mô tả một article được cho là không phù hợp với kết quả tìm kiếm thì nó sẽ được giải phóng ngay khỏi bộ nhớ máy tính. Đây là một lưu ý nhỏ nhưng khá quan trọng khi chúng ta viết code để làm việc với database, bởi khi số lượng các bản ghi trong database đủ nhiều thì chúng ta sẽ thấy sự khác biệt là có thể nhận biết được.

const readAllRecordIds = require("./sub-procedure/read-all-record-ids--async-throw");
const selectArticleById = require("../article/select-by-id--async-throw");
const Article = require("../../type/Article"); module.exports = async ( in_categoryId = "Infinity", out_matchedArticles = []
) => { var allRecordIds = []; await readAllRecordIds(allRecordIds); /* one-by-one select and check */ for (var recordId of allRecordIds) { var selected = new Article(); await selectArticleById(recordId, selected); /* collect the record if matched */ var selectedArticleIsMatched = (selected.get("category-id") == in_categoryId); if (selectedArticleIsMatched) out_matchedArticles.push(selected); else /* do nothing */; } // for
};

Như trong code ví dụ ở trên thì chúng ta đã thực hiện công việc thu thập tất cả các article-id từ tên thư mục của 1001 bản ghi article. Sau đó chúng ta thực hiện thao tác lặp và truy xuất từng bản ghi article để kiểm tra category-id, và quyết định lưu vào mảng kết quả hoặc bỏ object article đó ngay.

const Article = require("./database/type/Article");
const Category = require("./database/type/Category");
const databaseManager = require("./database/manager");
const view = require("./database/view/article-left-join-category--all-join-name--async-throw");
const ArticleJoinCategory = require("./database/type/ArticleJoinCategory/all-join-name"); void async function() { var procedureName, id, selected; await databaseManager.execute( procedureName = "select-articles-by-category-id", id = "01", selected = [] ); console.log(selected);
} (); // void

CMD | Terminal

npm test [ Article(7) [Map] { '@id' => '0001', 'title' => 'Làm Thế Nào Để Tạo Ra Một Trang Web?', 'short-title' => 'Giới Thiệu Mở Đầu', 'keywords' => [ 'hướng dẫn cơ bản', 'lập trình web', 'html', 'giới thiệu' ], 'edited-datetime' => 'Sat, 16 Apr 2022 10:13:22 GMT', 'category-id' => '01', 'markdown' => 'Nội dung của bài viết đầu tiên...' }, Article(7) [Map] { '@id' => '0002', 'title' => 'Cách Chèn Ảnh & Các Liên Kết', 'short-title' => 'Ảnh & Liên Kết', 'keywords' => [ 'hướng dẫn cơ bản', 'lập trình web', 'html', 'ảnh', 'liên kết' ], 'edited-datetime' => 'Sat, 16 Apr 2022 19:13:22 GMT', 'category-id' => '01', 'markdown' => 'Nội dung của bài viết thứ hai...' }
]

Thao tác truy vấn tổ hợp select-top

Thực tế thì trong bài viết trước, mình đã đề xuất việc tạo ra thủ tục procedure/article/select-by-category-id nhằm kiểm tra trước khi thực hiện thao tác xóa một category nhưng cốt bản là để lấy ví dụ về một thao tác truy vấn phức tạp hơn một chút và được xây dựng trên chất liệu là thao tác truy vấn cơ bản procedure/article/select-by-id.

Còn về mặt ứng dụng thì procedure này lại không phải là giải pháp tốt, vì nếu như chúng ta có nhiều bài viết thì việc lặp qua tất cả các bản ghi article sẽ rất mất công.

Thay vào đó thì chúng ta chỉ nên lặp cho đến khi gặp bài viết đầu tiên có category-id trùng hợp. Như vậy chúng ta có thể xem xét việc viết một procedure khác để kiểm tra thao tác xóa category hợp lệ. Ví dụ như procedure để chọn ra một vài bản ghi article mới nhất thuộc category đó (nếu có) -

const readAllRecordIds = require("./sub-procedure/read-all-record-ids--async-throw");
const selectArticleById = require("./select-by-id--async-throw");
const Article = require("../../type/Article"); module.exports = async ( in_options = { numberOfRecords: 0, reverseOrder: false }, in_categoryId = "Infinity", out_selected = []
) => { /* prepare list of all record ids */ var allRecordIds = []; await readAllRecordIds(allRecordIds); if (in_options.reverseOrder == false) /* do nothing */; else allRecordIds = allRecordIds.reverse(); /* select each record to check */ for (var recordId of allRecordIds) { var selected = new Article(); await selectArticleById(recordId, selected); /* collect the record if matched */ if (selected.get("category-id") != in_categoryId) /* not matched */; else out_selected.push(selected); /* stop if found enough records */ if (out_selected.length < in_options.numberOfRecords) /* continue collecting */; else break; } // for
}; // module.exports

Với procedure này thì chúng ta thực hiện lặp từ giá trị id lớn nhất của các bản ghi article trở lại tới bản ghi article đầu tiên. Ngay khi gặp một bản ghi phù hợp thì thao tác lặp sẽ được break để kết thúc procedure ngay tại đó. Như vậy số thao tác mà máy tính phải thực hiện có khả năng thấp hơn rất nhiều so với việc sử dụng procedure trước. Và code thủ tục xóa category có thể được sửa lại như thế này.

const selectTopArticleByCategoryId = require("../article/select-top-by-category-id--async-throw");
const removeRecordFromDatabase = require("./sub-procedure/remove-record-from-database--async-throw");
const Category = require("../../type/Category"); module.exports = async ( in_recordId = "Infinity", out_deleted = new Category()
) => { try { var selectedArticles = []; await selectTopArticleByCategoryId( { numberOfRecords: 1, reverseOrder: true }, in_recordId, selectedArticles ); var theCategoryIsEmpty = (selectedArticles.length == 0); if (theCategoryIsEmpty) await removeRecordFromDatabase(in_recordId, out_deleted); else throw new Error("Đang có bài viết thuộc danh mục này"); } catch (error) { throw error; }
}; // module.exports

Thao tác truy vấn select-top như trên là rất phổ biến và thường được sử dụng khi chúng ta muốn chọn ra một vài bản ghi đầu tiên trong một tập kết quả lớn.

Thao tác truy vấn liên hợp join

Giả sử chúng ta đang cần dữ liệu để tạo ra một trang đơn bài viết. Lúc này ngoài các trường dữ liệu do bản ghi article cung cấp thì chúng ta sẽ cần thêm trường name của bản ghi category tương ứng. Code sử dụng databaseManager từ bên ngoài có thể xử lý bằng cách gọi thủ tục select-article-by-id để có được bản ghi article, rồi sau đó truy xuất category-id và tiếp tục gọi thủ tục select-category-by-id để lấy bản ghi category tương ứng và sau đó truy xuất name.

Một thao tác truy vấn liên hợp như thế này là rất phổ biến khi sử dụng relational database và vì vậy nên các hệ quản trị relational database thường cung cấp một phương thức có tên là join giúp kết hợp hai bản ghi liên quan để tạo thành một kiểu dữ liệu liên hợp mới làm kết quả trả về.

Để viết code xử lý tương tự cho phần mềm quản lý database đơn giản thì chúng ta có thể định nghĩa một kiểu dữ liệu liên hợp class ArticleJoinCategory, và sau đó viết một procedure thực hiện thao tác truy vấn liên hợp để trả về một object kết quả thuộc kiểu dữ liệu đó.

const Article = require("./Article");
const Category = require("./Category"); const ArticleJoinCategory = class extends Map { constructor(...params) { super(...params); ArticleJoinCategory.initialize("@id", this) .initialize("title", this) .initialize("short-title", this) .initialize("keywords", this) .initialize("edited-datetime", this) .initialize("markdown", this) .initialize("category-id", this) .initialize("category-name", this); return this; } static initialize( in_key = "", out_article = new Article() ) { if (out_article.has(in_key)) /* do nothing */ ; else out_article.set(in_key, null); return Article; } static populate( in_article = new Article(), in_category = new Category(), out_joined = new ArticleJoinCategory() ) { var allArticleEntries = [ ...in_article ]; for (var entry of allArticleEntries) { var [key, value] = entry; out_joined.set(key, value); } // for var categoryName = in_category.get("name"); out_joined.set("category-name", categoryName); }
}; // ArticleJoinCategory module.exports = ArticleJoinCategory;
const ArticleJoinCategory = require("../../type/ArticleJoinCategory--all-join-name");
const Article = require("../../type/Article");
const Category = require("../../type/Category");
const selectArticleById = require("./select-by-id--async-throw");
const selectCategoryById = require("../category/select-by-id--async-throw"); module.exports = async ( in_articleId = "", out_selectedJoin = new ArticleJoinCategory()
) => { var selectedArticle = new Article(); await selectArticleById(in_articleId, selectedArticle); var categoryId = selectedArticle.get("category-id"); var selectedCategory = new Category(); await selectCategoryById(categoryId, selectedCategory); ArticleJoinCategory.populate(selectedArticle, selectedCategory, out_selectedJoin);
};
const storedProcedure = new Map(); /* other procedures ... */ storedProcedure.set( "select-article-by-id-join-category--all-join-name", require("./procedure/article-join-category--all-join-name/select-by-article-id--async-throw")
); exports.execute = async ( procedureName = "tên-thủ-tục", ...parameters
) => { await storedProcedure .get(procedureName) .call(null, ...parameters);
};
const databaseManager = require("./database/manager");
const ArticleJoinCategory = require("./database/type/ArticleJoinCategory--all-join-name"); void async function() { var procedureName, id, selected; await databaseManager.execute( procedureName = "select-article-by-id-join-category--all-join-name", id = "01", selected = new ArticleJoinCategory() ); console.log(selected);
} (); // void

CMD | Terminal

npm test ArticleJoinCategory(8) [Map] { '@id' => '0001', 'title' => 'Làm Thế Nào Để Tạo Ra Một Trang Web?', 'short-title' => 'Giới Thiệu Mở Đầu', 'keywords' => [ 'hướng dẫn cơ bản', 'lập trình web', 'html', 'giới thiệu' ], 'edited-datetime' => 'Sat, 16 Apr 2022 10:13:22 GMT', 'markdown' => 'Nội dung của bài viết đầu tiên...', 'category-id' => '01', 'category-name' => 'html5'
}

Khởi tạo & Sử dụng view

Khái niệm view - hay giao diện quan sát - trong quản lý database cũng không khác so với những bối cảnh khác mà chúng ta đã thấy từ view xuất hiện trước đó. Một view là một giao diện trình bày thông tin cho người quan sát - và trong quản lý database nói riêng thì một view được xem là một giao diện bảng dữ liệu với các trường dữ liệu được thiết kế để đáp ứng nhu cầu truy vấn và sử dụng nhất định.

Xuất phát từ procedure truy vấn liên hợp mà chúng ta mới viết ở trên; Kết quả mà chúng ta thu được là một object mô tả một bản ghi liên hợp thuộc class ArticleJoinCategory với các trường dữ liệu là @id, title, ..., category-name. Nếu như chúng ta truy vấn tất cả các bản ghi article liên hợp và đặt các object kết quả lần lượt vào một mảng, thì chúng ta có thể xem mảng đó là một view; Và view này có thể được biểu thị ở dạng bảng như sau -

+------+--------------------+-----------+-------------+---------------+
| @id | title | ......... | category-id | category-name |
+------+--------------------+-----------+-------------+---------------+
| 0000 | Làm Thế Nào Để ... | ......... | 01 | html |
+------+--------------------+-----------+-------------+---------------+
| 0001 | Cách Chèn Ảnh ... | ......... | 01 | html |
+------+--------------------+-----------+-------------+---------------+
| .... | .................. | ......... | .. | .... |
+------+--------------------+-----------+-------------+---------------+
| 1001 | Hoàn Thành Series | ......... | .. | .... |
+------+--------------------+-----------+-------------+---------------+

Và dưới đây là biểu thị trong code quản lý database -

[database]
. |
. +-----[data]
. +-----[procedure]
. | |
. | +-----[article]
. | +-----[category]
. | +-----[article-join-category--all-join-name]
. | |
. | +-----select-by-article-id--async-throw.js
. |
. +-----[type]
. +-----[view]
. |
. +-----article-join-category--all-join-name--async-throw.js
const readAllArticleIds = require("../procedure/article/sub-procedure/read-all-record-ids--async-throw");
const ArticleJoinCategory = require("../type/ArticleJoinCategory--all-join-name");
const selectArticleByIdJoinCategory = require("../procedure/article-join-category--all-join-name/select-by-article-id--async-throw"); const view = { indexData: async function* ( in_options = { reverseOrder: false } ) { var allArticleIds = []; await readAllArticleIds(allArticleIds); if (in_options.reverseOrder == false) /* do nothing */; else allArticleIds = allArticleIds.reverse(); for (var articleId of allArticleIds) { var joinedRecord = new ArticleJoinCategory(); await selectArticleByIdJoinCategory(articleId, joinedRecord); yield joinedRecord; } // for .. of } // indexData
}; // view module.exports = view;

Ở đây chúng ta có view là một object có chứa phương thức indexData là một hàm generator. Khi chúng ta gọi phương thức này thì kết quả trả về là một bộ dữ liệu trừu tượng chứa tất cả các bản ghi article liên hợp - và cũng chính là bảng dữ liệu mà chúng ta đã nói đến ở trên. Bây giờ chúng ta sẽ lặp qua từng bản ghi trong bảng này và in ra console.

const view = require("./database/view/article-join-category--all-join-name--async-throw"); void async function() { var allJoinedRecords = view.indexData({ reverseOrder: true }); for await (var record of allJoinedRecords) { console.log(record); }
} (); // void

CMD | Terminal

npm test ArticleJoinCategory(8) [Map] { '@id' => '0002', 'title' => 'Cách Chèn Ảnh & Các Liên Kết', 'short-title' => 'Ảnh & Liên Kết', 'keywords' => [ 'hướng dẫn cơ bản', 'lập trình web', 'html', 'ảnh', 'liên kết' ], 'edited-datetime' => 'Sat, 16 Apr 2022 19:13:22 GMT', 'markdown' => 'Nội dung của bài viết thứ hai...', 'category-id' => '01', 'category-name' => 'html5'
}
ArticleJoinCategory(8) [Map] { '@id' => '0001', 'title' => 'Làm Thế Nào Để Tạo Ra Một Trang Web?', 'short-title' => 'Giới Thiệu Mở Đầu', 'keywords' => [ 'hướng dẫn cơ bản', 'lập trình web', 'html', 'giới thiệu' ], 'edited-datetime' => 'Sat, 16 Apr 2022 10:13:22 GMT', 'markdown' => 'Nội dung của bài viết đầu tiên...', 'category-id' => '01', 'category-name' => 'html5'
}
ArticleJoinCategory(8) [Map] { '@id' => '0000', 'title' => 'Nội dung bạn tìm kiếm không tồn tại', 'short-title' => 'Nội Dung Không Tồn Tại', 'keywords' => [ 'hướng dẫn cơ bản', 'lập trình web' ], 'edited-datetime' => 'Sat, 16 Apr 2022 01:13:22 GMT', 'markdown' => 'Nội dung mà bạn đang tìm kiếm không tồn tại...', 'category-id' => '00', 'category-name' => 'unknown'
}

Như vậy, đối với các thao tác truy vấn khác liên quan đến kiểu bản ghi liên hợp ArticleJoinCategory, chúng ta có thể truy vấn thông qua view này. Ví dụ điển hình là khi chúng ta muốn chọn ra một vài bài viết mới nhất để bày các bản giới thiệu ngắn trên giao diện trang chủ. Lúc này chúng ta có thể viết một thủ thục select-top sử dụng view này như sau -

const view = require("../../view/article-join-category--all-join-name--async-throw"); module.exports = async ( in_options = { numberOfRecords: 0, reverseOrder: false }, out_selected = []
) => { var allJoinedRecords = view.indexData({ ...in_options }); for await (var record of allJoinedRecords) { if (out_selected.length < in_options.numberOfRecords) out_selected.push(record); else break; }
}; // module.exports
const storedProcedure = new Map(); /* other procedures ... */ storedProcedure.set( "select-top-articles-join-category--all-join-name", require("./procedure/article-join-category--all-join-name/select-top-by-category-id--async-throw")
); exports.execute = async ( procedureName = "tên-thủ-tục", ...parameters
) => { await storedProcedure .get(procedureName) .call(null, ...parameters);
};
const databaseManager = require("./database/manager"); void async function() { var procedure, options, selected; await databaseManager.execute( procedure = "select-top-articles-join-category--all-join-name", options = { numberOfRecords: 3, reverseOrder: true }, selected = [] ); console.log(selected);
} (); // void

CMD | Terminal

[ ArticleJoinCategory(8) [Map] { '@id' => '0002', 'title' => 'Cách Chèn Ảnh & Các Liên Kết', 'short-title' => 'Ảnh & Liên Kết', 'keywords' => [ 'hướng dẫn cơ bản', 'lập trình web', 'html', 'ảnh', 'liên kết' ], 'edited-datetime' => 'Sat, 16 Apr 2022 19:13:22 GMT', 'markdown' => 'Nội dung của bài viết thứ hai...', 'category-id' => '01', 'category-name' => 'html5' }, ArticleJoinCategory(8) [Map] { '@id' => '0001', 'title' => 'Làm Thế Nào Để Tạo Ra Một Trang Web?', 'short-title' => 'Giới Thiệu Mở Đầu', 'keywords' => [ 'hướng dẫn cơ bản', 'lập trình web', 'html', 'giới thiệu' ], 'edited-datetime' => 'Sat, 16 Apr 2022 10:13:22 GMT', 'markdown' => 'Nội dung của bài viết đầu tiên...', 'category-id' => '01', 'category-name' => 'html5' }, ArticleJoinCategory(8) [Map] { '@id' => '0000', 'title' => 'Nội dung bạn tìm kiếm không tồn tại', 'short-title' => 'Nội Dung Không Tồn Tại', 'keywords' => [ 'hướng dẫn cơ bản', 'lập trình web' ], 'edited-datetime' => 'Sat, 16 Apr 2022 01:13:22 GMT', 'markdown' => 'Nội dung mà bạn đang tìm kiếm không tồn tại...', 'category-id' => '00', 'category-name' => 'unknown' }
]

Kết thúc bài viết

Như vậy là chúng ta đã được biết sơ khai về khái niệm view trong quản lý database và một số phương thức truy vấn phức hợp phổ biến là select-topjoin. Chúng ta sẽ được gặp lại những khái niệm này trong Sub-Series SQL với nhiều đặc điểm chi tiết hơn. Còn bây giờ thì chúng ta sẽ tạm dừng Sub-Series Database tại đây một thời gian ngắn để hoàn thiện trang blog đơn giản đang xây dựng như đã dự kiến trước đó. Hẹn gặp lại bạn trong những bài viết tiếp theo. 😄

(Chưa đăng tải) [Database] Bài 8 - Từ từ để xem chúng ta cần học thêm cái gì đã. Học theo cách tự nhiên mà. 😄

Bình luận

Bài viết tương tự

- vừa được xem lúc

Mô hình quan hệ - thực thể (Entity – Relationship Model)

Mô hình quan hệ thực thể (Entity Relationship model - E-R) được CHEN giới thiệu vào năm 1976 là một mô hình được sử dụng rộng rãi trong các bản thiết kế cơ sở dữ liệu ở mức khái niệm, được xây dựng dựa trên việc nhận thức thế giới thực thông qua tập các đối tượng được gọi là các thực thể và các mối

0 0 117

- vừa được xem lúc

[Embulk #1] Công cụ giúp giảm nỗi đau chuyển đổi dữ liệu

Embulk là gì. Embulk là một công cụ open source có chức năng cơ bản là load các record từ database này và import sang database khác.

0 0 39

- vừa được xem lúc

Window Functions trong MySQL, Nâng cao và cực kì hữu dụng (Phần II).

Chào mọi người, lại là mình đây, ở phần trước mình đã giới thiệu với mọi người về Window Functions Phần I. Nếu chưa rõ nó là gì thì mọi người nên đọc lại trước nha, để nắm được định nghĩa và các key words, tránh mắt chữ O mồm chứ A vì phần này mình chủ yếu sẽ thực hành với các Window Functions.

0 0 89

- vừa được xem lúc

Window Functions trong MySQL, Nâng cao và cực kì hữu dụng (Phần I).

Chào mọi người, mình mới tìm hiểu đc topic Window Functions cá nhân mình cảm thấy khá là hay và mình đánh giá nó là phần nâng cao. Vì ít người biết nên Window Functions thấy rất ít khi sử dụng, thay vì đó là những câu subquery dài dằng dặc như tin nhắn nhắn cho crush, và người khác đọc hiểu được câu

0 0 895

- vừa được xem lúc

Disable và Enable trigger trong Oracle

Origin post: https://www.tranthanhdeveloper.com/2020/12/disable-va-enable-trigger-trong-oracle.html.

0 0 29

- vừa được xem lúc

Lưu trữ dữ liệu với Data Store

. Data Store là một trong những componet của bộ thư viện Android JetPack, nó là một sự lựa chọn hoàn hảo để thay thế cho SharedPreferences để lưu trữ dữ liệu đơn giản dưới dạng key-value. Chúng ta cùng làm một so sánh nhỏ để thấy sự tối ưu của Data Store với SharedPreferences nhé.

0 0 61