Как создать иерархический результат (родословная) из SQL-запроса без CTE? - PullRequest
1 голос
/ 24 марта 2019

У меня есть база данных собак.У каждой собаки есть родитель и родитель.Я не могу использовать CTE для этого из-за проблемы с phpmyadmin и MariaDB 10.0.

Я обновил MariaDB 10.2.20 для использования CTE.По-прежнему отображается «Нераспознанный тип оператора» (рядом с WITH) в phpMyAdmin

Таблица: animal

Столбцы, с которыми я работаю: id, akc_reg_num, akc_parent_sire, akc_parent_dam

Я пытался получить только одно поколение для загрузки подкатегорий, но это только одна сторона, когда есть два родителя.

Я пытался это решение,но я не могу обернуть голову вокруг двух родителей каждого родного брата. Как выбрать только одно поколение иерархического дерева, используя отношение родительский дочерний элемент таблицы с SQL?

Пример таблицы

CREATE TABLE  `animal` ( 
    `id` INT(11) NOT NULL AUTO_INCREMENT ,
     `akc_reg_num` VARCHAR(20) NOT NULL ,
     `akc_parent_sire` VARCHAR(20) NOT NULL ,
     `akc_parent_dam` VARCHAR(20) NOT NULL ,
     PRIMARY KEY (`id`)
) ENGINE = MyISAM;

INSERT INTO `animal` (`id`, `akc_reg_num`, `akc_parent_sire`, `akc_parent_dam`) VALUES
(NULL, '1', '2', '3'), 
(NULL, '2', '5', '6'), 
(NULL, '3', '9', ''), 
(NULL, '5', '', ''), 
(NULL, '6', '7', '8'), 
(NULL, '7', '', ''), 
(NULL, '8', '', ''), 
(NULL, '9', '10', '11'), 
(NULL, '10', '', ''), 
(NULL, '11', '12', ''), 
(NULL, '12', '', '');

Код:

include_once("db_conx.php");   

function getPedigree($node) { 
    // look up the parent of this node  
    $sql =  'SELECT akc_parent_sire, akc_parent_dam FROM animals WHERE akc_reg_num="'.$node.'";';
    $query = $db->prepare($sql);          
    $query->execute();
    $path = array();    
    while($row=$query->fetch(PDO::FETCH_ASSOC)){    
        if ($row['akc_parent_sire']!='') { 
            $path[] = $row['akc_parent_sire']; 
            echo $row['akc_parent_sire'];
            $path = array_merge(getPedigree($row['akc_parent_sire']), $path); 
        } 
        if ($row['akc_parent_dam']!='') { 
            $path[] = $row['akc_parent_dam']; 
            echo $row['akc_parent_dam'];
            $path = array_merge(getPedigree($row['akc_parent_dam']), $path); 
        } 
    }       
    return $path; 
} 
print_r(getPedigree('vvv'));

Мне нужно пройтись по каждому поколению, чтобы я мог вернуть массив json, а затем использовать javascript для привязки результатов к элементам DOM.запрос на 4 поколения, однако меня беспокоит стоимость циклов процессора. Как только я получу несколько сотен тысяч животных в базе данных, насколько эффективным будет этот запрос?

1 Ответ

1 голос
/ 25 марта 2019

Чтобы предотвратить злоупотребление вашей базой данных итеративными вызовами, ВЫБЕРИТЕ всю таблицу всего один раз и позвольте php выполнить всю рекурсивную работу с набором результатов.

AMENDMENT : С момента сбора ~100 000 строк - это слишком тяжело, вот альтернативный совет ... Вместо того, чтобы совершать до 31 отдельного обращения к базе данных в рамках рекурсивного процесса, я рекомендую вам создать отфильтрованный массив на основе до 5 обращений к базе данных..

Следующий фрагмент НЕ протестирован:

$generation = 1;
$needles = [1];
$animals = [];
while ($needles && $generation < 6) {
    $sth = $db->prepare("SELECT * FROM animals WHERE akc_reg_num IN (" . implode(',', array_fill(0, count($needles), '?')) . ")");
    $sth->execute($needles);
    if ($results = $sth->fetchAll(\PDO::FETCH_ASSOC)) {
        $needles = array_filter(array_merge(array_column($results, 'akc_parent_sire'), array_column($results, 'akc_parent_dam')));
        $animals[] = array_merge($animal, $results);
    } else {
        $needles = null;
    }
    ++$generation;
}
// $animals is ready to pass to the php recursion

