Here's a solution that offer:
negative numbers support
float numbers support
avoid typing long CTE queries*
* here the advantage is when you have to type queries frequently, CTE is nevertheless still a good option
All you have to do is select * from data order by json_weight(json_column,base_value);
In order to be able to do this, create those four functions json_max
, json_weight
, json_maxdigits
and json_pad
and use them in the order by clause:
delimiter //
create or replace function json_max(j json) returns float deterministic
begin
declare l int;
declare mv float;
declare v float;
set l = json_length(j);
for i in 0..l-1 do
set v = abs(json_value(j,concat('$[',i,']')));
if (mv is null) or (v > mv) then
set mv = v;
end if;
end for;
return mv;
end
//
create or replace function json_weight(j json, base int) returns float deterministic
begin
declare l int;
declare w float;
set w = 0;
set l = json_length(j);
for i in 0..l-1 do
set w = w + pow(base,-i) * json_value(j,concat('$[',i,']'));
end for;
return w;
end
//
create or replace function json_maxdigits(j json) returns int deterministic
return length(cast(floor(abs(json_max(j))) as char(16)))
//
create or replace function json_pad(j json, digitcount int) returns varchar(512) deterministic
begin
declare l int;
declare v int;
declare w varchar(512);
set w = '';
set l = json_length(j);
for i in 0..l-1 do
set v = json_value(j,concat('$[',i,']'));
set w = concat(w, if(v>=0,'0','-'), lpad(v, digitcount, 0));
end for;
return w;
end
//
delimiter ;
Then use them as follows:
select * from (
select json_array(2, 4) as `array`
union
select json_array(10) as `array`
union
select json_array(2, 3, 4) as `array`
union
select json_array(10, 11) as `array`
) data order by json_weight(`array`,max(json_max(`array`)) over ());
-- or if you know that 11 is the max value:
--) data order by json_weight(`array`,11);
-- alternative method:
--) data order by json_pad(`array`,max(json_maxdigits(`array`)) over ());
-- alternative method and you know that only two digits are enough to represent numbers in the array:
--) data order by json_pad(`array`,2);
Explanation:
json_max
gives you the max absolute value in an json_array:
select json_max('[22,33,-55]'); -- 55
json_maxdigits
gives you the max quantity of digits (of the absolute number) in an json_array:
select json_maxdigits('[21,151,-4]'); -- 3
json_weight
converts your json array to a float-equivalent value, where each number of the array is equivalent to a digit in a number in the base you specify as argument:
select json_weight('[1,3,5,7]', 10); -- 1.357
select json_weight('[1,0,1]', 2); -- 1.25 (like binary floats)
json_pad
converts your json array to a string of zero-padded numbers, with minus signal included as an extra symbol to guarantee negative ordering (or extra symbol 0
otherwise because +
is less than -
in ascii order):
select json_pad('[1,-3,15,7]', 2); --'001-03015007'
You can sort your query result set using float weights or padded strings. The two options are provided because:
- float weights lose precision when you have long json arrays, but have float support
- padded strings have great precision, here set to 512 digits, and you can even increase this number, however they don't offer float support (anyway you didn't ask for it).
If you use float weights you have to set the base. You set it manually or use the biggest number as base, which you get by using max(json_max(column_name)) over ()
. If you use base values which are less than this biggest value, you risk getting inconsistent results, if you use a number that is just too high you'll lose precision.
Similarly when using padded strings to sort, you have to provide the max number of digits to be consumed by the biggest absolute value (-35 would be 2 absolute digits).
Note: those functions work on early versions of MariaDB, that still didn't support json_table
function.