Spock specify data table for multiple result values? - spock

I have a set of inputs (a,b,c) and an output type for each set of them. The output type has two fields (p,q). Can I specify values for output type fields like this as part of where data table?
def "test service"(int a, int b, int c) {
setup:
req = reqBldr(a,b,c)
expectedRsp = rspBldr(p,q)
when:
rsp = service.call(req)
then:
expectedRsp=rsp
where:
a | b | c || p | q
1 | 3 | 3 || 0 | 0
7 | 4 | 4 || 0 | 1
0 | 0 | 0 || 1 | 1
}

Yes you can. As the error message will tell you, the method either needs to declare five or zero parameters (all data variables or none). Another error message will tell you that you need to use == rather than = in the assertion. Last but not least, Groovy requires local variables to be declared with def. Otherwise you should get a MissingPropertyException.

Related

Firebird stored procedure with indexed variable using execute statement

How can I use indexed variable in a Firebird stored procedure? I mean, I have output parameters ODATE1, ODATE2, ODATE3, can I use as ':ODATE' || COUNTER to set the value in a loop?
I have 2 tables like this:
1. T_EMPLOYEE
---------------
| ID_E | NAME |
---------------
| 1 | Anne |
---------------
| 2 | Bob |
---------------
2. T_WORK
----------------------------
| ID_W | DATE_W | ID_E |
----------------------------
| 1 | 2021-01-01 | 1 |
----------------------------
| 2 | 2021-01-01 | 2 |
----------------------------
| 3 | 2021-01-02 | 1 |
----------------------------
| 4 | 2021-01-03 | 2 |
----------------------------
From that table I want to make a stored procedure to get this result:
DASHBOARD
-----------------------------------------------------------
| OID_E | ONAME | ODATE1 | ODATE2 | ODATE3 |
----------------------------------------------------------
| 1 | Anne | 1 | 1 | 0 |
-----------------------------------------------------------
| 2 | Bob | 1 | 0 | 1 |
-----------------------------------------------------------
I tried using EXECUTE STATEMENT like this in stored procedure:
DECLARE VARIABLE COUNTER INT;
BEGIN
FOR
SELECT ID_E, NAME FROM T_EMPLOYEE
INTO :OID_E, :ONAME
D0
BEGIN
COUNTER = 1;
WHILE (COUNTER<=3) DO
BEGIN
EXECUTE STATEMENT 'SELECT COUNT(*) FROM T_WORK WHERE DATE_W = ''2021-01-0' || COUNTER ||
''' AND ID_E = ' || :OID_E || ' INTO :ODATE' || COUNTER;
COUNTER = COUNTER + 1;
END
SUSPEND;
END
END /*procedure*/
The procedure can't be compiled.
Then I tried the simple one like this without COUNTER index replacement:
DECLARE VARIABLE COUNTER INT;
BEGIN
FOR
SELECT ID_E, NAME FROM T_EMPLOYEE
INTO :OID_E, :ONAME
D0
BEGIN
COUNTER = 1;
WHILE (COUNTER<=3) DO
BEGIN
EXECUTE STATEMENT 'SELECT COUNT(*) FROM T_WORK WHERE ID_E = :OID_E ' ||
' AND DATE_W =''2021-01-02'' INTO :ODATE2';
COUNTER = COUNTER + 1;
END
SUSPEND;
END
END /*procedure*/
The procedure can be compiled, but when I execute, it will raise this error:
SQL Error: Dynamic SQL Error SQL error code = #1 Token unknown - line #1, column #2 #1. Error Code: -104. Invalid token
Please give me insight. How to use EXECUTE STATEMENT to make a flexible looping to set indexed variable.
Or you have another solution for my needs.
Additional information: Firebird v2.5
You cannot dynamically reference PSQL variables (including parameters) like this. However, you don't need to jump through all these hoops to get the desired results.
You can use something like the following(which doesn't even need a procedure):
select e.ID_E as OID_E, e.NAME as ONAME,
count(case when w.DATE_W = date '2021-01-01' then 1 end) as ODATE1,
count(case when w.DATE_W = date '2021-01-02' then 1 end) as ODATE2,
count(case when w.DATE_W = date '2021-01-03' then 1 end) as ODATE3
from T_EMPLOYEE e
inner join T_WORK w
on w.ID_E = e.ID_E
group by e.ID_E, e.NAME
order by e.ID_E
Fiddle: https://dbfiddle.uk/?rdbms=firebird_3.0&fiddle=59066afc0fd7f6b5cb87eac99164e899

creating a type consisting of a subset of int and float'

Suppose I wanted to represent ONLY these 8 temperatures:
98F
99F
100F
101F
37.0C
37.5C
38.0C
38.5C
I would have a type such as :
type Temp =
| F of int
| C of float
Perhaps rather than using int and float, I would use more specific types?
How do I create a type that will ONLY allow 8 different temperatures?
Well, if you are extremely confident that you only will ever require 8 temperatures, you could do something like this:
type AllowedTemperatures =
| F98
| F99
| F100
| F101
| C370
| C375
| C380
| C385
That said if you want to instead use your type Temp, this cannot be done easily as it basically requires the ability to have dependent types, which is a feature F# doesn't have (though apparently there's a package that I haven't tried that attempts to add this to the language). You could try with ssomething like this:
type Temp =
| F of int
| C of float with
static member fromInt value =
if value <> 98 && value <> 99 && value <> 100 && value <> 101 then
raise <| System.ArgumentOutOfRangeException ("value")
else
F value
static member fromFloat value =
if value <> 37.0 && value <> 37.5 && value <> 38.0 && value <> 38.5 then
raise <| System.ArgumentOutOfRangeException ("value")
else
C value
And always construct your discriminated union members via the static members. But this won't prevent someone from directly calling the constructor.
EDIT: Thanks to the suggestions from AMieres, one way to make it less likely to call F incorrectly would be to shadow over the constructors in Temp. For example, you could do something like:
type Temp =
| F of int
| C of float
let F value =
if value <> 98 && value <> 99 && value <> 100 && value <> 101 then
raise <| System.ArgumentOutOfRangeException ("value")
else
F value
However, someone could still call Temp.F.

Negative pattern match

I have the following code (`IgnoreCase is an active pattern I have defined elsewhere):
match myType with
| {Field1 = IgnoreCase "invalid"} -> None
| {Field2 = Some f2
Field3 = Some f3
Field4 = None | Some (0 | 1 | 2)}
-> Some (f2, f3)
| _ -> None
As you can see, Field1 has a blacklisted value and Field4 has whitelisted values (so do Field2 and Field3 in the sense that they must be Some). IMHO it would look slightly cleaner if I could do all checks in the same case, i.e. do the Field1 = IgnoreCase "invalid" match together with the other matches using e.g. Field1 <> IgnoreCase "invalid", but that particular example does not compile. I know about guards, but that does not seem cleaner than the original solution.
Is it possible to do "negative" (logical NOT) pattern matches in the sense that a value should not match some other value, without using guards?
As far as I know, there's no good way to do this. You can see the supported patterns here and verify that there's no construct for negation. One reason for the lack of such a feature might be that it would interact strangely with other pattern matching features (e.g. what would it mean to introduce an identifier with an as pattern inside of a negative context?). And this is not something that you could implement in a general fashion yourself via an active pattern, because an active pattern receives a value, not a pattern expression.
You could define an active pattern called Not:
let (|Not|_|) a b = if a <> b then Some () else None
Then you can use it inside the pattern match:
match myType with
| {Field1 = Not "invalid"
Field2 = Some f2
Field3 = Some f3
Field4 = None | Some (0 | 1 | 2)}
-> Some (f2, f3)
| _ -> None
A limitation of this active pattern is that it must take a literal value, not a sub-pattern. Active patterns aren't composable like the built-in patterns. However, that's also a potential advantage because you can pass it identifiers as values instead of binding a new value to the identifier:
let invalidString = "invalid"
match myType with
| {Field1 = Not invalidString
...
I would use active patterns like this
let (|Check|_|) x =
let = { Field2 = f2; Field3 = f3; Field4 = f4 }
let check =
f4 = None ||
f4 = Some 1 ||
f4 = Some 2 ||
f4 = Some 3
if check then Some (f2, f3) else None
match myType with
| {Field1 = "invalid"} -> None
| Check (x, y) -> Some (x, y)
| _ -> None

Progress 4GL nesting blocks to display related data

First all I'm very new to Progress 4GL and still trying to get my head around how nesting FOR EACH blocks works. I have the following two tables that I'm getting information out of, ivc_header and ivc_mchgs:
ivc_header
invoice_nbr | sold_to_cust_nbr | sold_to_cust_seq | invoice_amt
1000051 | 70 | 0 | $1,000
1000049 | 70 | 1 | $1,500
1000010 | 310 | 0 | $2,000
1000011 | 310 | 1 | $2,500
ivc_mchgs
invoice_nbr | line_nbr | misc_seq_nbr | extension
1000051 | 1 | 1 | $300
1000051 | 1 | 2 | $200
1000051 | 2 | 1 | $100
1000049 | 1 | 1 | $400
1000049 | 1 | 2 | $100
1000049 | 2 | 1 | $150
1000010 | 1 | 1 | $50
1000010 | 1 | 2 | $50
1000010 | 2 | 1 | $100
1000011 | 1 | 1 | $75
1000011 | 1 | 2 | $80
1000011 | 2 | 1 | $90
Just FYI, the primary key for ivc_header is invoice_nbr and for ivc_mchgs the primary is a composite key consisting of invoice_nbr, line_nbr, and misc_seq_nbr. The foreign key is invoice_nbr.
Just a note about the data, the information in ivc_mchgs are miscellaneous charges by invoice line_nbr.
What I'm trying to get is the total invoice_amt and extension by sold_to_cust_nbr + sold_to_cust seq. After doing some research I've decided to put the totals in variables instead of using Progress' built in ACCUMULATE function.
Here is the code that I have:
DEFINE VARIABLE cCustNum AS CHARACTER NO-UNDO.
DEFINE VARIABLE dInvoiceSubTotal AS DECIMAL NO-UNDO.
DEFINE VARIABLE dSurchargeTotal AS DECIMAL NO-UNDO.
FOR EACH ivc_header
NO-LOCK
WHERE (ivc_header.sold_to_cust_nbr = "000070")
OR (ivc_header.sold_to_cust_nbr = "000310")
BREAK BY ivc_header.sold_to_cust_nbr:
IF FIRST-OF(ivc_header.sold_to_cust_nbr) THEN
ASSIGN dInvoiceSubTotal = 0.
ASSIGN dInvoiceSUbTotal = dInvoiceSUbTotal + ivc_header.invoice_amt.
IF LAST-OF(ivc_header.sold_to_cust_nbr) THEN
DISPLAY ivc_header.sold_to_cust_nbr + ivc_header.sold_to_cust_seq FORMAT "x(9)" LABEL "CustNum"
dInvoiceSUbTotal LABEL "SubTotal".
FOR EACH ivc_mchgs WHERE ivc_header.invoice_nbr = ivc_mchgs.invoice_nbr
NO-LOCK
BREAK BY ivc_mchgs.invoice_nbr:
IF FIRST-OF(ivc_mchgs.invoice_nbr) THEN
ASSIGN dSurchargeTotal = 0.
ASSIGN dSurchargeTotal = dSurchargeTotal + ivc_mchgs.extension.
IF LAST-OF (ivc_mchgs.invoice_nbr) THEN
DISPLAY
dSurchargeTotal LABEL "Surcharge".
END.
END.
This code will give me the total invoice_amt by sold_to_cust_nbr + sold_to_cust_seq and totals the extension by invoice_nbr. What I can't figure out how to do is get a total of extension by sold_to_cust_nbr + sold_to_cust_seq.
Any help is appreciated.
Thanks
I think you might not be aware that you can specify multiple BY clauses.
IOW you might want to code the inner FOR EACH something like this:
FOR EACH ivc_mchgs NO-LOCK WHERE ivc_header.invoice_nbr = ivc_mchgs.invoice_nbr
BREAK BY ivc_mchgs.invoice_nbr
BY ivc_mchgs.sold_to_cust_nbr
BY ivc_mchgs.sold_to_cust_seq:
IF FIRST-OF(ivc_mchgs.invoice_nbr) THEN
ASSIGN dSurchargeTotal = 0.
IF FIRST-OF(ivc_mchgs.sold_to_cust_nbr ) THEN ...
etc.
On the assumption you want both the exchange total by invoice and summary, then you could just do this:
DEFINE VARIABLE cCustNum AS CHARACTER NO-UNDO.
DEFINE VARIABLE dInvoiceSubTotal AS DECIMAL NO-UNDO.
DEFINE VARIABLE dSurchargeTotal AS DECIMAL NO-UNDO.
DEFINE VARIABLE dSurchargeSubTl AS DECIMAL NO-UNDO.
FOR EACH ivc_header
NO-LOCK
WHERE (ivc_header.sold_to_cust_nbr = "000070")
OR (ivc_header.sold_to_cust_nbr = "000310")
BREAK BY ivc_header.sold_to_cust_nbr:
IF FIRST-OF(ivc_header.sold_to_cust_nbr) THEN
ASSIGN dInvoiceSubTotal = 0
dSurchargeSubTl = 0.
ASSIGN dInvoiceSUbTotal = dInvoiceSUbTotal + ivc_header.invoice_amt.
IF LAST-OF(ivc_header.sold_to_cust_nbr) THEN
DISPLAY ivc_header.sold_to_cust_nbr + ivc_header.sold_to_cust_seq FORMAT "x(9)" LABEL "CustNum"
dInvoiceSUbTotal LABEL "SubTotal"
dSurchargeSubTL LABEL "Srchg SubTl".
FOR EACH ivc_mchgs WHERE ivc_header.invoice_nbr = ivc_mchgs.invoice_nbr
NO-LOCK
BREAK BY ivc_mchgs.invoice_nbr:
IF FIRST-OF(ivc_mchgs.invoice_nbr) THEN
ASSIGN dSurchargeTotal = 0.
ASSIGN dSurchargeTotal = dSurchargeTotal + ivc_mchgs.extension.
IF LAST-OF (ivc_mchgs.invoice_nbr) THEN DO:
DISPLAY dSurchargeTotal LABEL "Surcharge".
ASSIGN dSurchargeSubTl = dSurchargeSubTl + dSurchargeTotal.
END.
END.
END.
The elegant way would be to combine both queries using a left outer join, and use the ACCUMULATE functions, but this should work.

How to list most frequent text values within a range?

I'm an intermediate excel user trying to solve an issue that feels a little over my head. Basically, I'm working with a spreadsheet which contains a number of orders associated with customer account #s and which have up to 5 metadata "tags" associated with them. I want to be use that customer account # to pull the 5 most commonly occurring metadata tags in order.
Here is a mock up of the first set of data
Account Number Order Number Metadata
5043 1 A B C D
4350 2 B D
4350 3 B C
5043 4 A D
5043 5 C D
1204 6 A B
5043 7 A D
1204 8 D B
4350 9 B D
5043 10 A C D
and the end result I'm trying to create
Account Number Most Common Tag 2nd 3rd 4th 5th
5043 A C B N/A
4350 B D C N/A N/A
1204 B A C N/A N/A
I was trying to work with the formula suggested here:
=ARRAYFORMULA(INDEX(A1:A7,MATCH(MAX(COUNTIF(A1:A7,A1:A7)),COUNTIF(A1:A7,A1:A7),0)))
But I don't know how to a) use the customer account # as a precondition for counting the text values within the range. b) how to circumvent the fact that the Match forumula only wants to work with a single column of data and c) how to read the 2nd, 3rd, 4th, and 5th most common values from this range.
The way I'm formatting this data isn't set in stone. I suspect the way I'm organizing this information is holding me back from simpler solutions, so any suggestions on re-thinking my organization would be just as helpful as insights on how to create a formula to do this.
Implementing this kind of frequency analysis using built-in functions is likely to be a frustrating exercise. Since you are working with Google Sheets, take advantage of the custom functions, written in JavaScript and placed into a script bound to the sheet (Tools > Script Editor).
The function I wrote for this purpose is below. Entering something like =tagfrequency(A2:G100) in the sheet will produce desired output:
+----------------+-----------------+-----+-----+-----+-----+
| Account Number | Most Common Tag | 2nd | 3rd | 4th | 5th |
| 5043 | D | A | C | B | N/A |
| 4350 | B | D | C | N/A | N/A |
| 1204 | B | A | D | N/A | N/A |
+----------------+-----------------+-----+-----+-----+-----+
Custom function
function tagFrequency(arr) {
var dict = {}; // the object in which to store tag counts
for (var i = 0; i < arr.length; i++) {
var acct = arr[i][0];
if (acct == '') {
continue; // ignore empty rows
}
if (!dict[acct]) {
dict[acct] = {}; // new account number
}
for (var j = 2; j < arr[i].length; j++) {
var tag = arr[i][j];
if (tag) {
if (!dict[acct][tag]) {
dict[acct][tag] = 0; // new tag
}
dict[acct][tag]++; // increment tag count
}
}
}
// end of recording, begin sorting and output
var output = [['Account Number', 'Most Common Tag', '2nd', '3rd', '4th', '5th']];
for (acct in dict) {
var tags = dict[acct];
var row = [acct].concat(Object.keys(tags).sort(function (a,b) {
return (tags[a] < tags[b] ? 1 : (tags[a] > tags[b] ? -1 : (a > b ? 1 : -1)));
})); // sorting by tag count, then tag name
while (row.length < 6) {
row.push('N/A'); // add N/A if needed
}
output.push(row); // add row to output
}
return output;
}
You also could get this report:
Account Number Tag count
1204 B 2
1204 A 1
1204 D 1
4350 B 3
4350 D 2
4350 C 1
5043 D 5
5043 A 4
5043 C 3
5043 B 1
with the formula:
=QUERY(
{TRANSPOSE(SPLIT(JOIN("",ArrayFormula(REPT(FILTER(A2:A,A2:A<>"")&",",5))),",")),
TRANSPOSE(SPLIT(ArrayFormula(CONCATENATE(FILTER(C2:G,A2:A<>"")&" ,")),",")),
TRANSPOSE(SPLIT(rept("1,",counta(A2:A)*5),","))
},
"select Col1, Col2, Count(Col3) where Col2 <>' ' group by Col1, Col2
order by Col1, Count(Col3) desc label Col1 'Account Number', Col2 'Tag'")
The formula will count the number of occurrences of any tag.

Resources