Autor Zpráva
Ages
Profil
Zdravím narazil jsem na problém:
Mám pole:
$pole = array(
            array('day' => '0', 'aliment' => '1', 'qty' => 1),
            array('day' => '0', 'aliment' => '1', 'qty' => 2),
            array('day' => '0', 'aliment' => '2', 'qty' => 1),
            array('day' => '0', 'aliment' => '2', 'qty' => 4),
            array('day' => '1', 'aliment' => '1', 'qty' => 1),
            array('day' => '1', 'aliment' => '1', 'qty' => 3),
            array('day' => '1', 'aliment' => '2', 'qty' => 1),
            array('day' => '1', 'aliment' => '2', 'qty' => 1),
        );
Z toho bych potřeboval vytvořit pole, které vypadá takto:
$pole2 = array(
            array('day' => '0', 'aliment' => '1', 'qty' => 3),
            array('day' => '0', 'aliment' => '2', 'qty' => 5),
            array('day' => '1', 'aliment' => '1', 'qty' => 4),
            array('day' => '1', 'aliment' => '2', 'qty' => 2),
        );

V podstatě bych potřeboval sečíst qty pro daný day a aliment.
Nemáte něko nápad jak to co nejjednodušeji udělat?
Díky
juriad
Profil
Pokud data pochází z databáze, tak proveď součet už tam. Pokud ne, můžeš použít níže uvedenou funkci:

<?php

function array_aggregate($array, $keyFunc, $reduceFunc, $default) {
    $agg = [];
    foreach ($array as $record) {
        $key = call_user_func($keyFunc, $record);
        $agg[$key] = call_user_func($reduceFunc, $record, isset($agg[$key]) ? $agg[$key] : $default);
    }
    return array_values($agg);
}

$pole = array();
$pole[] = array('day' => '0', 'aliment' => '1', 'qty' => 1);
$pole[] = array('day' => '0', 'aliment' => '1', 'qty' => 2);
$pole[] = array('day' => '0', 'aliment' => '2', 'qty' => 1);
$pole[] = array('day' => '0', 'aliment' => '2', 'qty' => 4);
$pole[] = array('day' => '1', 'aliment' => '1', 'qty' => 1);
$pole[] = array('day' => '1', 'aliment' => '1', 'qty' => 3);
$pole[] = array('day' => '1', 'aliment' => '2', 'qty' => 1);
$pole[] = array('day' => '1', 'aliment' => '2', 'qty' => 1);

$pole2 = array_aggregate($pole, # pole, které má zpracovat
    function($rec) {return "$rec[day]-$rec[aliment]";}, # definuje, které záznamy jsou shodné
    function($rec, $acc) {$rec['qty'] += $acc['qty']; return $rec;}, # definuje operaci na shodných záznamech
    array('qty' => 0) # výchozí hodnota pro provedení operace
);

var_dump($pole2);

Poznámka: tato funkce je překvapivě univerzální. Lze s ní provádět libovolnou agregační funkci, tady je to jen nejjednodušší součet.
Ages
Profil
juriad:
Děkuji večer vyzkouším.
Data sice pocházejí z DB ale nenapadlo mě jak to z ni dostat:
DB (chybějí tam definice FK):
CREATE TABLE `feedings` (
  `id` int(11) NOT NULL,
  `aliment_id` int(11) DEFAULT NULL,
  `alternativ_id` int(11) DEFAULT NULL,
  `qty` int(11) DEFAULT NULL,
  `qty2` int(11) DEFAULT NULL,
  `price` float DEFAULT NULL,
  `created` date DEFAULT NULL,
  `animal_id` int(11) DEFAULT NULL
) ;

CREATE TABLE `feedingtemplates` (
  `id` int(11) NOT NULL,
  `name` varchar(45) COLLATE utf8_czech_ci DEFAULT NULL,
  `reference` varchar(15) COLLATE utf8_czech_ci DEFAULT NULL,
  `primary_id` int(11) DEFAULT NULL,
  `qty` int(11) DEFAULT NULL,
  `days` int(11) DEFAULT NULL COMMENT
  `note` text COLLATE utf8_czech_ci,
  `user_id` int(11) DEFAULT NULL,
  `created` datetime DEFAULT NULL,
  `seccondary_id` int(11) DEFAULT NULL,
  `qty2` int(11) DEFAULT NULL
);

