BI Publisher in Oracle Fusion is a core reporting engine that organizations rely on for accurate, timely, and customized operational insights. While basic reports are easy to build, modern enterprises often need highly tailored, multi-source, multi-level reports that support audits, financial operations, supplier management, compliance, and executive decision-making. Delivering this level of reporting requires a strong understanding of Fusion’s data architecture, SQL-based data modeling, XML structuring, and automated bursting capabilities.Â
This guide outlines how to build complex, enterprise-grade BI Publisher reports in Oracle Fusion by creating optimized SQL data models, structuring hierarchical XML, enabling automated bursting, and applying performance best practices. These techniques help teams deliver scalable reports that reduce manual effort, improve data accuracy, and support faster decision-making across key business functions.Â
Prerequisites and SetupÂ
Required Roles and PrivilegesÂ
- BI Publisher Administrator:Â To create and manage reportsÂ
- Application Implementation Consultant:Â Access to data modelsÂ
- Database access:Â Query privileges on Fusion tablesÂ
Development EnvironmentÂ
- BI Publisher Desktop (optional but recommended for template development)Â
- SQL or PL/SQL Developer or a similar tool for query testingÂ
- Understanding of the Oracle Fusion data modelÂ
Understanding the ArchitectureÂ
Gain clarity on how BI Publisher components work together so that reports are structured, scalable, and aligned with Fusion’s data framework.Â
Components of a BI Publisher ReportÂ
Building Complex SQL Data ModelsÂ
Learn how to design efficient multi-dataset SQL models that retrieve accurate data from Fusion tables and support layered reporting needs.Â
- Data Model:Â The data model is the source of data, where the developer can write the SQL code as per requirement.Â
- Report: The report is the representation layer where the developer has to attach the templates (RTF, E-Text, Excel, XSLT, etc.). The report needs to be linked with the data model to extract data.Â
Let us go ahead and create the data model and report in Oracle SaaS.Â
Step 1: Create a Data Model in BI PublisherÂ
Navigate to:Â Tools > Reports and Analytics > Browse CatLog >Â Create and Report >Â Create >Â Data ModelÂ
(BI Publisher Console → Data Models → Create Data Model)Â
The analytics catalog page contains two folders (My Folders and Shared Folders) as shown below. My Folders is only accessible to the logged-in user; however, Shared Folders will be accessible to all users.Â
Note: It is always suggested to develop the report and data model in My Folders, and, post-development, migrate them to Shared Folders.Â
Example: Complex Invoice Report with Multiple DatasetsÂ
Navigate to: My Folders and click on New -> Data Model as shown below:Â
Once the data model is opened, click on Data Sets, click the + icon, and select SQL Query to start writing the SQL code.Â
The developer has to provide Name, Data Source, and Type of SQL, and proceed with writing the SQL code in the text area as shown below, and click OK to validate.Â
Main Query – Invoice HeadersÂ
SELECTÂ
    ai.invoice_id,Â
    ai.invoice_num,Â
    ai.invoice_date,Â
    ai.invoice_amount,Â
    ai.invoice_currency_code,Â
    ai.payment_status_flag,Â
    ai.invoice_type_lookup_code,    Â
   — Supplier InformationÂ
    pv.vendor_id,Â
    hp.party_name supplier_name,Â
    pv.segment1 AS supplier_number,Â
    pv.vendor_type_lookup_code,Â
    hp.email_address AS supplier_email,Â
   — Supplier SiteÂ
    pvs.vendor_site_id,Â
    pvs.vendor_site_code,Â
    pvs.purchasing_site_flag,Â
    pvs.pay_site_flag,Â
   — Location DetailsÂ
    loc.address1,Â
    loc.address2,Â
    loc.city,Â
    loc.state,Â
    loc.postal_code,Â
    loc.country,Â
   — Business UnitÂ
    ou.bu_name AS operating_unit,Â
    ou.bu_id,Â
   — Invoice DetailsÂ
    ai.description,Â
    ai.gl_date,Â
    ai.payment_method_code,Â
    ai.source,Â
   — Audit InformationÂ
    ai.created_by,Â
   TO_CHAR(ai.creation_date, ‘DD-MON-YYYY HH24:MI:SS’) AS creation_date_str,Â
    ai.last_updated_by,Â
   TO_CHAR(ai.last_update_date, ‘DD-MON-YYYY HH24:MI:SS’) AS last_update_date_str    Â
