Sorting in an cl_gui_alv_grid by column? - sorting

I'm generating an internal table with cl_gui_alv_grid that has five columns and I can't seem to figure out how to sort by column 1 once the table is loaded. I managed to do it with cl_salv_table, however not with the grid. Any idea please? I suppose it's done somehow with the CALL METHOD go_alv->set_sort_criteria however I'm getting a crash when triggering the table view.
DATA: lt_sort TYPE lvc_t_sort,
ls_sort TYPE lvc_s_sort.
FORM sort_data.
REFRESH: lt_sort.
CLEAR: ls_sort.
ls_sort-spos = '1'.
ls_sort-fieldname = 'Column1'.
ls_sort-up = abap_true.
APPEND ls_sort TO lt_sort.
ENDFORM.
FORM first_display.
PERFORM sort_data.
CALL METHOD go_alv->set_table_for_first_display
EXPORTING
i_structure_name = 'TABLE_STRUCTURE'
is_layout = gs_layout
CHANGING
it_outtab = gt_salv_table
ENDFORM.
CALL METHOD go_alv->set_sort_criteria
EXPORTING
it_sort = lt_sort
* EXCEPTIONS
* no_fieldcatalog_available = 1
* others = 2.

Do you call the set_sort_criteria before the set_table_for_first_display? It looks like there is no fieldcatalouge yet, this is why it does not work.
I think more straighforward is: The set_table_for_first_display method has a changing parameter it_sort, just use that one for importing the sort table into the class, no need to call set_sort_criteria:
CALL METHOD go_alv->set_table_for_first_display
EXPORTING
i_structure_name = 'TABLE_STRUCTURE'
is_layout = gs_layout
CHANGING
it_outtab = gt_salv_table
it_sort = lt_sort
EXCEPTIONS
...

Related

Laravel Save Multiple Data to 1 column