CREATE TABLE `animals` (
  `id` int(11) NOT NULL,
  `user_id` int(11) DEFAULT NULL,
  `feedingtemplate_id` int(11) DEFAULT NULL COMMENT
) ;

CREATE TABLE `aliments` (
  `id` int(11) NOT NULL,
  `name` varchar(20) COLLATE utf8_czech_ci DEFAULT NULL,
  `weight` int(11) DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL,
  `supplier_id` int(11) DEFAULT NULL,
  `reference` varchar(15) COLLATE utf8_czech_ci DEFAULT NULL
);

A potřebuji nejdříve najít k jednotlivým zvířatům daného uživatele polsední krmení (abych věděl kdy bylo krmeno), zjistit kdy se má opět krmit dle šablony krmení (days = interval krmení ve dnech) pak to sečís a vygenerovat z toho k jakémů datu bude potřeba kolik krmiv.

Tak mě nenapadlo jak to nějak smysluplně z DB dostat, nebo zda by měla DB vypadat jinak?
juriad
Profil
Ages:
Můžeš ukázat dotaz, kterým aktuálně získáváš ta data z databáze? Upravit jej bude snazší než se snažit pochopit všechny vazby.
Mimochodem, chválím tě za poskytnutí schématu, obvykle jej pracně z uživatelů mámíme.
Ages
Profil
juriad:
Je to v Nette proto ten zvláštní dotaz...celkově se mě to přijde velmi krkolomné, ale zatím se mi nepodařilo najít nějaké řešení.
btw: když už je mi někdo ochotný poradit tak mi to přeci nebudu ztěžovat :)
    /**
     * Výpočet potřeby krmiv
     * @param int $user
     * @return array
     */
public function timeline($user){
        $animals = $this->database->table('animals')->where('user_id', $user)->where('NOT feedingtemplate_id', NULL);
        $krmivo = array();
        $count = 0;
        foreach ($animals as $animal){
            if ($animal->related('feedings')->count('*') > 0){
              $feedBefore =  $animal->related('feedings')->select('TIMESTAMPDIFF (DAY, created, CURDATE()) AS feed_before')->order('created DESC')->limit(1)->fetch()->feed_before;
                    $feedDay = $animal->feedingtemplate->days - $feedBefore;
                    $krmivo[$count]=array(
                        'day' => $feedDay,
                        'aliment' => $animal->feedingtemplate->primary_id,
                        'qty' => $animal->feedingtemplate->qty,  
                        );
                    if($animal->feedingtemplate->seccondary_id){
                        $krmivo[$count]=array(
                        'day' => $feedDay,
                        'aliment' => $animal->feedingtemplate->seccondary_id,
                        'qty' => $animal->feedingtemplate->qty2,    
                        );
                    }
            }
            $count++;
        }
        return $krmivo;
    }
Tady je vizualizace:
juriad
Profil
Myslím, že celá ta funkce by šla nahradit jediným dotazem:
SELECT (ft.days - f.feed_before) AS day,
    COALESCE(ft.secondary_id, ft.primary_id) AS aliment, # vybereme vždy secondary, pokud to není NULL
    SUM(COALESCE(ft.qt2, ft.qty)) AS qty # sečteme seskupené qty (předpokládáme, že qty2 IS NULL, pokud secondary_id IS NULL)
FROM animal a # ke každému zvířeti
JOIN feeding_template ft ON a.feedingtemplate_id = ft.id # připojíme jeho krmící vzor
JOIN ( # a informaci o tom, kdy bylo naposledy krmeno
  SELECT animal_id, MIN(TIMESTAMPDIFF (DAY, created, CURDATE())) AS feed_before
  FROM feedings
  GROUP BY animal_id
) f ON a.id = f.animal_id
WHERE a.user_id = $user
GROUP BY day, aliment # seskupíme podle dne a alimentu