FROMÂ
    ap_invoices_all aiÂ
   INNER JOIN poz_suppliers pv Â
       ON ai.vendor_id = pv.vendor_idÂ
INNER JOIN hz_parties hp Â
       ON pv.party_id = hp.party_id Â
   INNER JOIN poz_supplier_sites_all_m pvs Â
       ON ai.vendor_site_id = pvs.vendor_site_idÂ
   LEFT JOIN hz_party_sites ps Â
       ON pvs.party_site_id = ps.party_site_idÂ
   LEFT JOIN hz_locations loc Â
       ON ps.location_id = loc.location_idÂ
   INNER JOIN fun_all_business_units_v  ou Â
       ON ai.org_id = ou.bu_id        Â
WHEREÂ
    ai.invoice_date BETWEEN :P_START_DATE AND :P_END_DATEÂ
   AND ai.org_id = :P_ORG_IDÂ
   AND pv.enabled_flag = ‘Y’Â
ORDER BYÂ
    ai.invoice_numÂ
Child Query – Invoice LinesÂ
SELECTÂ Â
    ail.invoice_id,Â
    ail.line_number,Â
    ail.line_type_lookup_code,Â
    ail.amount AS line_amount,Â
    ail.quantity_invoiced,Â
    ail.unit_price,Â
    ail.description AS line_description,Â
    ail.accounting_date,Â
    gcc.concatenated_segments AS charge_account,Â
    pol.item_description,Â
    msib.item_number AS item_code  — Fusion uses ITEM_NUMBER instead of SEGMENT1Â
FROMÂ Â
    ap_invoice_lines_all ailÂ
LEFT JOIN ap_invoice_distributions_all aidÂ
   ON ail.invoice_id=aid.invoice_idÂ
   LEFT JOIN gl_code_combinations gccÂ
       ON aid.dist_code_combination_id = gcc.code_combination_idÂ
   LEFT JOIN po_lines_all polÂ
       ON ail.po_line_id = pol.po_line_idÂ
   LEFT JOIN EGP_SYSTEM_ITEMS_B msibÂ
       ON ail.inventory_item_id = msib.inventory_item_idÂ
      AND ail.org_id = msib.organization_idÂ
ORDER BYÂ Â
    ail.invoice_id, ail.line_number;Â
Child Query – DistributionsÂ
SELECTÂ Â
    aid.invoice_id,Â
    aid.invoice_line_number,Â
    aid.distribution_line_number,Â
    aid.amount as dist_amount,Â
    aid.accounting_date as dist_accounting_date,Â
    gcc.concatenated_segments as distribution_account,Â
    gcc.segment1 as company,Â
    gcc.segment2 as department,Â
    gcc.segment3 as account,Â
    gcc.segment4 as sub_account,Â
    aid.description as dist_descriptionÂ
FROMÂ Â
    ap_invoice_distributions_all aid,Â
    gl_code_combinations gccÂ
WHEREÂ Â
    aid.dist_code_combination_id = gcc.code_combination_idÂ
ORDER BYÂ Â
    aid.invoice_id, Â
    aid.invoice_line_number, Â
    aid.distribution_line_numberÂ
