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

Giải bài SQL trên HackerRank - Kỹ thuật Tabibitosan

0 0 44

Người đăng: Tran Nhat Anh

Theo Viblo Asia

Mục lục

Giới thiệu

Hôm trước, tôi có giải một câu SQL trên HackerRank và tình cờ phát hiện ra một kỹ thuật hay tên là Tabibitosan (đọc là ta-bi-bi-tô-san). Kỹ thuật này được một người Nhật tên Aketi Jyuuzou giới thiệu lần đầu ở cộng đồng Oracle vào năm 2011 dựa trên đề thi đầu vào của một trường trung học Nhật.

Ý tưởng của kỹ thuật này là thực hiện là so sánh vị trí của các hàng trong nhóm với tập hợp các hàng tổng thể, để tìm ra liệu các hàng trong cùng một nhóm có cạnh nhau hay không.

Hừm, nghe có vẻ rắc rối nhỉ? Tạm bỏ qua cái định nghĩa kia đi. Chúng ta sẽ đi vào một ví dụ đơn giản nhé.

Bài toán

Giả sử bạn có cột dữ liệu val chứa các số như sau:

Screen Shot 2021-11-12 at 14.09.04.png

Chúng ta dễ thấy có những khoảng số liên tiếp không bị ngắt quãng là 1-3, 5-7, 10-1220-21. Câu hỏi đặt ra là làm sao có thể nhóm chúng với nhau trong SQL?

Phương pháp

Bây giờ, tôi thêm cột số thứ tự rn , rồi thêm cột grp là kết quả khi trừ cột val cho cột rn. Ồ nhìn xem, tôi đã tạo ra một identical number cho mỗi chuỗi số.

Tạo bảng mới từ bảng đã cho và thêm cột rn (số thứ tự).

Tạo bảng mới từ bảng đã cho và thêm cột rn (số thứ tự).

Để ý là khi dùng group by thì giá trị đầu và cuối của chuỗi được xác định là giá trị val nhỏ nhấtlớn nhất. Ví dụ, với grp = 0 thì chuỗi sẽ là 1-3. Dễ hiểu phải không nào?

SQL scripts

Giờ chúng ta sẽ dùng các scripts dưới đây để test trong MySQL.

create table Tabibitosan as select 1 as val from dual union all select 2 from dual union all select 3 from dual union all select 5 from dual union all select 6 from dual union all select 7 from dual union all select 10 from dual union all select 11 from dual union all select 12 from dual union all select 20 from dual union all select 21 from dual;
select val ,row_number() over (order by val) as rn ,val - row_number() over (order by val) as grp
from Tabibitosan
order by val;
select min(val) as range_start ,max(val) as range_end ,count(*) as range_count
from ( -- Đây là câu query để thêm 2 cột rn và grp ở trên -- Start query select val ,row_number() over (order by val) as rn ,val - row_number() over (order by val) as grp from Tabibitosan -- End query ) as t1
group by grp
order by 1;

Challenge

Đề bài HackerRank

Đây là toàn bộ đề bài HackerRank. Hãy chú ý vào những từ mà tôi gạch chân

Đây là toàn bộ đề bài HackerRank. Hãy chú ý vào những từ mà tôi gạch chân

Phân tích câu hỏi

  • Input: 1 bảng Projects có 3 cột Task_ID (integer), Start_Date (date) và End_Date (date).
  • Output: Ngày bắt đầu và kết thúc của tất cả project, sắp xếp theo thời gian hoàn thành project tăng dần và ngày bắt đầu project giảm dần. Hay chúng ta có thể hiểu là ngày bắt đầu và kết thúc của các chuỗi ngày liên tiếp nhau.
  • "It is guaranteed that the difference between the End_Date and the Start_Date is equal to 1 day for each row in the table." ⇒ Mỗi dòng sẽ có Start_Date - End_Date = 1.

Input là bảng có 3 cột

Input là bảng Projects có 3 cột

Kết quả trước khi sắp xếp

Kết quả trước khi sắp xếp

Lời giải

Cách giải 1 áp dụng kỹ thuật Tobibitosan

Vì mỗi dòng sẽ có Start_Date - End_Date = 1 nên chúng ta chỉ cần dùng tới cột Start_Date và bỏ qua cột End_Date. Chúng ta sẽ lặp lại các bước như ở bài toán ví dụ.

