MySQL tárolt eljárások meghívása táblázat szerint

Üdv mindenkinek,

MYSQL ügyben kérnék segítséget.

Egy tábla sok (20-50) tárolt eljárás adatait tartalmazza. Eljárás neve, milyen esetben kell végrehajtani, stb.
Valamilyen kulturált megoldást keresek amivel a táblára végrehajtott lekérdezés eredményétől függően meg tudom hívni a szükséges eljárásokat.



CREATE TABLE proctable (
    proc_name     VARCHAR(30),
    proc_priority INT,
    proc_type     ENUM('type1','type2','type3')
)


SET @qry = (
    SELECT GROUP_CONCAT(
        CONCAT('CALL ',proc_name,'(p1,p2,p3); ')
        ORDER BY `proc_priority` SEPARATOR '')
    FROM proctable
    WHERE `proc_type` = 'type1'
    );

PREPARE stmt FROM @qry;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

A lekérdezés előállít egy listát @qry amit végre lehetne hajtani:

CALL proc_aaa(p1,p2,p3); CALL proc_bbb(p1,p2,p3); CALL proc_ccc(p1,p2,p3);

De sajnos az a baj, hogy nem lehet pontosvesszővel elválasztott több utasítást végrehajtani (You have an error in your SQL syntax).

Milyen más megoldással lehetne a feladatot megoldani? Van valakinek ötlete?

A válaszokat előre is köszönöm.

Hozzászólások

Pl. megpróbálhatod egyenként is végrehajtani. De inkább olvasd el a hibaüzenet 2. sorát, a mysql el szokta mondani, hogy mi a baja.

Sajnos nem marad más mint egyenként végrehajtani.
A hiba oka az, hogy nem lehet pontosvesszővel elválasztva több utasítást megadni.


SET @qry = 'SELECT 1 ; SELECT 2';

PREPARE stmt FROM @qry;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

ERROR 1064 (42000) at line 117: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SELECT 2' at line 1

Ez nem segít? http://forums.mysql.com/read.php?98,358569

Ebből az url alól kivágott példából talán már el tudsz indulni:

