Project: YugabyteDB (App Definition and Development / Database)
File: two-case-studies.md

title: > Two case studies: Using various kinds of "loop" statement, the "exit" statement, and the "continue" statement [YSQL] headerTitle: > Two case studies: Using various kinds of "loop" statement, the "exit" statement, and the "continue" statement linkTitle: Two case studies description: Two case studies to demonstrate the use of the "for" loop (over a range of integers), the "for" loop (over the results of a query), the "foreach" loop, the "infinite" loop, the "exit" statement, and the "continue" statement [YSQL]. menu: stable: identifier: two-case-studies parent: loop-exit-continue weight: 60 type: docs showRightNav: true The first case study demonstrates the use of these statements: the foreach loop statement to iterate over all the elements of an array the infinite loop statement together with the exit statement to extract words from lines of text the cursor for loop statement to iterate over the results of a table function the integer range for loop statement to iterate over the elements of an array that lie between a specified range of index values The second case study demonstrates the use of these statements: the foreach loop statement to iterate over all the elements of an array the integer range for loop statement to iterate over the characters in a string the exit statement to terminate the iteration of both a foreach loop statement and an integer range for loop statement when enough results have been found the continue statement to abandon the current iteration of a loop and to start the next iteration of the same loop when a test shows that the current result fails to satisfy a requirement the continue statement with a label to abandon the current iteration of an inner loop and to start the next iteration of the outer loop when a test shows that the inner loop has already met the processing requirement for the outer loop's current element. Both studies use an array of lines of text. Set it up thus: ```plpgsql \c :db :u drop schema if exists s cascade; create schema s; create function s.dickens_lines() returns text[] set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare lines constant text[] not null := array[ ' The carriers horse was the laziest horse in the world, I should hope, and shuffled along, ', ' with his head down, as if he liked to keep people waiting to whom the packages were ', ' directed. I fancied, indeed, that he sometimes chuckled audibly over this reflection, ', ' but the carrier said he was only troubled with a cough. The carrier had a way of keeping ', ' his head down, like his horse, and of drooping sleepily forward as he drove, with one of ', ' his arms on each of his knees. I say drove, but it struck me that the cart would have ', ' gone to Yarmouth quite as well without him, for the horse did all that; and as to ', ' conversation, he had no idea of it but

Peggotty had a basket of refreshments ', ' on her knee, which would have lasted us out handsomely, if we had been going to London by ', ' the same conveyance. We ate a good deal, and slept a good deal. Peggotty always went to ', ' sleep with her chin upon the handle of the basket, her hold of which never relaxed; and I ', ' could not have believed unless I had heard her do it, that one defenceless woman could ', ' have snored so much. We made so many deviations up and down lanes, and were such a long ', ' time delivering a bedstead at a public house, and calling at other places, that I was ', ' quite tired, and very glad, when we saw Yarmouth. It looked rather spongy and soppy, I ', ' thought, as I carried my eye over the great dull waste that lay across the river; and I ', ' could not help wondering, if the world were really as round as my geography book said, how ', ' any part of it came to be so flat. But I reflected that Yarmouth might be situated at one ', ' of the poles; which would account for it. As we drew a little nearer, and saw the whole ', ' adjacent prospect lying a straight low line under the sky, I hinted to Peggotty that a ', ' mound or so might have improved it; and also that if the land had been a little more ', ' separated from the sea, and the town and the tide had not been quite so much mixed up, ', ' like toast and water, it would have been nicer. But Peggotty said, with greater emphasis ', ' than usual, that we must take things as we found them, and that, for her part, she was ', ' proud to call herself a Yarmouth Bloater. When we got into the street (which was strange ', ' enough to me) and smelt the fish, and pitch, and oakum, and tar, and saw the sailors ', ' walking about, and the carts jingling up and down over the stones, I felt that I had done ', ' so busy a place an injustice; and said as much to Peggotty, who heard my expressions of ', ' delight with great complacency, and told me it was well known (I suppose to those who had ', ' the good fortune to be born Bloaters) that Yarmouth was, upon the whole, the finest place ', ' in the universe. He was waiting for us, in fact, at the public house; and asked me how I ', ' found myself, like an old acquaintance. I did not feel, at first, that I knew him as well ', ' as he knew me, because he had never come to our house since the night I was born, and ', ' naturally he had the advantage of me. But our intimacy was much advanced by his taking me ', ' on his back to carry me

