【问题标题】:MySQL: how to query parent-child?MySQL:如何查询父子关系?
【发布时间】:2012-01-27 20:14:03
【问题描述】:

假设有下表记录:

TABLE: foo
==========================
| foo_id | foo_parent_id |
==========================
| 1      | NULL          |
| 2      | NULL          |
| 3      | 1             |
| 4      | 2             |
| 5      | 1             |
| 6      | 1             |
| 7      | 2             |
| 8      | 1             |
| 9      | NULL          |
--------------------------

例如,我想获取前 10 条父记录(那些 foo_parent_id = NULL 的记录),然后是该父记录的前 2 条子记录。所以,我正在寻找这样的结果:

1, NULL
3, 1
5, 1
2, NULL
4, 2
7, 2
9, NULL

如何查询这样的内容?

【问题讨论】:

标签: mysql database


【解决方案1】:

这是一个想法。但它基于许多关于数据设置方式的假设。在树中不断增加 ID,只有两个级别,等等。

SELECT f.foo_id,f.foo_parent_id FROM foo f
foo f

--给我前 X 个 parent_ids (这很好,你只需调整 LIMIT 10 来改变要显示的父级别的数量)

INNER JOIN 
(select foo_id from foo where foo_parent_id is null order by foo_parent_id 
LIMIT 10
) top_foo_parent
      on isnull(f.foo_parent_id,f.foo_id) = top_foo_parent.foo_id
WHERE

(这部分有点 hacky,因为你必须放更长的字符串才能通过两个孩子)

--这是第一个孩子,或者...

(f.foo_id in (select MIN(foo_id) from foo fc1 where fc1.foo_parent_id =f.foo_parent_id)
 )
 or

--是老二,还是……

(f.foo_id in (select MIN(foo_id) from foo fc1 where fc1.foo_parent_id =f.foo_parent_id  and fc1.foo_id not in (select MIN(foo_id) from foo fc2 where fc2.foo_parent_id=f.foo_parent_id))
 )
 or 

--它是父级

 f.foo_parent_id is null
order by isnull(f.foo_parent_id,f.foo_id)*100 + f.foo_id

所以我们在这里所做的基本上是按 parent_id 列排序,然后稍微扭曲它下面的子列。如果 parentid 列为 NULL,那么我们使用实际 ID。这意味着,出于排序目的,我们的表格如下所示:

==============================================================================
| foo_id | foo_parent_id |   isnull(f.foo_parent_id,f.foo_id)
==============================================================================
| 1      | NULL           |         (1)
| 2      | NULL           |         (2)
| 3      |  1             |         1
| 4      |  2             |         2
| 5      |  1             |         1
| 7      |  2             |         2
----------------------------------------------------------------------

然后我们将排序列乘以 *100

==============================================================================
| foo_id | foo_parent_id |   isnull(f.foo_parent_id,f.foo_id)*100
==============================================================================
| 1      | NULL           |         100
| 2      | NULL           |         200
| 3      |  1             |         100
| 4      |  2             |         200
| 5      |  1             |         100
| 7      |  2             |         200
----------------------------------------------------------------------

最后我们将 foo_id 列添加到其中

==============================================================================
| foo_id | foo_parent_id |   isnull(f.foo_parent_id,f.foo_id)*100 + foo_id
==============================================================================
| 1      | NULL           |         101
| 2      | NULL           |         202
| 3      |  1             |         103
| 4      |  2             |         204
| 5      |  1             |         105
| 7      |  2             |         207
----------------------------------------------------------------------

现在我们按该虚拟列对表格进行排序,然后...

==============================================================================
| foo_id | foo_parent_id |   ORDER BY isnull(f.foo_parent_id,f.foo_id)*100 + foo_id
==============================================================================
| 1      | NULL           |         101
| 3      |  1             |         103
| 5      |  1             |         105
| 2      | NULL           |         202    
| 4      |  2             |         204
| 7      |  2             |         207
----------------------------------------------------------------------

我们去吧!

【讨论】:

  • 这个级别将被限制为只有 2 个。我不明白这是如何让前 10 个父母跟随前 2 个孩子的。
  • 我在一个测试表上运行它并生成了你的结果。让我补充一些解释。
  • 好的。我不知何故希望在某处的查询中看到“10”和“2”。
  • 不确定您所说的 10 和 2 是什么意思,但如果您愿意,我很乐意进一步解释。这实际上是一个很酷的小问题。
  • @TetonSig 这是一个非常好的解释。
【解决方案2】:

我想提供另一种方法来回答这个问题:

SET @topN = 1;
SELECT foo_id, foo_parent_id
FROM (
    SELECT 
          f.foo_id
        , f.foo_parent_id
        , IFNULL(f.foo_parent_id, f.foo_id) AS grp_rank
        , CASE 
            WHEN f.foo_parent_id IS NULL 
                THEN @topN:= 1 
            ELSE 
                @topN:=@topN+1 
            END topN_rank
        , f_parent.foo_id AS f_parent_foo_id
    FROM 
        foo AS f
        RIGHT JOIN (
            SELECT foo_id 
            FROM foo 
            WHERE foo_parent_id IS NULL 
            ORDER BY foo_id LIMIT 10
        ) f_parent
        ON f_parent.foo_id = IFNULL(f.foo_parent_id, f.foo_id)
    ORDER BY grp_rank, f.foo_id
) AS foo_derived
WHERE topN_rank <= 3

一般说明:

I want to get, say, the first 10 parent records &lt;-- SUBQUERY "f_parent"

followed by, say, the first 2 child records of that parent record. &lt;-- topN &lt;= 3 (PARENT + 2 CHILDREN)

它是如何工作的:

  1. 为 foo_id 到 foo_parent_id 集成创建一个额外的列 grp_rank
  2. 加入仅拉取前 10 个父项的子查询(假设按其 foo_id 值排序)
  3. 记得按那个额外的列排序,后跟 foo_id 表示“父母..后跟..前两个孩子”
  4. 创建另一个列,而不是对将通过 foo_parent 遇到的重置排序的行进行排名。 NULL foo_parent_id 重置排名。这个新列topN_rank 受到排序的影响。
  5. 嵌套该查询以便能够派生 topN_rank 并仅返回所需的列。
  6. topN_rank列过滤,获取每组前N行,每组禁止超过3行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多