qParamMonths as used in the example database:
Code: Select all
SELECT * FROM "TBL"
WHERE ( "D" >= CAST( :Y1 || '-' || COALESCE ( :M1, 1 ) || '-1' AS DATE )
OR :Y1 IS NULL )
AND ( "D" < CAST( :Y2 + CASEWHEN( COALESCE ( :M2, 12 ) = 12, 1, 0 ) || '-' || MOD( COALESCE ( :M2, 12 ), 12 ) + 1 || '-1' AS DATE )
OR :Y2 IS NULL )
We select all columns from the single table "TBL". It is just one column in that table. The returned record set would be editable if I defined a primary key.
The WHERE condition consists of 2 conditions connected with AND. Both main conditions consist of 2 sub-conditions connected with OR.
Code: Select all
WHERE (<comparison_from> OR :Y1 IS NULL) AND (<comparison_until> OR :Y2 IS NULL)
If :Y1 is null, the first AND condition is TRUE anyway because of the OR operator: WHERE TRUE AND <comparison_until>
If :Y2 is null, the second AND condition is TRUE anyway: WHERE <comparison_from> AND TRUE
If both :Y1 and :Y2 are null, the expression expands to WHERE TRUE AND TRUE which simply returns all the records regardless of any date comparison.
If both :Y1 and :Y2 are given, the expression expands to WHERE <comparison_from> AND <comparison_until>. A record is selected if both comparisons return TRUE.
Code: Select all
"D" >= CAST( :Y1 || '-' || COALESCE ( :M1, 1 ) || '-1' AS DATE )
is the <comparison_from>. It compares the date field "D" with a start date which is a concatenated ISO date representing the 1st day of the given year and month: :Y1-Month-1.
COALESCE(:M1,1) substitutes a missing :M1 with the smallest month number 1 in order to compare the first month of the given year.
CAST('2020-5-1' AS DATE) converts the concatenated ISO string into a true date value comparable with the date values in the "D" column.
"D" >= CAST('2020-5-1' AS DATE) returns TRUE for every "D" being equal or bigger than 1st of May 2020.
Code: Select all
"D" < CAST( :Y2 + CASEWHEN( COALESCE ( :M2, 12 ) = 12, 1, 0 ) || '-' || MOD( COALESCE ( :M2, 12 ), 12 ) + 1 || '-1' AS DATE )
is the <comparison_until>. It compares the date field "D" with an end date which is a concatenated ISO date representing the 1st day of the month following the given month: :Y2-[Month+1]-1.
COALESCE(:M2,12) substitutes a missing :M2 with the highest month number 12. If no month is given we assume the last month of the given year as given month.
:Y2 + CASEWHEN( month = 12, 1, 0 ) adds 1 to :Y2 if the given month equals 12 and 0 otherwise.
MOD returns the residue of a division. MOD( month , 12 ) returns the same month number for months 1 to 11 and 0 for 12, so we get the next month number when we add +1. Next month after the 12 (December) is 1 (January). Instead of MOD( COALESCE ( :M2, 12 ), 12 ) we could use the longer expression CASEWHEN(COALESCE(:M2,12) = 12, 1, COALESCE(:M2,12)) alternatively.
CAST('2020-5-1' AS DATE) converts the ISO string into a true date value which can be compared with the date values in the "D" column.
If :Y2 = 2020 and :M2 is missing, COALESCE gives month number 12, MOD(12,12)+1 gives 1 and CASEWHEN(12 = 12, 1, 0) adds one to the year number.
The comparison reads "D" < CAST('2021-1-1' AS DATE) which returns TRUE for every "D" before the calculated date 2021-1-1. This will work with any given month even though we ignore how many days the given month has.
--------------------------------------------------------------------------
If you compare the query "qReportMonths" with the above parameter query, you will see that I only added the relation to the filter record and substituted the 4 parameters with the filter record's 4 criteria values. I forgot to substitute a COALESCE in the <condition_until>. Because of that mishap, the query returns no records when the M2 is missing while Y2 is given. This query returns a read-only row set because of the second table which is perfectly OK for reporting.