Oracle Purchase Order Tables in EBS R12 and Fusion Cloud
Aug 3, 2026
Aug 3, 2026
Convert a purchase order to Excel, CSV, or JSON
Submit your purchase orders
Drop documents here, or click to file
Up to 50 files per batch
Uploading...
Oracle stores a purchase order across four tables. PO_HEADERS_ALL holds one row per document, PO_LINES_ALL one row per line, PO_LINE_LOCATIONS_ALL one row per shipment schedule, and PO_DISTRIBUTIONS_ALL one row per accounting distribution. They chain together through PO_HEADER_ID, PO_LINE_ID, and LINE_LOCATION_ID. The same four table names are used in both Oracle E-Business Suite R12 and Oracle Fusion Cloud Procurement, which is why queries written for one often look almost right on the other.
Last updated August 2026.
If you are writing a report, building an extract, or trying to work out where a number on a PO printout actually lives, the table layout is the first thing you need and the hardest thing to find in one place. What follows is the structure, the join keys, the columns people actually query, and the differences between the EBS and Fusion versions. The tool above is a different job entirely: it reads purchase orders that arrive as PDFs and turns them into rows, which is what you need before any of these tables can be loaded.
Everything else in Purchasing hangs off these four. The grain column is the one that trips people up: a single PO line can have several shipments, and a single shipment can have several distributions, so a naive join from header to distribution multiplies rows.
| Table | What it holds | Grain | Primary key |
|---|---|---|---|
| PO_HEADERS_ALL | Document header: supplier, buyer, currency, status, dates | One row per document | PO_HEADER_ID |
| PO_LINES_ALL | What was ordered: item, description, category, unit price, UOM | One row per line | PO_LINE_ID |
| PO_LINE_LOCATIONS_ALL | Shipment schedules, ship-to, promised and need-by dates, quantity received and billed, and blanket price breaks | One row per shipment or price break | LINE_LOCATION_ID |
| PO_DISTRIBUTIONS_ALL | Accounting: charge account, quantity ordered and delivered, budget and project references | One row per distribution | PO_DISTRIBUTION_ID |
The _ALL suffix means the table is not restricted by operating unit. In EBS the matching views without the suffix, such as PO_HEADERS, apply the ORG_ID security policy for whichever operating unit your session is set to. Reports usually query the _ALL table and filter ORG_ID explicitly, because that is predictable.
PO_HEADERS_ALL carries six document types, not just purchase orders: standard purchase orders, planned purchase orders, blanket purchase agreements, contracts, RFQs, and quotations. TYPE_LOOKUP_CODE is what separates them, and forgetting to filter on it is the single most common reason a PO report returns too many rows.
| Column | Type | What it is |
|---|---|---|
| PO_HEADER_ID | NUMBER | System-generated primary key. Never shown to users. |
| SEGMENT1 | VARCHAR2(30) | The PO number a human reads. Not unique on its own. |
| TYPE_LOOKUP_CODE | VARCHAR2(25) | STANDARD, BLANKET, PLANNED, CONTRACT, RFQ, QUOTATION. |
| VENDOR_ID / VENDOR_SITE_ID | NUMBER | Supplier and supplier site. |
| AGENT_ID | NUMBER | The buyer. Joins to PO_AGENTS. |
| CURRENCY_CODE, RATE, RATE_DATE | VARCHAR2 / NUMBER | Document currency and the conversion applied. |
| APPROVED_FLAG, APPROVED_DATE | VARCHAR2(1) / DATE | Y once approved, plus the date of the most recent approval. |
| CANCEL_FLAG, CLOSED_DATE | VARCHAR2(1) / DATE | Cancellation and closure at header level. |
| BLANKET_TOTAL_AMOUNT, AMOUNT_LIMIT, AMOUNT_RELEASED | NUMBER | Agreement value, ceiling, and how much has been released. Blanket and planned only. |
| REVISED_DATE | DATE | When the document was last revised. |
There is no single status column. Fusion adds a DOCUMENT_STATUS column carrying values such as DRAFT, SUBMITTED, APPROVED, CLOSED, and CANCELLED, but in EBS the state of a purchase order is assembled from several flags: APPROVED_FLAG for approval, CANCEL_FLAG for cancellation, CLOSED_CODE for closure, and AUTHORIZATION_STATUS for where the document sits in the approval workflow. A PO that looks open on screen can be closed for receiving and still open for invoicing, because closure is tracked per shipment in PO_LINE_LOCATIONS_ALL as well as at header level. If you are chasing that distinction, the states are laid out in plain terms in our guide to the open purchase order report.
The chain runs downward, and each level carries the keys of the levels above it, which is convenient but also why row multiplication is easy to cause by accident.
SELECT h.segment1 AS po_number,
h.type_lookup_code,
l.line_num,
l.item_description,
ll.quantity AS qty_ordered,
ll.quantity_received,
d.code_combination_id
FROM po_headers_all h
JOIN po_lines_all l ON l.po_header_id = h.po_header_id
JOIN po_line_locations_all ll ON ll.po_line_id = l.po_line_id
JOIN po_distributions_all d ON d.line_location_id = ll.line_location_id
WHERE h.type_lookup_code = 'STANDARD'
AND h.org_id = :org_id;
Two habits save time later. Join shipments to distributions on LINE_LOCATION_ID rather than PO_LINE_ID, because a line with three shipments and one distribution each will otherwise return nine rows instead of three. And if you need a single amount per PO, aggregate at the level you actually mean: ordered value lives naturally at the shipment level, while accounted value lives at the distribution level.
There is no direct key from PO_HEADERS_ALL to PO_REQUISITION_HEADERS_ALL, which is why the join catches people out. The link is made at line level through PO_REQ_DISTRIBUTIONS_ALL: a requisition distribution carries the DISTRIBUTION_ID of the PO distribution it turned into, so you travel requisition header to requisition line to requisition distribution, across to the PO distribution, and only then up to the PO header. One requisition can feed several POs and one PO can consolidate several requisitions, so the relationship is many to many in both directions. The business side of that handoff is covered in purchase requisition vs purchase order.
The names survived the move to Fusion Cloud, and so did most of the structure. What changed is the surrounding model and, critically for anyone on Cloud, the access method.
| Area | Oracle EBS R12 | Oracle Fusion Cloud |
|---|---|---|
| Core table names | PO_HEADERS_ALL and the three children | Identical names |
| Schema owner | PO, accessed through APPS synonyms | FUSION schema, owner PO |
| Organization column | ORG_ID, the operating unit | PRC_BU_ID, the procurement business unit |
| Business key | ORG_ID plus SEGMENT1 plus TYPE_LOOKUP_CODE | PRC_BU_ID plus SEGMENT1 plus TYPE_LOOKUP_CODE |
| Header status | Assembled from APPROVED_FLAG, CLOSED_CODE, CANCEL_FLAG, AUTHORIZATION_STATUS | DOCUMENT_STATUS column, with the flags retained |
| Direct SQL access | Yes, you have the database | No. Use BI Publisher, OTBI, or the REST APIs |
That last row is the one that matters in practice. On Fusion the tables are documented and real, but you do not get a SQL prompt against production, so a query like the one above is written inside a BI Publisher data model rather than run directly. Teams migrating a shelf of EBS reports usually find the SQL ports easily and the delivery mechanism does not.
Data does not get written straight into PO_HEADERS_ALL. Imports land first in the interface tables, PO_HEADERS_INTERFACE and PO_LINES_INTERFACE, with the child levels in PO_LINE_LOCATIONS_INTERFACE and PO_DISTRIBUTIONS_INTERFACE. PO_LINES_INTERFACE is keyed on INTERFACE_LINE_ID and points back at its header through INTERFACE_HEADER_ID, and it carries the fields you would expect on a line: ITEM, ITEM_DESCRIPTION, QUANTITY, UNIT_PRICE, UNIT_OF_MEASURE, CATEGORY, LINE_TYPE, and NEED_BY_DATE. PROCESSING_ID groups the rows belonging to one run.
The Purchasing Documents Open Interface reads the staged rows, defaults and derives what is missing, validates the rest, and writes real documents. The old single PDOI concurrent program was split into two: Import Price Catalogs for agreements and price lists, and Import Standard Purchase Orders (POXPOPDOI) for approved or unapproved standard POs. Anything that fails validation is written to PO_INTERFACE_ERRORS with the record identifier and the reason, and that table is the first place to look when the program completes successfully but no orders appear. Teams running this nightly usually end up wanting for the staging table what they already have for the warehouse, a way to watch row volumes and schema drift for anomalies, so a silent load failure surfaces the same day rather than at month end.
Getting rows into those interface tables is the part nobody documents, because it depends entirely on where your purchase orders come from. If suppliers or customers send them as PDF attachments, someone is retyping them. That is the step our Oracle purchase order import page covers in detail, and the same structured output is available as JSON through the purchase order API if you would rather your middleware do the staging. To be clear about the boundary: we extract purchase order data from documents into structured rows. We do not write to Oracle tables, run POXPOPDOI, or create, approve, or match purchase orders.
A blanket purchase agreement lives in PO_HEADERS_ALL with TYPE_LOOKUP_CODE set to BLANKET, and its negotiated price breaks sit in PO_LINE_LOCATIONS_ALL alongside real shipments. Releases against it are separate documents in PO_RELEASES_ALL, one row per release, and shipments belonging to a release carry PO_RELEASE_ID. So the ordered quantity on a blanket agreement is not in the agreement at all, it is spread across the releases, which is why AMOUNT_RELEASED on the header exists. If the difference between these document types is the actual question, blanket vs standard purchase orders covers it without the SQL.
PO_HEADERS_ALL is the purchase order header table in Oracle Fusion, in the FUSION schema with owner PO. Lines are in PO_LINES_ALL, shipment schedules in PO_LINE_LOCATIONS_ALL, and accounting distributions in PO_DISTRIBUTIONS_ALL. The names match Oracle E-Business Suite, but Fusion uses PRC_BU_ID rather than ORG_ID and you reach the data through BI Publisher, OTBI, or REST rather than direct SQL.
SEGMENT1 holds the purchase order number that users see. PO_HEADER_ID is the internal primary key and is never displayed. SEGMENT1 is not unique by itself, because the same number can exist for a different document type or a different business unit, so match on SEGMENT1 together with TYPE_LOOKUP_CODE and either ORG_ID in EBS or PRC_BU_ID in Fusion.
The four base tables are PO_HEADERS_ALL, PO_LINES_ALL, PO_LINE_LOCATIONS_ALL, and PO_DISTRIBUTIONS_ALL, all owned by the PO schema and reachable through APPS synonyms. Receiving data sits separately in RCV_SHIPMENT_HEADERS and RCV_TRANSACTIONS, and supplier detail in PO_VENDORS and PO_VENDOR_SITES_ALL. Filter on ORG_ID for the operating unit you want.
Yes. Approved revisions are copied to archive tables, PO_HEADERS_ARCHIVE_ALL and PO_LINES_ARCHIVE_ALL, with a REVISION_NUM on each row. That is where you look to see what a purchase order said at revision 0 after it has been revised twice. The live tables only hold the current version, so any question about what changed and when has to be answered from the archive.
PO_LINES_ALL says what was ordered: the item, its description, category, unit price, and unit of measure. PO_LINE_LOCATIONS_ALL says when and where each part of that line is due, and tracks the quantity received and billed against it. A line ordered once but delivered to three sites on three dates is one row in PO_LINES_ALL and three in PO_LINE_LOCATIONS_ALL.
The equivalent structure on the other big enterprise system is covered in purchase order history in SAP, including the EKKO, EKPO, and EKBE tables and the transaction codes that read them. For the fields themselves rather than the schema, see purchase order fields. The general path for loading orders into any ERP is in how to import purchase orders to an ERP, and if the volume is a backlog rather than a daily trickle, bulk purchase order upload is the faster route.
Stop retyping purchase orders
Upload a PDF, scan, or photo of any PO and get clean Excel, CSV, or JSON line items in seconds.
Try it free25 pages free. No credit card required.