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

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

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 =[];awaitreadAllRecordIds(allRecordIds);/* one-by-one select and check */for(var recordId of allRecordIds){var selected =newArticle();awaitselectArticleById(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");voidasyncfunction(){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 =[];awaitreadAllRecordIds(allRecordIds);if(in_options.reverseOrder ==false)/* do nothing */;else
      allRecordIds = allRecordIds.reverse();/* select each record to check */for(var recordId of allRecordIds){var selected =newArticle();awaitselectArticleById(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 */;elsebreak;}// 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 =newCategory())=>{try{var selectedArticles =[];awaitselectTopArticleByCategoryId({
            numberOfRecords:1,
            reverseOrder:true},
         in_recordId,
         selectedArticles
      );var theCategoryIsEmpty =(selectedArticles.length ==0);if(theCategoryIsEmpty)awaitremoveRecordFromDatabase(in_recordId, out_deleted);elsethrownewError("Đ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 =classextends 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);returnthis;}staticinitialize(
      in_key ="",
      out_article =newArticle()){if(out_article.has(in_key))/* do nothing */;else
         out_article.set(in_key,null);return Article;}staticpopulate(
      in_article =newArticle(),
      in_category =newCategory(),
      out_joined =newArticleJoinCategory()){var allArticleEntries =[...in_article ];for(var entry of allArticleEntries){var[key, value]= entry;
         out_joined.set(key, value);}// forvar 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 =newArticleJoinCategory())=>{var selectedArticle =newArticle();awaitselectArticleById(in_articleId, selectedArticle);var categoryId = selectedArticle.get("category-id");var selectedCategory =newCategory();awaitselectCategoryById(categoryId, selectedCategory);

   ArticleJoinCategory.populate(selectedArticle, selectedCategory, out_selectedJoin);};
const storedProcedure =newMap();/* 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");voidasyncfunction(){var procedureName, id, selected;await databaseManager.execute(
      procedureName ="select-article-by-id-join-category--all-join-name",
      id ="01",
      selected =newArticleJoinCategory());

   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:asyncfunction*(
      in_options ={ reverseOrder:false}){var allArticleIds =[];awaitreadAllArticleIds(allArticleIds);if(in_options.reverseOrder ==false)/* do nothing */;else
         allArticleIds = allArticleIds.reverse();for(var articleId of allArticleIds){var joinedRecord =newArticleJoinCategory();awaitselectArticleByIdJoinCategory(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");voidasyncfunction(){var allJoinedRecords = view.indexData({ reverseOrder:true});forawait(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 });forawait(var record of allJoinedRecords){if(out_selected.length < in_options.numberOfRecords)
         out_selected.push(record);elsebreak;}};// module.exports
const storedProcedure =newMap();/* 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");voidasyncfunction(){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à. 😄

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