DCSIMG
March 2011 - Posts - גרי רשף

March 2011 - Posts

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

Select 'ט"ו בניסן' [פסח];

clip_image002

וברצינות- הפונקציה הנ"ל (ניתן למצוא אותה כאן בצירוף פונקציות נוספות) מתבססת על נוסחה שפיתח גאוס - מתמטיקאי גרמני שהיה אולי גדול המתמטיקאים מאז ומעולם, ואם לא- אז בוודאי בחמישיה הפותחת; ואשר נועדה במקורה לחשב את מועדו של ה-Easter - חג הפסחא הנוצרי שמועדו מתבסס על הירח ומשמר במידת מה את מקורו היהודי וחל ביום ראשון שלאחר מילוא הירח שלאחר יום השיוויון (כלומר- גם חג האביב וגם מתייחס לאמצע החודש העברי), ובשינויים מסויימים הוא הותאם לחישוב חג הפסח היהודי.

מי שרוצה לעקוב אחר שיטת החישוב של גאוס יכול להציץ כאן, ומי שרוצה בעברית-

clip_image004מתוך "המליץ"- כתב עת ציוני עברי שיצא ברוסיה במחצית השניה של המאה ה-19 (מי שרוצה לעיין בהמשך המאמר- כאן בעמוד 3).

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

CREATE function dbo.Passover(@Yr int)
returns datetime
AS
BEGIN
   Declare @HYear int, @Matonic int, @LeapException int, @Leap int, @DOW int, @Century int
   Declare @fDay float(20), @fFracDay float(20)
   Declare @Mo int, @Day int
   Set @HYear=@Yr+3760
   Set @Matonic=(12*@HYear+17) % 19
   Set @Leap=@HYear % 4
   Set @fDay=32+4343/98496.+@Matonic+@Matonic*(272953/492480.)+@Leap/4.
   Set @fDay=@fDay-@HYear*(313/98496.)
   Set @fFracDay=@fDay-FLOOR(@fDay)
   Set @DOW=cast (3*@HYear+5*@Leap+FLOOR(@fDay)+5 as int) % 7
   IF @DOW=2 or @DOW=4 or @DOW=6
      set @fDay=@fDay+1
   IF @DOW=1 and @Matonic>6 and @fFracDay>=1367/2160.
      set @fDay=@fDay+2
   IF @DOW=0 and @Matonic>11 and @fFracDay>=23269/25920.
      set @fDay=@fDay+1
   Set @Century=FLOOR(@Yr/100.)
   Set @LeapException=FLOOR((3*@Century-5)/4.)
   IF @Yr>1582
      set @fDay=@fDay+@LeapException
   Set @Day=FLOOR(@fDay)
   Set @Mo=3
   IF @Day>153
      Begin
         set @Mo=8
         set @Day=@Day-153
      End
   IF @Day>122
      Begin
         set @Mo=7
         set @Day=@Day-122
      End
   IF @Day>92
      Begin
         set @Mo=6
         set @Day=@Day-92
      End
   IF @Day>61
      Begin
         set @Mo=5
         set @Day=@Day-61
      End
   IF @Day>31
      Begin
         set @Mo=4
         set @Day=@Day-31
      End
   return cast(str(@Mo)+'/'+str(@Day)+'/'+str(@Yr) as datetime)
/* Based on mathematical algorithms first devised by the German mathematician Carl Friedrich Gauss (1777-1855).  I have used the date of Passover to determine most of the other Jewish holidays.*/
END
GO

וכעת- נחשב קודם כל את השנים, את ראשי השנה של כל אחד (הקודם והבא), ואת ההפרש בינהם בימים:

Declare    @MiShana Int,
        @AdShana Int;
Select    @MiShana=5661,
        @AdShana=6000;
With Shanim As
(Select    @MiShana ShanaI,
        @MiShana-3761 ShanaL,
        Cast(Null As DateTime) RoshHashanaHakodem,
        dbo.Passover(@MiShana-3761)+163 RoshHashanaHaba,
        Cast(Null As Int) Yamim
Union All
Select    ShanaI,
        ShanaL,
        RoshHashanaHakodem,
        RoshHashanaHaba,
        DateDiff(Day,RoshHashanaHakodem,RoshHashanaHaba) Yamim
From    (Select    ShanaI+1 ShanaI,
                ShanaL+1 ShanaL,
                RoshHashanaHaba RoshHashanaHakodem,
                dbo.Passover(ShanaI-3760)+163 RoshHashanaHaba
        From    Shanim
        Where    ShanaI<@AdShana) T)
Select    ShanaI-1 ShanaI,
        ShanaL-1 ShanaL,
        RoshHashanaHakodem,
        RoshHashanaHaba,
        Yamim
From    Shanim
Where    Yamim Is Not Null
option (MaxRecursion 0);
Go

ולבסוף נוכל להוסיף את החודשים והימים כמו בפוסט הקודם:

Declare    @MiShana Int,
        @AdShana Int;
Select    @MiShana=5661,
        @AdShana=6000;
With Shanim As
(Select    @MiShana ShanaI,
        @MiShana-3761 ShanaL,
        Cast(Null As DateTime) RoshHashanaHakodem,
        dbo.Passover(@MiShana-3761)+163 RoshHashanaHaba,
        Cast(Null As Int) Yamim
Union All
Select    ShanaI,
        ShanaL,
        RoshHashanaHakodem,
        RoshHashanaHaba,
        DateDiff(Day,RoshHashanaHakodem,RoshHashanaHaba) Yamim
From    (Select    ShanaI+1 ShanaI,
                ShanaL+1 ShanaL,
                RoshHashanaHaba RoshHashanaHakodem,
                dbo.Passover(ShanaI-3760)+163 RoshHashanaHaba
        From    Shanim
        Where    ShanaI<@AdShana) T),
Hodashim As
(Select 1 ID, 'תשרי' Hodesh,30 Yamim, Null Sug Union All
Select 2 ID, 'חשוון' Hodesh,29 Yamim, Null Sug Union All
Select 2 ID, 'חשוון' Hodesh,30 Yamim, 'מלא' Sug Union All
Select 3 ID, 'כסלו' Hodesh,30 Yamim, Null Sug Union All
Select 3 ID, 'כסלו' Hodesh,29 Yamim, 'חסר' Sug Union All
Select 4 ID, 'טבת' Hodesh,29 Yamim, Null Sug Union All
Select 5 ID, 'שבט' Hodesh,30 Yamim, Null Sug Union All
Select 6 ID, 'אדר' Hodesh,29 Yamim, Null Sug Union All
Select 6 ID, 'אדר א' Hodesh,30 Yamim, Null Sug Union All
Select 6 ID, 'אדר ב' Hodesh,29 Yamim, Null Sug Union All
Select 7 ID, 'ניסן' Hodesh,30 Yamim, Null Sug Union All
Select 8 ID, 'אייר' Hodesh,29 Yamim, Null Sug Union All
Select 9 ID, 'סיוון' Hodesh,30 Yamim, Null Sug Union All
Select 10 ID, 'תמוז' Hodesh,29 Yamim, Null Sug Union All
Select 11 ID, 'אב' Hodesh,30 Yamim, Null Sug Union All
Select 12 ID, 'אלול' Hodesh,29 Yamim, Null Sug),
Yamim As
(Select 1 Yom
Union All
Select    Yom+1
From    Yamim
Where    Yom<30)
Select    S.ShanaI-1 ShanaI,
        H.Hodesh,
        H.Sug,
        Y.Yom,
        DateAdd(Day,Row_Number() Over(Partition By S.ShanaI Order By H.ID,H.Hodesh,Y.Yom)-1,S.RoshHashanaHakodem) TaarihL
