מפתח אוטומטי

פורומים אפיון ופיתוח פריוריטי מפתח אוטומטי

  • הנושא הזה ריק.
  • Post
    אלמוני
    אורח
    שלום וברכה למומחי הפורום.

    יש לי טבלה שאין לה מפתח ראשי טבעי, [לדוגמא: טבלת לוגים. רשומה יכולה להיות זהה לחלוטין לחברתה, למעט המפתח הראשי]

    המשתמש איננו מתעניין במפתח, והוא לא מגדיר את המפתחות, ולכן עמודת המפתח מוסתרת.

    משום מה, פריוריטי החליטו שאי אפשר להשתמש בעמודת מספור אוטומטי [מפתח A] כמפתח ראשי, וחובה להוסיף גם מפתח U.

    ניסיתי לעשות כך:
    הגדרתי לטבלה עמודה בשם ID שהיא מספור אוטומטי.
    הגדרתי גם עמודה ID2 שהיא מפתח U.
    בטופס, ניסיתי להגדיר טריגר POST-UPDATE שיעתיק את הערך מעמודה ID לעמודה ID2.

    הבעיה: הטריגר איננו מעדכן את עמודה ID2, כנראה שהוא מתבצע לפני שעמודה ID מקבלת את ערך המספור האוטומטי.

    מה הדרך המקובלת להתמודד עם הבעיה?

    תודה רבה