DELIMITER $$
CREATE PROCEDURE `Capitalize`(INOUT str VARCHAR(1024))
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE myc, pc CHAR(1);
DECLARE outstr VARCHAR(1000) DEFAULT str;
WHILE i <= CHAR_LENGTH(str) DO
SET myc = SUBSTRING(str, i, 1);
SET pc = CASE WHEN i = 1 THEN ' '
ELSE SUBSTRING(str, i - 1, 1)
END;
IF pc IN (' ', '&', '''', '_', '?', ';', ':', '!', ',', '-', '/', '(', '.') THEN
SET outstr = INSERT(outstr, i, 1, UPPER(myc));
END IF;
SET i = i + 1;
END WHILE;
SET str = outstr;
END$$
DELIMITER ;
Here is the usage of the Capitalize stored procedure
SET @str = 'mysql stored procedure tutorial';
CALL Capitalize(@str);
SELECT @str;

Szijártó Zoltán
Aki tud az alkot, aki nem tud az csak szövegel.

Az alap feladat az, hogy egy több százezer sort tartalmazó tábla bizonyos soraira végre kell hajtani bizonyos tárolt eljárásokat megfelelő sorrendben.

Nem gond egyenként végrehajtani csak próbálom a lehető legkisebb költségű megoldást találni.

Most ott tartok, hogy két megoldás közül kellene választani a gyorsabb javára.

(1)
- A feldolgozás előtt lekérdezéssel eldől, hogy mely eljárásokat milyen sorrendben kell alkalmazni: @proc

ciklus a nagy tábla sok százezer sorára:
- A REPLACE() függvény segítségével kicserélem az összes eljárás paraméterét a @proc változóban.
- WHILE ciklussal feldarabolom a @proc változót és egyenként végrehajtom az eljárásokat.

(2)
- Definiálok egy kurzort ami meghatározza, hogy mely eljárásokat milyen sorrendben kell alkalmazni: @proc

ciklus a nagy tábla sok százezer sorára:
- WHILE ciklussal végiglépkedek a @proc kurzor segítségével az eljárás listán.

Kérdés, hogy melyik lehet a gyorsabb?

Főleg Oracle-t használok abból indulok ki:

Ami lassítani fog, az valszeg a prepare statement és az execute, illetve ha túl sokat nyitogatsz kurzorokat.

Szóval a 2-es verziót részesíteném előnyben azzal a különbséggel, hogy az futtatandó eljárásokat beletölteném egy gyors temporary táblába (mivel sajnos nincs tömb), és arra nyitnám a @proc kurzort.

3) ha nem túl gyakran változik a futtatandó eljárások listája, és viszonylag kevés proc_type van, akkor az alkalmazásból generálnék proc_type-onként egy tárolt eljárást, ami meghívja az alá tartozó proc-okat. Proc a tábla változása esetén kellene csak újragenerálni őket, akkor is csak az érintetteket. Ezzel komolyabban bevonod az alkalmazást, mint eredetileg szeretted volna, de ennyit talán megér.

Köszi a tippet.

Egy ilyen feldolgozás napi egy alkalommal történik. Pl. időszak függvényében is (napi, heti, havi, féléves, éves) más-más lesz a végrehajtandó eljárások listája. Viszont ezt az eljárás listát kell nagyon sokszor végrehajtani.

Én attól tartok, hogy pl. a sok kurzor nyitogatás fog sokáig tartani összességében. Nem tudom biztosan de gondolom kurzor nyitás alkalmával fut le a kurzorra definiált select. Tehát ha pl. hétszáz-ezer alkalommal történik ez akkor eléggé megnövelheti a feldolgozás időtartamát.

Ezért vagyok bizonytalan a megoldást illetően. De készítek egy tesztet és majd kiderül.

Lefuttattam egy tesztet a következő megoldással:


/* teszt eljaras */
DELIMITER //
CREATE PROCEDURE `egy` (`txt` VARCHAR(30))
    BEGIN
        SET @a=1;
    END //
DELIMITER ;


DECLARE `cur` CURSOR FOR SELECT `proc` FROM `t` WHERE `type`='t1';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET `do`=FALSE;

/* eljaras meghivasa (egymilliószor) */
OPEN `cur`;
SET `do`=TRUE;
WHILE `do` DO
    FETCH `cur` INTO `r`;
    IF `do` IS TRUE THEN
        SET @qry=CONCAT('CALL ',`r`,'(?)');
        SET @param='teszt';
        PREPARE stmt FROM @qry;
        EXECUTE stmt USING @param;
        DEALLOCATE PREPARE stmt;
    END IF;
END WHILE;
CLOSE `cur`;

Egymilliószor végrehajtva 2m13.203s lett az eredmény. Tehát a nettó futási időt kb 2 perccel növeli meg az egymillió soros tábla esetén.

Lefuttattam egy olyan tesztet is ahol statikusan egymás után végre lett hajtva ugyanaz a 3 eljárás mint az előző esetben:


/* eljaras meghivasa (egymilliószor) */
SET @qry=CONCAT('CALL egy(?)');
SET @param='teszt';
PREPARE stmt FROM @qry;
EXECUTE stmt USING @param;
DEALLOCATE PREPARE stmt;

SET @qry=CONCAT('CALL ketto(?)');
SET @param='teszt';
PREPARE stmt FROM @qry;
EXECUTE stmt USING @param;
DEALLOCATE PREPARE stmt;

SET @qry=CONCAT('CALL negy(?)');
SET @param='teszt';
PREPARE stmt FROM @qry;
EXECUTE stmt USING @param;
DEALLOCATE PREPARE stmt;

Egymilliószor végrehajtva 1m1.929s lett az eredmény.
Tehát a kurzorozás nagyjából megduplázza a veszteséget.

Sajnos ez nem túl nagy nyereség, úgy érzem, nem ezen fog múlni a teljesítmény.

Amit meg kellene még gondolnod, hogy biztosan a rekordonként n. db. execute módszerrel akarod-e megvalósítani a tárolt eljárások futtatását. Hatékonyabb lenne, ha maga a napi job szedné össze neki szükséges adatokat, és végezné el a munkát.

Pl. a jelenlegi módszered kb. ilyesmi (pszeudokód):

procedure job1(rowid int) is
begin
 update tab1 set col1=calc(col1) where id=rowid;
end;

for r in (select id from tab1 where rowtyp='job1typ') loop
 job1(r.id);
end loop;

Ehelyett próbálj ebben gondolkodni:

procedure fulljob1(filtertyp varchar) is
begin
 update tab1 set col1=calc(col1) where rowtyp=filtertyp;
end;

exec fulljob1('job1typ');

Sajnos a helyzet elég bonyolult.

Adott egy nagyobb táblázat (akár egymillió vagy még több rekorddal) ami egy bináris fát tárol ahol szülő-2gyerek kapcsolatok vannak és a szülő csomópontban lévő adatok kiszámítása a gyerek csomópont adatainak felhasználásával történik.

Erre a feldolgozásra készítettem egy (iteratív, mert a rekurzív hívások száma erősen korlátos a mysql-ben) post order fa bejáró eljárást ami egyszer bejárja a fát és kiszámolja minden csomópontban az adatokat.

A csomópontok adatainak kiszámolását végzik a most tárgyalt eljárások. Tehát a post order bejárás közben hajtódnak végre minden (akár egymillió vagy több) csomópontra a szükséges eljárások.

Ááá, szóval mégis volt rekurzió ;)

Elképzelhető-e egy ilyen megoldás:

Minden rekordhoz eltárolod, milyen mélyen van a fában: "LEVEL" oszlop, rajta pedig index. Ezt elég könnyű akár alkalmazásból, akár triggerrel karban tartani; sokat egyszerűsít, ha nem engedsz komplex fa műveleteket, de akkor sem vészes.

Az algoritmus valami hasonló:

CREATE FUNCTION calc ...
CREATE AGGREGATE FUNCTION aggcalc ...

curr_level:=select max(level) from tab;
update tab set col1=calc(col1) where level=curr_level;
curr_level:=curr_level-1;
while (curr_level>0) loop
 update tab t
  left outer join (select parent, aggcalc(col1) ncol1
   from tab
   where level=curr_level+1
   group by parent) tc on t.id = tc.parent
  set
   col1=(case when tc.parent is null then calc(col1) else ncol1 end)
  where level=curr_level;
 curr_level:=curr_level-1;
end loop;

A fában lentről felfelé megy az információ. Szintenként dolgozod fel az adatokat, mindig egyszerre egy teljes réteget a fában.

Először kiszámolod a levelekre, aztán egy a ciklussal haladsz felfelé.

A sebessége nagyban függ attól, hogy az aggcalc-ot mennyire gyorsra tudod megírni, feltéve, hogy egyáltalán szükség van rá, talán kiválthatod a meglévő aggregátorokkal, ezt nem tudhatom.

Upd: Ha nem lehet aggregációval megoldani (túl összetett a calc() algoritmus), akkor is maradhat a szintenkénti feldolgozás, de a ciklus belseje bonyolódni fog.

A feladatot még mindig felszínesen ismerem, de
HA komoly aggregáló algoritmussal kellene számolnom RDB-ben tárolt bináris fa esetén,
ÉS a fa közel statikus volna, vagy csak levélszinten terjeszkedhetne,
ÉS lehetőségem volna az adatstruktúrát és indexeket szabadon alakítani,
akkor a szokásos rekurzív b.id=a.szülőid-re és a szintekre alapozás helyett, vagy mellett felvenném az adatok közé a node-azonosítókat, amire indexet rakva (ha a motor támogatja a kifejezésalapú indexet, akkor a hosszára is) egyszerű sztringmanipulációkkal megkerülhető volna a rekurzió nyűgje.

Pl.:
nodeid varchar(50)

"0.1"
"0.2"
"0.1.1"
"0.1.2"
"0.1.1.1"
...

Szülő alapján a gyerek, gyerek alapján a szülő, adott szint mind végigsöpörhető, like-kal, substr() függvénnyel.

A fa szabályos jellege éppen nem szempont itt.
Sőt, a bináris jelleg sem kitétel, csak könnyebb, ha tudjuk, hogy max(count(gyerek)) = 2.

Nem árulok el ipari titkot azzal, hogy az IBM DB2 pureXML-e is erre az indexképzési technikára támaszkodik.

A viszonylagos konstans jelleg mint feltétel is "csak" a nodeazonosítók időrabló karbantartása miatt jelentkezik.

Erre feliratkozom!

--
Fontos! Ha berágok, nem feltétlen személyed ellen szól...
openSUSE 13.1 x86_64

Irj ra egy szkriptet.
Most komolyan, minek eroltetni azt, ami nem erre valo. Hacsak nincs valami extra kenyszerito korulmeny (peldaul radkiabalt a fonokod, hogy ezt a problemat PL/SQL-ben kell megoldani, kulonben kirug), akkor miert csinalsz magadnak pluszmunkat?