【问题标题】:MySQL - How to modify parent/child select query to add more children to existing array/JSON?MySQL - 如何修改父/子选择查询以向现有数组/JSON 添加更多子项?
【发布时间】:2019-10-02 08:51:30
【问题描述】:

我的以下查询工作正常:

SELECT core_condition AS name, NULL AS parent
FROM condition_theme_lookup
UNION ALL
SELECT theme_name AS name, condition_theme_lookup.core_condition AS parent
FROM theme, condition_theme_lookup
UNION ALL
SELECT strand.strand_name AS name, theme.theme_name AS parent
FROM strand
JOIN theme ON theme.theme_pk = strand.theme_fk

使用一些 PHP 的结果数组生成以下 JSON,到目前为止还不错,显示了“主题”父级的“链”子级:

{
    "name": "Condition",
    "children": [{
        "name": "Professional",
        "children": [{
            "name": "Professional Behavours"
        }, {
            "name": "Self-Care and Self-Awareness"
        }, {
            "name": "Medical Ethics and Law"
        }]
    }, {
        "name": "Leader",
        "children": [{
            "name": "Teamwork and Leadership"
        }, {
            "name": "Collaborative Practice"
        }, {
            "name": "Health Systems and Careers"
        }]
    }, {
        "name": "Advocate",
        "children": [{
            "name": "Health Advocacy"
        }, {
            "name": "Aboriginal Health"
        }, {
            "name": "Diversity and Inequality"
        }, {
            "name": "Health Promotion"
        }]
    }, {
        "name": "Clinician",
        "children": [{
            "name": "Scientific Knowledge"
        }, {
            "name": "Patient Assessment and Clinical Reasoning"
        }, {
            "name": "Patient Management"
        }, {
            "name": "Patient Perspective"
        }, {
            "name": "Clinical Communication"
        }, {
            "name": "Quality Care"
        }]
    }, {
        "name": "Educator",
        "children": [{
            "name": "Life-Long Learning"
        }, {
            "name": "Mentoring Relationships"
        }, {
            "name": "Patient Education"
        }, {
            "name": "Teaching and Learning"
        }, {
            "name": "Assessment and Evaluation"
        }]
    }, {
        "name": "Scholar",
        "children": [{
            "name": "Research and Biostatistics"
        }, {
            "name": "Evidence-Based Practice"
        }, {
            "name": "Information Literacy"
        }]
    }]
}

我现在想将相同的子集:'Year 1'、'Year 2'、'Year 3' 和 'Year 4',从表 strand.year 添加到每个 strand.strand_name 父级(例如专业行为,医学伦理和法律等)。

我尝试了以下修改后的查询:

SELECT core_condition AS name, NULL AS parent
FROM condition_theme_lookup
UNION ALL
SELECT theme_name AS name, condition_theme_lookup.core_condition AS parent
FROM theme, condition_theme_lookup
UNION ALL
SELECT strand.strand_name AS name, theme.theme_name AS parent
FROM strand, theme
UNION ALL
SELECT strand.year AS name, strand.strand_name AS parent
FROM strand
JOIN theme ON theme.theme_pk = strand.theme_fk

但正如您在下面看到的,现在的关系是不完整的;前五个节点失去了他们的孩子,只有一个,信息素养,有年孩子。

   {
    "name": null,
    "children": [{
        "name": "Professional"
    }, {
        "name": "Leader"
    }, {
        "name": "Advocate"
    }, {
        "name": "Clinician"
    }, {
        "name": "Educator"
    }, {
        "name": "Scholar",
        "children": [{
            "name": "Professional Behavours"
        }, {
            "name": "Self-Care and Self-Awareness"
        }, {
            "name": "Teamwork and Leadership"
        }, {
            "name": "Collaborative Practice"
        }, {
            "name": "Health Systems and Careers"
        }, {
            "name": "Health Advocacy"
        }, {
            "name": "Aboriginal Health"
        }, {
            "name": "Diversity and Inequality"
        }, {
            "name": "Health Promotion"
        }, {
            "name": "Scientific Knowledge"
        }, {
            "name": "Patient Assessment and Clinical Reasoning"
        }, {
            "name": "Patient Management"
        }, {
            "name": "Patient Perspective"
        }, {
            "name": "Clinical Communication"
        }, {
            "name": "Quality Care"
        }, {
            "name": "Life-Long Learning"
        }, {
            "name": "Mentoring Relationships"
        }, {
            "name": "Patient Education"
        }, {
            "name": "Teaching and Learning"
        }, {
            "name": "Assessment and Evaluation"
        }, {
            "name": "Research and Biostatistics"
        }, {
            "name": "Evidence-Based Practice"
        }, {
            "name": "Information Literacy",
            "children": [{
                "name": "Year 1"
            }, {
                "name": "Year 2"
            }, {
                "name": "Year 3"
            }, {
                "name": "Year 4"
            }]
        }, {
            "name": "Medical Ethics and Law"
        }]
    }]
}

