I have a crystal report I need to modify to leave out duplicate rows by "name". So in Section Expert I am putting in a formula in Suppress and I cannot figure out how to compare the current name field being added to all the previous names that are in the group already. I was trying to use the Filter() function, but for the String array parameters I don't know what to enter that would be all of the other names previously added to the group. I need to compare the current name being added and see if it is already in the group so I can then compare another field called "date" and if the date of the field being added is more recent then the date of the duplicate name it will over write the row and only show the row with the most recent date.
Basically the question is how do I create an array with all the current fields already in the group(or does one exist already) so that I may use the Filter() function to see if the current name being added is already in that array of names added?
Well I figured it out, so for anyone who runs into this here is my solution.
first off I made a formula in the "formula fields" section that creates two arrays when reading the data from the database and keeps only one copy of each id and date in the record. Then for any other records that have the same id it will compare the date of that record to the record in the array with the same name and if the date is greater(later) then it will replace the date with the currently read in one. I named this formula field idArray.
Global StringVar Array idArray;
Global DateVar Array expArray;
BooleanVar addName;
NumberVar x;
StringVar idTest;
StringVar expDateTest;
whilereadingrecords;
(
addName := true;
for x := 1 to Ubound(idArray) step 1 do
(
if({hrpersnl.p_empno} = idArray[x]) then
(
addName := false;
if(Date({nemphist.enddate}) > expArray[x]) then
expArray[x] := Date({nemphist.enddate});
)
);
if(addName = true) then
(
reDim Preserve idArray[Ubound(idArray) + 1];
reDim Preserve expArray[Ubound(expArray) + 1];
idArray[Ubound(idArray)] := {hrpersnl.p_empno};
expArray[Ubound(expArray)] := Date({nemphist.enddate});
//idTest := idTest + ' ' + {hrpersnl.p_empno};
//expDateTest := expDateTest + ' ' + toText(Date({nemphist.enddate}));
);
//idTest
//Ubound(idArray)
//expDateTest
)
The commented out lines are what I used for testing to see how the arrays were building. I left them in there just as an example of how to debug crystal reports, since it doesn't come with a debugger.
The next step is to create a record suppression formula. In the Report menu I went to "section expert" and in the "Details" section of my group I clicked the little x-2 button next to the "Suppress (No Drill-Down)" option. I then inserted this code that looks at the current record's id and date and if the id is in the first array it will take its position and use that to retrieve the date from the second array and if the current record's date is less than the date we now know to be the max then it will suppress the record.
Global StringVar Array idArray;
Global DateVar Array expArray;
NumberVar x;
BooleanVar suppress := false;
for x := 1 to Ubound(idArray) do
(
if({hrpersnl.p_empno} = idArray[x]) then
if(Date({nemphist.enddate}) < expArray[x]) then
suppress := true;
);
if(suppress = true) then
true
else
false
Some lessons learned along the way...
Crystal Reports does global variables in a weird way. It took me a few hours of fudging around with them to figure out that you can basically use them anywhere in the report as long as you declare them in each section you put them in with the Global [vartype] "name" syntax. Even though you are re-declaring it each time Crystal does not remove the value of it or reset it or anything.
This operator ":=" is different than "=". The ":=" operator is used to set values for variables whereas the "=" seems to be used only for comparisons.
Crystal reports is really weird with its design. If you do want your formula field to return a specific variable or something you just type in that variable name without a ";" after it. Anything without a ";" after it is considered the end of the formula. So if you get this dumb "oh this code looks like its not part of the formula" error then it is because you didn't put a ";" after something and Crystal is assuming your function is ending at the location. But if you don't put a variable without a ";" after it your formula with just return "false" by default. So in my formula where I have //idTest
//Ubound(idArray)
//expDateTest
all I have to do is uncomment the variable I want to be returned and the formula will do so.
Related
I'm very new in using Cognos report studio and trying to filter some of the values and replace them into others.
I currently have values that are coming out as blanks and want to replace them as string "Property Claims"
what i'm trying to use in my main query is
CASE WHEN [Portfolio] is null
then 'Property Claims'
ELSE [Portfolio]
which is giving me an error. Also have a different filter i want to put in to replace windscreen flags to a string value rather than a number. For example if the flag is 1 i want to place it as 'Windscreen Claims'.
if [Claim Windscreen Flag] = 1
then ('Windscreen')
Else [Claim Windscreen Flag]
None of this works with the same error....can someone give me a hand?
Your first CASE statement is missing the END. The error message should be pretty clear. But there is a simpler way to do that:
coalesce([Portfolio], 'Property Claims')
The second problem is similar: Your IF...THEN...ELSE statement is missing a bunch of parentheses. But after correcting that you may have problems with incompatible data types. You may need to cast the numbers to strings:
case
when [Claim Windscreen Flag] = 1 then ('Windscreen')
else cast([Claim Windscreen Flag], varchar(50))
end
In future, please include the error messages.
it might be syntax
IS NULL (instead of = null)
NULL is not blank. You might also want = ' '
case might need an else and END at the bottom
referring to a data type as something else can cause errors. For example a numeric like [Sales] = 'Jane Doe'
For example (assuming the result is a string and data item 2 is also a string),
case
when([data item 1] IS NULL)Then('X')
when([data item 1] = ' ')Then('X')
else([data item 2])
end
Also, if you want to show a data item as a different type, you can use CAST
I find an interesting bug in a Dialog List field.
I am trying to create a list of years starting from this year minus 17 and have the list go backwards 30 years. (ex: 2003, 2002, 2001, 2000, ... 1973). I don't want to hardcode the options obviously since every year I would have to go back in and change it.
I created a simple Dialog List field and selected "Use formula for choices" and entered the following formula:
startYear := #Year(#Now)-17; #For( x:=1; x<=30; x:=x+1; temp[x] := startYear-x); temp
When I went to save it, it is rewritten as
startYear := #Year(#Now)-17; #For( x:=1; x<=30; x:=x+1; temp[x := startYear-x ]); temp
Obviously temp[x := startYear-x ] is not going to work but no matter what I try the editor keeps resetting the code to that.
Anyone know of a way to write this type of formula so that it will work and do what I want?
(Notes Designer 9.01 FP8)
First of all to answer your question: Assigning subscript of arrays like you do in Formula is not possible, see this excerpt from designer help (thanx #Richard Schwartz):
The subscript operator cannot be used on the left side of an assignment statement. That is, you cannot assign a value to a subscripted element. You must build the complete list and then assign it.
Second: I checked your formula, and if you put it in the "Value"- Formula of a field, then it is not modified by F9, only when putting it in the Dialog List Formula I can reproduce the same behaviour... But I would not mind: As it is not a valid formula at all, this imho can't be called "Bug".
So here is how your corrected formula could look like:
_startYear := #Year(#Now)-17;
#For( _x:=1; _x<=30; _x:=_x+1;
_temp := #Text(_startYear-_x);
_myArr := #Trim( _myArr : _temp )
);
_myArr
Don’t be confused because of the underscores: I use them to distinguish variables from fieldnames.
As Dialoglists store text, you need to convert the single values to text.
If you want the years in reverse order, then just change one line:
_myArr := #Trim( _temp : _myArr )
#Formula language -by the way- is quite cool regarding lists and one could have solved this in different ways. E.g. by using daterange and explode:
_start := #Adjust(#Today;-17;0;0;0;0;0);
_end := #Adjust(#Today;13;0;0;0;0;0);
_daterange := #TextToTime( "[" + #Text(_start) + "-" +
#Text(_end)+"]");
_allDates := #Explode( _dateRange );
#Text(#year(_allDates))
But I admit: as cool as it is, it is not really intuitive, but that would have been the solution in Notes 5 when there was no #For yet.
Let's assume that I have Custom Table named Possible URL target parameters with code name xyz.PossibleTargets with 2 columns:
Explanation and Value.
How to feed drop-down field on page type with data to have Value (from table) as Value and Explanation as name in drop-down?
What I already tried and it is not working:
Generate value;name pairs divided by newline and place it as List of options:
z = ""; foreach (x in CMSContext.Current.GlobalObjects.CustomTables["xyz.PossibleTargets"].Items) {z += x.GetValue("Value"); z +=";"; z += x.GetValue("Explanation"); z += "\n" }; return z;
Validator do no allow me to do such trick.
Set option Macro expression and provide enumerable object:
CMSContext.Current.GlobalObjects.CustomTables["xyz.PossibleTargets"].Items
In Item transformation: {%Explanation%} and in Value column {%TargetValue%}.
This do not work also.
Dropdown configuration
How to do this correctly? Documentation and hints on the fields are not helpful.
Kentico v11.0.26
I think that you should do it without marking field as a macro. Just type there the macro. Take a look on screen
No need to use a macro, use straight SQL, using a macro only complicates what appears to be a simple dropdown list.
SELECT '', '-- select one --' AS Explanation
UNION
SELECT TargetValue, Explanation
FROM xyz_PossibleTargets -- make sure to use the correct table name
ORDER BY ExplanationText
This should populate exactly what you're looking for without the complication of a macro.
I have a scenario in which there are three fields, based on the selection of First Field( 3 values in first field),the next field is getting values ( it is a dialogue list field) through DBcolumn. and then on the basis of selection of second field the third field is coming. All the above mentioned field are dialogue list field.
The issue is with multi selection, when ever multi values are selected out of first field the next fields are coming as DBColumn is used (due ot lesser values), but Since second filed has lots of values, we have to use DB Look up and here the multi selection is not fetching the data for corresponding filed.
Eg. I have a field named Database (having 3 values), and other two fields are Project(many values) and Brand.
I am using DBColumn to get Values in Project Field from Database field Selection (multi-selection), which I am getting properly (using #if), but after multi selection of Project Field (there are many values) I am not able to get values in next field (using DBLookup).
Please let me know any workaround....
First: Your code would have probably helped in solving this problem, although I think, it is not code- related:
Please check the field- property "refresh fields on keyword change" on the Project and the property "Refresh Choices on document refresh" on the Brand- Field.
If you do not get ANY- values whenever you select a second Project, then check, if your Key for Lookup is correctly used as an Multiple value for your DBLookup and that it does not try to lookup something like "value1;value2" instead of "value1" : "value2"...
Bets practice for debugging something like this, is to have a "BrandList" field, Computed for Display, hidden, with the DBLookup in it:
_viw := "YourViewName";
_crit := Project;
_col := 2;
REM "Don't do lookups, if project is empty";
#if( _crit = "" ; #Return( "" ) ; "" );
_lkp := #DBLookup( "" : "NoCache" ; _viw ; _crit ; _col );
#if( #IsError( _lkp ) ; "" ; _lkp );
And the let the brand point to this field (simply use the fieldname as Formula). that way you can easily debug the returned values.
In addition: If you are not sure, that there will be Brands for every Project you select, you need to add [FailSilent] to the lookup, otherwise the whole lookup will fail, if only one of the projects cannot be found... _lkp := #DBLookup( "" : "NoCache" ; _viw ; _crit ; _col; [Failsilent] );
BUT: Failsilent is very bad when debugging something, as you never get an error Message...
AND: For debugging purposes, you of course do NOT use the #IsError- line and simply return _lkp...
I am rolling up a huge table by counts into a new table, where I want to change all the empty strings to NULL, and typecast some columns as well. I read through some of the posts and I could not find a query, which would let me do it across all the columns in a single query, without using multiple statements.
Let me know if it is possible for me to iterate across all columns and replace cells with empty strings with null.
Ref: How to convert empty spaces into null values, using SQL Server?
To my knowledge there is no built-in function to replace empty strings across all columns of a table. You can write a plpgsql function to take care of that.
The following function replaces empty strings in all basic character-type columns of a given table with NULL. You can then cast to integer if the remaining strings are valid number literals.
CREATE OR REPLACE FUNCTION f_empty_text_to_null(_tbl regclass, OUT updated_rows int)
LANGUAGE plpgsql AS
$func$
DECLARE
_typ CONSTANT regtype[] := '{text, bpchar, varchar}'; -- ARRAY of all basic character types
_sql text;
BEGIN
SELECT INTO _sql -- build SQL command
'UPDATE ' || _tbl
|| E'\nSET ' || string_agg(format('%1$s = NULLIF(%1$s, '''')', col), E'\n ,')
|| E'\nWHERE ' || string_agg(col || ' = ''''', ' OR ')
FROM (
SELECT quote_ident(attname) AS col
FROM pg_attribute
WHERE attrelid = _tbl -- valid, visible, legal table name
AND attnum >= 1 -- exclude tableoid & friends
AND NOT attisdropped -- exclude dropped columns
AND NOT attnotnull -- exclude columns defined NOT NULL!
AND atttypid = ANY(_typ) -- only character types
ORDER BY attnum
) sub;
-- RAISE NOTICE '%', _sql; -- test?
-- Execute
IF _sql IS NULL THEN
updated_rows := 0; -- nothing to update
ELSE
EXECUTE _sql;
GET DIAGNOSTICS updated_rows = ROW_COUNT; -- Report number of affected rows
END IF;
END
$func$;
Call:
SELECT f_empty2null('mytable');
SELECT f_empty2null('myschema.mytable');
To also get the column name updated_rows:
SELECT * FROM f_empty2null('mytable');
db<>fiddle here
Old sqlfiddle
Major points
Table name has to be valid and visible and the calling user must have all necessary privileges. If any of these conditions are not met, the function will do nothing - i.e. nothing can be destroyed, either. I cast to the object identifier type regclass to make sure of it.
The table name can be supplied as is ('mytable'), then the search_path decides. Or schema-qualified to pick a certain schema ('myschema.mytable').
Query the system catalog to get all (character-type) columns of the table. The provided function uses these basic character types: text, bpchar, varchar, "char". Only relevant columns are processed.
Use quote_ident() or format() to sanitize column names and safeguard against SQLi.
The updated version uses the basic SQL aggregate function string_agg() to build the command string without looping, which is simpler and faster. And more elegant. :)
Has to use dynamic SQL with EXECUTE.
The updated version excludes columns defined NOT NULL and only updates each row once in a single statement, which is much faster for tables with multiple character-type columns.
Should work with any modern version of PostgreSQL. Tested with Postgres 9.1, 9.3, 9.5 and 13.