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
}
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.
Good Day!
I have a field in my DAC where I need to change the Selector attribute depending on the setup that I set on my preferences. As you may know, there is an existing LeadSelector Attribute and CustomerSelector Attribute on acumatica. I wish to change the selector attribute of that given field if I set Customer on my preferences, and vice versa.
Is there any available resources right here now?
I've been thinking of creating an Extended Selector attribute on which I will check what is the preference setup then inherit the LeadSelector or CustomerSelector on the Extended Selector. But I think it might not be possible.
The other thing that I've been thinking, is to add both selectors on the attribute and remove them from the graph level whenever which preference is set up.
I'm also thinking of creating 2 selectors, on which I will hide the other depending on the preference setup. But the problem is, the selector is being used not only on one page, and it's a hassle if I create 2 selectors just to solve that issue. And also in the future it might not just lead and customer selectors.
I hope you can help me, I'm out of ideas. Thank you so much.
UPDATE 09-24-2019
I created a custom selector attribute for Lead and Customer Selector attributes. And it's working just I want it to be, but now my problem is, the description field won't show on the text box or on that field, also, there are error such as 'Investor name cannot be found in the system'.
Investor Selector Attribute
public class InvestorSelectorAttribute : PXCustomSelectorAttribute
{
public InvestorSelectorAttribute() : base(typeof(REInvestor.accountID))
{
DescriptionField = typeof(REInvestor.acctName);
SubstituteKey = typeof(REInvestor.acctName);
}
protected IEnumerable GetRecords()
{
var leads = new PXSelect<Contact,
Where<Contact.contactType, Equal<ContactTypesAttribute.lead>,
Or<Where<Contact.contactType, Equal<ContactTypesAttribute.person>,
And<Contact.status, Equal<LeadStatusesAttribute.converted>>>>>>(this._Graph);
var contacts = new PXSelect<BAccountR>(this._Graph);
REFeature setup = PXSelect<REFeature>.Select(this._Graph);
if (setup.InvestorType == InvestorTypesAttribute.LeadVal)
{
foreach (Contact lead in leads.Select())
{
yield return new REInvestor { AccountID = lead.ContactID, AcctName = lead.DisplayName };
}
}
else
{
foreach (BAccountR contact in contacts.Select())
{
yield return new REInvestor { AccountID = contact.BAccountID, AcctName = contact.AcctName, AcctCD = contact.AcctCD };
}
}
}
}
Unbound REInvestor DAC
[Serializable]
[PXCacheName("Investor")]
public class REInvestor : IBqlTable
{
public abstract class accountID : BqlInt.Field<accountID> { }
[PXDBInt(IsKey = true)]
[PXUIField(DisplayName = REMessages.DisplayNames.AccountID, Visibility = PXUIVisibility.SelectorVisible)]
public virtual int? AccountID { get; set; }
public abstract class acctName : BqlString.Field<acctName> { }
[PXDBString(128, InputMask = "", IsUnicode = true)]
[PXUIField(DisplayName = REMessages.DisplayNames.AccountName, Visibility = PXUIVisibility.SelectorVisible)]
public virtual string AcctName { get; set; }
public abstract class acctCD : BqlString.Field<acctCD> { }
[PXDBString(128, InputMask = "", IsUnicode = true)]
[PXUIField(DisplayName = REMessages.DisplayNames.AcctCD, Visibility = PXUIVisibility.SelectorVisible)]
public virtual string AcctCD { get; set; }
}
** DAC Integration **
[PXDBInt]
[PXUIField(DisplayName = REMessages.DisplayNames.InvestorsName, Required = true)]
[InvestorSelector()]
[PXDefault(PersistingCheck = PXPersistingCheck.NullOrBlank)]
public virtual int? ContactID { get; set; }
I really need your help and suggestions. Thank you so much.
Is ok to have 2 fields defined and one displayed.
Each one with his own selector, and PXUIField Description.
You set visibility for one of them base on the setup field at graph level (RowSelected event)
In case you need to merge them on persist (persist both fields in one bound field), you can simply use 2 unbound fields for collecting/displaying data.
on updating/persisting event, you can update the values from unbound fields to unique database field
on retrieve you can make an Data View - Delegate to populate the unbound fields base on configuration;
When you plan to move on multiple pages (graphs) you move the code from the graph to an PXEventSubscriberAttribute
You add the new attribute at the DAC level.
This way you have access on all Graph events that may need (persisting, selecting, updating, ...). All the code stays in one place.
For multiple DACs you still need to create the fields; add them the new attribute.
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;
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;