He was, now, a huge, strong fellow of six feet high, broad ', ' in proportion, and round-shouldered; but with a simpering boys face and curly light hair ', ' that gave him quite a sheepish look. He was dressed in a canvas jacket, and a pair of such ', ' very stiff trousers that they would have stood quite as well alone, without any legs in ', ' them. And you couldnt so properly have said he wore a hat, as that he was covered in a ', ' top, like an old building, with something pitchy. Ham carrying me on his back and a small ' ]; begin return lines; end; $body$; ``` It isn't necessary to clutter the text literal with $$ quoting because the text itself contains no straight single quotes but, rather, uses a proper (curly) apostrophe where this is needed. The heart of the solution is the code that extracts the words from a single line of text. This relies on just the infinite loop statement together with the exit when statement. But extending the problem statement to require extracting the words from an array of text lines brings opportunities to demonstrate other kinds of loop statement. First create a function to find the first word in a line of text and to return that word together with what remains when it is removed from the text. ```plpgsql create type s.firstwordand_remainder as (word text, remainder text); create function s.split_string(str in text) returns s.firstwordand_remainder set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare result s.firstwordand_remainder; begin result.word := (regexp_match(str, '[a-zA-Z]+'))[1]; result.remainder := regexp_replace(str, result.word, ''); return result; end; $body$; ``` The design of this function isn't of interest for the present case study's pedagogy. But it's of note that the implementation takes just two lines. Because the words in the text used here happen to use only latin letters and curly apostrophe, the regular expression that defines a word is very short. It would need to be more elaborate it had to accommodate European languages that use letters like these: . Test it like this. ```plpgsql create function s.splitstringtestresults(strin in text) returns text set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare str text not null := str_in; r text not null := ''; splitstring s.firstwordandremainder; begin loop splitstring := s.splitstring(str); exit when split_string.word is null; r := r||split_string.word||' | '; str := split_string.remainder; end loop; return r; end; $body$; select s.splitstringtest_results(' Marys most frequent typo: confusing its and its. '); ``` This is the result: ```output Marys | most | frequent | typo | confusing | its | and | its | ``` The test demonstrates the main algorithm for this case study. It uses the infinite loop statement like this: ```output set up for the first iteration repeat do some processing exit when finished note this iteration's result set up for the next iteration end repeat ``` It's a common patternand it's sometimes called the "loop-and-a-half" in generic computer science writings. As an exercise, try to rewrite it using a while loop

You'll find that the code is more elaborateand is therefore harder to understand and more prone to bugs. The solution to this case study's actual problem is just a trivial extension to the function s.splitstringtestresults()_. First, create a helper function so that the interesting code can be separated from the display of the results. It shows just the first ten words and the last ten words that have been extracted. Each set of ten words is split over two lines. ```plpgsql create function s.display(results in text[]) returns table(x text) set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare lb int not null := 0; begin for j in 1..4 loop case j when 1 then lb := 1; when 2 then lb := 6; when 3 then lb := cardinality(results) - 9; x := ''; return next; when 4 then lb := cardinality(results) - 4; end case; x := ''; for j in lb..(lb + 4) loop x := x||rpad(results[j], 15); end loop; return next; end loop; end; $body$; ``` It turns out the using the function s.tokenizedlines() demonstrates another interesting use of a loop_ statement. Look for this: ```plpgsql for z in (select x from s.display(results)) loop return next; end loop; ``` This is the standard pattern for consuming the output of a table function whose purpose is to display some results within a second table function whose purpose is to display other results. Create the function s.tokenizedlines()_ to implement the case study's solution: ```plpgsql create function s.tokenized_lines() returns table(z text) set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare lines constant text[] not null := s.dickens_lines(); line text not null := ''; results text[] not null := '{}'; splitline s.firstwordandremainder; begin <<lines>> foreach line in array lines loop <<words>> loop splitline := s.splitstring(line); exit words when split_line.word is null; results := results||split_line.word; line := split_line.remainder; end loop words; end loop lines; for z in (select x from s.display(results)) loop return next; end loop; end; $body$; ``` Execute it: ```plpgsql select s.tokenized_lines(); ``` This is the result: ```output The carriers horse was the laziest horse in the world pitchy Ham carrying me on his back and a small ``` This case study doesn't use any kinds of loop statement that case study #1 doesn't use. But it does use the continue statement. Especially, it shows its use with a label to abort the current iteration of an inner loop and then to start the next iteration of an outer loop. You might like to try to rewrite the code without using the continue statement. You'll need to use an if statement or a case statement together with a dedicated guard variable. The result will be cluttered code that is also less efficient than the code that's shown here. Suppose that you have a one-dimensional text array where each successive element holds the next line from, say, a novel. The goal is to implement this: Make the first line current. Traverse the characters in the current line from the first one to (up to) the last one. Inspect the current character. If it's a vowel, then note it. Otherwise skip

Abandon the current line: either if it contains Peggotty or when the same vowel is noted twice in succession or when the specified number of distinct vowels (say seven) have been noted Record the vowel string only if its length is the specified number of distinct vowels. Make the next line current. Repeat until the specified number of vowel strings (say ten) of the specified length have been recorded. Display the recorded vowel strings. Finish. Never mind if the goal seems artificial. It's always instructive to solve a well-specified problem when doing so requires some creative thinking, design, implementation, and testing. First, create a trivial helper function to test if a single character is a Latin alphabet vowel: ```plpgsql create function s.vowel(c in text) returns boolean set searchpath = pgcatalog, pg_temp language plpgsql as $body$ begin assert length(c) = 1; return c in ('a', 'e', 'i', 'o', 'u'); end; $body$; ``` Now create the table function s.f() to find the vowel strings and display them. ```plpgsql create function s.vowelsfromlines(noofvowels in int, noofresults in int) returns table(z text) set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare lines constant text[] not null := s.dickens_lines(); line text not null := ''; prev text not null := ''; c text not null := ''; vowels text not null := ''; results text[] not null := '{}'; r int not null := 0; begin <<lines>> foreach line in array lines loop -- Inspect up to every line. continue lines when line like '%Peggotty%'; -- Don't process any "line" that -- contains "Peggotty". c := ''; vowels := ''; prev := ''; <<characters>> for pos in 1..length(line) loop -- Inspect up to every character in each line. c := substring(line from pos for 1); if s.vowel(c) then vowels := vowels||c; continue lines when c = prev; -- Abandon this line when two successively -- found vowels are the same. prev := c; end if; exit characters when length(vowels) >= noofvowels; -- Don't want "vowels" values longer -- than "noofvowels". end loop characters; if length(vowels) = noofvowels then r := r + 1; results[r] := vowels; end if; exit lines when r >= noofresults; -- Finish when have enough results. end loop lines; foreach z in array results loop return next; end loop; end; $body$; ``` Test it like this: ```plpgsql select s.vowelsfromlines(7, 10); ``` This is the result: ```output eaieoea ueaieai ieaoiei iaoeaoi aeoeoue uieieae ouoeoei oeoeiou eouoeae aiaouae ``` Notice these uses of the continue statement: ```plpgsql -- At top level in the outer "lines" loop. continue lines when line like '%Peggotty%'; ``` and: ```plpgsql -- Nested inside the inner "characters" loop within the outer "lines" loop. continue lines when c = prev; ``` to abandon processing the current line and to move to the next one in the outer lines loop. Notice these uses of the exit statement: ```plpgsql exit characters when length(vowels) >= noofvowels; ``` to exit the inner characters loop when the vowels string is long enough. And: ```plpgsql exit lines when r >= noofresults; ``` to exit the lines loop when enough results have been recorded.