Â
Step 2: Configure Data Set RelationshipsÂ
In BI Publisher Data Model Editor:Â Â
Create Parent-Child Links:Â Â
1. Main Query (G_INVOICES) – ParentÂ
2. Lines Query (G_LINES) – Child of G_INVOICES Â
- Link: G_INVOICES.INVOICE_ID = G_LINES.INVOICE_IDÂ
3. Distributions (G_DIST) – Child of G_LINES Â
- Link: G_LINES.INVOICE_ID = G_DIST.INVOICE_ID AND G_LINES.LINE_NUMBER = G_DIST.INVOICE_LINE_NUMBERÂ
Step 3: Add Parameters
— Parameter: P_START_DATEÂ
Data Type: DateÂ
Default Value: SYSDATE – 30Â
— Parameter: P_END_DATEÂ
Data Type: DateÂ
Default Value: SYSDATEÂ
— Parameter: P_ORG_IDÂ
Data Type: IntegerÂ
List of Values:Â Â
SELECT bu_id as value, bu_name as displayÂ
FROMÂ fun_all_business_units_vÂ
ORDER BYÂ bu_nameÂ
To view data, click on the Data tab and click on View. Before proceeding with the report development, click on Save As Sample Data and save the data model.Â
XML Data Structure and OptimizationÂ
Understand how BI Publisher generates hierarchical XML and how to optimize it for faster processing, cleaner templates, and better report performance. Understanding BI Publisher XML Output When you execute the data model, BI Publisher generates XML like this:Â<?xml version=”1.0″ encoding=”UTF-8″?>Â
<DATA_DS>Â
 <G_INVOICES>Â
   <INVOICE_ID>123456</INVOICE_ID>Â
   <INVOICE_NUM>INV-2025-001</INVOICE_NUM>Â
   <INVOICE_DATE>2025-01-15</INVOICE_DATE>Â
   <INVOICE_AMOUNT>50000</INVOICE_AMOUNT>Â
   <VENDOR_NAME>ABC Corporation</VENDOR_NAME>Â
   <VENDOR_NUMBER>V001</VENDOR_NUMBER>Â
   <OPERATING_UNIT>US Operations</OPERATING_UNIT>Â
    Â
   <G_LINES>Â
     <INVOICE_ID>123456</INVOICE_ID>Â
     <LINE_NUMBER>1</LINE_NUMBER>Â
     <LINE_AMOUNT>30000</LINE_AMOUNT>Â
     <DESCRIPTION>Consulting Services</DESCRIPTION>Â
      Â
     <G_DIST>Â
       <DIST_AMOUNT>15000</DIST_AMOUNT>Â
       <DISTRIBUTION_ACCOUNT>01-100-5000-000</DISTRIBUTION_ACCOUNT>Â
       <COMPANY>01</COMPANY>Â
       <DEPARTMENT>100</DEPARTMENT>Â
     </G_DIST>Â
      Â
     <G_DIST>Â
       <DIST_AMOUNT>15000</DIST_AMOUNT>Â
       <DISTRIBUTION_ACCOUNT>01-200-5000-000</DISTRIBUTION_ACCOUNT>Â
     </G_DIST>Â
   </G_LINES>Â
    Â
   <G_LINES>Â
     <LINE_NUMBER>2</LINE_NUMBER>Â
     <LINE_AMOUNT>20000</LINE_AMOUNT>Â
     <DESCRIPTION>Hardware Purchase</DESCRIPTION>Â
   </G_LINES>Â
 </G_INVOICES>Â
  Â
 <G_INVOICES>Â
    <!– Next invoice –>Â
 </G_INVOICES>Â
