Hướng dẫn đệ quy trong mysql
Mình sẽ để nguyên bài viết cũ, và làm thêm 1 phần này để follow up. Ngoài việc giữ nguyên hiện trường, thật sự đây cũng là 1 minh chứng rất tuyệt vời cho lợi ích của việc thảo luận và trao đổi kiến thức. Nếu như không có comment của bạn @Midoriniji , chẳng những mình sẽ không biết được thêm một kiến thức mới, mà nhiều khả năng chắc cũng sẽ không quay lại nhìn để tự nhận ra cái sai của mình được. Spoiler trước như thế cho các bạn nào có hứng thú : Trong bài viết trước, cách làm thứ 2 mà mình đã đưa ra đã cho kết quả không chính xác. Giống như đọc truyện trinh thám vậy, một khi đã đọc qua đoạn phá án, cái cảm giác thích thú khi đi tìm câu trả lời sẽ không bao giờ đến với bạn được nữa. Vì thế, nếu bạn muốn tự mình mày mò 1 chút, xin hãy đừng vội đọc bài viết này. Spoiler ahead. You've been warned. Show Recursive QueryTrong MySql thì đây là một tính năng mới, mới được đưa vào chỉ từ phiên bản 8.0.1 trờ đi. Có thể vì còn tương đối mới và cũng không phải để phục vụ cho những use-case phổ biến nhất, nên thật sự có vẻ như cũng không có nhiều bài viết dạng guide hay tutorial giúp ta hiểu tính năng này một cách dễ dàng. Đó là tin xấu, còn tin tốt là, ơn giời, trong ngành lập trình này của chúng ta thì muốn tìm hiểu về cái gì cũng bắt nguồn từ documents của nó mà ra hết, nên cũng không có gì đáng ngại lắm. Và càng may mắn hơn, là chúng ta đang có công cụ sẵn trên tay để nghịch rồi, đọc được điều gì, đem ra thử luôn xem hiểu đúng hay sai, đây là cách học ( đủ để sử dụng ) một cách nhanh nhất. Thế thì cùng bắt tay vào làm nào. Vẫn lại là : Cần lao vi tiên thủNgười bạn bí ẩn để lại cho chúng ta một câu query
rất đáng tiếc là cứ thế vứt vào bộ dữ liệu chúng ta đang có thì nó không chạy được. Hơi đen, nhưng thôi, đời có mấy khi được ăn sẵn
dễ thế. Nhìn qua một chút thì có thể thấy, trong bộ dữ liệu mình tạo ra, bảng Cũng mất một hồi để nghiền ngẫm, tuy nhiên mình diễn dịch tàm tạm cái ví dụ trên ra thì thế này
syntax thôi, nhưng ở đây ngoài việc khai báo dùng
trong Wiki thì không thấy nói đây là yêu cầu bắt buộc, nhưng ngẫm một chút thì : chúng ta sẽ cần một truy vấn để đem đi đệ quy, và chẳng lẽ chỉ có mỗi thế, thường thì cũng sẽ cần một query nào khác đem thông tin đệ quy đó vào sử dụng chứ. Nghe có vẻ hợp hợp với trường hợp chúng ta đang cần, nhiều khả năng query ta cần viết ra cũng sẽ có dạng kiểu này, tạm note lại một điều là muốn sử dụng được
Đến đây là hết phần
Và cuối cùng là, có vẻ như chúng ta có thể kiểm soát việc trả về những dữ liệu gì từ trong bảng dữ liệu tạm. Cái này có vẻ không quan trọng lắm. HIểu tàm tạm thế đã, đem đi làm thử. Năng cán dĩ đắc thựcNhư đã nói, muốn kiểm chứng cách mình hiểu có thể ( không thể khẳng định là chắc chắn ) đúng không, cứ đem ra làm theo thử. Đầu tiên là xác định, chúng ta đang cần đệ quy cái gì. Ở đây chúng ta đang có nhiều user phụ thuộc vào nhau theo từng tầng, muốn lấy ra tất cả thì có thể hình dung ra, trước tiên ta phải tìm ra những user trực thuộc user đó, sau đó tiếp tục đi tìm user trực thuộc của trực thuộc ... Thế thì thứ cần đem ra để đệ quy ở đây sẽ là : "Lấy ra những user trực thuộc của user ". Như vậy, câu query ban đầu để ta đem đi đệ quy sẽ là
Đặt cho nó một cái tên, đồng thời khai báo những trường sẽ có trong bộ dữ liệu tạm của mình.
Tạm thời chưa nghĩ gì đến tên tuổi vội, lấy ra thông tin định danh đã. Tiếp theo là viết xem, mình muốn mỗi lần đệ quy thì làm cái gì. Ở đây như đã xác định từ đầu, chúng ta cần lấy ra những user trưc thuộc của 1 user khác.
Nhìn qua thì có vẻ như, bảng
Đến đây, mình cũng thật sự bất ngờ là nó chạy được, và có vẻ tương đối ổn luôn Khá là tuyệt vời. Lấy ra cả thông tin tên tuổi nữa đi cho máu. Thật ra đây là một điều mà mình đã muốn làm
từ bài viết trước, lấy ra cả id và tên của user phụ thuộc. Nhưng với cách làm cũ thì ở mỗi tầng lại phải join thêm với bảng
Ề tồ, không ổn lắm. Nếu sau khi lấy ra id xong, lại tiếp tục đem id đi join để lấy ra name là không ổn, thì thử lấy ra name ngay từ đầu, liệu có khá hơn không. Tuy nhiên mình đã thử qua 1 vài hướng, nhưng vẫn chưa có câu trả lời thỏa đáng, phần này có lẽ tạm để ngỏ ở đây vậy. Bới ra thêm 1 chút, thì theo cách query cũ, ta có thể biết được mỗi user phụ thuộc đang đứng ở level nào, ở đây lấy ra được mỗi ID không thôi, nghe có vẻ dễ bị bới móc. Ta thử tìm cách thêm thông tin level nữa, liệu có được không.
Bá vãi, được luôn. Nhưng các kết quả cuối toàn là Ngon rồi, có vẻ ổn. Theo cách này, ta có thể nhận diện những user đầu tiên, không phụ thuộc vào ai sẽ có level 0. Sau đó, mỗi cấp level phụ thuộc sẽ tương ứng tăng thêm 1. Theo cách này thì người mới vào hoàn toàn không làm đổi thông tin của người cũ luôn, quá ngon. Final VerdictNói là final thì cũng chưa hẳn đúng lắm, nhưng có thể tạm nói, đây là kết luận cuối cùng chúng ta đưa ra sau khi trải qua quá trình tìm hiểu trên. Ưu điểm
Nhược điểm
Tuy nhiên, cảm nhận cuối cùng của mình vẫn là : Đây là hướng làm đúng đắn nhất về mặt logic, nếu có thể, có lẽ nên tìm cách làm sao để áp dụng một cách tốt nhất hướng giải này cho bài toán của bạn, thay vì chọn các giải pháp "work around" , vì theo cá nhân mình nghĩ, các cách làm "mẹo" luôn có rủi ro tồn tại những nhược điểm mà mình chưa nhìn thấy hết. Mở rộng ra một chút.Sửa lại cách làm thứ 2.Nhắc đi nhắc lại mãi, nhưng thật ra vẫn đề cũng không có gì ghê gớm lắm. Đây là kết quả khi mình sử dụng recursive query Còn trong bài viết trước, kết quả khi mình dùng repeated-self-join lại là
Dễ nhận thấy là 2 kết quả này đang khác nhau rất nhiều. Thế thì cách nào mới là đúng. Cùng nhìn lại một chút, thì ở cách làm thứ 2, khi lấy ví dụ với level 2 (tương đối ít kết quả, dễ kiểm chứng ), ta có kết quar Tất cả các kết quả trả về đều đang có giá trị ở cả 2 trường
Có vẻ như không được ổn lắm ở đây. Ở đây chúng ta đang có 12 unique Id, nhưng theo cách biểu diễn này, 12 Id nầy sẽ nằm rải rác trong 3 cột, không có cột nào chứa tất cả 12 kết quả. Có vẻ như ta sẽ chỉ có thể đạt được kết quả này bằng cách
Đến đây có vẻ dễ hình dung hơn rồi đúng không. Hiển thị User và cấp trên của user đó, cấp trên có thể là
Có vẻ đúng là kết quả ta cần rồi. Việc cuối cùng ta cần làm, là mở rộng ra cho tới tất cả các level, khá đơn giản :
Thật ra là vẫn còn lệch 1 so với kết quả khi dùng recursive, lí do là ở đây ta đã tính cả bản ghi của chính
user đó ( bản ghi có id = 1, tất cả các cấp trên đều là Sử dụng Recursive query để tạo dữ liệu.Như ở phần trên ta đã đề cập đến, có công cụ mới là recursive query trong tay, ta hãy thử tự hỏi, liệu chúng ta có thể trực tiếp tạo dữ liệu trong database hay không. Bỏ qua trường WARNING : Đoạn dưới đây thật sự nhảmThật lòng mà nói thì, mình cũng không thích viết đoạn này lắm, vì có lẽ sẽ không có ai có hứng thú mà đọc phần này đâu. Nó khá là đau mắt. Mà thôi, cái này viết ra chắc chủ yếu để thỏa mãn bản thân, dù sao cũng là 1 quá trình nghịch ngợm chọc ngoáy. Diễn tả thành lời thì, việc ta cần làm ở đây cũng khá là đơn giản. Ta sẽ có 2 vòng lặp, vòng lặp thứ nhất đi từ 1 đến 10, bậc nhảy 1 đơn vị. Vòng lặp thứ 2, nằm bên trong, tăng từ lũy thừa n-1 của 4 đến lũy thừa n của 4, là số id tương ứng nằm trong 1 level. Tương đương với vòng lặp thứ nhất, ta có 1 câu query khá đơn giản, tăng dần đều từ 1 đến 10.
Vòng lặp thứ 2, tiếp nối vào với vòng thứ nhất, sẽ cần lấy ra các giá trị như sau :
Việc lấy ra 1 số random trong khoảng định nghĩa sẵn không được hỗ trợ sẵn trong MySQL, tuy nhiên ta cũng có thể lấy nó được bằng cách sử dụng kết hợp một số công cụ khác có sẵn
Như vậy, câu query sử dụng cho vòng lặp thứ nhất sẽ là
Khá gọn gàng và có vẻ đây chính là thứ chúng ta cần. Đem câu này vào, kết hợp với vòng lặp thứ 2, ta có kết quả cuối cùng
Chúng ta có thể kiểm tra
lại bộ kết quả này 1 chút, bằng cách dùng Vậy là xong, bài toán chúng ta tự đặt ra ban đầu : seed dữ liệu bằng cách sử dụng recursive query cũng đã xong. Nó làm mình nhớ tới 1 câu quote khá nổi tiếng : " You were so preoccupied with whether you could, you didn't stop to think if you should. " . Tốn khá nhiều thời gian cho việc thỏa mãn câu hỏi : Liệu mình có làm được việc này không ? để rồi đến khi nhìn lại, có vẻ chắc sẽ không ai care đến chuyện đó lắm ) . Nhưng thôi, dù sao thì bản thân mình thỏa mãn, thế là được rồi.Kết luận cuối cùng.Thực ra, như đã nói từ đầu, đây là 1 bài toán khá phổ biến. Và hiển nhiên, như mọi thứ phổ biến khác, tìm 1 hồi thì cũng thấy người ta đã giải nó từ lâu rất lâu rồi : https://stackoverflow.com/questions/20215744/how-to-create-a-mysql-hierarchical-recursive-query . Vào đây xem cũng mới biết, hóa ra lời giải được comment trong bài viết trước của mình cũng có vẻ là lấy nguyên xi từ trong đây ra :-s . Nếu bạn chỉ cần quan tâm đến chuyện làm sao giải quyết tình huống này, câu trả lời trên SO kia đã rất đầy đủ và xúc tích. Nhìn lại thì thật ra mình cũng thấy cả quá trình ngồi nghịch này, hơi mất thời gian 1 chút nhưng cũng khá vui, mày mò ra được nhiều trò là lạ. Ghi lại đây, để sau này bản thân có thể ngồi xem lại, hoặc không cũng gọi là được chút mua vui cho ae. |