Tạo 1 bảng mới từ bảng Projects có 3 cột là , (số thự tự) và cột (= - )

Tạo 1 bảng mới từ bảng Projects có 3 cột là Start_Date, rn (số thự tự) và cột grp (= Start_Date - rn)

Nhóm các dòng có cùng grp, Start_Date là giá trị nhỏ nhất, còn End_Date sẽ là giá trị lớn nhất + 1 ngày.

Nhóm các dòng có cùng grp, Start_Date là giá trị nhỏ nhất, còn End_Date sẽ là giá trị lớn nhất + 1 ngày.

create table if not exists ProjectS ( id int not null auto_increment, Start_Date date not null, End_Date date not null, primary key (id)
); insert into ProjectS (Start_Date, End_Date) values ("2015-10-01", "2015-10-02") ,("2015-10-02", "2015-10-03") ,("2015-10-03", "2015-10-04") ,("2015-10-13", "2015-10-14") ,("2015-10-14", "2015-10-15") ,("2015-10-28", "2015-10-30") ,("2015-10-30", "2015-10-31");
select Start_Date ,id as rn -- Chúng ta đã có sẵn cột số thứ tự là id ,Start_Date - row_number() over (order by Start_Date) as grp
from Projects;
select min(Start_Date) as range_start ,date_add(max(Start_Date), interval 1 day) as range_end ,count(*) as range_count
from ( -- Phần query tạo bảng chứa 3 cột (Start_Date, rn và grp) -- Start query select Start_Date ,row_number() over (order by Start_Date) as rn ,Start_Date - row_number() over (order by Start_Date) as grp from Project -- End query
) as t1
group by grp;
select min(Start_Date) as range_start ,date_add(max(Start_Date), interval 1 day) as range_end
from ( -- Phần query tạo bảng chứa 3 cột (Start_Date, rn và grp) -- Start query select Start_Date ,row_number() over (order by Start_Date) as rn ,Start_Date - row_number() over (order by Start_Date) as grp from Project -- End query
) as t1
group by grp
-- Sắp xếp theo thời gian hoàn thành project tăng dần và ngày bắt đầu project giảm dần
order by count(*) ,range_start;

Cách giải 2

Đây là cách lúc đầu tôi tiếp cận, ý tưởng ban đầu là tạo ra 2 bảng t1t2 từ bảng Projects lần lượt chứa các giá trị Start_DateEnd_Date thoả mãn đề bài, rồi tìm cách join 2 bảng đó lại với nhau và lọc ra kết quả đúng.

Các bước thực hiện sẽ như sau:

  • Tạo ra bảng t1 có 1 cột chứa các giá trị Start_Date và bảng t2 có 1 cột chứa các giá trị End_Date cần tìm
  • Dùng cross join để liên kết 2 bảng này với nhau
  • Lọc bỏ các giá trị không thoả mãn bằng where
  • Nhóm các giá trị trùng với group by
  • Cuối cùng, sắp xếp data bằng order by.
select Start_Date, min(End_Date) as End_Date
from -- tạo bảng t1 có 1 cột chứa các giá trị Start_Date (select Start_Date from Projects where Start_Date not in (select End_Date from Projects)) t1, -- tạo bảng t2 có 1 cột chứa các giá trị End_Date  (select End_Date from Projects where End_Date not in (select Start_Date from Projects)) t2
-- Bỏ các giá trị không hợp lệ
where Start_Date < End_Date
-- Nhóm các giá trị trùng bằng group by
group by Start_Date
order by -- Sắp xếp theo khoảng cách giữa ngày bắt đầu và ngày kết thúc giảm dần DATEDIFF(Start_Date, min(End_Date)) desc, -- Sắp xếp theo ngày bắt đầu tăng dần Start_Date;

Tổng kết

Wow, chỉ với một câu HackerRank tôi nghĩ mình đã học được kha khá kiến thức về SQL từ select, join, subquery, where, tới group by, order by và cả kỹ thuật xác định các phạm vi giá trị tuần tự có cái tên tiếng Nhật rất vui tai là Tabibitosan.

Tham khảo

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 132

- 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 55

- 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 107

- 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 923

- 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 41

- 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 71