Ü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.
- 8921 megtekintés
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.
- A hozzászóláshoz be kell jelentkezni
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
- A hozzászóláshoz be kell jelentkezni
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.
- A hozzászóláshoz be kell jelentkezni
Pl. ha php-val csinálod, akkor az "alap" mysql_query egyet tud végrehajtani, ha többet akarsz, arra külön proc. van:
- A hozzászóláshoz be kell jelentkezni
Nem szeretném kivinni a problémát a mysql térből egy másikba csak azért mert ott könnyebb programot írni a feladatra. Többet veszítenék vele mint amennyi a nyereség.
- A hozzászóláshoz be kell jelentkezni
Miért gond egyenként végrehajtani?
Funkcionalitásban ugyanazt fogja tudni, hosszabb se lesz a kódod, csak kicsit máshogy kell szervezni.
- A hozzászóláshoz be kell jelentkezni
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?
- A hozzászóláshoz be kell jelentkezni
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.
- A hozzászóláshoz be kell jelentkezni
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.
- A hozzászóláshoz be kell jelentkezni
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.
- A hozzászóláshoz be kell jelentkezni
A kurzorozást még megpróbálhatnád temporary memory táblával is, where nélkül.
A "megduplázza" helyett inkább azt mondanám rádob egy percet: ha a tárolt eljárásaid is érdemi munkát végeznének (pl. kurzorokat nyitogatnak), javulna az arány.
- A hozzászóláshoz be kell jelentkezni
Igen jobb megfogalmazás a "rádob egy percet".
Sajnos nem vagyok sql expert (csak hobbi szinten foglalkozok programozással) így nem értem a "temporary memory tábla" és a "where nélkül" összefüggését. Pár mondatban kifejtenéd mire gondoltál? Előre is köszönöm.
- A hozzászóláshoz be kell jelentkezni
Valami ilyesmi:
CREATE TEMPORARY TABLE proctemp (proc varchar(255)) ENGINE=MEMORY;
Ezután minden session előtt:
truncate table proctemp; /*csak a biztonsag kedveert*/
insert into proctemp SELECT proc FROM t WHERE type='t1';
DECLARE cur CURSOR FOR SELECT proc FROM proctemp;
[...]
- A hozzászóláshoz be kell jelentkezni
Köszi a tippet.
A fentiek megfogadásával módosítva a programot a futási idő csökkent: 1m50.246s
- A hozzászóláshoz be kell jelentkezni
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');
- A hozzászóláshoz be kell jelentkezni
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.
- A hozzászóláshoz be kell jelentkezni
Ááá, 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 hozzászóláshoz be kell jelentkezni
Köszi a tanácsokat, átgondolom.
- A hozzászóláshoz be kell jelentkezni
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 hozzászóláshoz be kell jelentkezni
Sajnos a fa egyáltalán nem szabályos. Szélsőséges esetekben lehetnek benne pl. "egyenes" részfák, stb. Ezért olyan megoldásokat kell alkalmazni ami általános esetekben is működik és nem támaszkodik semmilyen szabályszerűségre a szokásos összefüggéseken túl.
- A hozzászóláshoz be kell jelentkezni
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.
- A hozzászóláshoz be kell jelentkezni
Erre feliratkozom!
--
Fontos! Ha berágok, nem feltétlen személyed ellen szól...
openSUSE 13.1 x86_64
- A hozzászóláshoz be kell jelentkezni
A rekurzió lesz a te barátod: http://stackoverflow.com/questions/3438111/mysql-stored-procedure-that-…
--
Kis problémából egy kis munkával nagy problémát lehet gyártani. Ha valami müxik ne b***tasd :)
Uriember az, aki nem beszél a Windows-ról, pedig tudna...
- A hozzászóláshoz be kell jelentkezni
Hogy jön ide a rekurzió?
- A hozzászóláshoz be kell jelentkezni
Nem értem hogyan segít a rekurzió ebben az esetben.
- A hozzászóláshoz be kell jelentkezni
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?
- A hozzászóláshoz be kell jelentkezni