Hi I am having trouble some BQL syntax what I want to achieve is a BQL statement like the below SQL with nested sub query in the where clause.
SELECT * FROM ARInvoice I
WHERE (SELECT COUNT(*) FROM ARAdjust A WHERE I.RefNbr = A.AdjdRefNbr) > 0
Is this possible in BQL if so how would I write this?
Below is what I have got at the moment but this isn't correct I'm getting syntax errors
PXSelect<PX.Objects.AR.ARInvoice,
Where<PXSelectGroupBy<PX.Objects.AR.ARAdjust, Where<PX.Objects.AR.ARAdjust.adjdRefNbr, Equal<PX.Objects.AR.ARInvoice.refNbr>, Aggregate<Count>>, Greater<Zero>>>>.Select(new PXGraph());
thanks
You have two options to implement this.
Using Sub Queries:
You could add an unbound calculated field (PXDBScalar) in your ARInvoice
To add Sub Queries in BQL, you must do it at the the attribute level. Because you want to query another table, PXDBScalar, would be the best option. If you wanted to query other fields of the same record, PXDBCalced would be more adequate. For more informations about Advanced SQL Attributes please refer to the T200 under Using Advanced SQL Attributes and Acumatica’s help under Help -> Acumatica Framework -> API Reference -> Attributes -> Adhoc SQL for Fields.
Extend the ARInvoice (V5.1 and below)
public class ARInvoiceExtension : PXCacheExtension<ARInvoice>
{
public abstract class lastPaymentOrderNbr : IBqlField
{
}
#region LastPaymentOrderNbr
[PXString]
[PXUIField(DisplayName = "Last Payment Order Nbr.")]
[PXDBScalar(typeof(Search<ARAdjust.adjdOrderNbr,
Where<ARAdjust.adjdDocType, Equal<ARInvoice.docType>,
And<ARAdjust.adjdRefNbr, Equal<ARInvoice.refNbr>>>,
OrderBy<Desc<ARAdjust.adjgDocDate>>>))]
public string LastPaymentOrderNbr { get; set; }
#endregion
}
A new field has been added to ARInvoice in V5.2 to get the last payment date so you don't have to add another one:
public abstract class lastPaymentDate : PX.Data.IBqlField
{
}
protected DateTime? _LastPaymentDate;
/// <summary>
/// The date of the most recent payment associated with this document.
/// </summary>
[PXDate()]
[PXDBScalar(typeof(Search<ARAdjust.adjgDocDate,
Where<ARAdjust.adjdDocType, Equal<ARInvoice.docType>,
And<ARAdjust.adjdRefNbr , Equal<ARInvoice.refNbr>>>,
OrderBy<Desc<ARAdjust.adjgDocDate>>>))]
[PXUIField(DisplayName = "Last Payment Date")]
public virtual DateTime? LastPaymentDate
{
get
{
return this._LastPaymentDate;
}
set
{
this._LastPaymentDate = value;
}
}
Your PXSelect would then look like this:
V5.1 and below
public PXSelect<ARInvoice, Where<ARInvoiceExtension.lastPaymentOrderNbr, IsNotNull>> InvoicesTest;
V5.2
public PXSelect<ARInvoice, Where<ARInvoice.lastPaymentDate, IsNotNull>> InvoicesTest;
Inner join on table
Instead of sub-querying it you could simply add an inner join and filter record that do not have an ARAdjust. You then group by your key fields to avoid duplicates.
public PXSelectJoinGroupBy<ARInvoice,
InnerJoin<ARAdjust, On<ARAdjust.adjdRefNbr, Equal<ARInvoice.refNbr>,
And<ARAdjust.adjdDocType, Equal<ARInvoice.docType>>>>,
Where<ARAdjust.adjdOrderNbr, IsNotNull>,
Aggregate<GroupBy<ARInvoice.docType,
GroupBy<ARInvoice.refNbr>>>> InvoicesTest;
Related
I have an inquiry screen in which we filter historical AP records from a custom DAC by a VendorID or FinPeriodID from the filter.
I've created a View Delegate to handle the dynamic filtering logic, and through debugging have confirmed that it's being hit correctly and is appending the proper WhereAnd statement with the current value of the Filter. Running the SQL statement equivalent directly in the database is returning the correct records, however the View Delegate ends up returning no records to the screen.
The base view is just defined as: public PXSelectReadonly<AAAPDoc> Docs;
The View Delegate is defined as:
protected virtual IEnumerable<AAAPDoc> docs()
{
AAAPHistoricalFilter filter = Filter.Current;
PXSelectBase<AAAPDoc> cmd = new PXSelectReadonly<AAAPDoc>(this);
if (filter.VendorID.HasValue)
{
cmd.WhereAnd<Where<AAAPDoc.vendorID, Equal<Current<AAAPHistoricalFilter.vendorID>>>>();
}
if (filter.FinPeriodID.HasValue)
{
cmd.WhereAnd<Where<AAAPDoc.finPeriodID, Equal<Current<AAAPHistoricalFilter.finPeriodID>>>>();
}
foreach (AAAPDoc record in cmd.Select())
{
yield return record;
}
}
Filter DAC
[Serializable]
[PXHidden]
public class AAAPHistoricalFilter : IBqlTable
{
#region VendorID
public abstract class vendorID : BqlInt.Field<vendorID>
{
}
[Vendor(IsDBField = false, DisplayName = "Vendor ID")]
public virtual int? VendorID { get; set; }
#endregion
...
Edit: Updated with original partial Filter DAC to give context to solution
Turns out, it was a problem with my filter DAC. I used the [Vendor] attribute with the property IsDBField = false. Removing the IsDbField property altogether from the attribute gave me the expected results.
I have a requirement to write a BQL statement for below SQL query
SELECT * FROM CSATTRIBUTEGROUP INNER JOIN INVENTORYITEM ON CSATTRIBUTEGROUP.ENTITYCLASSID=CAST(INVENTORYITEM.ITEMCLASSID AS NVARCHAR(10))
LEFT JOIN CSANSWERS ON INVENTORYITEM.NOTEID=CSANSWERS.REFNOTEID WHERE INVENTORYCD='CPU'
The EntityClassID in CSAttribute & ItemClassID in inventory are different type. How to join the table using BQL.
One would be tempted to create a custom view representing the INVENTORYITEM table with a cast of ITEMCLASSID. However custom views are not recommended. Instead try creating a PXProjection to represent the INVENTORYITEM table as usrINVENTORYITEM. Then use a PXString to convert the Int? to a string. Once exposed you can work out your BQL as shown below.
#region InventoryItem - projection
[PXProjection(typeof(Select<InventoryItem>), Persistent = false)]
public partial class usrInventoryItem : InventoryItem
{
#region ItemClassIDStr
[PXString(10, IsUnicode = true)]
[PXUIField(DisplayName = "ItemClassIDStr", Visibility = PXUIVisibility.SelectorVisible)]
public virtual string ItemClassIDStr
{
get
{
return $"{ItemClassID}";
}
set
{
this.ItemClassIDStr = value;
}
}
public abstract class itemClassIDStr : IBqlField { }
#endregion
}
#endregion
then the BQL statement:
// basic example of your join.
public PXSelectJoin<CSAttributeGroup, LeftJoin<usrInventoryItem,
On<usrInventoryItem.itemClassIDStr, Equal<CSAttributeGroup.entityClassID>>>> ExampleJoin;
I've created a new custom bound field UsrMatchCode for the BAccount DAC. My goal is to create a field which I can query from a web service call to identify "potential" duplicate customer records. My plan is to concatenate the following into a single 9-character calculated field:
First 5 Characters of Address.PostalCode
First 2 Characters of Address.AddressLine1
First 2 Characters of Contact.FullName
But before I can get this far, I'm stuck getting the value of the PXFormula to display when updating/saving existing records. Strangely, it does output a value for new records. See screenshot.
I've reduced the PXFormula to using constants only to rule out other fields causing the problem.
public class BAccountExt : PXCacheExtension<PX.Objects.CR.BAccount>
{
private class index1 : Constant<int>
{
public index1() : base(0) { }
}
private class length5 : Constant<int>
{
public length5() : base(5) { }
}
private class testString : Constant<string>
{
public testString() : base("123456789") { }
}
#region UsrMatchCode
public abstract class usrMatchCode : IBqlField { }
[PXDBString(9)]
[PXUIField(DisplayName = "Match Code", Enabled = false)]
[PXFormula(typeof(Substring<testString, index1, length5>))]
public virtual string UsrMatchCode { get; set; }
#endregion
}
I feel like I'm missing something obvious, but I can't work it out.
I avoid using PXFormula and PXDBxxx types at the same time.
The PXDBxxx attribute tells the framework you want to persist the field value to DB and reload it's value from DB while PXFormula tells the framework the string value needs to be computed from the formula. The two approach appear incompatible because it's not clear cut whether the value will be coming from DB or from formula after the record has been persisted to DB. Other attributes like PXDefault are more suited for PXDBxxx types because it will only run at initialisation.
I would recommend either to use PXFormula with the unbound PXString type or to use PXDefault instead of PXFormula for initialisation of PXDBString.
I have been trying to select inventory using 'UsrAlternateIDs' (custom field) value (which are concatenation of cross reference value of stock items). This is where I am trying to implement.
So, if I type in correct InventoryID, it would select me the inventory which exists now. But now, whatever value I type, first it would check if it is the correct inventoryID and select inventory for me. if not it should look for alternateids of inventory item and if I type whichever (separated from ';') it should select inventory item for me.
Here is the code snippet I have written:
protected void FSAppointmentDetPart_InventoryID_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e)
{
var row = (FSAppointmentDetPart)e.Row;
if(row==null) return;
InventoryItem item = PXSelect<InventoryItem, Where<InventoryItem.inventoryID, Equal<row.InventoryID>>;
if(item!=null)
{
InventoryItem itm = PXSelect<Inventory, Whenre<InventoryItem.alternateIDs, Contains<row.InventoryID>>;
if(itm!=null)
{
}
else
{
throw new PXException("Invalid Inventory Item");
}
}
//sender.SetValue<ContactExt.usrCreditRecordVerified>(e.Row, false);
}
But it is not working. What I am I missing?
Besides, I also looked into the Business logic of the page 'Purchase Orders' which would look for column AlternateIDs and work if is not concatenated and only single value. But the code doesn't make much sense to me. It would be great if you could explain that to me.
Thank you.
If you are trying to make your selector search for both inventory id and the alternate id, consider using the
CrossItemAttribute
Sample usage can be like this, here INPrimaryAlternateType.CPN defines the type of alternate id.
#region InventoryID
public abstract class inventoryID : PX.Data.IBqlField
{
}
protected Int32? _InventoryID;
[CrossItem(INPrimaryAlternateType.CPN, Filterable = true)]
public virtual Int32? InventoryID
{
get
{
return this._InventoryID;
}
set
{
this._InventoryID = value;
}
}
#endregion
EDIT
Tried to use as an unbound field
#region InvID
public abstract class invID : PX.Data.IBqlField { }
[PXInt]
[CrossItem(DisplayName="TEST")]
public virtual int? InvID { get; set; }
#endregion InvID
Item master info
Unbound field, Keys in the barcode
System Finds the correct Item
When I join two DAC in BQL syntax its generates SQL which selects all the columns from both tables. What if I want to select one table's columns in order to reach good execution plan?
You could try to look into PXProjection where the Columns are defined as the fields within the projection class. PXProjection is like a SQL view as a DAC in Acumatica. Just search the Acumatica source for PXProjection and you should find many examples. Note that within the class you need to set BqlField for each "column" so the process knows which table.field your projection field maps to.
Quick join PXProjection below. In this example there will be only 1 column in the DAC and it maps to APRegister.docType
[PXProjection(typeof(Select2<APRegister,
InnerJoin<APInvoice, On<APInvoice.docType, Equal<APRegister.docType>,
And<APInvoice.refNbr, Equal<APRegister.refNbr>>>,
InnerJoin<APPayment, On<APPayment.docType, Equal<APRegister.docType>,
And<APPayment.refNbr, Equal<APRegister.refNbr>>>>>,
Where<APRegister.docType, Equal<APDocType.quickCheck>,
Or<APRegister.docType, Equal<APDocType.voidQuickCheck>>>>), Persistent = true)]
[Serializable]
public partial class APQuickCheck : APRegister
{
#region DocType
public new abstract class docType : PX.Data.IBqlField
{
}
[PXDBString(3, IsKey = true, IsFixed = true, BqlField = typeof(APRegister.docType))]
[PXDefault(APDocType.QuickCheck)]
[APQuickCheckType.List()]
[PXUIField(DisplayName = "Type", Visibility = PXUIVisibility.SelectorVisible, Enabled = false)]
[PXFieldDescription]
public override String DocType
{
get
{
return this._DocType;
}
set
{
this._DocType = value;
}
}
#endregion
}