So I have 2 variable for storing the selected time of the user ('time_to' and 'time_from) with these sample data('7:30','8:00')
How can I save these two into 1 column('c_time') so it would look like this('7:30-8:00')?
if i understand you correctly, you can create a column of string (varchar) type. and then create the data for your column like this :
$time_to = '7:30';
$time_from= '8:00';
$colValueToBeStored = "(".$time_to."-".$time_from.")";
then just put $colValueToBeStored inside your column.
and to reverse it:
$colValueToBeStored = "(7:30-8:00)";
$res = explode("-",str_replace([")","("],"",$colValueToBeStored));
$time_to = $res[0];
$time_from = $res[1];
define your c_time column type as JSON, that way you can store multiple values, as it will be easier to retrieve as well. Like,
...
$cTime['time_to'] = "7:30";
$cTime['time_from'] = "8:00";
$cTimeJson = json_encode($cTime);
// save to db
...

Passing a range table to an ABAP class/method causes a commit

I want to pass a range table for OBJNR, e.g.
call method ME->ME_GET_STATUS
exporting
I_OBJNR = <FS_DATA>-OBJNR_NTF
I_AEDAT = LV_AEDAT
I_AEZEIT = LV_AEZEIT
IT_OBJNR = LR_OBJNR
importing ...
In the PUBLIC section of the super class I have:
RT_OBJNR type range of JSTO-OBJNR . (this is inherited by the calling class)
which is used by both calling & called methods.
The method ME_GET_STATUS has a parameter:
IT_OBJNR Importing Type RT_OBJNR Range table for OBJNR
and code
,WA_OBJNR like line of IT_OBJNR
,LR_OBJNR type RT_OBJNR
LR_OBJNR[] = IT_OBJNR[].
The range table is only used to buffer an itab - LT_JCDS.
select S~OBJNR, S~STAT, S~CHGNR, S~UDATE, S~UTIME,
S~INACT, O~OBTYP, O~STSMA
from JCDS as S
join JSTO as O
on O~OBJNR = S~OBJNR
into table #IT_JCDS
where S~OBJNR in #LR_OBJNR
order by S~OBJNR, S~UDATE, S~UTIME, S~STAT, S~CHGNR
loop at IT_JCDS into data(LT_JCDS)
where OBJNR = I_OBJNR
group by ( OBJNR = LT_JCDS-OBJNR STAT = LT_JCDS-STAT
GS = group size GI = group index )
ascending
reference into data(OBJNR_REF)
It all works perfectly if there is just 1 record in the range table.
The issue is that if I pass more than 1 record it still works fine but seems to cause a commit (?) which closes the cursor causing a dump in MCEX_BW_LO_API. This occurs when calling the macro "sel" for the 2nd data package.
The idea is to pass multiple records of 'EQ' and 'BT' selections resulting in fewer records being returned from the database.
I've tried changing to a standard table and using = LR_OBJNR[]

Unable to create a constant value - only primitive types or Enumeration types allowed

I have seen some questions related to this Exception here but none made me understand the root cause of the problem. So here we have one more...
var testquery =
((from le in context.LoanEMIs.Include("LoanPmnt")
join lp in context.LoanPmnts on le.Id equals lp.LoanEMIId
where lp.PmntDtTm < date && lp.IsPaid == false
&& le.IsActive == true && lp.Amount > 0
select new ObjGetAllPendingPmntDetails
{
Id = lp.Id,
Table = "LoanEMI",
loanEMIId = lp.LoanEMIId,
Name = le.AcHead,
Ref = SqlFunctions.StringConvert((double)le.FreqId),
PmntDtTm = lp.PmntDtTm,
Amount = lp.Amount,
IsDiscard = lp.IsDiscarded,
DiscardRemarks = lp.DiscardRemarks
}).DefaultIfEmpty(ObjNull));
List<ObjGetAllPendingPmntDetails> test = testquery.ToList();
This query gives the following Exception Message -
Unable to create a constant value of type CashVitae.ObjGetAllPendingPmntDetails. Only primitive types or enumeration types are supported in this context.
I got this Exception after I added the SQL function statement to convert le.FreqId which is a byte to a string as ToString() is not recognized in the LINQ Expression Store.
ObjGetAllPendingPmntDetails is a partial class in my model which is added as it is used too many times in the code to bind data to tables.
It has both IDs as long, 'Amount' as decimal, PmntDtTm as Datetime,IsDiscard as bool and remaining all are string including 'Ref'.
I get no results as currently no data satisfies the condition. While trying to handle null, I added DefaultIfEmpty(ObjNull) and ObjNull has all properties initialized as follows.
ObjGetAllPendingPmntDetails ObjNull = new ObjGetAllPendingPmntDetails()
{ Id = 0, Table = "-", loanEMIId = 0, Name = "-", Ref = "-",
PmntDtTm = Convert.ToDateTime("01-01-1900"),
Amount = 0, IsDiscard = false, DiscardRemarks = "" };
I need this query to work fine as it has Union() called on it with 5 other queries. All returning the same ObjGetAllPendingPmntDetails columns. But there is some problem as this query has no data satisfying the conditions and the Exception Shared Above.
Any suggestions are appreciated as I am unable to understand the root cause of the problem.
#AndrewCoonce is right, the .DefaultIfEmpty(ObjNull) is the culprit here. Entity Framework turns DefaultIfEmpty into something like...
CASE WHEN ([Project1].[C1] IS NULL) THEN #param ELSE [Project1].[Value] END AS [C1]
...but there's no way to coerce an instance of ObjGetAllPendingPmntDetails into something that can take the place of #param, so you get an exception.
If you move the DefaultIfEmpty call to after the ToList it should work correctly (although you'll need to call ToList again after that if you really want a concrete list instance).

How do I set a parameter to a list of values in a BIRT report?

I have a DataSet with a query like this:
select s.name, w.week_ending, w.sales
from store s, weekly_sales_summary w
where s.id=w.store_id and s.id = ?
I would like to modify the query to allow me to specify a list of store IDs, like:
select s.name, w.week_ending, w.sales
from store s, weekly_sales_summary w
where s.id=w.store_id and s.id IN (?)
How do I accomplish this in BIRT? What kind of parameter do I need to specify?
The easy part is the report parameter: set the display type to be List Box, then check the Allow Multiple Values option.
Now the hard part: unfortunately, you can't bind a multi-value report parameter to a dataset parameter (at least, not in version 3.2, which is what I'm using). There's a posting on the BIRT World blog here:
http://birtworld.blogspot.com/2009/03/birt-multi-select-statements.html
that describes how to use a code plug-in to bind multi-select report parameters to a report dataset.
Unfortunately, when I tried it, it didn't work. If you can get it to work, that's the method I would recommend; if you can't, then the alternative would be to modify the dataset's queryText, to insert all the values from the report parameter into the query at the appropriate point. Assuming s.id is numeric, here's a function that can be pasted into the beforeOpen event script for the datasource:
function fnMultiValParamSql ( pmParameterName, pmSubstituteString, pmQueryText )
{
strParamValsSelected=reportContext.getParameterValue(pmParameterName);
strSelectedValues="";
for (var varCounter=0;varCounter<strParamValsSelected.length;varCounter++)
{
strSelectedValues += strParamValsSelected[varCounter].toString()+",";
}
strSelectedValues = strSelectedValues.substring(0,strSelectedValues.length-1);
return pmQueryText.replace(pmSubstituteString,strSelectedValues);
}
which can then be called from the beforeOpen event script for the dataset, like this:
this.queryText = fnMultiValParamSql ( "rpID", "0 /*rpID*/", this.queryText );
assuming that your report parameter is called rpID. You will need to amend your query to look like this:
select s.name, w.week_ending, w.sales
from store s, weekly_sales_summary w
where s.id=w.store_id and s.id IN (0 /*rpID*/)
The 0 is included in the script so that the query script is valid at design time, and the dataset values will bind correctly to the report; at runtime, this hard-coded 0 will be removed.
However, this approach is potentially very dangerous, as it could make you vulnerable to SQL Injection attacks: http://en.wikipedia.org/wiki/SQL_injection , as demonstrated here: http://xkcd.com/327/ .
In the case of purely numeric values selected from a predefined picklist, a SQL injection attack should not be possible; however, the same approach is vulnerable where freeform entry strings for the parameter are allowed.
FYI: the BIRT World article should work (I wrote it) but that was an earlier solution to the problem.
We have created an open source plugin that you can add to BIRT that has a much cleaner solution to this problem. The Bind Parameters function in the birt-functions-lib provides a simple way to do multi-selects from multi-value parameters.
If you are still interested have a look at the birt-functions-lib project on Eclipse Labs.
Here's another one. Based on some hints I found elsewhere and extended to preserve the number of parameters in your data set SQL. This solution works with a JavaScript function that you call at OnBeforeOpen of the data set:
prepare(this);
function prepare(dataSet) {
while (dataSet.queryText.indexOf("#IN?")>=0) {
dataSet.queryText = dataSet.queryText.replace(
"#XYZ?",
"('"+params["products"].value.join("','")+"') or ?=0"
);
}
}
In your query, replace occurrences of (?) with #XYZ?. The method above makes sure that
the query has the actual values and still a parameter (so that the dataset editor and preview doesn't complain).
Note: Beware of SQL injection, e.g. by not allowing string values
I created a more general solution, which handles optional/required parameters behaviour too. When parameter is not required and user doesn't select any value, the IN-clause gets disabled. It also allows the user to select both real values and null value.
In report initialize script I add this code:
/** Fullfill IN-clause in a data set query,
* using a List box report parameter.
* Placeholder must be the parentheses after IN keyword with wathever you want inside.
* If required is false then the whole IN-clause in the query
* must be surrounded by parentheses.
* dataType and required refers to the parameter, they must be passed,
* but should be better to find a way to retrieve them inside this function
* (given parameter name).
*/
function fulfillInClause(dataSet, placeholder, param, dataType, required) {
if (dataSet.queryText.indexOf(placeholder)>=0) {
var paramValue = params[param].value;
var emptyParam = (paramValue==null || paramValue.length<=0);
//build the list of possible values
// paramValue==null check in ternary operators
// will prevent exceptions when user doesn't select any value
// (it will not affect the query if param is optional,
// while we will never arrive here if it is required)
var replacement = " (";
if (dataType == "string")
replacement += (emptyParam ? "''" : createList(paramValue, ",", "'", "varchar(10)") );
else if (dataType == "integer")
replacement += (emptyParam ? "0" : createList(paramValue, ",", "" , "int" ) );
else
//TODO implement more cases
return;
replacement += ") ";
//if param is not required and user doesn't select any value for it
//then nullify the IN clause with an always-true clause
if (!required && emptyParam)
replacement += " or 0=0 ";
//put replacement in the query
dataSet.queryText = dataSet.queryText.replace( placeholder, replacement );
//DEBUG
params["debug" + dataSet.name + "Query"]=dataSet.queryText;
}
}
/** Create a string list of array values,
* separated by separator and each of them surrounded by a pair surrounders
*/
function createList(array, separator, surrounder, sqlDataType){
var result = "";
for(var i=0; i<array.length; i++) {
if(result.length>0)
result += separator;
if(array[i]!=null)
result += surrounder + array[i] + surrounder;
else
result += "cast(null as " + sqlDataType + ")";
}
return result;
}
Usage example
In dataset query put your special IN-clause:
select F1, F2
from T1
where F3='Bubi'
and ( F4 in (''/*?customers*/) )
In beforeOpen script of the dataset with the IN-clause write:
fulfillInClause(this, "(''/*?customers*/)", "customers", "string", false);
Note that I used a placeholder which allows the query to run also before the replacement (eg. it has quotes as F4 is a varchar). You can build a placeholder that fits your case.

linq 'range variable' problem

I have a strange problem when deleteting records using linq, my suspicion is that it has something to do with the range variable (named source). After deleting a record all targets for a customer are retrieved using the following statement:
var q = from source in unitOfWork.GetRepository<db_Target>().Find()
where source.db_TargetBase.db_Person.fk_Customer == customerID
select source.FromLinq();
where FromLinq is in extention method on db_target:
public static Target FromLinq(this db_Target source)
{
return new Target
{
id = source.id,
LastModified = source.db_TargetBase.LastModified,
...
}
}
When a record is deleted both db_Target and db_TargetBase are deleted. When, for example, two users are deleting records, linq tries to retrieve a record for user2 which is deleted by user1, causing a crash on the LastModified = source.db_TargetBase.LastModified line because db_TargetBase is null.
When using the following code the problem does not occure and only the non-deleted records are retrieved:
var q = from source in unitOfWork.GetRepository<db_Target>().Find()
where source.db_TargetBase.db_Person.fk_Customer == customerID
select new Target
{
id = source.id,
LastModified = source.db_TargetBase.LastModified,
...
};
This spawns two questions:
What is happening here? Am I making a copy of the range variable source because I'm using it in a extention method?
How can I "wrap" the return new Target code? I am using this in multiple places and do not want to copy it every time. Making my code harder to maintain.
TIA,
JJ
In the first set of code - since the initializer lives an a non-translatable method (extension or otherwise), it cannot be translated - so it is run locally.
In the second set of code - the initializer is represented by an elementinit expression, which is translated (examine/compare the select clause of the generated sql for proof).
if you want to wrap this, you need to have an Expression<Func<db_Target, Target>> that anyone can grab and use in thier query. Fortunately, that's easy to do:
public Expression<Func<db_Target, Target>> GetFromLinqExpressionForTarget()
{
return
source => new Target
{
id = source.id,
LastModified = source.db_TargetBase.LastModified,
...
}
}
Which may be used like so:
var FromLinq = GetFromLinqExpressionForTarget();
var q =
(
from source in ...
...
...
select source
).Select(FromLinq);
Now ... I'm really running on a guess here and am only about 60% confident that my answer is correct. So if someone wants to confirm this, that'll make my day. :)

Resources