From    Shanim S
Inner Join Hodashim H
        On ((S.Yamim<=355 And H.Hodesh Not In ('אדר א','אדר ב'))
            Or (S.Yamim>=383 And H.Hodesh Not In ('אדר')))
        And ((S.Yamim%10=3 
                And (H.Hodesh<>'חשוון' Or H.Sug Is Null)
                And (H.Hodesh<>'כסלו' Or H.Sug='חסר'))
            Or (S.Yamim%10=4 
                And H.Sug Is Null)
            Or (S.Yamim%10=5 
                And (H.Hodesh<>'חשוון' Or H.Sug='מלא')
                And (H.Hodesh<>'כסלו' Or H.Sug Is Null)))
Inner Join Yamim Y
        On H.Yamim>=Y.Yom
Order By S.ShanaI,
        H.ID,
        H.Hodesh,
        Y.Yom
option (MaxRecursion 0);
Go

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

תחילתה של הספירה לפי המסורת הוא ביום שישי בו נברא האדם - על פי המסופר בבראשית פרק א', בשעה 08:00 בבוקר (שעה 14 לפי המניין העברי המונה את השעות לא מחצות הלילה אלא משעה 18:00 ביום הקודם). בנקודה זמן זו היה מולד הירח.
אם מחשבים שנה קודם לכן - 12 חודשי ירח לאחור מבריאת האדם - מגיעים לנקודת זמן תיאורטית ביום שני 6 בספטמבר שנת 3761 לפני הספירה בשעה 23:11 (שעה 5 ו-204 חלקים על פי המניין העברי בו כל שעה מתחלקת ל-1080 חלקים); ונקודת זמן זו נקראת מולד תוהו (כלומר- מולד תיאורטי של הירח הרבה זמן לפני שהוא נברא ביום הרביעי..), וביחס אליה מחשבים את המולדים העתידיים של הירח. מדוע מחשבים את המולדים ביחס למולד תוהו ולא ביחס לבריאת האדם הראשון- אינני יודע.

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

אם מישהו מוטרד מהשאלה אם כל המסופר בספר בראשית באמת קרה- ניתן לדלג באלגנטיות מעל השאלה: אפשר לא להאמין ואפשר כן להאמין ולפרש את הסיפור בדרכים שונות. מעשית ברור שיש ספירה ויש לוח עברי ויש להם חוקיות משלהם. גם הספירה הלועזית מתבססת על מאורע שקרה במועד שונה ממה שחשבו אלו שיזמו אותה (ישו נולד כנראה בשנת 4 לפני הספירה ולא סמוך לתחילתה).

כמה זמן נמשך חודש עברי (חודש ירחי) בממוצע? לפי המסורת שהתגבשה לפני כאלפיים שנה כשכבר היה ידע אסטרונומי מדוייק יחסית- 29 ימים, 12 שעות, ו-793 חלקי שעה (מתוך 1080 כאמור), שזה סוטה סטייה זניחה ממדידות מדוייקות בנות ימינו (פחות מחצי שניה).

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

מנקודת מבטנו על כדור הארץ- הירח נראה כחרמש ימני ההולך ומתמלא בחציו הראשון של החודש (כמו האות ז = זורח),
וכחרמש שמאלי ההולך ופוחת בחציו השני של החודש (כמו האות ג = גורע).
באמצע החודש הירח מלא,
ובסוף החודש החרמש השמאלי נעלם וכעבור זמן מה מופיע חרמש ימני וזה המולד.

מכיוון שחודש צריך להיות נקוב בימים שלמים (לא ניתן לעבור חודש באמצע היום..) ומכיוון שחודש ירחי נמשך 29.5 ימים בקירוב, החודשים העבריים הם באורך של 30 יום ו-29 יום לסירוגין (תשרי - 30, חשוון - 29,.., אב - 30 ,אלול - 29); למעט תיקונים קטנים כפי שיוסבר בהמשך.
12 חודשים עבריים נמשכים איפוא 354 יום, בשעה ששנה שמשית לועזית נמשכת 365.25 יום בערך; וזה מצריך מנגנון התאמה של השנה הירחית לשמשית (בניגוד לשנה המוסלמית בה לא קיים מנגנון כזה ולכן היא זזה לאחור ומשלימה סיבוב כל 32 שנים פחות או יותר), ולכן 7 פעמים בכל מחזור של 19 שנה מתווסף חודש אדר א' ובו 30 יום (ב-19 שנה הסטייה השנתית של 11.25 יום מצטברת ל-214 ימים בערך, ואם מחלקים ב-7 מקבלים 30 ימים בקירוב). השנים המעוברות הן השנים בהן השארית מחלוקה ב-19 היא 3,6,8,11,14,17,19 (19=0 כמובן), ומקובל לציין זאת בראשי תיבות- גו"ח אדז"ט.

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