</DATA_DS>Â
XML Optimization TechniquesÂ
- Use SQL to pre-aggregate data rather than complex template logic.Â
- Limit result sets with appropriate WHERE clauses.Â
- Index columns used in JOIN conditions.Â
- Avoid SELECT * – specify only needed columns.Â
- Use bind variables for better performance.Â
XML Bursting ImplementationÂ
XML bursting allows you to split a single report into multiple outputs and deliver them to different recipients based on criteria.Â
Bursting Control File StructureÂ
Create a bursting control file in your data model:Â
— Bursting QueryÂ
SELECT DISTINCTÂ
    ai.org_id as KEY,Â
   ou.name as TEMPLATE,Â
   ‘en-US’ as LOCALE,Â
   ‘true’ as SAVE_OUTSIDE,Â
   ‘/Reports/Invoices/’ || ou.name || ‘/’ as OUTPUT_NAME,Â
   ‘Invoice_Report_’ || TO_CHAR(SYSDATE, ‘YYYYMMDD’) as OUTPUT_NAME_PREFIX,Â
   ‘pdf’ as OUTPUT_FORMAT,Â
   ‘[email protected]’ as PARAMETER1,Â
    hp.party_name as PARAMETER2 –pv.vendor_name as PARAMETER2,Â
   ‘oracle.apps.ap’ as PARAMETER3Â
FROMÂ Â
    ap_invoices_all ai,Â
    fun_all_business_units_v ou,Â
    poz_suppliers pv,Â
hz_parties hp Â
WHEREÂ Â
    ai.org_id = ou.organization_idÂ
   AND ai.vendor_id = pv.vendor_idÂ
AND pv.party_id = hp.party_idÂ
   AND ai.invoice_date BETWEEN :P_START_DATE AND :P_END_DATEÂ
Bursting Control File ElementsÂ
| Element | Purpose | Example |
|---|---|---|
| KEY | Split criterion (must match data grouping) | org_id, vendor_id |
| TEMPLATE | Template name or parameter | ‘InvoiceTemplate’ |
| LOCALE | Language locale | ‘en-US’, ‘es-MX’ |
| OUTPUT_FORMAT | Output file format | ‘pdf’, ‘excel’, ‘rtf’ |
| OUTPUT_NAME | File name/path | ‘Invoice_’ |
| SAVE_OUTSIDE | Save to UCM or file system | ‘true’ or ‘false’ |
| DELIVERY | Delivery channel | ‘EMAIL’, ‘FAX’, ‘PRINTER’ |
Advanced Bursting Example: Email DistributionÂ
— Email Bursting Control QueryÂ
SELECT DISTINCTÂ
    ai.vendor_id as KEY,Â
   ‘InvoiceDetailTemplate’ as TEMPLATE,Â
   ‘en-US’ as LOCALE,Â
   ‘pdf’ as OUTPUT_FORMAT,Â
   ‘EMAIL’ as DEL_CHANNEL,Â
    hp.party_name || ‘_Invoices_’ || Â
       TO_CHAR(SYSDATE, ‘YYYYMMDD_HH24MISS’) as OUTPUT_NAME,Â
    hp.email_address as PARAMETER1, — TO recipientÂ
   ‘[email protected]’ as PARAMETER2, — FROMÂ
   ‘[email protected]’ as PARAMETER3, — CCÂ
   ‘Invoice Report for ‘ || pv.vendor_name as PARAMETER4, — SubjectÂ
   ‘Please find attached invoice report for the period ‘ || Â
       TO_CHAR(:P_START_DATE, ‘DD-MON-YYYY’) || ‘ to ‘ ||Â
       TO_CHAR(:P_END_DATE, ‘DD-MON-YYYY’) as PARAMETER5, — BodyÂ
   ‘true’ as PARAMETER6, — Attach reportÂ
   ‘ucm://server/dDocName:AR_LOGO’ as PARAMETER7 — Additional attachmentÂ
FROMÂ Â
    ap_invoices_all aiÂ
   INNER JOIN poz_suppliers pv Â
       ON ai.vendor_id = pv.vendor_idÂ
INNER JOIN hz_parties hp Â
       ON pv.party_id = hp.party_id Â
   INNER JOIN poz_supplier_sites_all_m pvs Â
       ON ai.vendor_site_id = pvs.vendor_site_idÂ
   LEFT JOIN hz_party_sites ps Â
       ON pvs.party_site_id = ps.party_site_idÂ
   LEFT JOIN hz_locations loc Â
       ON ps.location_id = loc.location_idÂ
   INNER JOIN fun_all_business_units_v ou Â
       ON ai.org_id = ou.bu_id        Â
