מדריך T-SQL: יציאה ודילוג בתוך לולאות

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

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

דילוג באמצעות CONTINUE

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


DECLARE @counter int = 1,
		@characterNum int,
		@factionName varchar(50),
		@factionMaxNum int = (SELECT MAX(FactionID) FROM Factions)
--
	BEGIN
		WHILE @counter <= @factionMaxNum
			BEGIN
				SELECT @characterNum = COUNT(PersonID)
				FROM Characters
				WHERE Allegiance = @counter
--
				SELECT @factionName = FactionName
				FROM Factions
				WHERE FactionID = @counter
--			
				PRINT 'The number of characters loyal to the ' + @factionName + ' faction is: ' + CAST(@characterNum AS VARCHAR)
--
				SET @counter += 1
			END
	END 

והתוצאה נראית כך:

loop_result

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


IF @characterNum < 4 CONTINUE

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


DECLARE @counter int = 0,
		@characterNum int,
		@factionName varchar(50),
		@factionMaxNum int = (SELECT MAX(FactionID) FROM Factions)
--
	BEGIN
		WHILE @counter <= @factionMaxNum
			BEGIN
				SET @counter += 1
--
				SELECT @characterNum = COUNT(PersonID)
				FROM Characters
				WHERE Allegiance = @counter
--
				IF @characterNum < 4 CONTINUE
--
				SELECT @factionName = FactionName
				FROM Factions
				WHERE FactionID = @counter
--			
				PRINT 'The number of characters loyal to the ' + @factionName + ' faction is: ' + CAST(@characterNum AS VARCHAR)			
			END
	END 

עכשיו התוצאה הסופית נראית ככה:

loop_result2

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

יציאה מלולאה באמצעות BREAK

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


DECLARE @counter int = 0,
		@characterNum int,
		@factionName varchar(50),
		@factionMaxNum int = (SELECT MAX(FactionID) FROM Factions)
--
	BEGIN
		WHILE @counter <= @factionMaxNum
			BEGIN
				SET @counter += 1
--
				SELECT @characterNum = COUNT(PersonID)
				FROM Characters
				WHERE Allegiance = @counter
--
				SELECT @factionName = FactionName
				FROM Factions
				WHERE FactionID = @counter
--			
				PRINT 'The number of characters loyal to the ' + @factionName + ' faction is: ' + CAST(@characterNum AS VARCHAR)			
--
				IF @factionName = 'Targaryen' BREAK
			END
	END 

עכשיו התוצאה תראה ככה:

loop_result3

גם במקרה של CONTINUE וגם במקרה של BREAK התנאים אינם מחייבים. בדוגמא האחרונה, טבלת קבוצות שלא הייתה מכילה את בית Targaryen הייתה מקבלת סריקה מלאה והלולאה הייתה מסיימת את מספר הריצות שהוגדר לה. בדוגמא של CONTINUE היינו עשויים להראות את כל הבתים, אם כולם היו גדולים מספיק, או לא להראות אף שורה אם כל הבתים היו בעלי 3 עוקבים ומטה – הלולאה פשוט הייתה מדלגת על כל השורות בטבלה.