cross-tab conditional formatting based on column - crystal-reports

I have a cross-tab in crystal reports with column representing transport line, rows showing dates and summary field representing TEU (its just numerical value). So I have something like this:
L1 L2 L3 TOTAL
D1 10 5 0 15
D2 1 3 5 9
D3 3 50 17 70
Now I'd like to conditionally put background to the summary (numerical) field, but the condition depends also on the line. So in theory, after clicking on the summary field->format field->background->formula I'd put something like this:
select {#Line}
case "L1": (if currentfieldvalue >5 then crGreen else crRed)
case "L2": (if currentfieldvalue >10 then crGreen else crYellow)
case "L3": (if currentfieldvalue <8 then crBlue else crNoColor)
but for some reasons those conditions are completely ignored. I've even tried something as simple as
if {#Line} ="L1" then crGreen
but as above, crystal 2011 (14.06) ignores it.

Got it finally using GridRowColumnValue ("#Line"):
select GridRowColumnValue ("#Line")
case "L1": (if currentfieldvalue >5 then crGreen else crRed)
case "L2": (if currentfieldvalue >10 then crGreen else crYellow)
case "L3": (if currentfieldvalue <8 then crBlue else crNoColor)
Source: http://www.crystalreportsbook.com/Forum/forum_posts.asp?TID=6798

Related

Sum in range until value change

I'am trying to use this formula to make it work
=ARRAYFORMULA(IF(ISDATE_STRICT(S2:S) ; (MATCH(MAX(AB2:AB),AB2:AB;0)-1) ; "" ))
If there is a date in Column "S" I want it to display the sum of the blanks that would appear if in Column "S" is text
=ARRAYFORMULA(IF(ISDATE_STRICT(S2:S) ; ArrayFormula(MATCH(FALSE ; ISBLANK(AB2:AB) ; 0)-1) ; "" ))
I've tried this one as well but I only get 0's as a result.
Any idea how I can make it work?
Here is the sample sheet.
https://docs.google.com/spreadsheets/d/19f5phXeAwXwrKbWz7njgbznmurOav72GUuo_5IGcbls/edit?usp=sharing
in Q2 use:
=ARRAYFORMULA(IF(ISBLANK(
I1:INDEX(I:I; ROWS(I:I)-1));
{N2:INDEX(N:N; ROWS(N:N))\
I1:INDEX(N:N; ROWS(N:N)-1)};
I1:INDEX(O:O; ROWS(O:O)-1)))
in X2 use:
=INDEX(LAMBDA(x; IFNA(VLOOKUP(x; QUERY(VLOOKUP(ROW(x);
IF(ISDATE_STRICT(x); {ROW(x)\x}); 2; 1);
"select Col1,count(Col1) group by Col1"); 2; 0)-1))
(Q2:INDEX(Q:Q; MAX((Q:Q<>"")*ROW(Q:Q)))))
UPDATE:
we start with column Q. we can take a range Q2:Q but that range contains a lot of empty rows. the next best thing is to check the last non-empty row and set it as the end of the range resulting in Q2:Q73. but static 73 won't do in case the dataset would grow or shrink so to get 73 dynamically we take the MAX of multiplication of Q:Q not being empty and row number of that case eg. Q:Q<>"" will output only TRUE or FALSE so what we are getting is
...
TRUE * 72 = 1 * 72 = 72
TRUE * 73 = 1 * 73 = 73
FALSE * 74 = 0 * 74 = 0
...
so the formula for getting Q2:Q73 is:
=Q2:INDEX(Q:Q; MAX((Q:Q<>"")*ROW(Q:Q)))
it could also be:
=INDEX(INDIRECT("Q2:Q"&MAX((Q:Q<>"")*ROW(Q:Q))))
but it's just long to type... next, we use the new LAMBDA function that allows us to reference cell/range/formula with a placeholder. simple LAMBDA syntax is:
=LAMBDA(x; x)(A1)
where x is A1 and we can do whatever we want with the 2nd (x) argument of LAMBDA like for example:
=LAMBDA(a, a+a*120-a/a)(A1)
you can think of it as:
LAMBDA(A1, A1+A1*120-A1/A1)(A1)
or as just:
=A1+A1*120-A1/A1
the issue here is that we repeat A1 4 times but with LAMBDA we do it only once. also, imagine if we would have 100 characters long formula instead of A1 so the final formula with lambda would be 300 characters shorter compared to "old way" formula.
back to our formula... x is the representation of Q2:Q73. now let's focus on VLOOKUP. basically, the idea here is that IF Q column contains a date we return that date, otherwise we return the last date from above. simply put:
=ARRAYFORMULA(VLOOKUP(ROW(Q2:Q73);
IF(ISDATE_STRICT(Q2:Q73); {ROW(Q2:Q73)\Q2:Q73}); 2; 1))
as you can see Y2, Y3 and Y4 are the same so all we need to do is to count them up and later take away one to exclude Q2 but include just Q3 and Q4 eg. 3-1=2. for that we use simple QUERY where the output is:
date count
30.06.2022 3
so all we need to do is to pair up dates from Q column to QUERY output for that we use the outer VLOOKUP where the output is as follows:
3
#N/A
#N/A
9
#N/A
#N/A
...
now is the right time for that -1 correction while we have these errors coz ERROR-1=ERROR and 3-1=2 so after this -1 correction the output is:
2
#N/A
#N/A
8
#N/A
#N/A
...
and all we need to do now is to hide errors with IFERROR and the output is column X

Understanding how to read each-right and each-left combined in kdb

From q for mortals, i'm struggling to understand how to read this, and understand it logically.
1 2 3,/:\:10 20
I understand the result is a cross product when in full form: raze 1 2 3,/:\:10 20.
But reading from left to right, I'm currently lost at understanding what this yields (in my head)
\:10 20
combined with 1 2 3,/: ??
Help in understanding how to read this clearly (in words or clear logic) would be appreciated.
I found myself saying the following in my head whilst I program the syntax in q. q works from right to left.
Internal Monologue -> Join the string on the right onto each of the strings on the left
code -> "ABC",\:"-D"
result -> "A-D"
"B-D"
"C-D"
I think that's an easy way to understand it. 'join' can be replaced with whatever...
Internal Monologue -> Does the string on the right match any of the strings on the left
code -> ("Cat";"Dog";"CAT";"dog")~\:"CAT"
result -> 0010b
Each-right is the same concept and combining them is straightforward also;
Internal Monologue -> Does each of the strings on the right match each of the strings on the left
code -> ("Cat";"Dog";"CAT";"dog")~\:/:("CAT";"Dog")
result -> 0010b
0100b
So in your example 1 2 3,/:\:10 20 - you're saying 'Join each of the elements on the right to each of the elements on the left'
Hope this helps!!
EDIT To add a real world example.... - consider the following table
q)show tab:([] upper syms:10?`2; names:10?("Robert";"John";"Peter";"Jenny"); amount:10?til 10)
syms names amount
--------------------
CF "Peter" 8
BP "Robert" 1
IC "John" 9
IN "John" 5
NM "Peter" 4
OJ "Jenny" 6
BJ "Robert" 6
KH "John" 1
HJ "Peter" 8
LH "John" 5
q)
I you want to get all records where the name is Robert, you can do; select from tab where names like "Robert"
But if you want to get the results where the name is either Robert or John, then it is a perfect scenario to use our each-left and each-right.
Consider the names column - it's a list of strings (a list where each element is a list of chars). What we want to ask is 'does any of the strings in the names column match any of the strings we want to find'... that translates to (namesList)~\:/:(list;of;names;to;find). Here's the steps;
q)(tab`names)~\:/:("Robert";"John")
0100001000b
0011000101b
From that result we want a compiled list of booleans where each element is true of it is true for Robert OR John - for example, if you look at index 1 of both lists, it's 1b for Robert and 0b for John - in our result, the value at index 1 should be 1b. Index 2 should be 1b, index3 should be 1b, index4 should be 0b etc... To do this, we can apply the any function (or max or sum!). The result is then;
q)any(tab`names)~\:/:("Robert";"John")
0111001101b
Putting it all together, we get;
q)select from tab where any names~\:/:("Robert";"John")
syms names amount
--------------------
BP "Robert" 1
IC "John" 9
IN "John" 5
BJ "Robert" 6
KH "John" 1
LH "John" 5
q)
Firstly, q is executed (and hence generally read) right to left. This means that it's interpreting the \: as a modifier to be applied to the previous function, which itself is a simple join modified by the /: adverb. So the way to read this is "Apply join each-right to each of the left-hand arguments."
In this case, you're applying the two adverbs to the join - \:10 20 on its own has no real meaning here.
I find it helpful to also look at the converse case 1 2 3,\:/:10 20, running that code produces a 2x6 matrix, which I'd describe more like "apply join each-left to each of the right hand arguments" ... I hope that makes sense.
An alternative syntax which also might help is ,/:\:[1 2 3;10 20] - this might be useful as it makes it very clear what the function you're applying is, and is equivalent to your in-place notation.

Crystal Reports - Disable default rounding/Show all decimals

If I create a Formula Field A containing only 1/1000 and then drag it out into the report it will show "0,00" due to default number of decimals is set to 1.00 and default rounding is set to 0.01 for a field of with the type Number.
No problems.
When decimals and rounding are adjusted it will show 0,001.
When I create Formula Field B that only contains {#A} and increase decimals and rounding it will show as "0,001000000000".
But if I use A to create a text, totext({#A}) + ' test', the result will be "0,00 test" and I can not figure out a way to show the correct "0,001 test" without adding totext({#A},3). I don't want to use rounding because the number of decimals in A varies and I don't want to show any zeroes at the end.
I did modify this code (could be used in conditional formatting of decimals for a number field):
If CurrentFieldValue = Int(CurrentFieldValue) Then 0
Else If CurrentFieldValue * 10 = Int(CurrentFieldValue * 10) Then 1
Else If CurrentFieldValue * 100 = Int(CurrentFieldValue * 100) Then 2
Else If CurrentFieldValue * 1000 = Int(CurrentFieldValue * 1000) Then 3
Else If CurrentFieldValue * 10000 = Int(CurrentFieldValue * 10000) Then 4
Else DefaultAttribute
to
If {#A}= Int{#A} Then totext({#A},0)
Else If {#A}* 10 = Int{#A}* 10) Then totext({#A},1)
...
It works but I was hoping I wouldn't need all that extra code, I already have performance issues in my Crystal Reports and I don't want the risk of adding to that.
I just want to use the actual value in {#A} no matter the number of decimals.
I guess this could be caused by both default rounding settings as default number of decimals
And now I realise this might be caused by default number of decimals, not rounding.
If I change A to (1/1000)*6 it will show up as 0,01 unformatted, B will show "0,00600000 test". So it is a rounding problem.
Leave the output of your desired formula as a number.
Right click on the formula and select format field.
In the number tab select Custom Style and click customize.
For rounding option select 0.0000000001
Click the X-2 next to the Decimals option and enter this formula
local numbervar a;
local numbervar b;
for a := 1 to 10 do
(
if mid(strreverse(totext(CurrentFieldValue,10)),a,1) <> "0" then
(b := a;
a := 10);
);
11- b
I have found that the default rounding is defined by your system regional settings. If you increase the number of decimal places from 2 (usual) to say 8, all your problems will be resolved.

how to calculate total balance per state per country using accumulate function in openedge

I have tried the code.
DEFINE VARIABLE totalbalance AS DECIMAL NO-UNDO.
DEFINE FRAME f1
WITH CENTERED THREE-D SIZE 100 BY 50.
FOR EACH customer NO-LOCK BREAK BY country WITH FRAME f1:
ACCUMULATE balance(TOTAL BY country).
IF FIRST-OF(customer.country) THEN
DISPLAY customer.country.
DISPLAY customer.state customer.balance.
IF LAST-OF(customer.country) THEN
DO:
DISPLAY SKIP FILL("-", 25) AT 50 FORMAT "x(25)".
DISPLAY ACCUM TOTAL customer.balance AT 51.
DISPLAY SKIP FILL("-", 25) AT 50 FORMAT "x(25)".
END.
END.
It only gives total balance per country but I also want to display total balance per state.
You do not have a "break by state" so it will do nothing by state.
Something like this (not very tested):
define frame f1
with
centered
.
for each customer no-lock break by country by state with frame f1:
accumulate balance( total by country ).
accumulate balance( sub-total by state ).
if first-of( customer.country ) then
display customer.country.
if first-of( customer.state ) then
display customer.state.
display customer.balance.
if last-of( customer.country ) then
do:
display skip fill( "-", 25 ) at 50 format "x(25)".
display accum total customer.balance at 51.
display skip fill( "-", 25 ) at 50 format "x(25)".
end.
if last-of( customer.state ) then
do:
display skip fill( "-", 25 ) at 80 format "x(25)".
display accum sub-total customer.balance at 81.
display skip fill( "-", 25 ) at 80 format "x(25)".
end.
end.
(That probably doesn't actually work... but the multiple BY clauses are the main thing that you are missing.)
Personally I find the ACCUM syntax to be unpleasant and not worth bothering with. I usually just create and manage simple variables. I find that sort of coding to be much cleaner. So unless this is a "gotchya" question on a skills test I strongly suggest that you avoid coding in this manner. If it is a "gotchya" question move on to a more suitable prospective employer -- you don't want to work for anyone who thinks that this is a good idea.
I would do it more like this:
define variable countryBalance as decimal no-undo.
define variable stateBalance as decimal no-undo.
for each customer no-lock break by country by state with frame f1:
if first-of( customer.country ) then
do:
display customer.country.
countryBalance = 0.
end.
if first-of( customer.state ) then
do:
display customer.state.
statebalance = 0.
end.
assign
countryBalance = countryBalance + customer.balance
stateBalance = stateBalance + customer.balance
.
display customer.balance.
if last-of( customer.country ) then
do:
display countryBalance.
end.
if last-of( customer.state ) then
do:
display stateBalance.
end.
end.
I think that is a whole lot easier to read, understand and maintain.

Sumifs in Crystal

I'm working on a database that basically looks like this (in its simplest form):
{Phase} {Code} {Qty}
Example:
{Qty} for {Phase}="R" and {Code}="Nat" = 0
{Qty} for {Phase}="F" and {Code}="Nat" = 5
{Qty} for {Phase}="R" and {Code}="Int" = 10
{Qty} for {Phase}="F" and {Code}="Int" = 15
I am trying to get a result to show me the Qty for phase "R" and Code "Nat" (where R is <> 0) otherwise give me the qty for phase "F". So for the above example I would get an answer = 5 for Nat (because qty for phase R is 0) and an answer of 10 where the code is Int (because qty for phase R <> 0)
I have used three formula fields to do this:
1: if ({PHASE}="F" and {CODE}="NAT") then {QTY} else 0
2: if ({PHASE}="R" and {Code}="NAT") then {QTY} else 0
3: if {2} = 0 then {1} else {2}
Formula fields 1 & 2 come up with the correct amounts. However formula field {3} returns both phases. For example Code "Int" Phase "R" shows as qty = 25 instead of qty = 10.
How do I get around this?
You need to group by {table.code} because this is not a one-row calculation, but needs to be a calculation over each code for 2+ phases (meaning 2+ rows of data).
Create a formula with two variables that will store the values of each phase, F and R. This formula needs to go in the Details section of the report.
whileprintingrecords;
numbervar Fqty;
numbervar Rqty;
if {table.phase}="F" then Fqty:={table.qty}
else Rqty:={table.qty};
Now, in the group footer, you can reference both quantity values via the variables.
whileprintingrecords;
numbervar Fqty;
numbervar Rqty;
if Fqty=0 then Rqty else Fqty
And you're done. Don't forget to reset the two variables in the group header so you don't carry the quantity over between different codes.