WHEREÂ
    ai.invoice_date BETWEEN :P_START_DATE AND :P_END_DATEÂ
   AND ai.org_id = :P_ORG_IDÂ
   AND pv.enabled_flag = ‘Y’Â
ORDER BYÂ
    ai.invoice_numÂ
Bursting Configuration StepsÂ
1. In Data Model Editor:Â
- Click on the “Bursting” sectionÂ
- Select “Enable Report Bursting”Â
- Choose “SQL Query” as bursting definition typeÂ
2. Add Bursting Query:Â
- Paste your bursting control SQLÂ
- Ensure KEY column matches grouping in main queryÂ
- Test the queryÂ
3. Configure Delivery Options:Â
| Parameter | Email Configuration in bursting query |
|---|---|
| PARAMETER1 | TO email address |
| PARAMETER2 | FROM email address |
| PARAMETER3 | CC email address |
| PARAMETER4 | Subject line |
| PARAMETER5 | Email body text |
| PARAMETER6 | ‘true’ (attach report) |
4. Set Bursting Properties:Â
- Split by KEY fieldÂ
- Apply security settingsÂ
- Configure retry logicÂ
Using Flexfields in ReportsÂ
— Capturing Descriptive Flexfield valuesÂ
SELECTÂ Â
    ai.invoice_id,Â
    ai.invoice_num,Â
    ai.attribute1 as dff_segment1,Â
    ai.attribute2 as dff_segment2,Â
    ai.attribute3 as dff_segment3,Â
    fnd_flex_ext.get_segs(Â
       ‘SQLAP’,Â
       ‘AP_INVOICES’,Â
        ai.org_id,Â
        ai.attribute_categoryÂ
   ) as dff_formattedÂ
FROMÂ Â
    ap_invoices_all aiÂ
WHEREÂ Â
    ai.attribute_category IS NOT NULLÂ
Handling Large Data SetsÂ
— Pagination techniqueÂ
SELECT * FROM (Â
   SELECT Â
        invoice_details.*,Â
       ROWNUM rnÂ
   FROM (Â
       SELECT Â
            ai.invoice_id,Â
            ai.invoice_num,Â
            ai.invoice_amountÂ
       FROM Â
            ap_invoices_all aiÂ
        WHERE Â
            ai.invoice_date BETWEEN :P_START_DATE AND :P_END_DATEÂ
       ORDER BY Â
            ai.invoice_date DESCÂ
   ) invoice_detailsÂ
   WHERE ROWNUM <= :P_MAX_ROWS + :P_OFFSETÂ
)Â
WHERE rn > :P_OFFSETÂ
Conditional Formatting in TemplatesÂ
Using RTF template with conditional logic:Â
<?if:INVOICE_AMOUNTÂ > 10000?>Â
<?attribute@incontext:color;’red’?>Â
High Value InvoiceÂ
<?end if?>Â
Â
<?if:PAYMENT_STATUS_FLAG=’Y’?>Â
Status: PAIDÂ
<?else?>Â
Status: OUTSTANDINGÂ
<?end if?>Â
Best Practices and TroubleshootingÂ
Apply proven methods to improve performance, avoid common issues, validate outputs, and ensure reliability in production environments.Â
Performance Best PracticesÂ
1. Query Optimization:Â
- Use indexed columns in WHERE and JOIN clausesÂ
- Avoid functions on indexed columnsÂ
- Use EXISTS instead of IN for subqueriesÂ
- Limit DISTINCT usageÂ
2. Data Model Design:Â
- Keep data sets focused and minimalÂ
- Use data set linking instead of complex joinsÂ
- Implement row limits for testingÂ
- Cache frequently used data modelsÂ
3. Template Optimization:Â
- Minimize complex calculations in templatesÂ
- Pre-calculate in SQL rather than in templateÂ
- Use simple grouping structuresÂ
- Avoid nested loops where possibleÂ
Common Issues and SolutionsÂ
Identify frequent challenges in BI Publisher development and use targeted fixes to resolve errors, improve stability, and ensure accurate report output.Â
Issue 1: Bursting Not Working
Solution:Â
- Verify KEY column matches grouping in main query exactlyÂ
- Check that TEMPLATE name exists and is accessibleÂ
- Ensure delivery parameters are correctly formattedÂ
- Review bursting log filesÂ
Issue 2: Slow Report GenerationÂ
Solution:Â
— Add hints for better performanceÂ
SELECT /*+ FIRST_ROWS(100)Â INDEX(ai AP_INVOICES_N1) */Â
    ai.invoice_id,Â
    ai.invoice_numÂ
