如何将关系层次结构转换为MySQL中的闭包表?

时间:2013-08-30 18:23:23

标签: mysql select foreign-keys relational-database hierarchy

我最近开始使用closure table,当我不得不将简单的父子关系从人转换为血统时,它工作得很好,但现在我必须转换祖父母 - 亲子和祖父母 - 父子关系,甚至可能比关闭表中的关系更长。这是我需要从中获取关系的表:

CREATE TABLE `person` (
    `person` BIGINT UNSIGNED NOT NULL ,
    `parent` BIGINT UNSIGNED NULL ,
    PRIMARY KEY (`person`) ,
    INDEX `idx_person_has_parent` (`parent` ASC),
    CONSTRAINT `fk_person_has_parent`
        FOREIGN KEY (`parent`)
        REFERENCES `person` (`person`)
        ON DELETE NO ACTION
        ON UPDATE NO ACTION
);

这是我需要转换为的表格:

CREATE TABLE `lineage` (
    `ancestor` BIGINT UNSIGNED NOT NULL,
    `descendant` BIGINT UNSIGNED NOT NULL,
    `length` INT UNSIGNED NOT NULL,
    PRIMARY KEY (`ancestor`, descendant`),
    CONSTRAINT `fk_ancestor_has_descendant`
        FOREIGN KEY (`descendant`)
        REFERENCES `person` (`person`)
        ON DELETE NO ACTION
        ON UPDATE NO ACTION,
    CONSTRAINT `fk_descendant_has_ancestor`
        FOREIGN KEY (`ancestor`)
        REFERENCES `person` (`person`)
        ON DELETE NO ACTION
        ON UPDATE NO ACTION
);

我遇到的最大问题是为祖父母子女的条目保存正确的长度,该长度应为长度2.祖父母 - 父母和父母子女都是1,并且每个孩子的长度都是0自己(孩子,父母,父母等)。我可以在查询中执行此操作,还是需要某种程序?

1 个答案:

答案 0 :(得分:0)

这是一个古老的脚本,我在6到7年前完成了此工作,以在PHP / Propel中完成此任务。希望它对其他人有用:

require_once 'common/Autoloader.php';
require_once 'propel/Propel.php';
\Propel::init('db/runtime-conf.php');

function truncateHierarchy(\PropelPDO $propel) {
    /* @var $state \PropelStatement */
    $state = $propel->prepare('TRUNCATE database.person_hierarchy');
    $state->execute();
}

function insertHierarchy(\PropelPDO $propel, $length) {
    if ($length == 0) {
        $state = $propel->prepare('INSERT INTO database.person_hierarchy SELECT id, id, 0 FROM person;');
        $state->execute();
        return $state->rowCount();
    } else if ($length == 1) {
        $state = $propel->prepare('INSERT INTO database.person_hierarchy SELECT parent_person_id, id, 1 FROM person WHERE id != parent_person_id;');
        $state->execute();
        return $state->rowCount();
    } else {
        $sql = "INSERT INTO database.person_hierarchy \n";
        $sql .= "SELECT p.parent_person_id, c" . ($length - 1) . ".id, " . $length . " FROM database.person AS p \n";
        for ($i = 1; $i <= $length - 1; $i++) {
            $sql .= "LEFT JOIN person AS c" . $i . " ON " . ($i == 1 ? 'p.id' : 'c' . ($i - 1) . '.id') . " = c" . $i . ".parent_person_id \n";
        }
        $sql .= "WHERE p.parent_person_id != p.id \n";
        for ($i = 1; $i <= $length - 1; $i++) {
            $sql .= "AND c" . $i . ".parent_person_id != c" . $i . ".id \n";
        }
        echo $sql;
        $state = $propel->prepare($sql);
        $state->execute();
        return $state->rowCount();
    }
}

/* @var $connect \PropelConnection */
$propel = \Propel::getConnection();
$propel->beginTransaction();

try {
    truncateHierarchy($propel);
    $propel->commit();
} catch (\Exception $e) {
    error_log_exc($e);
    echo "Failed to truncate person hierarchy!\n";
    $propel->rollBack();
    exit();
}

$length = 0;
$inserts = -1;
while ($inserts !== 0 || $length != 10) {
    $propel->beginTransaction();
    try {
        $inserts = insertHierarchy($propel, $length);
        if ($inserts == 0) { 
            echo "No persons exist at length " . $length . ".\n";
        } else {
            echo $inserts . " rows inserted for length " . $length . ".\n";
        }
        $length++;
        $propel->commit();
    } catch (\Exception $e) {
        error_log_exc($e);
        echo "Failed to regenerate person hierarchy!\n";
        $propel->rollBack();
        exit();
    }
}

echo "Regenerated person hierarchy!\n";
exit();