sql >> Database teknologi >  >> RDS >> Mysql

Udskrive hierakiske data i en overordnet underordnet form fra uordnet liste php?

OK arbejder fra backend mod frontend...

Du kan kalde en enkelt ikke-rekursiv lagret procedure (sproc) fra dit php-script, som genererer meddelelseshierarkiet for dig. Fordelen ved denne tilgang er, at du kun behøver at lave en SINGLE opkald fra php til din database, hvorimod hvis du bruger inline SQL, vil du foretage lige så mange opkald, som der er niveauer (som minimum). En anden fordel er, at da det er en ikke-rekursiv sproc, er den ekstremt effektiv, og den holder også din php-kode pæn og ren. Endelig, og jeg må sige det for en god ordens skyld, at opkald til lagrede procedurer er mere sikkert og mere effektivt end nogen anden metode, fordi du kun behøver at give tilladelser til din app-bruger, og lagrede procedurer kræver færre rundrejser til databasen end nogen anden andre metoder, herunder parametriserede forespørgsler, som kræver mindst 2 kald for en enkelt forespørgsel (1 til at konfigurere forespørgselsskabelonen i db, den anden til at udfylde parametrene)

Så her er, hvordan du vil kalde den lagrede procedure fra MySQL-kommandolinjen.

call message_hier(1);

og her er det resultatsæt, det skaber.

msg_id  emp_msg    parent_msg_id    parent_msg   depth
======  =======    =============    ==========   =====
1        msg 1            NULL          NULL          0
2        msg 1-1             1          msg 1         1
3        msg 1-2             1          msg 1         1
4        msg 1-2-1           3          msg 1-2       2
5        msg 1-2-2           3          msg 1-2       2
6        msg 1-2-2-1         5          msg 1-2-2     3
7        msg 1-2-2-1-1       6          msg 1-2-2-1   4
8        msg 1-2-2-1-2       6          msg 1-2-2-1   4

Ok, så nu har vi mulighed for at hente et helt eller delvist meddelelsestræ ved blot at kalde vores sproc med den startknude vi har brug for, men hvad skal vi med resultatsættet ??

Nå, i dette eksempel har jeg besluttet, at vi skal generere en XML DOM med den, så alt hvad jeg skal gøre er at transformere (XSLT) XML'en, og vi vil have en indlejret meddelelses-webside.

PHP-script

php-scriptet er ret simpelt, det forbinder bare til databasen, kalder sproc'en og sløjfer resultatsættet for at bygge XML DOM'et. Husk, at vi kun ringer ind i databasen én gang.

<?php

// i am using the resultset to build an XML DOM but you can do whatever you like with it !

header("Content-type: text/xml");

$conn = new mysqli("localhost", "foo_dbo", "pass", "foo_db", 3306);

// one non-recursive db call to get the message tree !

$result = $conn->query(sprintf("call message_hier(%d)", 1));

$xml = new DomDocument;
$xpath = new DOMXpath($xml);

$msgs = $xml->createElement("messages");
$xml->appendChild($msgs);

// loop and build the DOM

while($row = $result->fetch_assoc()){

    $msg = $xml->createElement("message");
    foreach($row as $col => $val) $msg->setAttribute($col, $val); 

    if(is_null($row["parent_msg_id"])){
        $msgs->appendChild($msg);
    }
    else{
        $qry = sprintf("//*[@msg_id = '%d']", $row["parent_msg_id"]);
        $parent = $xpath->query($qry)->item(0);
        if(!is_null($parent)) $parent->appendChild($msg);
    }
}
$result->close();
$conn->close();

echo $xml->saveXML();
?>

XML-output

Dette er den XML, som php-scriptet genererer. Hvis du gemmer denne XML i en fil og åbner den i din browser, vil du være i stand til at udvide og skjule niveauerne.

<messages>
    <message msg_id="1" emp_msg="msg 1" parent_msg_id="" parent_msg="" depth="0">
        <message msg_id="2" emp_msg="msg 1-1" parent_msg_id="1" parent_msg="msg 1" depth="1"/>
        <message msg_id="3" emp_msg="msg 1-2" parent_msg_id="1" parent_msg="msg 1" depth="1">
            <message msg_id="4" emp_msg="msg 1-2-1" parent_msg_id="3" parent_msg="msg 1-2" depth="2"/>
            <message msg_id="5" emp_msg="msg 1-2-2" parent_msg_id="3" parent_msg="msg 1-2" depth="2">
                <message msg_id="6" emp_msg="msg 1-2-2-1" parent_msg_id="5" parent_msg="msg 1-2-2" depth="3">
                    <message msg_id="7" emp_msg="msg 1-2-2-1-1" parent_msg_id="6" parent_msg="msg 1-2-2-1" depth="4"/>
                    <message msg_id="8" emp_msg="msg 1-2-2-1-2" parent_msg_id="6" parent_msg="msg 1-2-2-1" depth="4"/>
                </message>
            </message>
        </message>
    </message>
</messages>

Nu kan du undlade at bygge XML DOM og bruge XSL til at gengive en webside, hvis du ønsker det, og måske bare sløjfe resultatsættet og gengive beskederne direkte. Jeg har simpelthen valgt denne metode for at gøre mit eksempel så omfattende og informativt som muligt.