כדאי להדגיש שהחודש הנוסף הוא אדר א' ולא אדר ב' שכן יש בו 30 יום (באדר רגיל ובאדר ב' יש 29 יום), ולכן את פורים חוגגים באדר ב'.

זה הכל? לא- יש עוד מנגנון התאמה שנועד לתיקונים קטנים: את השנה העברית שנמשכת 354 יום או 384 יום (בשנה מעוברת) ניתן להאריך ביום או לקצר ביום. אם רוצים להאריך ביום- מאריכים את חודש חשוון מ-29 ל-30 יום, ואם רוצים לקצר ביום- מקצרים את כסלו מ-30 יום ל-29 יום (וברור שאם מאריכים את חשוון לא משנים את כסלו, ואם מקצרים את כסלו לא משנים את חשוון).
כאשר קורה אחד או יותר מהתנאים הבאים ראש השנה נדחה ביום:
1. מולד זקן- אם המולד התרחש לאחר שעה 12:00 בצהריים (שעה 18 לפי מניין השעות העברי).

2. גטר"ד- אם המולד התרחש ביום שלישי בשנה לא מעוברת לאחר השעה 3:11 (9 שעות ו-204 חלקים לפי המניין העברי)

3. בטותקפ"ט- אם המולד התרחש ביום שני בשנה לאחר שנה מעוברת לאחר השעה 9:33 (15 שעות ו-589 חלקים)

הסיבה לתנאים המשונים הללו היא גם להתגבר על ההפרשים הקטנים שנותרו לאחר עיבור השנים, וגם כדי למנוע מצב בו אורך השנה חורג ביותר מיום אחד מ-354 (או 384 בשנה מעוברת).
לא אד"ו ראש- אם לאחר התיקון הנ"ל ראש השנה חל ביום ראשון או רביעי או שישי - הוא נדחה ביום, וזה נועד בעיקר למנוע מיום כיפור לחול בסמוך לשבת ומהושענא רבא לחול בשבת. יש מי שמכירים את הכלל הסימטרי - לא בד"ו פסח: אם פסח חל ביום ב' / ד' / ו', ראש השנה אחריו יחול ביום ד' / ו' / א' בהתאמה.

לפני שניגש לקוד- בעייה טכנית "פעוטה": משתני התאריך של SQL Server אינם תומכים בתאריכים לפני הספירה: חלקם תומכים בתאריכים החל משנה 1 לספירה (וגם אלו - מגרסת 2008 ואילך) וחלקם בתאריכים משנת 1753 ואילך. לפיכך לא נוכל לחשב בעזרתם את הלוח העברי מתחילתו, ונאלץ להתחיל בנקודת זמן מאוחרת יותר; למשל משנת 1900 ה'תרס"א (5661). ראש השנה חל באותה שנה ביום שני 24/09/1900, אבל אנחנו זקוקים לנקודת הזמן המדוייקת של המולד המחושב. ברגע שתהיה לנו נקודה כזו- נוכל לחשב בעזרתה את המולד בשנים העוקבות וכן את מועדי ראש השנה.

שיטה מקובלת לעשות זאת היא לחשב כמה זמן עבר ממולד תוהו (ימים, שעות, וחלקי שעה),
ולפי השארית מחלוקה ב-7 נדע באיזה יום בשבוע חל המולד המחושב, וכן את השעה וחלקי השעה.
לאחר מכן צריך למצוא את השבוע בו ציינו את ראש השנה או שבו חל המולד לפי הלוחות האסטרונומיים, ובו למקם את היום והזמן שחישבנו. יש כאן תחמון מסויים מכיוון שנעזרים בחישובים של אחרים, ואולי בהזדמנות אנסה להעמיד חישוב מלא ובלתי תלוי.
בכל מקרה- אורכו של חודש עברי הוא 29 יום, 12 שעות ו-793 חלקים, ובסה"כ (בחלקי שעה עבריים):

29*24*1080+12*1080+793=765,433

מולד תוהו חל ביום שני בשעה 5 (עברית) ו-204 חלקים שזה סה"כ בחלקים:

2*24*1080+5*1080+204=57,444

5661 שנים כוללים 297 מחזורים של 19 שנה ועוד 17 שנה במחזור האחרון.
297 מחזורים של 19 שנה (בכל מחזור 235 חודשים = 12*19+7) שזה בחלקים:

297*235*765,433=53,423,396,235

וב-17 השנה של המחזור הנוכחי (מתוכן 6 שנים מעוברות) היו בחלקים:

(17*12+6)*765,433=160,740,930

ובסה"כ בחלקים:

57,444+53,423,396,235+160,740,930=53,584,194,609

נחלק ב-1080 ונקבל 49,614,995 ושארית 9.
נחלק ב-24 ונקבל 2,067,291 ושארית 11.
נחלק ב-7 ונקבל 295,327 ושארית 2.
בסה"כ קיבלנו שהמולד של שנת ה'תרס"א חל ביום שני, בשעה 11 ו-9 חלקים למניין העברי, שזה 05:00:03 לפנות בוקר.
ראש השנה באותה שנה חל ביום שני 24/09/1900 ולכן המולד חל בתאריך זה.

חישוב בדרך מעט שונה- בדעת ובוויקיפדיה.
הערות מתודיות- מולד תוהו חל למיטב הבנתי ביום אחד + 5 שעות + 204 חלקים (ולא שני ימים), אבל מכיוון שכך נהוג משום מה לחשב במקורות עליהם הסתמכתי, ומכיוון שזה ממילא מתקזז בחישוב של היום בשבוע בו חל המולד- אני משאיר את זה כך.
בנוסף- למיטב הבנתי השנה ההיפותטית ממולד תוהו ועד בריאת האדם הייתה אמורה להיות שנה מעוברת, ואינני יודע אם יש לזה תירוץ מתאים..
ואחרונה- שנת 5661 מתחילה 5660 שנה מתחילת הספירה (בריאת האדם) ומסתיימת ב-5661 לספירה; וכך השנה הראשונה לספירה היא שנה 1 (א'), והשנה שלפניה היא שנה 1 לפני הספירה: אין שנת אפס (0) לספירה! יחד עם זאת- החישוב הוא ממולד תוהו ולכן השנה מתחילה בכל זאת 5661 ממנו..

נו טוב, ומה עם קצת SQL? נתחיל עם חישוב ראשי השנה:

Declare    @Molad DateTime,
        @Shana Int,
        @Shanim Int,
        @Hodesh Decimal(20,10);
Select    @Molad='19000924 05:00:30',
        @Shana=5661,
        @Shanim=200,
        @Hodesh=29+12./24+793./1080./24;
 
With Shanim As
(Select    Shana,
        Meuberet,
        Molad,
        Yom,
        MeuberetKodemet,
        Cast(Null As DateTime) RoshHashanaHakodemet,
        Dhia1,
        Dhia2,
        Dhia3,
        Case When DatePart(WeekDay,RoshHashana) In (1,4,6) Then 1 Else 0 End Dhia4,
        RoshHashana+Case When DatePart(WeekDay,RoshHashana) In (1,4,6) Then 1 Else 0 End RoshHashana
From    (Select    *,
                Cast(DateDiff(Day,0,Molad) As DateTime)+Case When 1 In (Dhia1,Dhia2,Dhia3) Then 1 Else 0 End RoshHashana
        From    (Select    *,
                        Case When Molad-Cast(DateDiff(Day,0,Molad) As DateTime)>(12./24) Then 1 Else 0 End Dhia1,--מולד זקן
                        Case When Yom=3 And Molad-Cast(DateDiff(Day,0,Molad) As DateTime)>(3.+204./1080)/24 And Meuberet=0 Then 1 Else 0 End Dhia2,--ג' ט' ר"ד בשנה פשוטה
                        Case When Yom=2 And Molad-Cast(DateDiff(Day,0,Molad) As DateTime)>(9.+589./1080)/24 And MeuberetKodemet=1 Then 1 Else 0 End Dhia3 --ב' ט"ו תקפ"ט בפשוטה שאחרי מעוברת
                From    (Select    @Shana Shana,
                                Case When @Shana%19 In (3,6,8,11,14,17,0) Then 1
                                    Else 0
                                    End Meuberet,
                                @Molad Molad,
                                DatePart(Weekday,@Molad) Yom,
                                Case When (@Shana-1)%19 In (3,6,8,11,14,17,0) Then 1
                                    Else 0
                                    End MeuberetKodemet) T) T) T
Union All
Select    Shana,
        Meuberet,
        Molad,
        Yom,
        MeuberetKodemet,
        RoshHashanaHakodemet,
        Dhia1,
        Dhia2,
        Dhia3,
        Case When DatePart(WeekDay,RoshHashana) In (1,4,6) Then 1 Else 0 End Dhia4,--לא אדו ראש
        RoshHashana+Case When DatePart(WeekDay,RoshHashana) In (1,4,6) Then 1 Else 0 End RoshHashana
From    (Select    *,
                Cast(DateDiff(Day,0,Molad) As DateTime)+Case When 1 In (Dhia1,Dhia2,Dhia3) Then 1 Else 0 End RoshHashana
        From    (Select    *,
                        Case When Molad-Cast(DateDiff(Day,0,Molad) As DateTime)>(12./24) Then 1 Else 0 End Dhia1,--מולד זקן
                        Case When Yom=3 And Molad-Cast(DateDiff(Day,0,Molad) As DateTime)>(3.+204./1080)/24 And Meuberet=0 Then 1 Else 0 End Dhia2,--ג' ט' ר"ד בשנה פשוטה
                        Case When Yom=2 And Molad-Cast(DateDiff(Day,0,Molad) As DateTime)>(9.+589./1080)/24 And MeuberetKodemet=1 Then 1 Else 0 End Dhia3 --ב' ט"ו תקפ"ט בפשוטה שאחרי מעוברת
                From    (Select    *,
                                Case When Shana%19 In (3,6,8,11,14,17,0) Then 1
                                    Else 0
                                    End Meuberet,
                                DatePart(Weekday,Molad) Yom
                        From    (Select    Shana+1 Shana,
                                        Molad+Cast(12+Meuberet As Decimal(20,10))*@Hodesh Molad,
                                        Meuberet MeuberetKodemet,
                                        RoshHashana RoshHashanaHakodemet
                                From    Shanim) T
                        Where    Shana<@Shana+@Shanim) T) T) T)
Select    Shana,
        Meuberet,
        Molad,
        Yom,
        MeuberetKodemet,
        RoshHashanaHakodemet,
        Dhia1,--מולד זקן
        Dhia2,--ג' ט' ר"ד בשנה פשוטה
        Dhia3 --ב' ט"ו תקפ"ט בפשוטה שאחרי מעוברת,
        Dhia4,--לא אדו ראש
        RoshHashana,
        DateDiff(Day,RoshHashanaHakodemet,RoshHashana) Yamim
From    Shanim
option (MaxRecursion 0);
Go

clip_image002

אני משתמש ב-CTE רקורסיבי: קצת איטי, אבל לא מצריך ליצור אובייקטים, ולכן מספיק שיש לכם הרשאה להתחבר לשרת כדי שתוכלו להריץ את זה. מי שירצה- יוכל "לשפוך" את השליפות שלהלן לתוך טבלה- מדובר בסופו של דבר בטבלה סטטית שאינה משתנית.
בכל שלב אני מוסיף 12 או 13 חודשים - תלוי אם השנה מעוברת (אורכו של החודש מוגדר כקבוע בהתחלה).

אני "שומר" את תאריך ראש השנה הקודמת ואת אינדיקציית העיבור כדי לחשב את אורכה (לצורך חישוב אורכם של חשוון וכסלו) ואת הדחייה של ראש השנה.

נוסיף כעת את החישוב של החודשים בכל שנה:

Declare    @Molad DateTime,
        @Shana Int,
        @Shanim Int,
        @Hodesh Decimal(20,10);
Select    @Molad='19000924 05:00:30',
        @Shana=5661,
        @Shanim=200,
        @Hodesh=29+12./24+793./1080./24;
 
With Hodashim As
(Select 1 ID, 'תשרי' Hodesh,30 Yamim, Null Sug Union All
Select 2 ID, 'חשוון' Hodesh,29 Yamim, Null Sug Union All
Select 2 ID, 'חשוון' Hodesh,30 Yamim, 'מלא' Sug Union All
Select 3 ID, 'כסלו' Hodesh,30 Yamim, Null Sug Union All
Select 3 ID, 'כסלו' Hodesh,29 Yamim, 'חסר' Sug Union All
Select 4 ID, 'טבת' Hodesh,29 Yamim, Null Sug Union All
Select 5 ID, 'שבט' Hodesh,30 Yamim, Null Sug Union All
Select 6 ID, 'אדר' Hodesh,29 Yamim, Null Sug Union All
Select 6 ID, 'אדר א' Hodesh,30 Yamim, Null Sug Union All
Select 6 ID, 'אדר ב' Hodesh,29 Yamim, Null Sug Union All
Select 7 ID, 'ניסן' Hodesh,30 Yamim, Null Sug Union All
Select 8 ID, 'אייר' Hodesh,29 Yamim, Null Sug Union All
Select 9 ID, 'סיוון' Hodesh,30 Yamim, Null Sug Union All
Select 10 ID, 'תמוז' Hodesh,29 Yamim, Null Sug Union All
Select 11 ID, 'אב' Hodesh,30 Yamim, Null Sug Union All
Select 12 ID, 'אלול' Hodesh,29 Yamim, Null Sug),
Shanim As
(Select    Shana,
        Meuberet,
        Molad,
        Yom,
        MeuberetKodemet,
        Cast(Null As DateTime) RoshHashanaHakodemet,
        Dhia1,
        Dhia2,
        Dhia3,
        Case When DatePart(WeekDay,RoshHashana) In (1,4,6) Then 1 Else 0 End Dhia4,
        RoshHashana+Case When DatePart(WeekDay,RoshHashana) In (1,4,6) Then 1 Else 0 End RoshHashana
From    (Select    *,
                Cast(DateDiff(Day,0,Molad) As DateTime)+Case When 1 In (Dhia1,Dhia2,Dhia3) Then 1 Else 0 End RoshHashana
        From    (Select    *,
                        Case When Molad-Cast(DateDiff(Day,0,Molad) As DateTime)>(12./24) Then 1 Else 0 End Dhia1,--מולד זקן
                        Case When Yom=3 And Molad-Cast(DateDiff(Day,0,Molad) As DateTime)>(3.+204./1080)/24 And Meuberet=0 Then 1 Else 0 End Dhia2,--ג' ט' ר"ד בשנה פשוטה
                        Case When Yom=2 And Molad-Cast(DateDiff(Day,0,Molad) As DateTime)>(9.+589./1080)/24 And MeuberetKodemet=1 Then 1 Else 0 End Dhia3 --ב' ט"ו תקפ"ט בפשוטה שאחרי מעוברת
                From    (Select    @Shana Shana,
                                Case When @Shana%19 In (3,6,8,11,14,17,0) Then 1
                                    Else 0
                                    End Meuberet,
                                @Molad Molad,
                                DatePart(Weekday,@Molad) Yom,
                                Case When (@Shana-1)%19 In (3,6,8,11,14,17,0) Then 1
                                    Else 0
                                    End MeuberetKodemet) T) T) T
Union All
Select    Shana,
        Meuberet,
        Molad,
        Yom,
        MeuberetKodemet,
        RoshHashanaHakodemet,
        Dhia1,
        Dhia2,
        Dhia3,
        Case When DatePart(WeekDay,RoshHashana) In (1,4,6) Then 1 Else 0 End Dhia4,
        RoshHashana+Case When DatePart(WeekDay,RoshHashana) In (1,4,6) Then 1 Else 0 End RoshHashana
From    (Select    *,
                Cast(DateDiff(Day,0,Molad) As DateTime)+Case When 1 In (Dhia1,Dhia2,Dhia3) Then 1 Else 0 End RoshHashana
        From    (Select    *,
                        Case When Molad-Cast(DateDiff(Day,0,Molad) As DateTime)>(12./24) Then 1 Else 0 End Dhia1,--מולד זקן
                        Case When Yom=3 And Molad-Cast(DateDiff(Day,0,Molad) As DateTime)>(3.+204./1080)/24 And Meuberet=0 Then 1 Else 0 End Dhia2,--ג' ט' ר"ד בשנה פשוטה
                        Case When Yom=2 And Molad-Cast(DateDiff(Day,0,Molad) As DateTime)>(9.+589./1080)/24 And MeuberetKodemet=1 Then 1 Else 0 End Dhia3 --ב' ט"ו תקפ"ט בפשוטה שאחרי מעוברת
                From    (Select    *,
                                Case When Shana%19 In (3,6,8,11,14,17,0) Then 1
                                    Else 0
                                    End Meuberet,
                                DatePart(Weekday,Molad) Yom
                        From    (Select    Shana+1 Shana,
                                        Molad+Cast(12+Meuberet As Decimal(20,10))*@Hodesh Molad,
                                        Meuberet MeuberetKodemet,
                                        RoshHashana RoshHashanaHakodemet
                                From    Shanim) T
                        Where    Shana<@Shana+@Shanim) T) T) T)
Select    S.Shana-1 Shana,
        *,
        DateDiff(Day,S.RoshHashanaHakodemet,S.RoshHashana) Yamim
From    Shanim S
Inner Join Hodashim H
        On ((S.MeuberetKodemet=0 And H.Hodesh Not In ('אדר א','אדר ב'))
                Or (S.MeuberetKodemet=1 And H.Hodesh Not In ('אדר')))
            And ((DateDiff(Day,S.RoshHashanaHakodemet,S.RoshHashana)%10=3 
                    And (H.Hodesh<>'חשוון' Or H.Sug Is Null)
                    And (H.Hodesh<>'כסלו' Or H.Sug='חסר'))
                Or (DateDiff(Day,S.RoshHashanaHakodemet,S.RoshHashana)%10=4 
                    And H.Sug Is Null)
                Or (DateDiff(Day,S.RoshHashanaHakodemet,S.RoshHashana)%10=5 
                    And (H.Hodesh<>'חשוון' Or H.Sug='מלא')
                    And (H.Hodesh<>'כסלו' Or H.Sug Is Null)))
Where    S.RoshHashanaHakodemet Is Not Null
option (MaxRecursion 0);
Go

clip_image004

ציינתי את שמות החודשים, בחרתי בין אדר לבין אדר א' & אדר ב', וכן חישבתי אם חשוון מלא או אם כסלו חסר.

לבסוף החישוב המלא הכולל את כל הימים בכל חודש, אבל רק עם העמודות החשובות:

Declare    @Molad DateTime,
        @Shana Int,
        @Shanim Int,
        @Hodesh Decimal(20,10);
Select    @Molad='19000924 05:00:30',
        @Shana=5661,
        @Shanim=200,
        @Hodesh=29+12./24+793./1080./24;
 
With Yamim As
(Select 1 Yom
Union All
Select    Yom+1
From    Yamim
Where    Yom<30),
Hodashim As
(Select 1 ID, 'תשרי' Hodesh,30 Yamim, Null Sug Union All
Select 2 ID, 'חשוון' Hodesh,29 Yamim, Null Sug Union All
Select 2 ID, 'חשוון' Hodesh,30 Yamim, 'מלא' Sug Union All
Select 3 ID, 'כסלו' Hodesh,30 Yamim, Null Sug Union All
Select 3 ID, 'כסלו' Hodesh,29 Yamim, 'חסר' Sug Union All
Select 4 ID, 'טבת' Hodesh,29 Yamim, Null Sug Union All
Select 5 ID, 'שבט' Hodesh,30 Yamim, Null Sug Union All
Select 6 ID, 'אדר' Hodesh,29 Yamim, Null Sug Union All
Select 6 ID, 'אדר א' Hodesh,30 Yamim, Null Sug Union All
Select 6 ID, 'אדר ב' Hodesh,29 Yamim, Null Sug Union All
Select 7 ID, 'ניסן' Hodesh,30 Yamim, Null Sug Union All
Select 8 ID, 'אייר' Hodesh,29 Yamim, Null Sug Union All
Select 9 ID, 'סיוון' Hodesh,30 Yamim, Null Sug Union All
Select 10 ID, 'תמוז' Hodesh,29 Yamim, Null Sug Union All
Select 11 ID, 'אב' Hodesh,30 Yamim, Null Sug Union All
Select 12 ID, 'אלול' Hodesh,29 Yamim, Null Sug),
Shanim As
(Select    Shana,
        Meuberet,
        Molad,
        Yom,
        MeuberetKodemet,
        Cast(Null As DateTime) RoshHashanaHakodemet,
        Dhia1,
        Dhia2,
        Dhia3,
        Case When DatePart(WeekDay,RoshHashana) In (1,4,6) Then 1 Else 0 End Dhia4,
        RoshHashana+Case When DatePart(WeekDay,RoshHashana) In (1,4,6) Then 1 Else 0 End RoshHashana
From    (Select    *,
                Cast(DateDiff(Day,0,Molad) As DateTime)+Case When 1 In (Dhia1,Dhia2,Dhia3) Then 1 Else 0 End RoshHashana
        From    (Select    *,
                        Case When Molad-Cast(DateDiff(Day,0,Molad) As DateTime)>(12./24) Then 1 Else 0 End Dhia1,--מולד זקן
                        Case When Yom=3 And Molad-Cast(DateDiff(Day,0,Molad) As DateTime)>(3.+204./1080)/24 And Meuberet=0 Then 1 Else 0 End Dhia2,--ג' ט' ר"ד בשנה פשוטה
                        Case When Yom=2 And Molad-Cast(DateDiff(Day,0,Molad) As DateTime)>(9.+589./1080)/24 And MeuberetKodemet=1 Then 1 Else 0 End Dhia3 --ב' ט"ו תקפ"ט בפשוטה שאחרי מעוברת
                From    (Select    @Shana Shana,
                                Case When @Shana%19 In (3,6,8,11,14,17,0) Then 1
                                    Else 0
                                    End Meuberet,
                                @Molad Molad,
                                DatePart(Weekday,@Molad) Yom,
                                Case When (@Shana-1)%19 In (3,6,8,11,14,17,0) Then 1
                                    Else 0
                                    End MeuberetKodemet) T) T) T
Union All
Select    Shana,
        Meuberet,
        Molad,
        Yom,
        MeuberetKodemet,
        RoshHashanaHakodemet,
        Dhia1,
        Dhia2,
        Dhia3,
        Case When DatePart(WeekDay,RoshHashana) In (1,4,6) Then 1 Else 0 End Dhia4,
        RoshHashana+Case When DatePart(WeekDay,RoshHashana) In (1,4,6) Then 1 Else 0 End RoshHashana
From    (Select    *,
                Cast(DateDiff(Day,0,Molad) As DateTime)+Case When 1 In (Dhia1,Dhia2,Dhia3) Then 1 Else 0 End RoshHashana
        From    (Select    *,
                        Case When Molad-Cast(DateDiff(Day,0,Molad) As DateTime)>(12./24) Then 1 Else 0 End Dhia1,--מולד זקן
                        Case When Yom=3 And Molad-Cast(DateDiff(Day,0,Molad) As DateTime)>(3.+204./1080)/24 And Meuberet=0 Then 1 Else 0 End Dhia2,--ג' ט' ר"ד בשנה פשוטה
                        Case When Yom=2 And Molad-Cast(DateDiff(Day,0,Molad) As DateTime)>(9.+589./1080)/24 And MeuberetKodemet=1 Then 1 Else 0 End Dhia3 --ב' ט"ו תקפ"ט בפשוטה שאחרי מעוברת
                From    (Select    *,
                                Case When Shana%19 In (3,6,8,11,14,17,0) Then 1
                                    Else 0
                                    End Meuberet,
                                DatePart(Weekday,Molad) Yom
                        From    (Select    Shana+1 Shana,
                                        Molad+Cast(12+Meuberet As Decimal(20,10))*@Hodesh Molad,
                                        Meuberet MeuberetKodemet,
                                        RoshHashana RoshHashanaHakodemet
                                From    Shanim) T
                        Where    Shana<@Shana+@Shanim) T) T) T)
Select    DateAdd(Day,Row_Number() Over(Partition By S.Shana Order By H.ID,H.Hodesh,Y.Yom)-1,S.RoshHashanaHakodemet) Taarih,
        S.Shana-1 Shana,
        S.RoshHashanaHakodemet RoshHashana,
        S.Meuberet,
        DateDiff(Day,S.RoshHashanaHakodemet,S.RoshHashana) YamimBashana,
        H.ID MisparHodesh,
        H.Hodesh ShemHodesh,
        H.Sug SugHodesh,
        H.Yamim YamimBahodesh,
        Y.Yom
From    Shanim S
Inner Join Hodashim H
        On ((S.MeuberetKodemet=0 And H.Hodesh Not In ('אדר א','אדר ב'))
                Or (S.MeuberetKodemet=1 And H.Hodesh Not In ('אדר')))
            And ((DateDiff(Day,S.RoshHashanaHakodemet,S.RoshHashana)%10=3 
                    And (H.Hodesh<>'חשוון' Or H.Sug Is Null)
                    And (H.Hodesh<>'כסלו' Or H.Sug='חסר'))
                Or (DateDiff(Day,S.RoshHashanaHakodemet,S.RoshHashana)%10=4 
                    And H.Sug Is Null)
                Or (DateDiff(Day,S.RoshHashanaHakodemet,S.RoshHashana)%10=5 
                    And (H.Hodesh<>'חשוון' Or H.Sug='מלא')
                    And (H.Hodesh<>'כסלו' Or H.Sug Is Null)))
Inner Join Yamim Y
        On H.Yamim>=Y.Yom
Where    S.RoshHashanaHakodemet Is Not Null
Order By S.Shana,
        H.ID,
        H.Hodesh,
        Y.Yom
option (MaxRecursion 0);
Go

clip_image006

מה הלאה?
אני אנסה בהמשך להציג דרכי חישוב נוספות.
מי שמוצא לכך שימוש- יכול לצרף טבלת חגים עבריים ובה לציין את שם החודש והתאריך, ועל ידי Left Join בין הטבלה שלי לטבלת החגים - לקבל לוח שנה עברי כולל מועדים.
מי שממש משעמם לו- יכול להוסיף חישוב של השנה העברית ושל התאריך השוטף באותיות עבריות (במקום במספרים)..

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

 
Create Proc P_TblDif @Tbl As Sysname,
                    @RmtServer As Sysname='MyProdServer' 
As 
 
Declare    @LclServer As Sysname, 
        @Cols As Varchar(Max), 
        @SQL As NVarchar(Max); 
 
Set        @LclServer=@@ServerName; 
 
Set        @SQL='Select    @Cols_=IsNull(@Cols_+'','','''')+''[''+L.name+'']''' 
            +Char(13)+'From    ['+@LclServer+'].'+DB_Name()+'.sys.columns L' 
            +Char(13)+'Inner Join ['+@RmtServer+'].'+DB_Name()+'.sys.columns R' 
            +Char(13)+'        On L.name=R.name' 
            +Char(13)+'Where    L.object_id=(Select object_id From ['+@LclServer+'].'+DB_Name()+'.sys.tables Where name='''+@Tbl+''')' 
            +Char(13)+'        And R.object_id=(Select object_id From ['+@RmtServer+'].'+DB_Name()+'.sys.tables Where name='''+@Tbl+''')' 
            +Char(13)+'Order By L.column_id;'; 
 
Exec    SP_ExecuteSQL @SQL, N'@Cols_ Varchar(Max) Out',@Cols_ = @Cols Out; 
 
Set        @SQL='With L As' 
            +Char(13)+'(Select    '+@Cols 
            +Char(13)+'From    ['+@LclServer+'].'+DB_Name()+'.dbo.'+@Tbl+'),' 
            +Char(13)+'R As' 
            +Char(13)+'(Select    '+@Cols 
            +Char(13)+'From    ['+@RmtServer+'].'+DB_Name()+'.dbo.'+@Tbl+')' 
            +Char(13)+'Select    '''+@LclServer+''' [Server],' 
            +Char(13)+'        *' 
            +Char(13)+'From    (Select * From R' 
            +Char(13)+'        Except' 
            +Char(13)+'        Select * From L) T' 
            +Char(13)+'Union All' 
            +Char(13)+'Select    '''+@RmtServer+''' [Server],' 
            +Char(13)+'        *' 
            +Char(13)+'From    (Select * From L' 
            +Char(13)+'        Except' 
            +Char(13)+'        Select * From R) T' 
            +Char(13)+'Order By 1;' 
Print    @SQL; 
Exec(@SQL);

הפרוצדורה אינה מכזה את כל הפינות, ויש לשנות ולתקן אותה על פי הצורך:
1. הקלט הוא שם הטבלה, והשרת (Linked Server) מולו משווים; כשאצלנו ברירת המחדל הוא שרת הייצור (יש לשנות את MyProdServer לשם האמיתי).
2. אני מניח ששתי הטבלאות בעלות אותו שם, נמצאות בדטבייסים בעלי שם זהה, ויש להן אותן עמודות (ליתר דיוק- הבדיקה היא על העמודות המשותפות); וכן שאני נמצא בדטבייס בו הטבלה להשוואה נמצאת.
3. ההפעלה של הפרוצדורה: 'Exec P_TblDiff 'MyTbl

לפני זמן מה כתבתי פוסט בו הצגתי סקריפט שמאפשר לשלוף בקלות את הסקריפטים של האובייקטים המקודדים במערכת (בעיקר פרוצדורות, פונקציות, Views).
בסקריפט היו חסרים קודם כל טבלאות המשתמש, ובנוסף האובייקטים המקודדים של המערכת;
ולכל זה יש להוסיף את העובדה שששליפת הקודים לא הייתה הכי אלגנטית..

ב-ק-י-צ-ו-ר: טרחתי והוספתי שליפה שמחוללת את הסקריפטים של הטבלאות (עדיין לא סקריפט מלא אלא רק של העיקר),
שיפרתי את הטעון שיפור והשלמה בשליפת הקודים,
וזו תרומתי לקהילה (אלא אם כן מישהו הקדים אותי ואף עשה זאת טוב ממני):

With Tbl As
(Select    object_id,
        name,
        Schema_Name(schema_id) SchemaName,
        'Create Table '+Schema_Name(SCHEMA_ID)+'.'+name+'(' CreateTable
From    sys.tables),
Col As
(Select    object_id,
        column_id,
        Char(13)+Char(9)+Char(9)+'['+name+']'
        +Case When is_computed=0 Then
            ' '+Type_Name(user_type_id)+Case When Type_Name(user_type_id) In ('Char','Varchar') Then '('+Case When max_length=-1 Then 'Max' Else CAST(max_length AS Varchar) End+')'
                                            When Type_Name(user_type_id) In ('NChar','NVarchar') Then '('+Case When max_length=-1 Then 'Max' Else CAST(max_length/2 AS Varchar) End+')'
                                            When Type_Name(user_type_id)='Decimal' Then '('+CAST(precision As Varchar)+','+CAST(scale As Varchar)+')'
                                            Else '' End
            +IsNull(' '+'Collate '+collation_name,'') 
            +Case When is_identity=1 Then ' Identity('+(Select Cast(seed_value As Varchar)+','+Cast(increment_value As Varchar) From sys.identity_columns IC where IC.object_id=C.object_id And IC.column_id=C.column_id)+')' Else '' End
            +' '+Case When is_nullable=0 Then 'Not Null' Else 'Null' End
            +IsNull(' ='+OBJECT_DEFINITION(default_object_id),'')
        Else ' ='+(Select definition From sys.computed_columns CC Where CC.object_id=C.object_id And CC.column_id=C.column_id)
        End
        +',' ColDef
From    sys.columns C),
Cols As
(Select    object_id,
        (Select C1.ColDef As [text()]
            From    Col C1
            Where    C1.object_id=C.object_id
                    --And C1.column_id=C.column_id
            Order By column_id
            For XML Path('')) ColDef
From    Col C
Group By object_id),
PK As
(Select    ','+Char(13)+Char(9)+Char(9)+'Constraint ['+KC.name+'] Primary Key Clustered'
        +'('+IC.Cols+')' SQL,
        IC.object_id
From    (Select    object_id,
                Stuff((Select ','+Char(13)+Char(9)+Char(9)+Char(9)+'['+Col_Name(c1.object_id,c1.column_id)+'] '+Case When C1.is_descending_key=0 Then 'Asc' Else 'Desc' End As [text()]
                    From    sys.index_columns C1
                    Where    C1.object_id=C.object_id
                            --And C1.column_id=C.column_id
                    Order By C1.key_ordinal
                    For XML Path('')),1,1,'') Cols
        From    sys.index_columns C
        Group By C.object_id) IC
Inner Join sys.key_constraints KC 
        On IC.object_id = KC.parent_object_id  
Where    KC.type='PK'),
T As
(Select    T.object_id,
        T.SchemaName,
        T.name ObjectName,
        'User Table' TypeName,
        T.CreateTable
                +Left(C.ColDef,Len(C.ColDef)-1)
                +Cast(IsNull(PK.SQL,'') As Varchar(8000))+');' SQL
From    Tbl T
Inner Join Cols C
        On T.object_id=C.object_id
Left Join PK
        On T.object_id=PK.object_id
Union All
Select    object_id,
        Schema_Name(schema_id) SchemaName,
        Object_Name(object_id) ObjectName,
        type_desc TypeName,
        (Select Object_Definition(object_id) As [text()] From sys.all_objects S1 Where S1.object_id=S.object_id For XML Path('')) SQL
From    sys.all_objects S
Where    type Not In ('AF','C','D','FS','IT','PK','PC','S','SQ','U','X'))
Select    object_id,
        SchemaName,
        ObjectName,
        TypeName,
        Cast(SQL As XML) SQL
From    T
Where    T.SQL Like '%%'
Order By T.ObjectName;

במקומות עבודה מסודרים (לא כאלה שמפתחים על ה-Production..) מקובל ליצור סקריפט העברת גרסה שכולל בדרך כלל:

1. פקודות Create לאובייקטים חדשים שיש ליצור.

2. פקודות Alter לאובייקטים קיימים שיש לשנות.

3. הוספה ושינוי נתונים בטבלאות ניהול.

4. הרשאות (בדרך כלל בהמשך ל-Create הנ"ל).

5. שונות..

הסקריפט צריך להיות בנוי כך שהרצה כפולה שלו לא תגרום נזקים, למשל:

בהמשך לסעיף 1 - לא תנסה ליצור אובייקטים שכבר נוצרו,

ובהמשך לסעיף 3 - לא תנסה להוסיף נתונים שכבר קיימים.

מדוע שתתרחש הרצה כפולה? טעויות אנוש, שכחנו שהרצנו כבר וניסינו שוב, רצינו לבדוק את הסקריפט בסביבת הפיתוח (בה כבר התבצעו השינויים) לפני העברה לסביבת הבדיקות לוודא שהוא תקין; ולפעמים נוצרות תקלות כי פקודת יצירת הפרוצדורה נמצאת לפני יצירת הטבלה בה היא מטפלת וזה יוצר התראה על בעיית תלות בין אובייקטים, ומריצים שוב כדי לוודא שהבעייה נפתרה (כמובן שהחכם רואה את הנולד ומקפיד לבנות את הסקריפט כך שהטבלאות בהתחלה, אחר כך ה-Views בסדר עולה של תלות, ולבסוף הפרוצדורות והפונקציות, אלא שרובנו נוהגים מנהג חל"ם: חכמים לאחר מעשה).

כל עוד מדובר ביצירה של טריגרים, פרוצדורות, פונקציות או Views - ניתן להקדים ל-Create פקודת Drop מותנית:

If Object_ID('MyNewObject') Is Not Null Drop .. MyNewObject;
Go

אם מדובר ב-Alter לאובייקטים כנ"ל- לא תהיה כל בעייה אם הקוד יורץ מספר פעמים; וכך במתן הרשאות או בגריעתן.

כשמדובר בטבלה - הטיפול ב-Create אמור להיות הפוך; בודקים אם היא קיימת, ואם כן - לא יוצרים. טבלה עם נתונים לא מבטלים בקלות דעת:

If Object_ID('MyNewTable') Is Not Null
    Begin
    Create Table ..
    Grant Select On ..
    ..
    End;

כשמשנים טבלה- יש לבדוק אם השינוי בוצע:

עבור אובייקטים כמו Check Constraint ניתן להשתמש ב-Object_ID כנ"ל (בתנאי שהקפדנו לתת לו שם בעצמנו ולא סמכנו על השמות שהמערכת ממציאה).

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

If ObjectProperty(Object_ID('MyTbl'),'TableHasPrimaryKey')<>1
    Alter Table MyTbl ..

מה לגבי הוספת עמודה? הפונקציה Col_Name מחייבת לדעת את מספר העמודה בטבלה וזו בעייה, ולכן ניתן להשתמש ב-Col_Length במקום:

If Col_Length('MyTbl','MyCol') Is Null
    Alter Table MyTbl Add MyCol ..

לגבי הוספת נתונים לטבלאות ניהול - כתבתי על כך פוסט בעבר.

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

בפוסט קודם הראתי כיצד ללכוד פלט פרוצדורה, כשהדגש היה על הפלט המופנה ללשונית ה-Messages (בתחתית צילום המסך):

clip_image002

את הפלט המופנה ללשונית ה-Results ניתן להפנות לטבלה ולשלוף ממנה לאחר מכן. למשל:

Create Table #SP_Who(spid Int,
                    ecid Int,
                    status Varchar(20),
                    loginame Sysname Null,
                    hostname Sysname Null,
                    blk Int,
                    dbname Sysname Null,
                    cmd Varchar(20),
                    request_id Int);
Go
 
Insert
Into #SP_Who
Exec SP_Who;
Go
 
Select * From #SP_Who;
Go

clip_image004

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

Select *
Into #SP_Who
From OPENROWSET('SQLOLEDB','Server=MyServer;Trusted_Connection=yes;','Exec SP_Who;');
Go
 
Select * From #SP_Who;
Go

clip_image006

Posted by גרי רשף | with no comments

ב-TSQL יש פקודות רבות.
לחלקן אין תחליף ובלעדיהן השפה תהיה כמכונית בלי גלגלים: Select, From,
ולחלקן יש שימוש פה ושימוש שם, אולי לא כולם עושים בהן שימוש תדיר, אבל טוב שהן קיימות בבחינת- אין פקודה אשר אין לה שעה: GetDate, Reverse, Float..
מדי פעם אני מגלה פקודה חדשה שלא ידעתי על קיומה, מהרהר ובונה תסריט דמיוני בו יש בה צורך, וחיש ניגש לכתוב פוסט בו אני מייחס את התסריט ל"מספר לקוחות התקשרו אלי לאחרונה ושאלו האם וכיצד.." ומספר עד כמה ערכה לא יסולא בפז (כמובן- בהנחה שהתסריט הדמיוני הנ"ל יתממש).

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

NullIf - פקודה זו מקבלת שני פרמטרים, אם הם שווים היא מחזירה Null ואם הם שונים את הראשון; כאשר אסור שאחד הפרמטרים יהיה Null:

clip_image002

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

Posted by גרי רשף | with no comments

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

Select Schema_Name(Schema_ID) Schema_Name,
        Object_Name(parent_object_id) parent_Object_Name,
        *
From sys.objects;

clip_image002

Select Object_Name(object_id) Object_Name,
        Schema_Name(ObjectProperty(object_id,'SchemaId')) Schema_Name,
        Type_Name(system_type_id) Type_Name,
        Type_Name(user_type_id) User_Type_Name2,
        Object_Name(default_object_id) Default_Constraint_Name,
        Object_Name(rule_object_id) Rule_Constraint_Name,
        *
From sys.columns;

clip_image004

Select Object_Name(object_id) [Object_Name],
        ObjectPropertyEx (object_id,'BaseType') Object_Type,
        Schema_Name(Cast(ObjectPropertyEx (object_id,'SchemaId') As Int))
        Schema_Name,
        Col_Name(object_id,Column_id) Column_Name,
        *
From sys.index_columns
Order By Object_Name(object_id),
        Index_Id,
        key_ordinal;

clip_image006

לפני קרוב לשנה כתבתי על שימוש באופרטור Like תוך שימוש בתווים בעייתיים.
הצעתי פתרון כללי העושה שימוש ב-Replace,
ופתרון ספציפי לתו הג'וקר %.
גם הסוגריים המרובעים יוצרים בעיות מכיוון שמשתמשים בהם לתחימת התווים הבעייתיים עצמם..
מה עושים?
הפתרון הוא להקיף את הסוגר המרובע הפותח בסוגריים מרובעים []]. את הסוגר המסיים יש להשאיר כמו שהוא.

למשל (ניצור טבלה זמנית ונכניס לתוכה מחרוזת אחת לנסיון)-

Create Table #T(Txt Varchar(Max));
Go
 
Insert
Into    #T
Select    'ab[cd]ef';
Go

כעת ננסה לשלוף את "כל" השורות בהן מופיעה המחרוזת b[cd]e (כלומר- הרשורה היחידה בטבלה), ולשם כך נשים מסביב לסוגר הראשון השמאלי סוגריים מרובעים:

Select    *
From    #T
Where    Txt Like '%b[[]cd]e%';
Go

image
Posted by גרי רשף | with no comments
תגים:, , ,

כל מי שעובד עם טרנזקציות מפורשות - אמור לדעת שבטרנזקציות אין קינון אמיתי. כלומר- אם בתוך טרנזקציה נפתח טרנזקציה חדשה, כל עוד לא התבצע ה-Commit האחרון - שום דבר לא נשמר באמת, ואם נבצע Rollback - הוא "יפיל" את כל הטרנזקציות.

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

דוגמה - ניצור פרוצדורה שמבצעת פעולה כלשהי, פותחת טרנזקציה, מבצעת עוד פעולה, פותחת עוד טרנזקציה, מבצעת עוד פעולה ומסיימת (הפעולה תמיד תהיה שליפת פרטי הטרנזקציה הנוכחית בעזרת שליפות DMV אך מי שרוצה יכול להוסיף פעולות "אמיתיות" עמו הכנסת נתונים לטבלה):

If Object_id('tempdb..#P') Is Not Null Drop Proc #P;
Go
 
Create Procedure #P As
 
Select  R.transaction_id,
        T.name 
From sys.dm_exec_requests R 
Inner Join sys.dm_tran_active_transactions T
        On R.transaction_id=T.transaction_id
Where R.session_id=@@SPID;
 
Begin Tran MyTran1
 
Select  R.transaction_id,
        T.name 
From sys.dm_exec_requests R 
Inner Join sys.dm_tran_active_transactions T
        On R.transaction_id=T.transaction_id
Where R.session_id=@@SPID;
 
Begin Tran MyTran2
 
Select  R.transaction_id,
        T.name 
From sys.dm_exec_requests R 
Inner Join sys.dm_tran_active_transactions T
        On R.transaction_id=T.transaction_id
Where R.session_id=@@SPID;
 
Commit Tran MyTran3;
Commit Tran MyTran3;
Go

וכעת נפעיל אותה:

Exec #P;
Go

clip_image002

(מספרי הטרנזקציות משתנים מהרצה להרצה)

השליפה הראשונה הייתה טרנזקציה בודדה שנפתחה ונסגרה אוטומטית (ללא פקודות Begin Tran ו-Commit) ומספרה 14562947.

השליפות השניה והשלישית היו שתיהן בתוך אותה טרנזקציה 14562948, MyTran1 שנפתחה לפני השליפה השניה, וזאת למרות שבין השליפה השניה והשלישית נפתחה לכאורה טרנזקציה נוספת - MyTran2.

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

Posted by גרי רשף | with no comments

ה-Collation מגדיר את השפות והמאפיינים בהן המערכת תומכת.
בארץ למשל מקובל להשתמש ב-Hebrew_CI_AS שמאפשר כתיבה בעברית ובאנגלית, אי רגישות של המערכת להבדלים בין אותיות רגילות לסופיות (בעברית) ובין קטנות לגדולות (בלועזית), ורגישות לאותיות מנוקדות (בעברית).
אי רגישות בין אותיות רגילות לסופיות פירושה שמבחינת המערכת מ=ם או פ=ף לצורך השוואה, אך כמובן שהאותיות נשמרות כאותיות שונות.

בעת התקנת SQL Server נקבע ה-Collation של השרת שהוא יהיה ה-Collation של הדטבייסים של המערכת, וכן Collation ברירת המחדל של הדטבייסים שיווצרו בהמשך (אם לא יוגדר להם במפורש אחד אחר):

clip_image002

(בצילום המסך שתי דרכים למצוא מה ה-Collation של השרת)

שינוי ה-Collation של השרת הוא פעולה די רגישה שבונה מחדש את הדטבייסים של המערכת וכוללת Restart של השרת, כך שעדיף לא לנסות על שרת הייצור..

כשיוצרים דטבייס חדש - ה-Collation שלו יהיה זה של השרת אלא אם כן נאמר אחרת, למשל כך:

Create Database MyDB Collate Hebrew_CI_AI;
Go

וניתן למצוא את ה-Collation של הדטבייס באחת מהדרכים הבאות:

Select DatabasePropertyEx('master','Collation');
Select DatabasePropertyEx(DB_Name(),'Collation');
Select name,collation_name From sys.databases Where database_id=DB_ID();

clip_image004

כשיוצרים טבלה ובה עמודות טקסט Char/NChar/VarChar/NVarChar ה-Collation שלהן יהיה כשל הדטבייס אלא אם כן הוגדר אחרת. כדאי לציין שלטבלה עצמה אין Collation למרות שעיון ב-Properties שלה יוצר מצג שווא כאילו כן:

clip_image006

טעות שנתקלתי בה: ניסו לברר כיצד זה שיצר את הטבלה או את הדטבייס התייחס ל-Collation על ידי יצירה של סקריפט של האובייקט:

clip_image008

הסקריפט לא כלל התייחסות ל-Collation ולא מפני שמי שיצר אותו לא התייחס לנושא וסמך על ברירת המחדל, אלא מפני שבהגדרות המערכת של ה-SSMS לא ציינו שאנחנו מעוניינים לכלול בסקריפט גם את ה-Collation:

clip_image010

אם נשנה את האופציה ל-True הגדרת ה-Collation תופיע בכל פקודות ה-Create של הטבלאות והדטבייסים גם אם היא לא נכללה במפורש בפקודה המקורית, ולכן אין ללמוד מכך דבר.

בכל מקרה- את ה-Collation של הדטבייס או של העמודה ניתן לשנות בקלות על ידי פקודת Alter Database או Alter Table מתאימה,
ואם ה-Collation של העמודה שונה מזה של הדטבייס אזי או שמלכתחילה העמודה הוגדרה במפורש כ-Collation שונה, או שבדיעבד שונה ה-Collation של אחד מהם.