1

I want my Firebird SQL to loop through part of the code WHILE a condition is meet.

Initially I didn't even think it was possible. However I have done some reading and now believe that I can use WHILE loop.

I understand a FOR loop is not what I want as it applies to the whole code, not just part of it.

I am using this in Excel and could use some VBA code to do what I want, but it would be better if I can do it all via Firebird SQL as then I can apply it elsewhere.

SELECT 
'1' as "Qty",
'of ' || ALP3.PROPERTYVALUE AS "Total Qty"

FROM ASSEMBLYLINES
LEFT JOIN ASSEMBLYLINEPROPS ALP1 ON  ALP1.HEADERSYSUNIQUEID = ASSEMBLYLINES.SYSUNIQUEID AND ALP1.PROPERTYNAME = 'Process2'
LEFT JOIN ASSEMBLYLINEPROPS ALP2 ON  ALP2.HEADERSYSUNIQUEID = ASSEMBLYLINES.SYSUNIQUEID AND ALP2.PROPERTYNAME = 'Process3'
LEFT JOIN ASSEMBLYLINEPROPS ALP3 ON  ALP3.HEADERSYSUNIQUEID = ASSEMBLYLINES.SYSUNIQUEID AND ALP3.PROPERTYNAME = 'Job Quantity'
LEFT JOIN ASSEMBLYLINEPROPS ALP4 ON  ALP4.HEADERSYSUNIQUEID = ASSEMBLYLINES.SYSUNIQUEID AND ALP4.PROPERTYNAME = 'Drawing No'

WHERE ASSEMBLYLINES.ORDERNUMBER='16708R01' 
AND ASSEMBLYLINES.LINECODE='FABPART'
AND ASSEMBLYLINES.SYSUSERCREATED <> 'EXTERNAL USER'

ORDER BY ALP4.PROPERTYVALUE

My results using the code above is:

Qty Total Qty
1       4

However, what I want is:

My results using the code above is:

Qty Total Qty
1       4
2       4
3       4
4       4

I understand the While loop would be something like:

While Qty <= ALP3.PROPERTYVALUE Do
    <<output>>
Loop
ain
  • 22,394
  • 3
  • 54
  • 74
Simon King
  • 165
  • 2
  • 17
  • 2
    `while` is supported in PSQL only: stored procedures, execute blocks, triggers, not in simple select statements. In SPs, EBs you may do your own logic using a `for select` to iterate rows, `while` loop and `suspend` to output rows. Your question is not enough clear nor well described. If you want to "expand" the Qty from 1 to Total Qty then the way above should work. – Marcodor Feb 07 '19 at 07:33
  • 1
    Please provide sample data and and try to give a better description of your expected output. Why do you want to output Qty 1, 2, 3, 4 (which yields a total of 10 in my interpretation)? Do you just want to repeat the row as mean times as the total quantity? The simplest might be to create a selectable stored procedure or execute block. – Mark Rotteveel Feb 07 '19 at 09:39

1 Answers1

2
Qty Total Qty
1       4
2       4
3       4
4       4


I understand the While loop would be something like:
While Qty <= ALP3.PROPERTYVALUE Do
    <<output>>
Loop

So, your "quantity" column is not actually a quantity of some real data (like quantity of containers in cargo ship), but a row number in some your output report/grid. And then what you want is limiting the output "rowset" - matrix, table, grid - to some N first rows.

Well, that is exactly how it is done, asking for the first rows only.

Select FIRST(4) column1, column2, column3 
From table 1
Where condition1 and condition2 or condition3

See the "first" clause in documentation: https://www.firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-dml-select.html

Also see "Limiting result rows" chapyer in Wikipedia: https://en.wikipedia.org/wiki/Select_%28SQL%29#Limiting_result_rows

You can also use "window functions" starting with Firebird version 3, but they are somewhat overkill for the simple task of "only give me first N rows".

Now, there is one more method that provides for embedding a totally voluntary condition, but that is from "ugly hacks" toolsets and does not work in a typical situation when several simultaneous connections form different client programs are running. You can use a "generator" as part of the WHILE clause:

Select .....
Where (GEN_ID(cancel_generator_name, 0) = 0) AND ( ...you normal conditions...)

You set the generator value to 0 before the query, and your client evaluates some conditions of your choice while reading the data, and when it wants to - from some another SQL command library object it issues the generator change command, which would immediately skip the rest of the query. However while sometimes this is a useful technique, but only in very specific rare situations.


Since Mark seems to be better guessing than me, then some outlines for future guesswork.

SP is a standard abbreviation for SQL Stored Procedure. Firebird's Execute Block is essentially an anonymous non-persistent SP.

So, we start with a persistent and named SP.