应如何更改查询以显示第一个 JSON 中的所有关系,并将四个“Year X”子代添加到每个链中?

Required JSON result up to Year children (ignore children of Year x

见fiddle for original query

SQL:

theme.sql

strand.sql

原始 JSON 版本的工作 PHP/MySQL 是:

$condition = $_POST['condition'];

$query = "SELECT core_condition AS name, NULL AS parent
FROM condition_theme_lookup
UNION ALL
SELECT theme_name AS name, condition_theme_lookup.core_condition AS parent
FROM theme, condition_theme_lookup
UNION ALL
SELECT strand.strand_name AS name, theme.theme_name AS parent
FROM strand
JOIN theme ON theme.theme_pk = strand.theme_fk";
$result = $connection->query($query);
$data = array();
while ($row = $result->fetch_object()) {
     $data[$row->name] = $row;
 }

foreach ($data as $row) {   
    if ($row->name == 'Condition') {
        $row->name = $condition;
    }
    if ($row->parent === null) {
        $roots[]= $row;
    } else {
        $data[$row->parent]->children[] = $row;
    }
    unset($row->parent);
}

$json = json_encode($roots);

【问题讨论】:

  • 你不应该在year表和其他表之间有关系吗?那里没有外键,您打算如何为每个链项目获取正确的年份信息?
  • 在下面查看我的答案。如果你需要一个关系,然后设置它。如果没有,那么只需使用下面的解决方案。
  • 我意识到年份信息只能放在链表下。如果你正在做一个 UNION,你需要相同数量的列,所以每个表都会有年份字段,这不是你想要的。
  • 如果你知道它不会改变,为什么不以编程方式而不是在查询中处理它?
  • 在最终的 JSON 中会有更多的子级别,所以我希望所有内容都来自数据库查询。我已经修改了 OP 以反映链表有多年。

标签: php mysql sql json d3.js


【解决方案1】:

正如我在other answer 中所写:“名称在所有表格中都应该是唯一的”。这是基于来自您的previous question 的样本数据的假设。但strand 表并非如此。如果一个名字在 SQL 结果集中出现多次,这里会覆盖之前的同名行:

$data[$row->name] = $row;

因为$row->name 具有相同的值。因此,您需要一列作为唯一标识符,并将该列用作$data 数组的索引。您不能使用name,因为它在strand 表中不是唯一的。而且您不能使用主键,因为它们在所有表中都不是唯一的。但是您可以使用表名(或唯一的表别名)和主键的组合,例如

CONCAT('condition:', condition_theme_lookup_pk) AS global_id
...
CONCAT('theme:', theme_pk) AS global_id
....
CONCAT('strand:', strand_pk) AS global_id

parent 列应该具有相同的模式

CONCAT('theme:', theme_fk) AS parent_global_id

下一个问题是 - 如何按主题按年份分组?嵌套逻辑不遵循模式parentTable <- childTable <- grandChildTable。那将是condition <- theme <- year <- strand。相反,两个级别(年份和链名称)在一个表中。您需要使用 DISTINCT 查询从 strand 表中“提取”年份,就好像它们存储在单独的表中一样。唯一标识符应该是主题PK和年份的组合。各个链应引用父列中的那些标识符。最终的查询就像

SELECT CONCAT('condition:', condition_theme_lookup_pk) AS global_id,
       core_condition AS name,
       NULL AS parent_global_id
FROM condition_theme_lookup
UNION ALL
SELECT CONCAT('theme:', theme_pk) AS global_id,
       theme_name AS name,
       CONCAT('condition:', condition_theme_lookup_pk) AS parent_global_id
FROM theme CROSS JOIN condition_theme_lookup
UNION ALL
SELECT DISTINCT
       CONCAT('theme:', theme_fk, ',year:', strand.year) AS global_id,
       strand.year AS name,
       CONCAT('theme:', theme_fk) AS parent_global_id
FROM strand
UNION ALL
SELECT CONCAT('strand:', strand_pk) AS global_id,
       strand.strand_name AS name,
       CONCAT('theme:', theme_fk, ',year:', strand.year) AS parent_global_id
FROM strand

db-fiddle

结果看起来像

global_id           | name                         | parent_global_id
--------------------|------------------------------|---------------------
condition:1         | Condition                    | null
theme:1             | Professional                 | condition:1
theme:2             | Leader                       | condition:1
...
theme:1,year:Year 1 | Year 1                       | theme:1
theme:2,year:Year 1 | Year 1                       | theme:2
...
theme:1,year:Year 2 | Year 2                       | theme:1
theme:2,year:Year 2 | Year 2                       | theme:2
...
strand:1            | Professional Behavours       | theme:1,year:Year 1
strand:2            | Self-Care and Self-Awareness | theme:1,year:Year 1
strand:3            | Teamwork and Leadership      | theme:2,year:Year 1
strand:4            | Collaborative Practice       | theme:2,year:Year 1
...
strand:27           | Teamwork and Leadership      | theme:2,year:Year 2

你看 - “团队合作和领导力”出现了两次。但是这两行有不同的global_id 和不同的parent_global_id。您还可以看到parent_global_id 如何明确引用父行的global_id。

结果基本上是一个由不同表中的数据组成的邻接表。这些模式很容易转换为 PHP 中的嵌套结构。 PHP 代码只需稍作改动即可适应新列:

$result = $connection->query($query);
$data = array();
while ($row = $result->fetch_object()) {
    $data[$row->global_id] = $row;
}

$roots = [];
foreach ($data as $row) {   
    if ($row->name == 'Condition') {
        $row->name = $condition;
    }
    if ($row->parent_global_id === null) {
        $roots[]= $row;
    } else {
        $data[$row->parent_global_id]->children[] = $row;
    }
    unset($row->parent_global_id);
    unset($row->global_id);
}

$json = json_encode($roots);

注意事项:

  • 结果与链接中的结果不同。但我不知道一个链行(如“专业行为”)如何成为其他链行的父级,而数据中没有任何相关信息。
  • 我用明确的CROSS JOIN 替换了您的逗号连接,这样可以更清楚地说明意图。这里的假设是condition_theme_lookup 表中只有一行。否则,您将需要一个 JOIN 条件,而这对于给定的架构是不可能的。
  • 您在 cmets 中写道:“最终 JSON 中将有多个子级别”。所有级别都必须遵循相同的嵌套逻辑,或者至少是可转换的(就像年份一样)。如果您有更多惊喜,则该解决方案可能不合适。在某些时候,我会考虑对每个级别执行一个查询并构建“自下而上”的层次结构(从叶子到根)。

MySQL 8 - CTE + JSON 支持

结合使用JSON_OBJECT() 函数、JSON_ARRAYAGG() 聚合函数和公用表表达式 (CTE),我们现在能够通过单个查询获得具有多个嵌套级别的嵌套 JSON 结果:

with years as (
  select 
    theme_fk,
    year,
    json_arrayagg(json_object('name', strand_name)) as children
  from strand
  group by theme_fk, year
), themes as (
  select
    t.theme_pk,
    t.theme_name as name,
    json_arrayagg(json_object('name', year, 'children', children)) as children
  from theme t
  left join years y on y.theme_fk = t.theme_pk
  group by t.theme_pk
)
select json_object(
    'name', c.core_condition,
    'children', json_arrayagg(json_object('name', t.name, 'children', t.children))
  ) as json
from condition_theme_lookup c
cross join themes t
group by c.condition_theme_lookup_pk

db-fiddle

formatted result

每个嵌套级别都包含在自己的 CTE 中,这提高了可读性。每个级别都可以有自己的嵌套逻辑。由于结果是逐步构建的,因此添加更多级别应该没什么大不了的。

更新

要在 UNION 查询中交换链和年份的级别,只需在最后两个子查询中进行少量更改:

...
SELECT DISTINCT
       CONCAT('theme:', theme_fk, ',strand:', strand_name) AS global_id,
       strand_name AS name,
       CONCAT('theme:', theme_fk) AS parent_global_id
FROM strand
UNION ALL
SELECT CONCAT('strand_year:', strand_pk) AS global_id,
       strand.year AS name,
       CONCAT('theme:', theme_fk, ',strand:', strand_name) AS parent_global_id
FROM strand

db-fiddle

如果您需要以特定方式对节点的子节点进行排序,但级别不同,我建议在每个子查询中添加两列(num_sort 和 str_sort)。例如,如果您希望主题按其 PK 排序 - 添加

theme_pk as num_sort, '' as str_sort

如果链应该按名称排序 - 添加

0 as num_sort, strand_name as str_sort

如果年份应该按值排序但以自然的方式(“Year 10”>“Year 2”)

cast(replace(year, 'Year ', '') as signed) as num_sort, '' as str_sort

然后将ORDER BY num_sort, str_sort 附加到查询中。

db-fiddle

然后您需要从 PHP 对象中删除这些列(属性)

unset($row->parent_global_id);
unset($row->global_id);
unset($row->num_sort);
unset($row->str_sort);

【讨论】:

  • 再次感谢保罗!这些看起来像是可行的解决方案,尤其是我在 UWA 的服务器上还没有的 MySQL 8。查看需要添加的其他级别的长期解决方案,然后 MySQL 8 看起来像是要走的路。不过,在星期三 AWST 回到办公室之前,我无法测试您的第一个解决方案。但两者看起来都很划算。谢谢,彼得
  • 哦,MySQL 8 的解决方案有错误的顺序或关系。它应该是 Condition as top,然后是主题 -> 链 -> 年份,而不是主题 -> 年份 - 链...
  • 在这个 D3.js 示例中,我手动构建了 JSON healthed.hms.uwa.edu.au/md/pages/map.html
  • 很遗憾JSON_ARRAYAGG() 不支持ORDER BY 子句。解决方法是使用GROUP_CONCAT 和一些转换:db-fiddle.com/f/b4TsQx93X92AyN1R9MwTiN/0 - 我没有添加这个答案,因为它已经太长了。
  • 看起来不正确。下一个级别parent_global_id 应该与最后一个级别global_id 匹配,即CONCAT('strand_year:', strand_pk)。而FROM unit, strand 是一个交叉连接。我怀疑你想要那个。
【解决方案2】:

如果您确切知道要使用哪些值(年份),您可以在查询中创建(伪造)它们:

SELECT *, 'Year 1' as year1, 'Year 2' as year2 from strands... and so on

【讨论】:

    【解决方案3】:

    当您尝试向原始查询添加额外部分时 - 应该在“JOIN”部分之后完成,而不是在它之前。 “JOIN”属于先前的查询。这个版本应该可以工作:

    SELECT core_condition AS name, NULL AS parent
    FROM condition_theme_lookup
    UNION ALL
    SELECT theme_name AS name, condition_theme_lookup.core_condition AS parent
    FROM theme, condition_theme_lookup
    UNION ALL
    SELECT strand.strand_name AS name, theme.theme_name AS parent
    FROM strand
    JOIN theme ON theme.theme_pk = strand.theme_fk
    -- beginning of added query --
    UNION ALL
    SELECT strand.year AS name, strand.strand_name AS parent
    FROM strand WHERE strand.year is not NULL;
    

    我还添加了条件“WHERE strand.year is not NULL” - 如果您确定所有记录都设置了年份,请跳过此部分。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-26
      • 1970-01-01
      • 2021-02-03
      相关资源
      最近更新 更多