Rất nhiều câu trả lời khác mà tôi thấy ở đây (và trong các câu hỏi trùng lặp) về cơ bản chỉ hoạt động đối với dữ liệu được định dạng rất cụ thể, ví dụ: một chuỗi hoàn toàn là số hoặc có tiền tố chữ cái có độ dài cố định. Điều này sẽ không hoạt động trong trường hợp chung.
Đúng là không thực sự có bất kỳ cách nào để triển khai 100% nat-sort chung trong MySQL, bởi vì để thực hiện nó, điều bạn thực sự cần là một hàm so sánh đã sửa đổi , chuyển đổi giữa sắp xếp từ vựng của chuỗi và sắp xếp số nếu / khi nó gặp phải một số. Mã như vậy có thể triển khai bất kỳ thuật toán nào bạn có thể mong muốn để nhận ra và so sánh các phần số trong hai chuỗi. Tuy nhiên, thật không may, hàm so sánh trong MySQL nằm trong mã của nó và người dùng không thể thay đổi được.
Điều này dẫn đến một sự tấn công nào đó, trong đó bạn cố gắng tạo một khóa sắp xếp cho chuỗi của mình, trong đó các phần số được định dạng lại sao cho loại từ vựng chuẩn thực sự sắp xếp chúng theo cách bạn muốn .
Đối với các số nguyên thuần túy có đến một số chữ số tối đa, giải pháp rõ ràng là chỉ cần đệm trái chúng bằng các số không để chúng đều có chiều rộng cố định. Đây là cách tiếp cận được thực hiện bởi plugin Drupal và các giải pháp của @plalx / @RichardToth. (@Christian có một giải pháp khác và phức tạp hơn nhiều, nhưng nó không mang lại lợi ích nào mà tôi có thể thấy).
Như @tye đã chỉ ra, bạn có thể cải thiện điều này bằng cách thêm độ dài chữ số cố định cho mỗi số, thay vì chỉ cần đệm bên trái nó. Mặc dù vậy, còn rất nhiều điều bạn có thể cải thiện, ngay cả khi có những hạn chế về cơ bản là một vụ hack khó xử. Tuy nhiên, dường như không có bất kỳ giải pháp được xây dựng sẵn nào ngoài đó!
Ví dụ, những gì về:
- Dấu cộng và dấu trừ? +10 so với 10 so với -10
- Số thập phân? 8,2, 8,5, 1,006, 0,75
- Số không hàng đầu? 020, 030, 00000922
- Ngàn ngăn cách? "1.001 con chó đốm" so với "1001 con chó đốm"
- Số phiên bản? MariaDB v10.3.18 so với MariaDB v10.3.3
- Con số rất dài? 103.768.276.592.092.364.859.236.487.687.870.234.598,55
Mở rộng trên phương thức của @ tye, tôi đã tạo một hàm được lưu trữ NatSortKey () khá nhỏ gọn sẽ chuyển đổi một chuỗi tùy ý thành khóa nat-sort và xử lý tất cả các trường hợp trên, hiệu quả hợp lý và bảo toàn tổng số sắp xếp- thứ tự (không có hai chuỗi khác nhau có khóa sắp xếp so sánh bằng nhau). Tham số thứ hai có thể được sử dụng để giới hạn số lượng số được xử lý trong mỗi chuỗi (chẳng hạn như 10 số đầu tiên), có thể được sử dụng để đảm bảo đầu ra phù hợp với độ dài nhất định.
LƯU Ý: Chuỗi khóa sắp xếp được tạo với một giá trị nhất định của tham số thứ 2 này chỉ nên được sắp xếp so với các chuỗi khác được tạo với cùng giá trị cho tham số, nếu không chúng có thể không được sắp xếp chính xác!
Bạn có thể sử dụng nó trực tiếp khi đặt hàng, ví dụ:
SELECT myString FROM myTable ORDER BY NatSortKey(myString,0);
Nhưng để sắp xếp hiệu quả các bảng lớn, tốt hơn nên lưu trữ trước khóa sắp xếp trong một cột khác (có thể có chỉ mục trên đó):
INSERT INTO myTable (myString,myStringNSK) VALUES (@theStringValue,NatSortKey(@theStringValue,10)), ...
...
SELECT myString FROM myTable ORDER BY myStringNSK;
[Tốt nhất, bạn nên làm cho điều này xảy ra tự động bằng cách tạo cột khóa dưới dạng cột được lưu trữ được tính toán, sử dụng một cái gì đó như:
CREATE TABLE myTable (
...
myString varchar(100),
myStringNSK varchar(150) AS (NatSortKey(myString,10)) STORED,
...
KEY (myStringNSK),
...);
Nhưng hiện tại cả MySQL và MariaDB đều không cho phép các hàm được lưu trữ trong các cột được tính toán , vì vậy rất tiếc là bạn chưa thể thực hiện điều này .]
Chức năng của tôi chỉ ảnh hưởng đến việc sắp xếp các số . Nếu bạn muốn thực hiện những việc chuẩn hóa sắp xếp khác, chẳng hạn như xóa tất cả các dấu câu hoặc cắt bớt khoảng trắng khỏi mỗi đầu hoặc thay thế các chuỗi nhiều khoảng trắng bằng các khoảng trắng, bạn có thể mở rộng hàm hoặc có thể thực hiện trước hoặc sau NatSortKey()
là áp dụng cho dữ liệu của bạn. (Tôi khuyên bạn nên sử dụng REGEXP_REPLACE()
cho mục đích này).
Tôi cho rằng nó cũng có phần thiên về Anh ngữ '.' cho dấu thập phân và ',' cho dấu phân tách hàng nghìn, nhưng nó phải đủ dễ dàng để sửa đổi nếu bạn muốn đảo ngược hoặc nếu bạn muốn chuyển đổi dưới dạng tham số.
Nó có thể được cải thiện hơn nữa theo những cách khác; ví dụ, nó hiện sắp xếp các số âm theo giá trị tuyệt đối, vì vậy -1 đứng trước -2, thay vì ngược lại. Cũng không có cách nào để chỉ định thứ tự sắp xếp DESC cho các số trong khi vẫn giữ lại sắp xếp từ điển ASC cho văn bản. Cả hai vấn đề này có thể được khắc phục với một chút công việc; Tôi sẽ cập nhật mã nếu / khi tôi nhận được thời gian.
Có rất nhiều chi tiết khác cần lưu ý - bao gồm một số phụ thuộc quan trọng vào chaset và collation mà bạn đang sử dụng - nhưng tôi đã đặt tất cả chúng vào một khối nhận xét trong mã SQL. Vui lòng đọc kỹ điều này trước khi sử dụng chức năng cho chính mình!
Vì vậy, đây là mã. Nếu bạn tìm thấy lỗi hoặc có cải tiến mà tôi chưa đề cập, vui lòng cho tôi biết trong phần bình luận!
delimiter $$
CREATE DEFINER=CURRENT_USER FUNCTION NatSortKey (s varchar(100), n int) RETURNS varchar(350) DETERMINISTIC
BEGIN
DECLARE x,y varchar(100);
DECLARE r varchar(350) DEFAULT '';
DECLARE suf varchar(101);
DECLARE i,j,k int UNSIGNED;
IF n<=0 THEN SET n := -1; END IF;
LOOP
SET i := REGEXP_INSTR(s,'\\d');
IF i=0 OR n=0 THEN RETURN CONCAT(r,s); END IF;
SET n := n-1, suf := ' ';
IF i>1 THEN
IF SUBSTRING(s,i-1,1)='.' AND (i=2 OR SUBSTRING(s,i-2,1) RLIKE '[^.\\p{L}\\p{N}\\p{M}\\x{608}\\x{200C}\\x{200D}\\x{2100}-\\x{214F}\\x{24B6}-\\x{24E9}\\x{1F130}-\\x{1F149}\\x{1F150}-\\x{1F169}\\x{1F170}-\\x{1F189}]') AND (SUBSTRING(s,i) NOT RLIKE '^\\d++\\.\\d') THEN SET i:=i-1; END IF;
IF i>1 AND SUBSTRING(s,i-1,1)='+' THEN SET suf := '+', j := i-1; ELSE SET j := i; END IF;
SET r := CONCAT(r,SUBSTRING(s,1,j-1)); SET s = SUBSTRING(s,i);
END IF;
SET x := REGEXP_SUBSTR(s,IF(SUBSTRING(s,1,1) IN ('0','.') OR (SUBSTRING(r,-1)=',' AND suf=' '),'^\\d*+(?:\\.\\d++)*','^(?:[1-9]\\d{0,2}(?:,\\d{3}(?!\\d))++|\\d++)(?:\\.\\d++)*+'));
SET s := SUBSTRING(s,LENGTH(x)+1);
SET i := INSTR(x,'.');
IF i=0 THEN SET y := ''; ELSE SET y := SUBSTRING(x,i); SET x := SUBSTRING(x,1,i-1); END IF;
SET i := LENGTH(x);
SET x := REPLACE(x,',','');
SET j := LENGTH(x);
SET x := TRIM(LEADING '0' FROM x);
SET k := LENGTH(x);
SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294) + IF(i=j,0,1),10,36),2,'0'));
SET i := LOCATE('.',y,2);
IF i=0 THEN
SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x,y,suf);
ELSE
SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x);
WHILE LENGTH(y)>0 AND n!=0 DO
IF i=0 THEN SET x := SUBSTRING(y,2); SET y := ''; ELSE SET x := SUBSTRING(y,2,i-2); SET y := SUBSTRING(y,i); SET i := LOCATE('.',y,2); END IF;
SET j := LENGTH(x);
SET x := TRIM(LEADING '0' FROM x);
SET k := LENGTH(x);
SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x);
SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294),10,36),2,'0'));
SET n := n-1;
END WHILE;
SET r := CONCAT(r,y,suf);
END IF;
END LOOP;
END
$$
delimiter ;