Выборка и сравнение данных массива PHP

Главные вкладки

Аватар пользователя buddy90210 buddy90210 21 июля 2020 в 14:39

Добрый день, ни как не могу допереть с выборкой и сравнением.
Есть объект Этап, у него может быть неограниченное количество работ, у каждой работы есть дата начала и дата окончания работы. Необходимо Этапу присваивать значения начала самой "ранней работы" и значения окончания самой "поздней" работы.
Т.е. делаю выборку из БД:

<?php                $query = \Drupal::entityQuery('node');
        
$query->condition('status'1);
        
$query->condition('field_project_id'$prid);
        
$or $query->orConditionGroup();
        
$or->condition('type''project_stage');
        
$or->condition('type''project_job');
        
$query->condition($or);
        
$query->sort('nid' 'ASC'); 
        
$entity_ids $query->execute();
        if (
$entity_ids) {
            
$nodes =  \Drupal\node\Entity\Node::loadMultiple($entity_ids);
            foreach (
$nodes as $node) {
                                
$id $node->id();
                
$title $node->getTitle();
                
$type $node->getType();
                if (
$type == 'project_job') {
                    
$parentId $node->get('field_project_stage_id')->getValue()[0]['value'];
                    if (!
$node->get('field_job_date_start')->isEmpty()) {
                        
$start $node->get('field_job_date_start')->date->getTimestamp()*1000;
                    }
                    if !
$node->get('field_job_date_end')->isEmpty()) {
                        
$end $node->get('field_job_date_end')->date->getTimestamp()*1000;
                    } else {
                        
$end $start 23*60*60*1000;
                    }
                    
//Получил дату начала и конца для определенной работы
                                        
$time[$parentId] = array('start'=>$start'end'=>$end);//добавил в общий массив с ключом ID родителя
                                  
}
                          }
                  }
?>

А дальше не знаю как в этом массиве $time, сравнить все даты (числа timestamp) с одинаковыми ключами и потом еще и добавить эту информацию к родителю. Может не в ту сторону копаю.

Лучший ответ

Аватар пользователя buddy90210 buddy90210 23 июля 2020 в 9:29

Спасибо всем за ответы, да код представлен не полностью, выборка данных выполняется для дальнейшей обработки в стороннем скрипте, поэтому мне нужны поля из обеих сущностей. Подсказали вот такое решение:

<?phpfunction extractMaxIntervalsFromChildren(array $elements) {
    return array_reduce($elements, function($res, $element) {
        if (!isset($element['parent_id'])) {
            return $res;
        }
        
        $parentId = $element['parent_id'];
        $parent = $res[$parentId] ?? ['id' => $parentId, 'min' => null, 'max' => null];

        $parent['min'] = min($parent['min'], $element['start']) ?: $element['start'];
        $parent['max'] = max($parent['max'], $element['end']) ?: $element['end'];
        
        $res[$parentId] = $parent;
        
        
        return $res;
    }, []);
}?>

Комментарии

Аватар пользователя OldWarrior OldWarrior 21 июля 2020 в 17:41

Имхо, подобные "полотенца" обычно никто не рассматривает всерьёз, поскольку это предполагает детальное вникание в какие-то личные "велосипеды" автора.

Мне, например, с ходу показалось странным, что запрашиваются два разных типа сущностей, хотя обработка происходит только по одному из них ('project_job').

Кроме того, есть подозрение (поскольку весь код не виден и вообще назначение полей непонятно), что $parentId не будет являться уникальным ключом массива, стало быть элемент массива $time[$parentId] = array('start'=>$start, 'end'=>$end); может тупо перезаписываться с новыми значениями в случае совпадения 'field_project_stage_id'. И тогда в этом случае:

buddy90210 wrote: А дальше не знаю как в этом массиве $time, сравнить все даты (числа timestamp) с одинаковыми ключами

сравнивать будет просто нечего и не с чем.

В общем случае можно порекомендовать обычную сортировку массива чтобы выбрать большее/меньшее значения.

Аватар пользователя voviko voviko 21 июля 2020 в 19:01
<?php
//На коленке написано и только чтобы понять в какую сторону смотреть
foreach ($nodes as $node) {
    
$time[$start][] = array('start'=>$start'end'=>$end'id'=>$id);

}

