Archive for the 'Formulas' Category
If you want your parameter’s list of values to be pulled from the database you can use a dynamic parameter. But one of the down sides of a dynamic parameter is that you can’t type additional values to include in the list. A dynamic parameter can only show values pulled from the data source. So if, for instance, you are pulling in a list of products and you want the list to have an “All” option at the top, you can’t simply add the word “All” to the list like you could with a static parameter.
My preferred way for adding an “All” option to a dynamic parameter is to use a SQL command as the source for the dynamic parameter. Using a SQL command gives you several other advantages as well, such as allowing you to filter your the list of values. Here is an example of a SQL command that will add an “All” option to the list of values (incorporating suggestions from MHurwood below):
Select Items.ID, Items.Desc
From Items
Where Items.Status = ‘A’
UNION ALL
Select ‘…All’, ‘…All’
The part above the UNION creates a list of all the active items, showing both the ID and the description of the items. The part below the UNION adds one row to the results of the query with the “All” option. Notice that “All” entry has several periods in front of it. This is one way to sort that value to the top of the list. You can use this method to add several values to your dynamic list, if needed.
Note that you wan to avoid using the fields from this command in other parts of the report. It should be used only for the dynamic parameter.
One of my colleagues, Angela Meharg of Datisfy, reminded me that you can use optional parameters to do something similar. Instead of explicitly selecting a word like “All”, you can skip over the parameter. Then you can program the selection formula to say that when the users doesn’t select a value they get all values. The formula would look something like this in the Crystal selection formula:
and (if not (HasValue({?Items})) then True else {Table.Item} = {?Items})
In English this means, if there are no values in the items parameter, then every record qualifies. Otherwise the items that qualify are the ones that match the parameter.
If you have trouble with one of these options, you can schedule a short consult and I can give you a hand.
I have written several articles about using Notepadd++ for writing long formulas or SQL statements. I just found out that one of the features I use in Notepad++ has been (partially) available in Crystal Reports forever. I never noticed. The feature is called “column mode” and allows you to select text in a column without selecting the entire row.
Lets say you start a formula with DateTime variables like this:
WhilePrintingRecords;
DateTimeVar DateA;
DateTimeVar DateB;
DateTimeVar DateC;
DateTimeVar DateD;
DateTimeVar DateE;
Then you realize that you want them to be Date instead of DateTime. You can highlight a “column” made up of the word “Time” on all 5 rows at once, and then hit delete. To select a column you hold down the ALT key and then click the mouse in the upper left corner of rectangle and drag to the lower right corner. In this case you would start just before the “T” in Time in the first row, and drag down and across until your cursor was just after the “e” in Time in the last row. By using the ALT key you will highlight a rectangle of 4 characters across and 5 rows down. Then you hit delete and just those 20 characters are deleted.
You can also copy and paste a rectangle. Say you have to write a formula that is something like this:
If month({Trans.Date}) = 01 then {Balance.Pd01} else
If month({Trans.Date}) = 02 then {Balance.Pd02} else
If month({Trans.Date}) = 03 then {Balance.Pd03} else
If month({Trans.Date}) = 04 then {Balance.Pd04} else
If month({Trans.Date}) = 05 then {Balance.Pd05} else
If month({Trans.Date}) = 06 then {Balance.Pd06} else
If month({Trans.Date}) = 07 then {Balance.Pd07} else
If month({Trans.Date}) = 08 then {Balance.Pd08} else
If month({Trans.Date}) = 09 then {Balance.Pd09} else
If month({Trans.Date}) = 10 then {Balance.Pd10} else
If month({Trans.Date}) = 11 then {Balance.Pd11} else
If month({Trans.Date}) = 12 then {Balance.Pd12}
Normally I would start by typing the first row and then copy it 11 more times. Then I would change each row to use a different number from 1 to 12. Once I have changed the column of values after the ‘=’ sign I could copy that 2-digit column and past it over the values in the other 2-digit column in one step.
To do this you select the 2 digit column as a rectangle using the ALT key. You can right clock in the column to copy (or use Ctrl-C). Then you select the other 2-digit column and right-click to paste (or use Ctrl-V).
Note, in Crystal you should select these rectangles starting in the upper left corner, especially if you plan to copy and paste. You can get inconsistent results if you start in one of the other corners.
NotePad++ has a much more sophisticated column mode, allowing you to:
- Paste a single word or character value into multiple rows at once.
- Select a column and start typing. The new text is added to ALL the rows at the same time.
- Use Shift-Alt to mark the column using arrow keys , instead of the mouse.
- Select the column of text starting in any corner.
Unfortunately, these don’t work in Crystal.
I recently heard from Gordon Portanier (from the Crystalize consulting group in Canada). He was doing something I have seen several customer do over the years. He would start certain formula names with a symbol so those formulas would sort to the top of the field explorer list. Gordon happened to use the @ symbol.
When he upgraded to CR 2020 these reports ran fine, but he found that he could not create a new formula that had an @ symbol in the name. A message told him that four characters [ @ { } ? ] were now considered reserved characters and could no longer be used anywhere within the name of a formula field.
Existing formulas, however, are not affected unless you try to rename them. So Gordon found another report that had the formula names he needed and copied them to the current report. The formulas work fine.
Up through CRv12(CR 2008) a formula name could include ANY character in any position. But when I tested this in CRv14.2 (CR 2016) I got the same warning as Gordon. My guess is that this restriction started with CRv14 (CR 2011) and so it would also affect CRv14.1 (CR 2103) and all later versions. If any of you are using CRv14 (CR 2011) or CRv14.1 (CR 2103) you can confirm this by trying to add a formula with an @ in the name. Let me know what happens.
An interesting point. This rule only applies to formula names. It does not affect the names of SQL expressions, parameters or running totals.
Another interesting point is that the @ (which represents formulas) and the ? ( which represents parameters) are reserved. But you can still use the % symbol (which represents SQL Expressions) and also the # (which represents running totals).
I have written before about using the ExtractString function. This function is found in the “Additional Functions” of Crystal because it comes from a UFL. But this UFL has come with Crystal Reports for a long time.
Today I found out that this function has an undocumented limit -it can return a maximum of 510 characters. If you need to allow for more than that you will have to use an alternative approach. Here is the formula that I used before I discovered the ExtractString() function. It will also work if you need more than 510 characters.
Local stringVar x := {Your.Field};
Local stringVar Beg := "BeginString";
Local stringVar End := "EndString";
Local NumberVar BegPos := Instr(x,Beg );
Local NumberVar EndPos := Instr(x,End );
Local Numbervar BegLen := Length(Beg);
if BegPos > 0 and EndPos > 0 and EndPos > BegPos
then trim(x [ BegPos+BegLen to EndPos-1 ])
else ''
In the first 3 lines you put in the name of the field, and then the two strings that mark the beginning and the end of the string you want to return. You shouldn’t need to modify the other lines.
The formula will then return everything between those two strings but will not include the strings themselves.
Sometimes database fields store their data with HTML formatting tags. When you put that field on the report it will display the raw HTML tags. However, if you format the field to use “HTML interpretation” Crystal will try to use the HTML tags to format the text. Crystal doesn’t support ALL HTML tags and properties, but there is a list of the ones that are supported on this page.
Another way to use HTML in Crystal is to include HTML tags in your formulas. For instance I could use a few HTML tags to highlight only a portion of a formula field’s output. Below is the formula for a 3-line address with the City name in bold blue text:
{Customer.Customer Name} & '<br>' &
{Customer.Address1} & "<br>" &
"<b><font color='#00559c'>" &
{Customer.City} &
'</font></b>, ' &
{Customer.Region} & ' ' &
{Customer.Postal Code}
The formula uses 4 different tags:
The tag <br> after the first 2 lines creates a new line.
The tags <b> </b> mark the beginning and end of the bold text.
The tags <font color=’#00559c’> </font> mark the beginning and end of the blue text.
You can look up other color code using a color picker site.
When you first put the formula on the report it looks like the left example below. You then use the menu options “Format > Field” and select the “Paragraph” tab to see the “Text Interpretation” option at the bottom. Select “HTML Text” and the formula will look like the right example below.
Last week I mentioned the U2lwin.DLL. It is a Crystal Reports user function library (UFL) that is currently available in 32-bit but not yet in 64-bit. This means that users who have this dll and who upgrade to CR 2020 will lose some of their formula functions.
This week I checked in with the developer, Bjarke Viksoe. He has always posted the source code for this dll on his web site. He said it is fine if someone else takes the source code and creates a 64-bit version of the dll. He doesn’t even mind hosting the upgraded file if that helps.
Of course, most of my readers are not application programmers, and neither am I. But I know that a few of you write serious code. If anyone wants to take a crack at this you can download the source code and give it a shot. The current source code uses Microsoft Visual C++ 6.0.
In the meantime, if you upgrade to CR 202o, there is another DLL that you can purchase called Cut Light by Millet Software. It is one the UFLs in my annual comparison of UFLs. Cut light has at least twice as many functions as U2lwin with many that are the same. So it might have the functions you need, and it comes in both 32-bit and 64-bit. It isn’t free but it also isn’t very expensive.
Last summer I posted a draft of an improved formula for calculating business hours between two dates. It was meant to replace formula #13 on my formulas page, but I was waiting to do some additional real-world testing. Yesterday I got a good test case when a customer asked me to tweak the original formula to deal with start and end times outside the business day. I cleaned up a few typos and updated my formulas page.
This new version lets you separately specify a business start time and business end time for each of the 7 days of the week. It also includes additional logic to deal with events that start and/or end outside the business day (e.g. on a weekend, a holiday or after hours). The formula is now about 70 lines long. Fortunately, you only need to make changes in the first 20 lines or so. Here you specify:
- The DateTime fields to use for the Begin and End of each event
- The business start and end times assigned to each day of the week
- The list of holiday dates, which you can enter for multiple years
The output is a numeric value in hours as a decimal. If you want to show the value in “HH:MM” format you can use the “Elapsed Time String” formula on my site to convert this value into that format. Remember to multiply this formula’s result by 3600 since the input for the “Elapsed Time String” formula is seconds.
If you need help implementing this formula or any of my formulas you can always call to schedule a short consult.
One of my readers recently stumbled onto a trick and decided to share it with me. He wanted to display how long his reports took to run. He knew he could tap into the DataDate and DataTime to get the time the report started. His recent realization was that he could put a subreport in the report footer and get a second DataDate/Time at the end of the process. By comparing the two he would know the time it took for the report to run. By using DataDate and DataTime the values wouldn’t change, even if he saved the report with data and opened it a few days later.
The subreport can be completely blank other than this formula:
WhilePrintingRecords;
Shared DateTimeVar EndDT
Then in a separate report footer (below the section containing the subreport) we add this formula:
WhilePrintingRecords;
Shared DateTimeVar EndDT;
NumberVar TotalSec := DateDiff ('s', DataDate + DataTime, EndDT);
NumberVar H := Truncate(Remainder(TotalSec, 86400)/3600);
NumberVar M := Truncate(Remainder(TotalSec, 3600)/60);
NumberVar S := Remainder(TotalSec , 60) ;
Totext ( H, '0' ) + ':' +
Totext ( M,'00' ) + ':' +
Totext ( S,'00' )
The end result uses one of my web formulas to generate a string formatted as h:mm:ss, but there are other format options.
And thanks to Mark Edwards at DataReport Consulting for sharing his discovery.
Recent versions of Crystal allow you to automatically “Convert Nulls” in specific formulas. The setting is on the formula editor tool bar as a drop down. You can change “Exceptions for Nulls” to “Convert Null Values to Default values”. Crystal then automatically replaces any null values encountered in the formula with an empty string [""]
. This prevents your formulas from returning a blank whenever they encounter a null value.
This works great for formula fields, but not so great in the record selection formula. This problem is that this setting doesn’t get converted into SQL.
Let me give a simple example from the Xtreme sample data. There are 10 records in the customer table that have a null value for Postal Code. If I write a formula field that says:
if IsNull ({Customer.Postal Code})
then "Unknown"
else {Customer.Postal Code}
It will show TRUE for these 10 records. I could also write a formula that says:
if {Customer.Postal Code} = ""
then "Unknown"
else {Customer.Postal Code}
These 10 records will show blank at first, but if I set that formula to “Convert Null to Default”. Then it will also show TRUE for those 10 records. Now lets say I use this as my selection formula:
IsNull ({Customer.Postal Code})
That will pass to the WHERE clause as:
`Customer`.`Postal Code` IS NULL
and it will select those 10 records. But, if I write the selection formula like this:
{Customer.Postal Code} = ""
Even if I tell the selection formula to “Convert Null to Default”, the WHERE clause will be:
`Customer`.`Postal Code` = ''
There are no records that meet that criteria and so no records will be selected. The key here is that using “Convert Null to Default” works within Crystal, but is not translated to SQL. That is why I avoid using this setting in the record selection formula.
I had a customer today who wanted to write a formula that assigned a numeric value to each Employee. Normally I would write an IF THEN statement like this:
if {table.EmpNO} = 123 then 10 else
if {table.EmpNO} = 124 then 15 else ...
But the list of employees and values was in a spreadsheet with 4000 employee rows. A linked table wasn’t an option so I decided to do something I don’t do very often. I wrote the formula using SELECT CASE instead of IF THEN.
Normally I prefer to use IF THEN because you can use more complex conditions. I also find that IF THEN reads more naturally. But in cases like this where you have a single field and LOTS of comparison values, the SELECT CASE pattern is more efficient. The pattern below reduced the total character count by 70% of an equivalent IF THEN structure:
Select {table.EmpNO}
case 123:10
case 124:15
...
default : 0
The first time I used SELECT CASE was when I had to translate a large spreadsheet grid into a formula. I wanted the formula structure to look like the spreadsheet so we could visually check the formula by comparing the columns. The spreadsheet grid had 30 rows (Types) and 20 columns (Sizes) so my formula ended up with 30 rows that looked like those below, except that each row had 16 more case instances where you see the ellipsis […]:
if {prod.Type}=1 then(select {prod.Size} case 01:03 case 02:03 case 03:05 case 04:06 ... default:00) else
if {prod.Type}=2 then(select {prod.Size} case 01:02 case 02:03 case 03:04 case 04:05 ... default:00) else
if {prod.Type}=2 then(select {prod.Size} case 01:02 case 02:03 case 03:04 case 04:05 ... default:00) else
Note that I combined an IF THEN to determine the row (Type) with a SELECT CASE to determine the value for each column(Size). I found this more manageable and easier to validate than 600 additional IF THEN loops.