מוצגות 13 תגובות – 1 עד 13 (מתוך 13 סה״כ)
  • Replies
    אלמוני
    אורח
    פריוריטי מתגברת על הבעיה הזו באמצעות טבלה בשם LASTS אשר מכילה את הערך האחרון של כל מיני דברים, כולל טבלאות. לפני שמירת שורה בטבלה, הקוד קודם משיג את הערך האחרון ששמור בטבלת LASTS, מעדכנת את המספר הזה ואז מנסה להכניס לתוך טבלת הלוג את השורה החדשה. יש לולאה אשר מטפלת במקרה מישהו כבר תפס את המספר שהוקצב. בסוף המספר החדש נשמר בחזרה בטבלת LASTS לפעם הבאה.

    :LOGFILE_KLINE = :LOGFILE_DATE = SQL.DATE ;
    SELECT VALUE INTO :LOGFILE_KLINE FROM LASTS
    WHERE NAME = 'XXXX_CHANGE_LOG';
    GOTO 512 WHERE :RETVAL > 0;
    INSERT INTO LASTS (NAME, VALUE)
    VALUES ('XXXX_CHANGE_LOG', SQL.DATE);
    LABEL 512;
    :LOGFILE_KLINE = :LOGFILE_KLINE + 1;
    SELECT ENTMESSAGE('ORDERS','F',800) INTO :TEXT FROM DUMMY ;
    INSERT INTO XXXX_ORD_CHANGE_LOG
    (LOG, ORD, USER, UDATE, TEXT)
    VALUES(:LOGFILE_KLINE, :ORD, SQL.USER, SQL.DATE, :TEXT);
    LOOP 512 WHERE :RETVAL < 1;
    UPDATE LASTS SET VALUE = :LOGFILE_KLINE
    WHERE NAME = 'XXXX_CHANGE_LOG';
    אלמוני
    אורח
    תודה מכל הלב! אנסה ואעדכן.
    אלמוני
    אורח
    נעם, תודה רבה, יישמתי את השיטה וכתבתי קוד כזה.
    אשמח לקבל הערות או שיפורים לקוד.

    :LASTID = :NEWID = 0;
    :NAMEIDENTIFIER = 'MYTABLE_ID';
    LABEL 100;
    GOTO 120 WHERE EXISTS (SELECT * FROM LASTS WHERE NAME
    = :NAMEIDENTIFIER);
    INSERT INTO LASTS(NAME, VALUE)
    VALUES(:NAMEIDENTIFIER, 0);
    LABEL 120;
    SELECT VALUE INTO :LASTID FROM LASTS
    WHERE NAME = :NAMEIDENTIFIER;
    :NEWID = :LASTID + 1;
    UPDATE LASTS SET VALUE = :NEWID
    WHERE NAME = :NAMEIDENTIFIER AND VALUE = :LASTID;
    /* resolve concurrency conflicts */
    LOOP 100 WHERE :RETVAL < 1;
    :$.ID = :NEWID; /* set new id */

    השאלה: איזו הפעלה תפעיל את הקוד הזה?

    ניסיתי הפעלות ברמת הטופס, כמו PRE-INSERT, לא עובד. כאשר אני בא לשמור את הרשומה אני מקבל התראת שגיאה שהשדה ID אין בו ערך.

    ניסיתי הפעלות ברמת השדה ID, זה עובד אבל רק כאשר נכנסים לשדה או יוצאים ממנו.
    אם השדה מוסתר, לא עובד.

    מה הפתרון?

    אלמוני
    אורח
    נדמה לי שהגעתי לתוצאה משביעת רצון, תודה רבה לנועם על העזרה.

    במקרה שמישהו אחר יתקל במשימה, להלן הפתרון:

    1. בטבלה מגדירים עמודה מסוג INT ועליה מפתח U שישמש במפתח הראשי.
    2. לטופס, מוסיפים את שדה המפתח כשהוא נסתר או לקריאה בלבד.
    3. בטופס, צריך להגדיר אירוע POST-FIELD, באחת משתי אפשרויות:
    – אם לטופס יש שדות חובה, אפשר להגדיר את האירוע לאחד משדות החובה.
    – אם אין לטופס אף שדה חובה, צריך להגדיר את האירוע בכל שדות הטופס, כדי שכל אתחול של הרשומה יריץ את האירוע.
    4. הקוד של האירוע הוא כדלהלן:
    END WHERE :$.your_unique_column_name > 0;
    :LAST_NAME = 'your_table_name_UNIQUEID';
    :LASTID = :NEWID = 0;
    LABEL 100;
    GOTO 120 WHERE EXISTS (SELECT * FROM LASTS WHERE NAME
    = :LAST_NAME);
    INSERT INTO LASTS(NAME, VALUE)
    VALUES(:LAST_NAME, 0);
    LABEL 120;
    SELECT VALUE INTO :LASTID FROM LASTS
    WHERE NAME = :LAST_NAME;
    :NEWID = :LASTID + 1;
    UPDATE LASTS SET VALUE = :NEWID
    WHERE NAME = :LAST_NAME AND VALUE = :LASTID;
    /* resolve concurrency conflicts */
    LOOP 100 WHERE :RETVAL < 1;
    :$.your_unique_column_name = :NEWID; /* set new id */

    כמובן, שיש להחליף את השמות שבקוד לשמות האמיתיים [בשתי השורות הראשונות ובשורה האחרונה].

    מטעמי נוחות וסדר, כדאי לעשות עוד כמה פעולות:
    5. להוסיף BUFFER כללי [בשם BUFSetId או דומה לזה]
    6. בטופס, ברמת הטופס, להגדיר אירוע בשם הBUFFER שלנו, עם הקוד דלעיל.
    7. באירועי השדות, לעשות INCLUDE לבBUFFER שלנו.

    אני מקווה שזה יעזור למישהו.

    אלמוני
    אורח
    יש לציין שכתיבת הערך החדש לטבלת LASTS צריך להתבצע *רק אחרי* רישום הרשומה החדשה לטבלה הפרטית. בדוגמא שלי, היא מתבצעת אחרי פקודת ה-LOOP שמנסה לשמור. בקוד שלך אינני רואה את כתיבת הרשומה החדשה לטבלה שלה.
    אלמוני
    אורח
    לעניות דעתי, הקוד שלך איננו מונע עריכה בו-זמנית ע"י שני משתמשים. עד שאתה עושה UPDATE ייתכן שמישהו כבר תפס את המספר הזה.

    בקוד שלי, הבעיה נפתרה כך
    בתחילה אני מוודא שיש רשומה בטבלת LASTS. אם אין רשומה, אני מוסיף אותה כשהערך ההתחלתי הוא 0.

    אחר-כך אני שולף את הערך הקיים ברשומת LAST, מוסיף לו 1, ומייד מעדכן אותו בטבלת LASTS כדי לתפוס את המספר הזה.
    תוך כדי שאני תופס, אני מוודא שאף אחד אחר לא תפס לי את המספר הזה.
    UPDATE LASTS ...
    WHERE VALUE = :LASTID

    אם אחרי הUPDATE הזה RETVAL = 1, אני יודע שיש לי מספר תפוס, שאף אחד אחר לא ישתמש בו, ואני יכול להשתמש בו בבטחה.
    אם RETVAL = 0, אני יודע שמישהו נגע לי ברשומה, ותפס לי את המספר שרצית לעדכן, ואני צריך לחזור על הלולאה.
    [מטעמי בטחון, אני חוזר לשורה LABEL 100, כי אולי מישהו מחק לי את הרשומה]

    אלמוני
    אורח
    להלן השוני בגישות: בקוד של פריוריטי, יש נסיון קודם לכתוב אל הטבלה הפרטית ורק לאחר כתיבה מוצלחת שומרים את ערך המפתח . בגישה שלך, אתה שומר את המפתח בחזרה בטבלת LASTS עוד לפני נסיון כתיבה. זה עלול ליצור בעיות אם שני אנשים מנסים לכתוב בו זמנית.

    הקוד בפריוריטי מנצל את מנוע ה-SQL בכך שניתן ליצור רק רשומה אחת בעל מפתח מסוים. במקום לבדוק מול LASTS אם המפתח תקין, הבדיקה נעשית ישירות מול הטבלה הפרטית לכן מונעת מה שנקרא בתורת האלגורתמים RACE CONDITIONS, כאשר שני משתמשים מנסים לעדכן את אותה הדבר. המפתח הראשי של הטבלה הפרטית הוא ID, בעוד המפתח הראשי בטבלת LASTS אינו מספר המפתח אלא שם כלשהו.

    הקוד למטה מונע את המצב בו אתה מקבל מספר מפתח תקין אל מישהו אחר מקבל את אותו המספר ואף מצליח לכתוב איתו לפני שאתה מנסה. יותר חשוב לעדכן את הטבלה הפרטית מאשר טבלת LASTS.

    LABEL 100;
    :ID = :ID + 1;
    INSERT INTO XXXX_TABLE (ID, FIELD)
    VALUES (:ID, 'A');
    LOOP 100 WHERE :RETVAL < 1;
    UPDATE LASTS
    SET ....

    אני רוצה להדגיש שהקוד למעטה פותח ע"י מפתחי פריוריטי ולא על ידי, לכן ניתן להסיק שהם ידעו מה נכון.

    אלמוני
    אורח
    אינני יודע פריוריטי היטב, אבל SQL אני יודע היטב.

    למיטב הבנתי, ואשמח אם תתקן אותי, אם אתה מתחיל רשומה חדשה ומריץ את הקוד הבא:
    :$.MY_UNIQUE_COLUMN = 1001;

    זה לא מונע ממשתמש אחר לבוא אחריך ולהתחיל גם הוא רשומה חדשה עם אותו ID.
    האפליקציה שלו איננה יודעת שאתה כבר תפסת את המספר הזה.
    אם הוא יייצא מהמסך לפניך, הרשומה שלו תישמר בהצלחה, וכאשר אתה תבוא לשמור את הרשומה תקבל שגיאת הפרת מפתח ותהיה בבעיה.

    בדקתי באמצעות SQL PROFILER וראיתי שכאשר אתה מגדיר את הID, אין שום עדכון בדטהבייס ואף אחד איננו יודע שתפסת את המספר 1001.

    הקוד שלי פותר את הבעיה בדרך פשוטה ואלגנטית.

    אלמוני
    אורח
    מחקתי את ההודעה וכללתי אותה בהודעתי הקודמת.
    אלמוני
    אורח
    נכון, כל אחד יכול *לנסות* ולשמור רשומה עם מפתח 1001. אבל רק אחד יצליח! זו הסיבה שבודקים את ערך המשתנה RETVAL – למשתמש שהצליח, RETVAL יהיה 1 והוא ייצא מהלולאה אל הפקודה ששומרת את הערך שלו בטבלת LASTS. למשתמש השני, RETVAL יהיה 0 לכן ינסה לשמור את רשומה עם מפתח 1002. הצליח – שומר את הערך 1002. לא מצליח – מנסה את 1003 וכך הלאה.
    אלמוני
    אורח
    חזרתי וקראתי את הקוד שלך ועתה הבנתי שהINSERT הוא לתוך טבלת היעד, ובעצם הוא יוצר את הרשומה המבוקשת.

    אכן, זה פותר את בעיית הבו-זמניות, אבל לעניות דעתי פתרון תמוה ומסוכן מאד.

    אם אתה מכניס את הרשומה בצורה ידנית אתה שובר את העקרון שאין לשנות נתונים ידנית!

    נניח שיש לי טופס עם 20 שדות חובה, ועוד חוקים עסקיים ועוד טריגרים, ואתה בא ומכניס את הרשומה בצורה ידנית, באמצעות INSERT.
    מה עם שדות החובה? ומה עם כל הטריגרים?

    לדעתי זו פרקטיקה בעייתית, ואם מתכנתי פריוריטי עשו כך זה חמור מאד. בשביל מה יש טפסים אם בסוף אתה עוקף אותם ועושה INSERT ידני?

    אבל הקוד שלי לא עושה שום INSERT אלא רק תופס ID. אחרי שיש לך ID פנוי אתה יכול להמשיך להזין את הרשומה כרגיל.

    וזו הדרך המקובלת להתמודד עם מספור אוטומטי. השרת מקצה ID לכל משתמש שרוצה להזין רשומה, אבל הרשומה נשמרת רק כאשר המשתמש בא לשמור.

    אלמוני
    אורח
    אל תשכח שאנחנו מדברים על טבלה ללא מפתח טבעי, כמו יומן סטטוסים. אתה מתאר משהו כמו תנועות יומן: אם תסתכל על טבלת FNCITEMS, תראה שאין מפתח אחד יחודי. מתפח הטבלה מורכב משני שדות: FNCTRANS, מספר תנועת היומן, ו-KLINE, מספר השורה בתוך התעודה. אף אחד מהמספרים האלה אינו יחודי אך ביחד הם יוצרים מפתח יחודי.
    אלמוני
    אורח
    טבלת לוגים זו היתה דוגמא, יש עוד הרבה מקרים שאין מפתח ראשי טבעי וצריכים מספור אוטומטי.

    ובכל מקרה, אני חושב שINSERT ידני זו פרקטיקה גרועה ומסוכנת.

    ולצערי מפתחי חב' פריוריטי עלולים לטעות וגם לעשות עושים דברים מוזרים.

    כשקראתי את הודעתך הראשונה הפותחת במילים

    פריוריטי מתגברת על הבעיה הזו באמצעות טבלה בשם LASTS

    חייכתי חיוך מר, כי לדעתי פריוריטי איננה מתגברת על הבעיה אלא יוצרת אותה.
    השימוש בטבלת LASTS הוא פתרון עקום מאד וגם מסורבל מאד. אין שום הגיון לאלץ את המפתחים לכתוב קוד שכל תפקידו הוא לנהל מיספור אוטומטי.

    פתרון אמיתי של הבעיה הוא באמצעות מפתח A שהוא גם מפתח U.

מוצגות 13 תגובות – 1 עד 13 (מתוך 13 סה״כ)
  • יש להתחבר למערכת על מנת להגיב.