Из набора результатов $animals, подобного следующему:

$animals = [
    ['id' => 1, 'akc_reg_num' => 1, 'akc_parent_sire' => 2, 'akc_parent_dam' => 3],
    ['id' => 2, 'akc_reg_num' => 2, 'akc_parent_sire' => 5, 'akc_parent_dam' => 6],
    ['id' => 3, 'akc_reg_num' => 3, 'akc_parent_sire' => 9, 'akc_parent_dam' => 0],
    ['id' => 4, 'akc_reg_num' => 5, 'akc_parent_sire' => 0, 'akc_parent_dam' => 0],
    ['id' => 5, 'akc_reg_num' => 6, 'akc_parent_sire' => 7, 'akc_parent_dam' => 8],
    ['id' => 6, 'akc_reg_num' => 7, 'akc_parent_sire' => 0, 'akc_parent_dam' => 0],
    ['id' => 7, 'akc_reg_num' => 8, 'akc_parent_sire' => 0, 'akc_parent_dam' => 0],
    ['id' => 8, 'akc_reg_num' => 9, 'akc_parent_sire' => 10, 'akc_parent_dam' => 11],
    ['id' => 9, 'akc_reg_num' => 10, 'akc_parent_sire' => 0, 'akc_parent_dam' => 0],
    ['id' => 10, 'akc_reg_num' => 11, 'akc_parent_sire' => 12, 'akc_parent_dam' => 0],
    ['id' => 11, 'akc_reg_num' => 12, 'akc_parent_sire' => 0, 'akc_parent_dam' => 0]
];

Подробные задачи:

  • Найдите в вашем массиве строку целевого akc_reg_num, затем удалите эту строку из "стога сена", чтобы предотвратить возможность бесконечной рекурсии, затем прервите цикл поиска для достижения максимальной эффективности
  • Если естьнет соответствующего akc_reg_num в стоге сена, вернуть пустой массив
  • Если есть соответствующий akc_reg_num, зациклить стог сена и восстановить все найденные родители.Я отфильтровываю «deadends», чтобы массив результатов был маленьким и чистым.
  • Если оба родителя найдены в данном поколении, прервите цикл, чтобы предотвратить ненужные итерации.
  • Рекурсия должна продолжатьсяпока число поколений не превысит 4, или не останется больше родителей для сбора.

Код: ( Демо )

function buildPedigree($haystack, $akc_reg_num, $generation = 0) {
    ++$generation;
    foreach ($haystack as $index => $row) {
        if ($row['akc_reg_num'] == $akc_reg_num) {
            $result = ['sire' => $row['akc_parent_sire'], 'dam' => $row['akc_parent_dam']];
            unset($haystack[$index]);             // reduce the haystack to improve efficiency and avoid infinite loop
            break;                                // stop searching
        }
    }
    if (!isset($result)) {
        return [];  // $akc_reg_num not found
    }

    foreach ($haystack as $row) {
        if ($row['akc_reg_num'] == $result['sire']) {
            $result['sire_parents'] = array_filter(buildPedigree($haystack, $row['akc_reg_num'], $generation));  // recurse and purge empty parent arrays
            if (array_key_exists('dam_parents', $result)) {
                break;  // both parents found in generation, stop this loop
            }
        } elseif ($row['akc_reg_num'] == $result['dam']) {
            $result['dam_parents'] = array_filter(buildPedigree($haystack, $row['akc_reg_num'], $generation));  // recurse and purge empty parent arrays
            if (array_key_exists('sire_parents', $result)) {
                break;  // both parents found in generation, stop this loop
            }
        }
    }
    return $generation <= 4 ? $result : [];
} 

var_export(buildPedigree($animals, 1));

Вывод:

array (
    'sire' => 2,
    'dam' => 3,
    'sire_parents' => array (
        'sire' => 5,
        'dam' => 6,
        'dam_parents' => array (
            'sire' => 7,
            'dam' => 8,
        ),
    ),
    'dam_parents' => array (
        'sire' => 9,
        'sire_parents' => array (
            'sire' => 10,
            'dam' => 11,
            'dam_parents' => array (
                'sire' => 12,
            ),
        ),
    ),
)
...