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;
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'm trying to show a value calculated with PXFormula but the field doesn't show the value.
I have my CustomDAC named EDITran
public class EDITran : IBqlTable
{
#region Doctype
[PXDBString(50, IsKey = true, IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "Doctype")]
[PXStringList
(new string[]
{"SO", "SHI", "INV" },
new string[]
{"Sales Order", "Shipment", "Invoice"}
)]
public virtual string Doctype { get; set; }
public abstract class doctype : PX.Data.BQL.BqlString.Field<doctype> { }
#endregion
#region Erprefnbr
[PXDBString(30, IsKey = true, IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "ERP RefNbr")]
public virtual string Erprefnbr { get; set; }
public abstract class erprefnbr : PX.Data.BQL.BqlString.Field<erprefnbr> { }
#endregion
#region Sync
[PXDBBool()]
[PXUIField(DisplayName = "Sync")]
public virtual bool? Sync { get; set; }
public abstract class sync : PX.Data.BQL.BqlBool.Field<sync> { }
#endregion
}
}
so I what to show the value of sync field on the Sales Order screen. The key is the ERP RefNbr (which would be SOOrder.OrderNbr)
I have added the custom non persisted field on the SOOrderExt DAC with this attributes
using PX.Objects.SO;
[PXBool]
[PXUIField(DisplayName="EDI Sync" , Enabled = false)]
[PXFormula(typeof(Selector<SOOrder.orderNbr,
Selector<EDITran.erprefnbr,
EDITran.sync>>))]
But when I added a record in EDITran and try to visualize it in SOOrder Form and I checked that EDITran.Sync = 1, it doesn't show the saved value.
Sales Order Screen
What I'm I doing wrong? Is the PXFormula correctly used?
PXFormula used incorrectly. PXFormula attribute works only with current DAC (which is SOOrder) or foreign DAC, existing in PXSelector join condition (you can use Selector keyword to get it).
For example, here is the SOOrder.OrderNbr selector declaration
[SO.RefNbr(typeof(Search2<SOOrder.orderNbr,
LeftJoinSingleTable<Customer, On<SOOrder.customerID, Equal<Customer.bAccountID>,
And<Where<Match<Customer, Current<AccessInfo.userName>>>>>>,
Where<SOOrder.orderType, Equal<Optional<SOOrder.orderType>>,
And<Where<Customer.bAccountID, IsNotNull,
Or<Exists<Select<SOOrderType,
Where<SOOrderType.orderType, Equal<SOOrder.orderType>,
And<SOOrderType.aRDocType, Equal<ARDocType.noUpdate>,
And<SOOrderType.behavior, Equal<SOBehavior.sO>>>>>>>>>>,
OrderBy<Desc<SOOrder.orderNbr>>>), Filterable = true)]
public virtual String OrderNbr
you can get some fields from the related Customer record, using Selector keyword
[PXFormula(typeof(Selector<
SOOrder.orderNbr,
Customer.consolidateStatements>))]
As a result, there are two possible solutions:
1) Rewrite SOOrder.OrderNbr selector declaration with your EDITran DAC
...
[SO.RefNbr(typeof(Search2<SOOrder.orderNbr,
LeftJoinSingleTable<Customer, On<SOOrder.customerID, Equal<Customer.bAccountID>,
And<Where<Match<Customer, Current<AccessInfo.userName>>>>>,
LeftJoin<EDITran, On<EDITran.doctype, Equal<SOOrder.orderType>,
And<EDITran.erprefnbr, Equal<SOOrder.orderNbr>>>>>,
Where<SOOrder.orderType, Equal<Optional<SOOrder.orderType>>,
And<Where<Customer.bAccountID, IsNotNull,
Or<Exists<Select<SOOrderType,
Where<SOOrderType.orderType, Equal<SOOrder.orderType>,
And<SOOrderType.aRDocType, Equal<ARDocType.noUpdate>,
And<SOOrderType.behavior, Equal<SOBehavior.sO>>>>>>>>>>,
OrderBy<Desc<SOOrder.orderNbr>>>), Filterable = true)]
public virtual String OrderNbr
Then it will be possible to get you field from there
[PXFormula(typeof(Selector<
SOOrder.orderNbr,
EDITran.sync>))]
2) Use PXDBScalar attribute to just get what you need. Note this will be a separate request to the database!!
...
[PXBool]
[PXDBScalar(typeof(Search<EDITran.sync,
Where<EDITran.doctype, Equal<SOOrder.orderType>,
And<EDITran.erprefnbr, Equal<SOOrder.orderNbr>>>>))]
public virtual bool? Sync
I'm not sure that's the right use of PXFormula. Here's the reference I normally use when I need to use PXFormula. See if it helps you simplify your PXFormula. You may need to simplify to a single Selector and define a foreign key to be able to complete your lookup.
Also, are you sure that the database contains the values that you expect? I often find that my problem is not where I think, and your issue may lie in setting the values or writing them to the database in your business logic.
It appears that you need the PXFormula to look to your EDITran table to find the record identified by the key (1st field specified in Selector) to then return back the value of the second field specified, which would reside in EDITran.
From Acumatica Developers Blog (AsiaBlog):
Selector
Selector does following:
Fetches a PXSelectorAttribute defined on the foreign key field (KeyField) of the current DAC.
Fetches the foreign data record currently referenced by the selector.
Using calculates and returns an expression on that data record as defined by ForeignOperand.
public class APVendorPrice : IBqlTable
{
// Inventory attribute is an aggregate containing a PXSelectorAttribute
// inside, which is also valid for Selector<>.
[Inventory(DisplayName = "Inventory ID")]
public virtual int? InventoryID
[PXFormula(typeof(Selector<
APVendorPrice.inventoryID,
InventoryItem.purchaseUnit>))]
public virtual string UOM { get; set; }
}
From that, I'd expect your PXFormula to look more like:
[PXFormula(typeof(Selector<
SOOrder.orderNbr,
EDITran.sync>))]
... assuming you have enough defined to tell Acumatica how to relate SOOrder.orderNbr to EDITran.erprefnbr.
I have a BQL resultset consisting of several left joins, and one of the tables (ARCstRptControl) is joined multiple times under an 'alias' by way of inheriting the original DAC The code is shown below:
public class ARInv : PXGraph<ARInv, ARInvoice>
{
[Serializable]
public class ARCstRptControl1 : ARCstRptControl { }
[Serializable]
public class ARCstRptControl2 : ARCstRptControl { }
foreach (PXResult<ARTran
,PMTran
,ARCstRptControl
,ARCstRptControl1
,PMTask
,Account
,ARCstRptControl2> thistran in PXSelectJoin<ARTran,
LeftJoin<PMTran,
On<ARTran.pMTranID, Equal<PMTran.tranID>>,
LeftJoin<ARCstRptControl,
On<PMTran.origAccountGroupID, Equal<ARCstRptControl.accountID>>,
LeftJoin<ARCstRptControl1,
On<PMTran.accountGroupID, Equal<ARCstRptControl1.accountID>>,
LeftJoin<PMTask,
On<PMTask.projectID, Equal<PMTran.projectID>,
And<PMTask.taskID,Equal<PMTran.taskID>>>,
LeftJoin<Account,
On<ARTran.accountID, Equal<Account.accountID>>,
LeftJoin<ARCstRptControl2,
On<Account.accountGroupID, Equal<ARCstRptControl2.accountID>>>>>>>>,
Where<ARTran.tranType, Equal<Current<ARInvoice.docType>>,
And<ARTran.refNbr, Equal<Current<ARInvoice.refNbr>>>>>.Select(this))
{
ARTran artran = (ARTran)thistran;
PMTran pmtran = (PMTran)thistran;
ARCstRptControl srcgrp = (ARCstRptControl)thistran;
ARCstRptControl1 destgrp = (ARCstRptControl1)thistran;
PMTask pmtask = (PMTask)thistran;
Account account = (Account)thistran;
ARCstRptControl2 destgrp2 = (ARCstRptControl2)thistran;
All the table / DAC variables have the expected results, except ARCstRptControl2. I've moved this entire query into SQL Server and it gives me the results I want, including ARCstRptControl2 - but I must be doing something wrong with the join here in the BQL. Any ideas?
Since I'm using aliased DACs (ARCstRptControl1 and 2), and since I'm using two fields from those aliased DACs, I needed to have those fields (and any others that would be used) from that alias declared in the DAC declaration, as follows (i.e., I'm using accountID and columnID in the join):
[Serializable]
public class ARCstRptControl1 : ARCstRptControl
{
public abstract new class accountID : IBqlField { }
public abstract new class columnID : IBqlField { }
}
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
}
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;