MySQL结果集按固定位置排序

时间:2016-03-09 07:29:41

标签: php mysql

我有以下简单的表格

Item (id, name, date, fixed_position)

(1, 'first entry', '2016-03-09 09:00:00', NULL)
(2, 'second entry', '2016-03-09 04:00:00', 1)
(3, 'third entry', '2016-03-09 05:00:00', NULL)
(4, 'fourth entry', '2016-03-09 19:00:00', NULL)
(5, 'fifth entry', '2016-03-09 13:00:00', 4)
(6, 'sixth entry', '2016-03-09 21:00:00', 2)

项目数量不固定,实际上可以在~100到~1000之间变化。

我想要实现的是执行查询以返回由date字段排序的项目集合,该字段考虑fixed_position字段,其代表类似“固定”结果到特定位置的内容。如果给定条目的fixed_position不为NULL,则结果应固定到第n个位置,如果fixed_position为NULL,则ORDER BY应优先。

查询的理想输出以获得更明确的解释:

(2, 'second entry', '2016-03-09 04:00:00', 1)    // pinned to 1-st position
(6, 'sixth entry', '2016-03-09 21:00:00', 2)     // pinned to 2-nd position
(3, 'third entry', '2016-03-09 05:00:00', NULL)  // ORDER BY `date`
(5, 'fifth entry', '2016-03-09 13:00:00', 4)     // pinned to 4-th position
(1, 'first entry', '2016-03-09 09:00:00', NULL)  // ORDER BY `date`
(4, 'fourth entry', '2016-03-09 19:00:00', NULL) // ORDER BY `date`

我已尝试在Ordering MySql results when having fixed position for some items中发布解决方案,但即使使用复制粘贴方法,这似乎根本不起作用。

我迄今为止尝试的是这个查询:

SELECT
  @i := @i +1 AS iterator,
  t.*,
  COALESCE(t.fixed_position, @i) AS positionCalculated
FROM
  Item AS t,
  (
SELECT
  @i := 0
) AS foo
GROUP BY
  `id`
ORDER BY
  positionCalculated,
  `date` DESC

返回:

iterator | id | name        | date                | fixed_position | positionCalculated 
1          1    first entry   2016-03-09 09:00:00   NULL             1
2          2    second entry  2016-03-09 04:00:00   1                1
6          6    sixth entry   2016-03-09 21:00:00   2                2
3          3    third entry   2016-03-09 05:00:00   NULL             3
4          4    fourth entry  2016-03-09 19:00:00   NULL             4
5          5    fifth entry   2016-03-09 13:00:00   4                4

MySQL是否可以执行此类任务,还是应该采用后端方法并在两个结果集上执行PHP array_merge()

2 个答案:

答案 0 :(得分:3)

解决这个问题的强力方法是首先创建一个比原始表大的行数的计数表:

SELECT @rn := @rn + 1 AS rn
FROM (
   SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t1
CROSS JOIN (   
   SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t2
CROSS JOIN (SELECT @rn := 0) AS v

然后,您可以将此表连接到包含原始表的所有固定位置的派生表:

SELECT Tally.rn
FROM (
   ... tally table query here 
) AS Tally
LEFT JOIN (
   SELECT fixed_position
   FROM Item
) AS t ON Tally.rn = t.fixed_position
WHERE t.t.fixed_position IS NULL

以上内容将返回待填补的缺失订单位置。

Demo here

您现在可以使用上面的查询作为另一个连接到原始表的派生表来实现所需的排序:

SELECT id, name, `date`, fixed_position, Gaps.rn, 
       derived.seq, Gaps.seq
FROM (
  SELECT id, name, `date`, fixed_position,
         @seq1 := IF(fixed_position IS NULL, @seq1 + 1, @seq1) AS seq
  FROM Item     
  CROSS JOIN (SELECT @seq1 := 0) AS v
  ORDER BY `date`
 ) AS derived
LEFT JOIN ( 
    SELECT Tally.rn,
           @seq2 := @seq2 + 1 AS seq
    FROM (
      SELECT @rn := @rn + 1 AS rn
      FROM (
        SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t1
      CROSS JOIN (   
        SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t2
      CROSS JOIN (SELECT @rn := 0) AS v
    ) AS Tally
    LEFT JOIN (
      SELECT fixed_position
      FROM Item
   ) AS t ON Tally.rn = t.fixed_position  
   CROSS JOIN (SELECT @seq2 := 0) AS v
   WHERE t.t.fixed_position IS NULL
   ORDER BY rn
 ) AS Gaps ON (derived.seq = Gaps.seq) AND (derived.fixed_position IS NULL)
 ORDER BY COALESCE(derived.fixed_position, Gaps.rn) 

Demo here

答案 1 :(得分:0)

如果有人偶然发现这一点,我在 dk1 的回答的帮助下想出了一个很好的解决方案。虽然他的回答原则上是好的,但它有一个缺陷,即固定行的每次迭代都将其位置增加一。例如:固定在第 1 个位置的帖子位于第 1 个位置 (+0),固定到第 2 个位置的帖子位于第 3 个位置 (+1),固定到第 4 个位置的帖子位于第 6 个位置 (+2 ).

我通过添加第二个变量来减少固定帖子的最终位置来解决这个问题。

代码说明:

  • minor = 带有未固定帖子的派生表
  • major= 带有固定帖子的派生表
  • @rownum = 次要表中帖子的迭代器
  • @decrease = 主要表中帖子的迭代器
SELECT
    final.*
FROM
    (
        (
            # see note #1 about this wrapper query
            SELECT * FROM
                (
                    SELECT
                        minor.*,
                        (@rownum := @rownum + 1) AS 'rowPosition',
                        0 AS 'rowDecrease',
                        0 AS 'majorEntry'
                    FROM table_with_your_posts minor
                    JOIN (@rownum := 0) x
                    WHERE minor.your_fixed_position IS NULL
                    ORDER BY minor.your_date DESC
                ) y
            ORDER BY y.your_date DESC
        )
        UNION ALL
        (
            SELECT
                major.*,
                major.your_fixed_position AS 'rowPosition',
                (@decrease := @decrease + 1) AS 'rowDecrease',
                1 AS 'majorEntry'
            FROM table_with_your_posts major
            JOIN (@decrease := -1) x
            WHERE major.your_fixed_position IS NOT NULL
            ORDER BY major.your_fixed_position ASC
        )
    ) final
# see note #2 about this ORDER BY clause
ORDER BY final.rowPosition - final.rowDecrease ASC, final.majorEntry DESC

注意#1:现在我不是专家,但是如果您不将次表的查询包装在另一个查询中并且不按照与内部查询中相同的内容对其进行排序,则排序次要表不起作用。也许有人可以对此有所了解。

注意 #2:ORDER BY final.rowPosition - final.rowDecrease - final.majorEntry / 2 ASC 也有效且看起来更酷

请注意,如果您想进一步过滤结果(这里的好例子是仅显示已发布的帖子),您很可能需要将过滤条件添加到两个派生表的 WHERE 子句中。否则最终位置将不正确。

最后我想补充一点,这不是万无一失的。例如,如果有两个位置相同的固定帖子,它会将结果移动一个。这可以通过使列具有固定位置 UNIQUE 来解决。我想到的另一件事是,如果表格的结果少于固定帖子的位置值,则该帖子显然不会位于其所需的位置(例如,如果表格包含 5 个帖子并且您将帖子固定到第 8 个帖子)位置,它可能最终会排在第 5 位)。请记住,我没有对此进行有意义的测试,请确保您的查询有效满足您的需求。