Firebird-Master-detail
2018-07-17T13:57:01+02:00
2018-07-20T09:39:41+02:00
2022-08-11T05:05:29+02:00
trefimanni
Sziasztok, van két Firebird táblám. Egy fej és hozzá tétel adatok. Amikor létrehozok a fej táblában egy rekordot, akkor az létrejön rendesen. Ehhez kellene hozzátennem a tétel adatokat. Ott akadtam el, hogy honnan tudom a garantáltan általam létrehozott utolsó fej rekord egyedi kulcsát, ami a tétel táblában idegen kulcsként persze benne van. Hálózati alkalmazásról van szó, ezért ha simán egy inserttel létrehozok fejet és szimplán lekérem az utolsó generátor állást, akkor lehet már egy másik userét kapom vissza. Amikor a fej adat létrejön, akkor az nem látszik semmilyen objektumban, tehát gridben stb.. így nincs mire kattintani, onnan nyilván egyszerű lenne kiszedni a már mentett rekord adatát. Ötlet valakinek?
Mutasd a teljes hozzászólást!

  • Tranzakció:
    beirod a fej adatot, lekérdezed a last_identify (nem biztos, hogy igy hivhák, MSSQL-ben igy) értékét, ez biztosan az, amit a session-ben a DB kiadott. Aztán insert a detail sorokat, végül commit, ha minden rendben ment.

    [Szerk]
    gyorsan rákerestem, mert régen használtam FB-t. A kiosztott ID-t egy generátortól kéred el, és azt  használod.
    Olvasd el ezt és ezt. Mindkettő elég jól leirja.
    Mutasd a teljes hozzászólást!
  • Triviális ötlet, ha júzerhez tudod kötni, a fejléc tábla INSERT-jébe tedd bele a user id-jét, ez alapján tudni fogod, melyik az utolsó fejléc sor, az ID kiolvasás után ezt akár nullázhatod is.
    Mutasd a teljes hozzászólást!
  • A generátorok tranzakció függetlenül léptetődnek.
    Mutasd a teljes hozzászólást!
  • Mutasd a teljes hozzászólást!
  • Ez valójában teljesen lényegtelen, mármint a tranzakció, ez jól látszik az első linken, amit adtam. Ha közvetlenül a generátortól kéri el az azonositót és azt használja, akkor nem kap 2 session azonosat.
    Mutasd a teljes hozzászólást!
  • Sziasztok, hú köszi a gyors és sok alternatívát. Igazából szinte mindegyiket érintettem már mint lehetséges megoldást, legalább megnyugszom hogy annyira nem vagyok lüke. Nade! Ez egy hálózatban futó alkalmazás lesz. Ha egy user egy táblára lekér egy új generátor osztotta ID-t, azzal le is commitolja mondjuk a táblát. A programkódban nyilván egyből lekérem az utoljára kiosztott ID-t, ezt amúgy nyilván egy trigger lökdösi előre. Mi van akkor ha előfordul az a valószínűtlen eset, hogy amíg egy user post-al rögzít egy rekordot, közben valaki szintén ezt teszi, vagy legalább igényel egy újabb ID-t, akkor én már ebben a nagyon vékony időrésben már a soron következő ID-t kapom vissza. A userkódhoz kötési ötlet tetszik. Mi a véleményetek, ki tud olyan helyzet alakulni amit előbb leírtam?
    Mutasd a teljes hozzászólást!
  • Én ZéZé válaszára szavazok.
    Ehhez át kell alakítanod a folyamatot, hogy a fejléc insertje adja vissza a beszúrt rekord neked kedves mezőjének értékét, ezzel tudsz tovább operálni a gyerek rekordok idegen kulcsaként.
    Ha lekérdezésként futtatsz egy ilyen insertet, 

    INSERT INTO t1 (...) values (...) returning id;
    eredményként egy oszlopos, egy soros választ kapsz, benne a beszúrt sor, adott mezőjének értékével.
    Mutasd a teljes hozzászólást!
  • Ne az insert-ben kérd le a generator id-t - mint ahogy a linkelt példában látszik - hanem a tranzakció elején és tárold el egy változóban, majd azt a változót használd a master és a detail insertekben is. Nem tudom, hogy pontosan, hogy épül fel a folyamatod, de ha a kliens oldali kód végzi a master-detail rögzítést, akkor ott, a tranzakció indításakor hívd meg a GEN_ID függvényt. Ha ezt egy tárolt eljárás végzi, akkor is ugyanez a recept.

    Ha kliens oldalon zajlik a történet és használsz entity-ket / objektumokat / rekordokat, amik reprezentálják a mastert és a detaileket, amik alapján az insert scripteket felépíted (vagy teszi ezt egy osztály önállóan), akkor a Master.Id-nak add értékül a GEN_ID eredményét a tranzakció elején, majd a Detail.ID-knak a Master.ID-t. 

    Ez a userkódhoz kötés nem tűnik egy elegáns megoldásnak, bár kötött szabályok szerinti környezetben működhet, de én nem csinálnék ilyet, mert lehet, hogy később problémákhoz vezet. A fenti minden körülmény között működik.
    Mutasd a teljes hozzászólást!
  • Szia!

    Korábban írta dgergely kolléga, hogy "A generátorok tranzakció függetlenül léptetődnek." Vagyis lényegtelen, hogy ki mikor és milyen Session/Tranzakcióban kéri le. Egyedi lesz. Táblához sem kötődik. Szerintem te kavarod az Autoincrement mezőkkel.

    Vagyis rögzítéskor:
    1. Köv. generátor érték lekérése
    (SQL statements for generators )

    2. Rekord kiírása az 1. pontban megadott Generátor felhasználásával.
    3. Rekord visszaolvas az 1. pontban kapott generátor felhasználásával.

    Üdv,
    Tibor
    Mutasd a teljes hozzászólást!
  • Nem kiigazítani akartalak, csak kiemeltem egy fontos részletet.
    Mutasd a teljes hozzászólást!
  • Első kérdés, hogy számít-e a ID kihagyásmentes folytonossága. Ha nem akkor, jó a generátor. Amennyiben fontos a kihagyásmentesség akkor a generátorokat el lehet felejteni.
    Én jelenleg így oldom meg:
    - Tranzakció nyitás
    - Utolsó sorszám meghatározása
    - Új rekord beszúrása fejbe/tételbe
    - Tranzakció zárás

    Commitnál két eredmény lehet: sikerül vagy nem. Ha nem sikerült és a hiba az egyező kulcs miatt van akkor újra próbálkozok. Ekkor ugyanis egy időben több helyen is nyitottak tranzakciót és ugyanarra ugrott. Helyi hálózaton erre nagyon kicsi az esély, mert a lekérdezés és beszúrás annyira kis idő, hogy valószínű nem fog ütközni a két bevitel. A lényeg, hogy a tranzakció nyitás és zárás között a lehető legrövidebb idő legyen.
    Kézi adatrögzítésnél a deadlock ellen úgy védekezek, hogy egyező kulcs esetén random várakozok 1-500ms ideig és utána próbálkozok max 5 alkalommal, utána dobok üzenetet a felhasználónak, hogy valami gond van.
    Mutasd a teljes hozzászólást!
  • Köszönöm a sok segítséget! Úgy látom azért nem ok nélkül merült fel bennem a probléma. Amit DGergely írt az a mondat piszkál, lehet ott van a kutya elásva, illetve a többiek hozzászólása szerint is lehet tévedek egy helyen. Tehát az kimondható, hogyha egy felhasználó létrehoz egy rekordot azt commit-olja, s rögtön utána lekérem az utolsó generátor számot, akkor az minden esetben az adott session-höz tartozó utolsót adja vissza, függetlenül attól, hogy egy másik user egy másik session-ben már növelte a generátor-t? Ezt hámoztam ki a sok leírásból, de lehet még mindig tévedek, akkor bocs. Valahogy én globálisan képzelem el az adatbáziskezelő generátorát. Úgy gondolkodtam, hogy futtasson egy programot akárhány felhasználó, hozzon létre rekordokat akárhány felhasználó, akkor a lekérdezett utolsó generátor állapot minden esetben az adatbázis globáljára értelmezett állapotot adja vissza. Ha ez nem igaz és úgy megy ahogyan leírtam, azaz session-onként kezeli, akkor meg van oldva a problémám. 

    Írta ezt Bery:"Ne az insert-ben kérd le a generator id-t - mint ahogy a linkelt példában látszik - hanem a tranzakció elején és tárold el egy változóban, majd azt a változót használd a master és a detail insertekben is." Ezt is csak úgy tudom elképzelni, hogyha session-önként kezeli. Mert ha nem akkor Bery leírása alapján a tranzakció kezdetén kiolvasok egy ID állapotot, közben létrejön a rekord az ID-vel és hozzá a detail is. Ha eközben egy másik user másik gépen ugyanezt teszi pont akkor amíg én a kiolvasott ID-vel rögzítem a rekordot, akkor összeütköznénk és kulcshibával elszállna a történet. 

    Hihetetlen milyen apróság (vagy annak tűnik) és mennyire nem egyértelmű a megoldás. 

    Amúgy hogy értsétek, a klasszikus működés az lenne, hogy létrehozok egy rekordot, mint fej, nyomok egy mentést a programban és megjelenik a létrehozott fej adat. Rajta lesz a kiosztott fej ID és hurrá. Nyomok rá egy tétel hozzáadást és már létre is tudom hozni a tételeket a megjelenített ID alapján. De itt most nem ez van sajna, a környezet és folyamat ezt nem teszi lehetővé, bár még elmélkedem miként érhetem el ezt a modelt. nem grideket használok, nincs master-detail grid. Minden esetben ragaszkodok egy rekord létrehozása során egy felviteli rögzítő képernyőhöz, azon normál mezőkkel, combókkal stb...

    Üdv.. 

    ja és szuper ez az oldal és a csapat! most használom először, de tuti nem utoljára.
    Mutasd a teljes hozzászólást!
  • Szerintem sehol sem szabad, hogy fontos legyen egy SQL-ben a tábla elsődleges kulcsának kihagyásmentes folytonossága.
    Mutasd a teljes hozzászólást!
  • Nem tudom, mit értesz session alatt, adatbáziskezelésben tranzakciók vannak.
    A generátorok a tranzakciók felett állnak, nem tudhatod, hogy melyik tranzakcióból növelték épp az értékét. A tranzakció nem zárolja a generátort.

    Amúgy a generátor aktuális értékének kiolvasása a GEN_ID függvénnyel lehetséges, de trükkös a dolog, mert két paramétere van.
    Ha lefuttatod a

    select GEN_ID(GENERA_TOROM, 0) from RDB$DATABASE;
    parancsot, akkor megkapod a nevezett generátor aktuális értékét, de mivel tranzakció független, ezért nem tudod, hogy ki, mikor, miért növelte.
    Ha lefuttatod az alábbi 

    select GEN_ID(GENERA_TOROM, 28) from RDB$DATABASE;
    parancsot, akkor az történik, hogy a generátor értéke megnő 28-cal, és eredményül kapod az új értékét.

    Milyen környezetben fejlesztesz egyébként?
    Mutasd a teljes hozzászólást!
  • Kavarod a dolgokat

    A generátor tranzakciófüggetlen globális számláló. Gyakorlatilag ahányszor lekéred az értékét visszaadja az aktuális értéket és automatikusan növelheti azt. Mindentől függetlenül (Tranzakció,Insert, stb) Így tudod biztosítani az egyedi azonosítót. De mivel nem tranzakcióhoz kötött, ha visszagörgeted a tranzakciót a generátorod értéke attól ugyanúgy a növelt érték marad vagyis nem lesz kihagyásmentes.

    Freewind-Gomboc adott egy egyszerű megoldást a kihagyásmentes sorszámozásra.

    Vedd külön a sorszámozást és az egy egyedi rekord azonosítót. Az egyedi rekord azonosítód legyen a Primary Key és legyen egy sorszám meződ ami Unique.
    Mutasd a teljes hozzászólást!
  • DGergely:

    delphi-ben dolgozok 

    session-ként azt értem, hogy párhuzamosan futtatott programok egy-egy számítógépről, amelyek ugyanazt az adatbázis használják, tehát klasszikus hálózati alkalmazás. Maga a program ugyanazon a szerveren fut, ahol az adatbázis is működik, a felhasználók egy parancsikonnal indítják a programot. Amit lehet próbálok adatbázisba tenni, nagyobb lekérdezéseket, fontosabb rekordkezelési műveleteket. 

    Közben írt nekem Vberko is: 

    A problémámat vonatkoztassuk el egy master-detail történettől. Írok már inkább egy példát, akkor hátha jobban tudtok segíteni nekem, valami még nagyon nem esett le, hiányzik az a nagy áttörés:

    Működik hálózatban egy program, használják mondjuk 5-en. Van egy táblám X. Annak van egy PrimaryKey mezője ID, ebbe egy generátor adta számokat szándékozok íratni egy triggerrel.

    Létrehoz A1 user egy rekordot egy insert paranccsal, generátor kiadja neki az 1-es számot. Közben A2......A5-ig ugyanezt teszi, tegyük fel sorrendben. A1 user hozza létre az első rekordot, megkapja a generátortól az 1-es számot. Én lekérdezem azonnal, hogy melyiket kapta, azaz lekérem a generátor állapotot. Amíg én lekérem és közben A2 már növelte 2-re a generátor, mert az ő commit-ja előbb lement, mint az én query-m akkor a 2-est fogom visszakapni? Jól tévedek? Ha ez így megy, akkor miként kapom vissza A1 user 1-es sorszámát tutira?
    Mutasd a teljes hozzászólást!
  • Amíg én lekérem és közben A2 már növelte 2-re a generátor, mert az ő commit-ja előbb lement, mint az én query-m akkor a 2-est fogom visszakapni?

    Fentebb írtam, hogy miért. Mert a generátor felette áll minden tranzakciónak.

    Milyen komponensekkel valósítod meg a beszúrást?
    A Delphiben van egy komponens, amiben meg tudod írni a select, delete, insert, update szkripteket és a refresh szkriptet is előre.
    Mutasd a teljes hozzászólást!
  • Nálam számlázóprogramnál a számlaszám az elsődleges kulcs, így ott nem árt ha folytonos annak a száma :)
    Sok helyen valóban nem számítana, de én egyéb esetben is szoktam ügyelni rá. Igaz, lehet ez az én hülyeségem.
    Mutasd a teljes hozzászólást!
  • Az elsődleges kulcsnak egyetlen feladata van, hogy azonosítsa a rekordot.
    Gondold el, ha X év múlva a megrendelőd azt mondja, hogy archiválja a számlákat, amiket már nem kell megőriznie, de neki kell a rendszerben, hogy látszódjon, és át akarja íratni veled a számlaszámokat, mondjuk egy ARCH prefixszel, ezt egy egyszerű update-tel már nem tudod megcsinálni.
    Mutasd a teljes hozzászólást!
  • szoktam használni az IBDataSet komponenst, jelen esetben paraméterként adom oda az értékeket egy insert utasításnak. 

    Akkor azért a generátort csak jól láttam, hogy az vakvágány lesz, azaz függetlenül megy előre ki mit csinál. Pont ez a bajom kezdetek óta. Akkor a rekord egyedi ID-jét nehezen tudom használni arra, hogy egy adott létrehozott rekordot beazonosítsak azonnal az insert után. Amint írtam régebben ez nem okozott problémát, létrejött a rekord, megmutattam a usernek egy gridben. Ő kiválasztotta a rekordot és azzal utána tudott dolgozni, egyértelmű volt beazonosítani, hogy ha a rekordhoz kötök bármit, akkor mi a kulcs, hisz ott volt a grid mezőjében. Most ugye nem jelenik meg nekem sehol mondjuk úgy a fej infó, amihez majd 1:N kapcsolattal tételeket kötök. Lehet valahogyan a dizájnt és a folyamatot kell úgy átalakítsam, hogy ez egyértelmű legyen. Bár nem! Még így se lesz jó, mert amikor egy user létrehoz egy rekordot egy táblába, illetve párhuzamosan a többi user is, amikor a gridbe lekérem egy query-vel a tábla adatokat, akkor abban már ott fog virítani minden user rekordja. megint nem tudom melyik kell nekem, csak abban az esetben ha a programban a user rákattintana a saját adatára. Mostani programnál sajna ez nem kivitelezhető...
    Mutasd a teljes hozzászólást!
  • Figyi, van valahol egy megírt insert szkripted, amit felparaméterezel, és lefuttatsz, így jön létre az új rekord. Ezt alakítsd át úgy,hogy a végére beírod a "returning id" -t.

    INSERT INTO t1 (...) values (...) returning id;
    Ezt úgy futtasd, mintha select lenne, és olvasd ki az ID field értékét, és ott a keresett érték.
    Innentől ezt paraméterként már tovább tudod adni a gyerek soroknak.
    Mutasd a teljes hozzászólást!
  • Szia, ez 1.5-ös firebird-ön működik? Nálunk még ehhez kell ragaszkodjak. Úgy olvastam ez nem egy csak magasabb verzión...AMúgy beírtam, más segítő is feldobta ezt az ötletet, de nem hagyja a delphi...
    Mutasd a teljes hozzászólást!
  • Írta ezt Bery:"Ne az insert-ben kérd le a generator id-t - mint ahogy a linkelt példában látszik - hanem a tranzakció elején és tárold el egy változóban, majd azt a változót használd a master és a detail insertekben is." Ezt is csak úgy tudom elképzelni, hogyha session-önként kezeli. Mert ha nem akkor Bery leírása alapján a tranzakció kezdetén kiolvasok egy ID állapotot, közben létrejön a rekord az ID-vel és hozzá a detail is. Ha eközben egy másik user másik gépen ugyanezt teszi pont akkor amíg én a kiolvasott ID-vel rögzítem a rekordot, akkor összeütköznénk és kulcshibával elszállna a történet.

    Nem, nem lenne ütközés, mivel a generátor lekérése egyből automatikusan növeli is a generátor értékét, ha megfelelően hívod meg. Ha ezzel a select-el növeled és lekérdezed a generátort egyben:

    select GEN_ID(GENERA_TOROM, 1) from RDB$DATABASE;
    akkor megkapod a következő (az utoljára kiosztottnál 1-el nagyobb) értékét, amit még senki nem használt és egyből nő egyel az értéke. Fontos, hogy a select második paramétere 1 legyen (lehet 1-nél nagyobb szám is, de minek?!), mert ez mondja meg a generátornak, hogy az utoljára használthoz képest 1-el növelt értéket adjon vissza. Tranzakció Rollback nincs rá hatással, tehát nem fog visszaállni, csak mindig makacsul növekszik :). Kivéve, ha direkt átállítod az értékét.

    Tehát még egyszer:  ha egy felvitel előtt így lekéred (és növeled is egyben) a generátort és ezt adod meg a master.id-ba, majd a detail.master_id-ba is, akkor ez egy telkesen egyedi kulcs lesz, ezt konkurens felhasználó nem tudja már lekérdezni.

    Nem egy programot írtam Delphi-ben, de még C#-ban is részt vettem olyan ügyviteli rendszer fejlesztésében, ami ezzel a módszerrel dolgozott, több konkurens felhasználóval és tutira állítom neked, hogy ez egy működő módszer. Már Firebird 1.0 alatt is ezt használtam és Firebird 1.5 és 2.0 és 2.5 alatt sem volt gond vele (3.0-át sosem használtam, de gondolom semmi nem változott ezen a téren).
    Mutasd a teljes hozzászólást!
  • Szia Bery, ez tetszik! Így belátható, hogy bármit is kérek le, attól mindíg eggyel nagyobb azaz különbözőt kapok. Ezt akár már mondjuk egy szimpla delphi query komponens, vagy TIBtable komponens esetén akár a before insert-nél lekérhetem, ugye?  bemegy egy változóba és azzal dolgozok egészen a post-ig, illetve commit-ig. 

    MINDENKI válaszát nagyon köszönöm, bárki is segített! 

    Üdv. és további jó munkát.
    Mutasd a teljes hozzászólást!
  • Igazából bármikor lekérheted a generátort, kapsz egy egyedi sorszámod. Oda teszed be a rögzítési folyamatodba, ahová akarod.
    Mutasd a teljes hozzászólást!
abcd