MySQL-script

Dette er et komplet script inklusive tabeller, sprocs og testdata.

drop table if exists messages;
create table messages
(
msg_id smallint unsigned not null auto_increment primary key,
msg varchar(255) not null,
parent_msg_id smallint unsigned null,
key (parent_msg_id)
)
engine = innodb;

insert into messages (msg, parent_msg_id) values
('msg 1',null), 
  ('msg 1-1',1), 
  ('msg 1-2',1), 
      ('msg 1-2-1',3), 
      ('msg 1-2-2',3), 
         ('msg 1-2-2-1',5), 
            ('msg 1-2-2-1-1',6), 
            ('msg 1-2-2-1-2',6);


drop procedure if exists message_hier;

delimiter #

create procedure message_hier
(
in p_msg_id smallint unsigned
)
begin

declare v_done tinyint unsigned default(0);
declare v_dpth smallint unsigned default(0);

create temporary table hier(
 parent_msg_id smallint unsigned, 
 msg_id smallint unsigned, 
 depth smallint unsigned
)engine = memory;

insert into hier select parent_msg_id, msg_id, v_dpth from messages where msg_id = p_msg_id;

/* http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */

create temporary table tmp engine=memory select * from hier;

while not v_done do

    if exists( select 1 from messages e inner join hier on e.parent_msg_id = hier.msg_id and hier.depth = v_dpth) then

        insert into hier select e.parent_msg_id, e.msg_id, v_dpth + 1 
            from messages e inner join tmp on e.parent_msg_id = tmp.msg_id and tmp.depth = v_dpth;

        set v_dpth = v_dpth + 1;            

        truncate table tmp;
        insert into tmp select * from hier where depth = v_dpth;

    else
        set v_done = 1;
    end if;

end while;

select 
 m.msg_id,
 m.msg as emp_msg,
 p.msg_id as parent_msg_id,
 p.msg as parent_msg,
 hier.depth
from 
 hier
inner join messages m on hier.msg_id = m.msg_id
left outer join messages p on hier.parent_msg_id = p.msg_id;

drop temporary table if exists hier;
drop temporary table if exists tmp;

end #

delimiter ;

-- call this sproc from your php

call message_hier(1);

Den fulde kilde til dette svar kan findes her:http://pastie.org/1336407 . Som du allerede har bemærket, har jeg udeladt XSLT, men du vil sandsynligvis ikke gå XML-ruten, og hvis du gør det, er der masser af eksempler på nettet.

Håber du finder dette nyttigt :)

EDIT:

Tilføjet lidt flere data, så du har mere end én rodmeddelelse (msg_ids 1,9,14).

truncate table messages;

insert into messages (msg, parent_msg_id) values
('msg 1',null), -- msg_id = 1
  ('msg 1-1',1), 
  ('msg 1-2',1), 
      ('msg 1-2-1',3), 
      ('msg 1-2-2',3), 
         ('msg 1-2-2-1',5), 
            ('msg 1-2-2-1-1',6), 
            ('msg 1-2-2-1-2',6),
('msg 2',null), -- msg_id = 9
    ('msg 2-1',9), 
    ('msg 2-2',9), 
    ('msg 2-3',9), 
        ('msg 2-3-1',12),
('msg 3',null); -- msg_id = 14

Hvis du nu bare ønsker at få de beskeder, der er specifikke for en rodknude (startmeddelelse), kan du kalde den oprindelige lagrede procedure, der passerer i start-msg_id for den rod, du har brug for. Ved at bruge de nye data ovenfor ville det være msg_ids 1,9,14.

call message_hier(1); -- returns all messages belonging to msg_id = 1

call message_hier(9); -- returns all messages belonging to msg_id = 9

call message_hier(14); -- returns all messages belonging to msg_id = 14

du kan sende et hvilket som helst msg_id du kan lide, så hvis jeg vil have alle beskederne under msg 1-2-2-1, så vil du sende msg_id =6:

call message_hier(6); -- returns all messages belonging to msg_id = 6

Men hvis du vil have alle beskederne for alle rødderne, kan du kalde denne nye sproc, jeg har oprettet, som følger:

call message_hier_all(); -- returns all messages for all roots.

Hovedproblemet med dette er, efterhånden som din beskedtabel vokser, vil den returnere masser af data, hvorfor jeg fokuserede på en mere specifik sproc, der kun hentede beskeder for en given rodknude eller startende msg_id.

Jeg vil ikke poste den nye sproc-kode, da den stort set er den samme som originalen, men du kan finde alle ændringerne her:http://pastie.org/1339618

Den sidste ændring, du skal foretage, er i php-scriptet, som nu vil kalde den nye sproc som følger:

//$result = $conn->query(sprintf("call message_hier(%d)", 1)); // recommended call

$result = $conn->query("call message_hier_all()"); // new sproc call

Håber dette hjælper :)

call message_hier_all();


  1. Sådan får du row_number i MySQL

  2. Implementering af automatiseret databasebackup og gendannelse med standardmidler

  3. Hvad er SQL Server-deadlock?

  4. Få alle brugerne undtagen den nuværende loggede bruger i laravel veltalende