Opravdu chceš krmit zvíře jen pomocí secondary alimentu? V té své funkci totiž ten záznam pro primární přepisuješ.
Ages
Profil
juriad:
Děkuji skutečně to funguje (ačokli na ten select koukám jak vosa do flašky).
To přepisování tim seccondary je chybně chybí tam inkrementace, mohl bych ještě poprosit o ukázu této úpravy?
Děkuji
juriad
Profil
SELECT (ft.days - f.feed_before) AS day, ft.aliment_id, SUM(ft.qty) AS qty # žádný NULL už nikde nehrozí, proto je tato část jednodušší
FROM animal a
JOIN ( # tento poddotaz z každého záznamu v tabulky feeding_template udělá zvlášť záznam pro primary a secondary
  SELECT id, days, primary_id AS aliment_id, qty AS qty # vytáhne primary část záznamu
  FROM feedingtemplates
  WHERE primary_id IS NOT NULL
  UNION ALL
  SELECT id, days, secondary_id AS aliment_id, qty2 AS qty # vytáhne secondary část záznamu
  FROM feedingtemplates
  WHERE secondary_id IS NOT NULL
) ft ON a.feedingtemplate_id = ft.id
JOIN ( # pokud by byl problém s výkonem, tuto část by šlo ještě zlepšit
  SELECT animal_id, MIN(TIMESTAMPDIFF (DAY, created, CURDATE())) AS feed_before
  FROM feedings
  GROUP BY animal_id
) f ON a.id = f.animal_id
WHERE a.user_id = $user
GROUP BY day, ft.aliment_id

Pokud bys chtěl tímto dotazem vytáhnout i informace o alimentu, stačil by přidat JOIN na tabulky aliments:
JOIN alimements al ON al.id = ft.aliment_id

Mimochodem, ta tabulka feedingtemplates je špatně navržená, měla by se rozpadnout na dvě:
* feedingtemplates, který bude obsahovat obecné informace
* feedingaliments, které bude obsahovat vazbu mezi templatem a alimentem spolu s množstvím; pak půjde krmit zvířata i více než dvěma alimentama.
Po této změně by se ten poddotaz s UNION ALL nahradil za JOIN na tabulku feedingaliments.
Ages
Profil
Děkuji ti za rady večer to vyzkouším implementovat.
Ohledně toho návrhu z 99% se bude vyplněn pouze první aliment, ten druhý tam je pro jistotu, kdyby náhodou (proto jsem ani nenašel tu chybu s inkrementací - nikde jsem to neměl vyplněno), tak mi přišlo zbytečné dělat další tabulku.
Ještě jdenou díky, budu se muset prokousal MySQL trochu dál :)
juriad
Profil
Ages:
Ono se to teď nezdá, ale přidání té jedné tabulky navíc ti umožní krmit zvířata i podle sezóny: například seno potřebuješ jen v zimě, v létě můžou být zvířata na pastvě; další příklad je přidávání vitamínů k běžné krmi. Toto vše by tím šlo krásně namodelovat.

Stejnou transformací by měla projít tabulka feedings. Kdykoli máš potřebu indexovat sloupce čísly, je to špatně. Otázkou je také zda feeding nemá mít vazbu na feedingtemplate, i když to by neumožnilo krmit dočasně alternativní krmí, protože standardní není dostupná.

Já jen poukazuji na to, že čím dříve se změna provede, tím méně bolestivá bude. Je to tvé rozhodnutí; věřím, že to máš rozmyšlené lépe než já.
Ages
Profil
Děkuji za připomínku, jen na vysvětlenou: Jedná se o krmení hadů, tam krmíš tím druhhým většinou pouze pokud ti to zbyde od jiného zvířete. Ty šablony jsou na to aby se dalo vypočítat kolik myší budeš potřebovat koupit a ještě to používám na jednotlačítkové krmení dle šablony. Vitamíny se také vetšinou nedávají a případně pro to jsou lékařské záznamy. Tak snad jsem na nic nezapomněl, ale pokud ano určitě si na tvou radu vzpomenu :)

Vaše odpověď

Mohlo by se hodit


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm: