Change default Sorting by Adding a Simple Collation to an 8-Bit Character Set

偶尔善良 提交于 2019-12-10 09:44:37

问题


Posted previously the question with zero responses, so I am replacing it with further clarification and details from my research results based on this MySQL manual entry.

In the past, I've worked with alternate collations that allowed us to specify alternate default sorting. Mysql allows for this down to the column level, but I don't understand something to get it working.

Our customers use a standard set of one character codes in almost all references to any master table, and presenting these codes in the order they need is always very cumbersome and difficult using functions and routines in PHP.

SELECT * FROM myTable order by my_code

NORMAL, default sorting would return this:      DESIRED, default sorting should return this:
my_code | Description                           my_code | Description
 1      | Grade 1                               P  | Pre-Kindergarten
 2      | Grade 2                               K  | Kindergarten
 3      | Grade 3                               1  | Grade 1
 A      | Adult                                 2  | Grade 2
 K      | Kindergarten                          3  | Grade 3
 P      | Pre-Kindergarten                      A  | Adult

The steps to accomplish this are described in the Docs at 10.4.3. , and examples are shown in the Docs at 10.1.78.

In the steps, it shows this table, and how the weights are specified. This, I think is where I get lost. I've altered the weights as shown below, putting "P" (x50P) and "K" (x4B) before "0" (x30), but all it accomplishes is changing the sorting so that "1" (x31) appears between "P" and "K", all other sorting appears to remain unchanged.

<collation name="latin1_test_ci">
<map>
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
50 4B 30 31 32 33 34 35 36 37 38 39 41 43 55 3A
3B 3C 3D 3E 3F 40 42 44 45 46 47 48 49 4A 4C 4D
4E 4F 51 52 53 54 56 57 58 59 5A 5B 5C 5D 5E 5F
60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
41 41 41 41 5C 5B 5C 43 45 45 45 45 49 49 49 49
44 4E 4F 4F 4F 4F 5D D7 D8 55 55 55 59 59 DE DF
41 41 41 41 5C 5B 5C 43 45 45 45 45 49 49 49 49
44 4E 4F 4F 4F 4F 5D F7 D8 55 55 55 59 59 DE FF
</map>
</collation>

Sort results WITH the Alternate Collation above

Hex |my_code | Description
 32 |  2     |Grade 2
 33 |  3     |Grade 3
 41 |  A     |Adult Ed
 4B |  K     |Kindergarten
 31 |  1     |Grade 1
 50 |  P     |Pre-K

回答1:


I realize you said you wanted to change the collation, but this requires no ORDER BY and is worth considering. You can convert these to an ENUM type and they will sort in the order they appear in the ENUM.

CREATE TABLE myTable (
    my_code ENUM('P', 'K', '1', '2', '3', 'A'), 
    ...
)

Using numbers in ENUMs is strongly discouraged, so you'll have to be careful. The main issue is that numbers can be treated as an index or a value in the ENUM. So it's behavior depends on it's type, leading to unexpected results.




回答2:


This table are weights table. If you want that P i less than K, then put 00 weight to P and 01 weight to K. To put a weight you should assign a value in 'letter position': for P position 50. Sample to put P as first order letter:

<collation name="latin1_test_ci">
<map>
FF FF FF FF FF FF FF 07 08 09 0A 0B 0C 0D 0E 0F
10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
00 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F   <-- first weight
80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
41 41 41 41 5B 5D 5B 43 45 45 45 45 49 49 49 49
44 4E 4F 4F 4F 4F 5C D7 5C 55 55 55 59 59 DE DF
41 41 41 41 5B 5D 5B 43 45 45 45 45 49 49 49 49
44 4E 4F 4F 4F 4F 5C F7 5C 55 55 55 59 59 DE FF
</map>
</collation>

Edit: Adding test and compete table.

The complete table for should be:

<collation name="latin1_test_ci">
<map>
 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
 30 02 03 04 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
 40 05 42 43 44 45 46 47 48 49 4A 01 4C 4D 4E 4F
 00 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
 60 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
 50 51 52 53 54 55 56 57 58 59 5A 7B 7C 7D 7E 7F
 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
 A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
 B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
 41 41 41 41 5B 5D 5B 43 45 45 45 45 49 49 49 49
 44 4E 4F 4F 4F 4F 5C D7 5C 55 55 55 59 59 DE DF
 41 41 41 41 5B 5D 5B 43 45 45 45 45 49 49 49 49
 44 4E 4F 4F 4F 4F 5C F7 5C 55 55 55 59 59 DE FF
</map>
</collation>

* Testing: *

mysql> create table  b (a varchar(1) collate latin1_test_ci );

mysql> insert into b values
    -> ( 'P' ),
    -> ('K'),
    -> ('A'),
    -> ('1'),
    -> ('2'),
    -> ('3');


mysql> SHOW COLLATION LIKE 'latin1_test_ci';
+----------------+---------+----+---------+----------+---------+
| Collation      | Charset | Id | Default | Compiled | Sortlen |
+----------------+---------+----+---------+----------+---------+
| latin1_test_ci | latin1  | 56 |         |          |       0 |
+----------------+---------+----+---------+----------+---------+
1 row in set (0.00 sec)

mysql> select * from b order by a;
+------+
| a    |
+------+
| P    |
| K    |
| 1    |
| 2    |
| 3    |
| A    |
+------+
6 rows in set (0.00 sec)



回答3:


I don't think you need a custom collation to accomplish your goals.

To order the result set:

ORDER BY FIELD(my_code, 'P','K','1','2','3','4','5','6',
    '7','8','9','10','11','12','A')

To limit the result set:

 WHERE my_code IN('K','1','2','3','4','5')

If you'll be writing this sort of functionality into a lot of queries, a helper function might be a good idea:

DELIMITER $$
CREATE FUNCTION `f_position`(in_char CHAR(1)) RETURNS INTEGER
BEGIN
    RETURN FIELD(in_char, 'P','K','1','2','3','4','5','6',
        '7','8','9','10','11','12','A');
END$$
DELIMITER ;

Just make sure that all possible codes are referenced in the function, and are placed in the order that you want.

With a helper function like that, you can write queries like so:

WHERE f_position(grade) BETWEEN f_position('K') AND f_position('5')
ORDER BY f_position(grade)

The only downside to using a helper function to limit result sets like that (as opposed to the WHERE grade IN(...)) is that the function call would prevent any indexes on the column "grade" from being used.



来源:https://stackoverflow.com/questions/11036621/change-default-sorting-by-adding-a-simple-collation-to-an-8-bit-character-set

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!