create or alter procedure SEQ (
    FROM_1_TO integer not null)
returns (
    COUNTER integer)
as
begin
  counter = 1;
  while ( counter <= from_1_to ) do begin
    suspend;
    counter = counter + 1;
  end
end

Select 1, s.counter from rdb$database, seq(5) s

CONSTANT    COUNTER
1   1
1   2
1   3
1   4
1   5

The next question would be how to

  • join the table with SP (stored procedure) dependent upon specific table row values
  • avoid SP being executed with NULL parameter values

The answer is - LEFT JOIN, as shown in the FAQ: http://www.firebirdfaq.org/faq143/

CREATE TABLE T2 (
    ID     INTEGER NOT NULL PRIMARY KEY,
    TITLE  VARCHAR(10) NOT NULL,
    QTY    INTEGER NOT NULL
);

INSERT INTO T2 (ID, TITLE, QTY) VALUES (1, 'aaaa', 2);
INSERT INTO T2 (ID, TITLE, QTY) VALUES (2, 'bbbb', 5);
INSERT INTO T2 (ID, TITLE, QTY) VALUES (3, 'ccccc', 4);

Select * from t2 t
left join seq(t.qty) s on 1=1

ID  TITLE   QTY COUNTER
1   aaaa    2   1
1   aaaa    2   2
2   bbbb    5   1
2   bbbb    5   2
2   bbbb    5   3
2   bbbb    5   4
2   bbbb    5   5
3   ccccc   4   1
3   ccccc   4   2
3   ccccc   4   3
3   ccccc   4   4

If you would have many different queries on different tables/fields that would require this rows-cloning added then having a dedicated counter-generating SP makes sense.

However if you only need this rather exotic rows cloning once, then maybe polluting a global namespace with an SP you would never need again would be less of a good idea.

It seems one can not select from an EB, though: Select from execute block?

So you would have to make a specific ad-hoc EB exactly for your select statement. Which, arguably, might be the very reason d'etre for anonymous non-persistent EB.

execute block
   returns (ID INTEGER, TITLE VARCHAR(10), QTY INTEGER, COUNTER INTEGER)
as
begin
  for select
    id, title, qty from t2
    into :id, :title, :qty
  do begin
    counter = 1;
    while
      (counter <= qty)
    do begin
      suspend;
      counter = counter + 1;
    end
  end
end

However the data access library your application uses to connect to Firebird should understand then that while this query is not SELECT-query it still returns the "rowset". Usually they do, but who knows.

Arioch 'The
  • 15,799
  • 35
  • 62
  • I think the problem is that there is one row with a total quantity, and the OP wants to repeat that row 'total quantity' times. – Mark Rotteveel Feb 07 '19 at 09:40
  • @MarkRotteveel well, d'oh! Make an SP or even exec-block returning 1,2,3,...N and then cross-join with it, or so it seems – Arioch 'The Feb 07 '19 at 12:28
  • @Mark Rottenveel is correct with what I want to achieve. – Simon King Feb 07 '19 at 19:22
  • @Arioch'The The "qty" value is not from a data set. It is set to 1 as the "Total Qty" is often 1 so 1 of 1 makes sense. But when there are more than 1 then I need the qty to increment by 1 each time. The output is being used to make labels in a manufacturing environment. what is an SP? I have read up about exec-block which I have edited into my question. Just not sure where to put the EXECUTE BLOCK at this stage. – Simon King Feb 07 '19 at 19:49
  • @SimonKing SP - stored procedure. `Execute Block` is essentially an anonymous non-persistent SP. `I need the qty to increment by 1 each time` - so it is the counter, row number. Total Quantity would incremented not by one, but by a specific quantity in every row. Like: row no / t.qty / sp.qty: 1 / 1 / 1 || 2 / 2 / 1 || 3 / 5 / 3 || 4 / 7 / 2 and so forth. Also, what Firebird version you use? – Arioch 'The Feb 08 '19 at 09:00
  • @SimonKing basically you have to show us the source data and then explain your logic of limiting it. As of now for me it definitely looks you want to get "first N rows" - and that is implemented by `select first(N) .....` clause. Show the source data, show the desired output, show how your task is different from "select first(N) ...." and explain why and how you intend to cut off the output. – Arioch 'The Feb 08 '19 at 09:03
  • BTW, Marcodor already explained what SP is yesterday, in the first comment to the question. – Arioch 'The Feb 08 '19 at 09:50
  • In Firebird 3 there can be yet one more approach, though perhaps yet more ugly. To make a table having one column with values 1,2,3,...100000, then cross-join it with the original `select` using Window Functions to cut cloning to size. – Arioch 'The Feb 08 '19 at 10:02