SQL - одна строка для каждого имени

Попытка использовать технику из https://stackoverflow.com/a/123481 я пытаюсь извлечь одну строки для каждого name, с определенным видом, а именно верхним / первый ряд после сортировки в порядке убывания active, created, то prid. Однако activeстолбец может содержать числовые NULLзначения или значения, которые вызывают дубликат в этом name=batслучае. Любая помощь будет принята с благодарностью.

Исходная таблица:

+------+-------+--------+---------+
| prid | name  | active | created |
+------+-------+--------+---------+
| 1    | bat   | NULL   | 3       |
| 2    | bat   | 1      | 2       |
| 3    | bat   | 2      | 1       |
| 4    | bat   | 3      | 0       |
| 5    | noise | NULL   | 2       |
| 6    | noise | NULL   | 1       |
| 7    | cup   | NULL   | 0       |
| 8    | cup   | NULL   | 0       |
| 9    | egg   | 4      | 4       |
| 10   | egg   | 4      | 2       |
+------+-------+--------+---------+

Желаемый результат:

+------+-------+--------+---------+
| prid | name  | active | created |
+------+-------+--------+---------+
| 9    | egg   | 4      | 4       |
| 4    | bat   | 3      | 0       |
| 5    | noise | NULL   | 2       |
| 8    | cup   | NULL   | 0       |
+------+-------+--------+---------+

SQL:

SELECT p1.*
FROM source_table p1
LEFT JOIN source_table p2 ON (
  p1.name = p2.name
  AND (
    p1.active < p2.active
    OR (
      (p1.active = p2.active OR (p1.active IS NULL AND p2.active IS NULL))
      AND (
        p1.created < p2.created
        OR (
          p1.created = p2.created AND p1.prid < p2.prid
        )
      )
    )
  )
)
WHERE p2.prid IS NULL
ORDER BY p1.active DESC, p1.created DESC, p1.prid DESC

Фактический результат:

+------+-------+--------+---------+
| prid | name  | active | created |
+------+-------+--------+---------+
| 9    | egg   | 4      | 4       |
| 4    | bat   | 3      | 0       |
| 1    | bat   | NULL   | 3       |
| 5    | noise | NULL   | 2       |
| 8    | cup   | NULL   | 0       |
+------+-------+--------+---------+

@Gordon Linoff

Спасибо за помощь, я пытаюсь использовать вторую версию с индексами (name, active, created, prid)и (active, created, prid), тем не менее, это довольно медленно.

Это занимает 1 секунду, возвращает правильные результаты, но в неправильном порядке:

SELECT t1.prid
FROM source_table t1
WHERE t1.prid = (
  SELECT t2.prid
  FROM source_table t2
  WHERE t2.name = t1.name
  ORDER BY t2.active DESC, t2.created DESC, t2.prid DESC
  LIMIT 1
)
LIMIT 50

И это занимает 55 секунд:

SELECT t1.prid
FROM source_table t1
WHERE t1.prid = (
  SELECT t2.prid
  FROM source_table t2
  WHERE t2.name = t1.name
  ORDER BY t2.active DESC, t2.created DESC, t2.prid DESC
  LIMIT 1
)
ORDER BY t1.active DESC, t1.created DESC, t1.prid DESC
LIMIT 50

И действительно мне нужны LIMIT 500какие-то идеи?


@Rick James

Ссылка SQL Fiddle: http://sqlfiddle.com/#!9/f9b39/2/0

mysql,sql,mariadb,aurora,

3

Ответов: 2


@Gordon Linoff

Спасибо за помощь, я пытаюсь использовать вторую версию с индексами (name, active, created, prid)и (active, created, prid), тем не менее, это довольно медленно.

Это занимает 1 секунду, возвращает правильные результаты, но в неправильном порядке:

SELECT t1.prid
FROM source_table t1
WHERE t1.prid = (
  SELECT t2.prid
  FROM source_table t2
  WHERE t2.name = t1.name
  ORDER BY t2.active DESC, t2.created DESC, t2.prid DESC
  LIMIT 1
)
LIMIT 50

И это занимает 55 секунд:

SELECT t1.prid
FROM source_table t1
WHERE t1.prid = (
  SELECT t2.prid
  FROM source_table t2
  WHERE t2.name = t1.name
  ORDER BY t2.active DESC, t2.created DESC, t2.prid DESC
  LIMIT 1
)
ORDER BY t1.active DESC, t1.created DESC, t1.prid DESC
LIMIT 50

И действительно мне нужны LIMIT 500какие-то идеи?


@Rick James

Ссылка SQL Fiddle: http://sqlfiddle.com/#!9/f9b39/2/0

23
0

Используйте стандартную row_number()функцию ANSI :

select t.*
from (select t.*,
             row_number() over (partition by name
                                order by active desc, created desc, pid desc
                               ) as seqnum
      from source_table t
     ) t
where seqnum = 1;

Вышеизложенное работает в MySQL 8+ и MariaDB 10.2+. В более ранних версиях вы можете:

select t.*
from source_table t
where t.prid = (select t2.prid
                from source_table t2
                where t2.name = t.name
                order by t2.active desc, t2.created desc, t2.pid desc
                limit 1
               );

Для производительности вам нужен индекс source_table(name, actdive desc, created desc, pid desc, prid).


0
ORDER BY IFNULL(active,  -1) DESC,
         IFNULL(created, -1) DESC,
         IFNULL(pid,     -1) DESC,

Полный Stmt

SELECT  prid, name, active, created
    FROM  
        ( SELECT  GROUP_CONCAT(prid
                    ORDER BY  IFNULL(active,  -1) DESC,
                              IFNULL(created, -1) DESC,
                              IFNULL(prid,    -1) DESC 
                              ) AS ids
            FROM  source_table
            GROUP BY  name 
        ) AS s1
    JOIN  source_table AS s2
        ON s2.prid = SUBSTRING_INDEX(s1.ids, ',', 1)
    ORDER BY  IFNULL(active,  -1) DESC,
              IFNULL(created, -1) DESC,
              IFNULL(prid,    -1) DESC ;
MySQL, SQL, MariaDB, полярные сияния,
Похожие вопросы