//Сортируем массив по возрастианию 
    
$time_start ksort($time);
//Сортируем массив по убыванию
    
$time_end asort($time);
    
$start_parent current($time_start)['start'];
    
$end end($time_end)['end'];
?>
Аватар пользователя marassa marassa 21 июля 2020 в 19:04

Всё одним SQL-запросом делается, типа

SELECT field_project_stage_id, MIN(field_job_date_start), MAX(field_job_date_end)
FROM project_job
GROUP BY field_project_stage_id;

Я, естественно упростил физическую структуру данных, но идея должна быть ясна.
Эту же выборку без труда можно накликать мышкой во views и просто считать нужные результаты на лету, ничего лишнего не сохраняя.

Аватар пользователя OldWarrior OldWarrior 22 июля 2020 в 2:21

marassa wrote:... Всё одним SQL-запросом делается ...

Тссс! Спрячьте быстрее, пока не набежали разъярённые приверженцы Drupal-way. Wink

Аватар пользователя marassa marassa 22 июля 2020 в 8:04

Даже интересно было бы послушать набежавших Wink Неужели друпал-вей - это, презрев возможности СУБД (для которых кстати Друпал представляет шикарную обертку и на уровне API, и на уровне админки Views), вытащить всю базу в пхп-массив и потом ковыряться в нем?

Аватар пользователя marassa marassa 22 июля 2020 в 16:58

Меня опять неправильно поняли Wink Я вовсе не предлагаю лезть в БД напрямую - я просто описал в самой простой и наглядной форме правильный запрос, который можно (и нужно) реализовать либо мышкой во вьюс (если нет реальной необходимости хранить рассчитанные данные в Этапе) либо, если всё-таки их зачем-то нужно хранить, через API. Мой запрос возвращает готовые рассчитанные данные, и с массивом результатов не нужно проводить вообще никаких манипуляций - просто бери готовые значения и перекладывай куда надо.
Тут уже не первый раз задают вопрос как на PHP решить задачу, с которой явно лучше справится СУБД.

Аватар пользователя OldWarrior OldWarrior 22 июля 2020 в 17:20

"Напрямую" и я не имел в виду. Т.е. кастомные запросы через Database Service (который есть API) тоже могут быть признаны аморальными - поскольку уже не EntityQuery Smile

То есть, это всё как раз-таки понятно (в том числе и про задачи, с которыми лучше справляются SQL-запросы), но как, например, будет реализовано вышеуказанное решение через Drupal Database Connection? Хотя бы просто, чтобы показать ТС, как решается вопрос средствами API Drupal 8.

Аватар пользователя marassa marassa 24 июля 2020 в 15:58
1
$query = Drupal::entityQueryAggregate('node');
$result = $query
  ->condition('type', 'project_job')
  ->groupBy('field_project_stage_id')
  ->aggregate('field_job_date_start', 'MIN')
  ->aggregate('field_job_date_end', 'MAX')
  ->execute();

вернёт массив с количеством строк, равным количеству этапов, где для каждого этапа будут лежать готовые даты его начала и конца. Остаётся только положить готовые значения в поля Этапов.

Аватар пользователя buddy90210 buddy90210 23 июля 2020 в 9:29

Спасибо всем за ответы, да код представлен не полностью, выборка данных выполняется для дальнейшей обработки в стороннем скрипте, поэтому мне нужны поля из обеих сущностей. Подсказали вот такое решение:

<?phpfunction extractMaxIntervalsFromChildren(array $elements) {
    return array_reduce($elements, function($res, $element) {
        if (!isset($element['parent_id'])) {
            return $res;
        }
        
        $parentId = $element['parent_id'];
        $parent = $res[$parentId] ?? ['id' => $parentId, 'min' => null, 'max' => null];

        $parent['min'] = min($parent['min'], $element['start']) ?: $element['start'];
        $parent['max'] = max($parent['max'], $element['end']) ?: $element['end'];
        
        $res[$parentId] = $parent;
        
        
        return $res;
    }, []);
}?>