mysqlsortingwindow-functions

How to build sequence in MySQL using ROW_NUMBER while some values of sequence are present?


Hello stack overflow community. I've got a following problem.

Given the following, I have an entries table

CREATE TABLE `entries` (
  `name` varchar(50) DEFAULT NULL,
  `sort_order` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Which contains the following values:

INSERT INTO `entries` (`name`, `sort_order`) VALUES
('a', NULL),
('c', 2),
('b', 3),
('d', NULL),
('e', NULL),
('f', NULL),
('g', NULL),
('h', NULL),
('i', 5),
('ii', 4);

What is the best possible way to query and sort values by name asc keeping values with defined sort_order in its positions?

I tried different workarounds with ROW_NUMBER() and CTE but nothing worked.

Here's the query, however it returns incorrect results.

WITH ranked_entries AS (
    SELECT 
        *,
        ROW_NUMBER() OVER (ORDER BY `name` ASC) AS `rn`
    FROM entries
),
merged_entries AS (
    SELECT 
        *,
        COALESCE (`sort_order`, `rn`) AS merged_rank
    FROM ranked_entries
)
SELECT * FROM merged_entries ORDER BY merged_rank ASC;

The expected result should be the following:

name sort_order
a 1
c 2
b 3
ii 4
I 5
d 6
e 7
f 8
g 9
h 10

Solution

  • Why not possible ?

    So, the logic behind this answer is,

    1. First need to short the names where sort_order is not null - Table T1
    2. Next need to short the names where sort_order is null - Table T2

    Now create an empty table, T0 with length of entries with columns name as empty data and rn as ROW_NUMBER() OVER (ORDER BY NULL).

    And tables are showing as follows:

    enter image description here

    Now, join Tables T0 & T1 on columns rn, short_order respectively, to get the Table T3 and also create a row_number (name_null_rn) where name is null

    Then, join Tables T3 & T2 on columns name_null_rn & rn respectively and merge T3 & T2 names in that joining.

    Your Final Query is:

    with T0 AS (
        select '' as name,
            ROW_NUMBER() OVER (ORDER BY NULL) AS `rn`
        from entries
    ),
    T1 as (
        select *
        from entries
        where sort_order is not null
        order by sort_order asc
    ) -- select * from T1
    , T2 as (
        select name,
            ROW_NUMBER() OVER (ORDER BY `name` ASC) AS `rn`
        from entries
        where sort_order is null
        order by name asc
    ) -- select * from T2;
    , T3 as (
        SELECT 
            T1.name as name,
            T0.rn,
            case when (T1.name is null)
                then row_number() over (partition by T1.name) 
            end as name_null_rn
        from T0 LEFT JOIN T1
        ON T0.rn = T1.sort_order
    ) -- select * from T3;
    select 
        COALESCE(T3.name, T2.name) as name,
        T3.rn as sort_order
    from T3 left join T2
    on T3.name_null_rn = T2.rn
    order by T3.rn;
    

    Final Output already show in attached image.

    Check Sample query line by line: db<>fiddle

    Happy Coding !!!!!!


    @user3486769 After understanding my logic you can modify SQL query. I could make it complicated. Since this is a complex question, I made it as simple as possible.
    Modify the query according to your requirements.