FROM ap_invoices_all aiÂ
WHERE ai.invoice_date >= :P_START_DATEÂ
Issue 3: XML Structure Not HierarchicalÂ
Solution:Â
- Verify data set links are configured correctlyÂ
- Check parent-child relationships in Data ModelÂ
- Ensure linking columns are in both queriesÂ
- Test with “View XML” optionÂ
Issue 4: Template Data Not DisplayingÂ
Solution:Â
- Check XML structure matches template field namesÂ
- Verify namespace and group namesÂ
- Use “View Sample Data” to confirm XML structureÂ
- Check for case sensitivity in field namesÂ
Report Access Control:Â Â
- Assign reports to appropriate foldersÂ
- Use BI Publisher roles and permissionsÂ
- Implement custom function securityÂ
- Audit report executionÂ
Debugging TechniquesÂ
1. Enable Logging:Â
- Set log level to STATEMENT in data modelÂ
- Review Admin > Diagnostics logsÂ
- Check scheduled job logsÂ
2. Test Incrementally:Â
- Test main query independentlyÂ
- Add child queries one at a timeÂ
- Verify XML structure at each stepÂ
- Test bursting without delivery firstÂ
3. Use Sample Data:Â
- Generate sample XML with limited rowsÂ
- Test templates with sample dataÂ
- Verify all scenarios are coveredÂ
Deployment ChecklistÂ
- Test data model with various parameter combinationsÂ
-  Verify template rendering in all output formatsÂ
- Â Test bursting with small data setÂ
- Â Validate email delivery (if applicable)Â
- Â Check performance with production data volumeÂ
- Â Document parameters and expected outputsÂ
- Set up job scheduling (if required)Â
- Configure error notificationsÂ
- Migrate to production environmentÂ
- Conduct user acceptance testingÂ
Additional ResourcesÂ
Template Syntax ReferenceÂ
For Each Loop:Â
<?for-each:G_LINES?>Â
 Line: <?LINE_NUMBER?> – <?LINE_DESCRIPTION?>Â
<?end for-each?>Â
Calculations:Â
Sum: <?sum(LINE_AMOUNT)?>Â
Count: <?count(LINE_NUMBER)?>Â
Average: <?avg(LINE_AMOUNT)?>Â
Grouping:Â
<?for-each-group:G_LINES;./VENDOR_ID?>Â
 Vendor: <?VENDOR_NAME?>Â
 <?for-each:current-group()?>Â
   <?LINE_DESCRIPTION?>Â
 <?end for-each?>Â
<?end for-each-group?>Â
ConclusionÂ
Building complex BI Publisher reports in Oracle Fusion requires understanding of SQL, XML data structures, and the bursting mechanism. Key takeaways:Â
- Design efficient SQL queries with proper joins and filteringÂ
- Structure data models with clear parent-child relationshipsÂ
- Leverage XML bursting for automated distributionÂ
- Follow best practices for performance and maintainabilityÂ
- Test thoroughly before production deploymentÂ
With these techniques, we can create sophisticated, enterprise-grade reports that meet complex business requirements.Â
For teams aiming to strengthen their Fusion reporting, automation, and integration landscape, our Oracle Fusion services provide the right foundation. Contact us to learn more.

