MySQL-如何修改父项/子项选择查询以将更多子项添加到现有数组/ JSON?

时间:2019-05-15 09:05:52

标签: php mysql sql json d3.js

我的以下查询正常运行:

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,到目前为止,此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"
        }]
    }]
}

我现在想将相同的子集:表strand.year中的'Year 1','Year 2','Year 3'和'Year 4'添加到每个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

但是,正如您在下面看到的那样,关系现在不完整;前五个节点失去了孩子,只有一串信息素养拥有Year儿童。

   {
    "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);

3 个答案:

答案 0 :(得分: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);

注意:

  • 结果将与您的链接中的结果不同。但是我不知道在没有数据的任何相关信息的情况下,钢绞线行(例如“ Professional Behavours”)如何成为其他钢绞线行的父级。
  • 我用显式CROSS JOIN替换了您的逗号联接,这使意图更加清晰。这里的假设是condition_theme_lookup表中只有一行。否则,您将需要一个JOIN条件,这对于给定的架构是不可能的。
  • 您在评论中写道:“最终的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_sortstr_sort)。例如,如果您希望主题按其PK排序-添加

theme_pk as num_sort, '' as str_sort

如果应按名称对链进行排序-添加

0 as num_sort, strand_name as str_sort

如果应该按自然值对年份进行排序(“ 10年级”>“ 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);

答案 1 :(得分:0)

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

x

答案 2 :(得分:0)

当您尝试向原始查询中添加额外的部分时-应该在“ 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;

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