PER_ALL_PEOPLE_F is the single most important table in Oracle HCM Cloud. It is the master person record: every employee, contingent worker, contact, and applicant has at least one row here. If you are building reports, integrations, or HCM Extracts, you will use this table constantly.
This guide covers its structure, key columns, date-effective behavior, common join patterns, and practical SQL examples.
Table Overview
- Full Name:
PER_ALL_PEOPLE_F - Module: Core HR (Human Resources)
- Type: Date-Effective Table (_F suffix)
- Primary Key:
PERSON_ID+EFFECTIVE_START_DATE - Translation Table:
PER_ALL_PEOPLE_TL - Convenience View:
PER_ALL_PEOPLE_VL
Important: PERSON_ID alone is not unique in this table. Because PER_ALL_PEOPLE_F is date-effective, each person can have multiple rows. Always include a date filter or use PERSON_ID + EFFECTIVE_START_DATE as your key.
Key Columns Reference
| Column | Type | Description |
|---|---|---|
| PERSON_ID | NUMBER | Unique person identifier. FK to nearly every HCM table. |
| EFFECTIVE_START_DATE | DATE | Start of this date-effective row's validity period. |
| EFFECTIVE_END_DATE | DATE | End of validity. Current row has 4712-12-31. |
| PERSON_NUMBER | VARCHAR2 | Human-readable person/employee number. |
| DATE_OF_BIRTH | DATE | Person's date of birth. |
| DATE_OF_DEATH | DATE | Date of death (if applicable). |
| COUNTRY_OF_BIRTH | VARCHAR2 | Country code where person was born. |
| REGION_OF_BIRTH | VARCHAR2 | Region/state of birth. |
| TOWN_OF_BIRTH | VARCHAR2 | Town/city of birth. |
| BLOOD_TYPE | VARCHAR2 | Blood type (used in some regional deployments). |
| CREATED_BY | VARCHAR2 | Username who created this row. |
| CREATION_DATE | TIMESTAMP | When this row was first created. |
| LAST_UPDATED_BY | VARCHAR2 | Username who last modified this row. |
| LAST_UPDATE_DATE | TIMESTAMP | When this row was last modified. |
| OBJECT_VERSION_NUMBER | NUMBER | Optimistic locking version counter. |
For the full column listing (60+ columns), view PER_ALL_PEOPLE_F in our table browser.
Where are names? First name, last name, and display name are stored in PER_PERSON_NAMES_F, not in PER_ALL_PEOPLE_F. This is because Oracle Fusion supports multiple name types (legal name, preferred name, etc.) and translations.
Date-Effective Behavior
Every change to a person's record creates a new date-effective row. Suppose an employee's record is first created on January 1, 2025:
PERSON_ID | EFFECTIVE_START_DATE | EFFECTIVE_END_DATE | PERSON_NUMBER
----------|---------------------|--------------------|---------------
10001 | 2025-01-01 | 4712-12-31 | EMP001
When a correction is made on March 15, Oracle end-dates the old row and creates a new one:
PERSON_ID | EFFECTIVE_START_DATE | EFFECTIVE_END_DATE | PERSON_NUMBER
----------|---------------------|--------------------|---------------
10001 | 2025-01-01 | 2025-03-14 | EMP001
10001 | 2025-03-15 | 4712-12-31 | EMP001
This is why you must always filter on dates. Without the filter, a SELECT COUNT(*) on PER_ALL_PEOPLE_F will return a much larger number than your actual headcount.
Getting Current Records
-- Current person record
SELECT p.PERSON_ID, p.PERSON_NUMBER, p.DATE_OF_BIRTH
FROM PER_ALL_PEOPLE_F p
WHERE TRUNC(SYSDATE) BETWEEN p.EFFECTIVE_START_DATE
AND p.EFFECTIVE_END_DATE;
Getting Records as of a Specific Date
-- Person records as of January 1, 2025
SELECT p.PERSON_ID, p.PERSON_NUMBER
FROM PER_ALL_PEOPLE_F p
WHERE DATE '2025-01-01' BETWEEN p.EFFECTIVE_START_DATE
AND p.EFFECTIVE_END_DATE;
Common Join Patterns
Person + Name (Most Common Query)
Since names are stored separately, the most common query pattern joins PER_ALL_PEOPLE_F with PER_PERSON_NAMES_F:
SELECT
p.PERSON_ID,
p.PERSON_NUMBER,
n.FIRST_NAME,
n.LAST_NAME,
n.DISPLAY_NAME
FROM PER_ALL_PEOPLE_F p
JOIN PER_PERSON_NAMES_F n
ON p.PERSON_ID = n.PERSON_ID
AND n.NAME_TYPE = 'GLOBAL'
AND TRUNC(SYSDATE) BETWEEN n.EFFECTIVE_START_DATE
AND n.EFFECTIVE_END_DATE
WHERE TRUNC(SYSDATE) BETWEEN p.EFFECTIVE_START_DATE
AND p.EFFECTIVE_END_DATE;
NAME_TYPE values: 'GLOBAL' is the default display name. Other values include 'US' (US legal name), 'JP' (Japanese name format), etc. Always filter by NAME_TYPE or you will get duplicate rows.
Person + Assignment (Employee Details)
To get employee-level details like job, position, department, and business unit, join to PER_ALL_ASSIGNMENTS_F:
SELECT
p.PERSON_ID,
p.PERSON_NUMBER,
n.DISPLAY_NAME,
a.ASSIGNMENT_NUMBER,
a.ASSIGNMENT_STATUS_TYPE,
a.BUSINESS_UNIT_ID,
a.JOB_ID,
a.POSITION_ID,
a.DEPARTMENT_ID,
a.LOCATION_ID,
a.MANAGER_ID
FROM PER_ALL_PEOPLE_F p
JOIN PER_PERSON_NAMES_F n
ON p.PERSON_ID = n.PERSON_ID
AND n.NAME_TYPE = 'GLOBAL'
AND TRUNC(SYSDATE) BETWEEN n.EFFECTIVE_START_DATE AND n.EFFECTIVE_END_DATE
JOIN PER_ALL_ASSIGNMENTS_F a
ON p.PERSON_ID = a.PERSON_ID
AND a.PRIMARY_FLAG = 'Y'
AND a.ASSIGNMENT_TYPE = 'E'
AND TRUNC(SYSDATE) BETWEEN a.EFFECTIVE_START_DATE AND a.EFFECTIVE_END_DATE
WHERE TRUNC(SYSDATE) BETWEEN p.EFFECTIVE_START_DATE AND p.EFFECTIVE_END_DATE;
Person + Address
SELECT
p.PERSON_ID,
p.PERSON_NUMBER,
addr.ADDRESS_LINE_1,
addr.TOWN_OR_CITY,
addr.REGION_2 AS state,
addr.POSTAL_CODE,
addr.COUNTRY
FROM PER_ALL_PEOPLE_F p
JOIN PER_ADDRESSES_F addr
ON p.PERSON_ID = addr.PERSON_ID
AND addr.ADDRESS_TYPE = 'HOME'
AND TRUNC(SYSDATE) BETWEEN addr.EFFECTIVE_START_DATE AND addr.EFFECTIVE_END_DATE
WHERE TRUNC(SYSDATE) BETWEEN p.EFFECTIVE_START_DATE AND p.EFFECTIVE_END_DATE;
Person + Period of Service (Hire/Termination Dates)
SELECT
p.PERSON_ID,
p.PERSON_NUMBER,
pos.DATE_START AS hire_date,
pos.ACTUAL_TERMINATION_DATE,
pos.PERIOD_TYPE
FROM PER_ALL_PEOPLE_F p
JOIN PER_PERIODS_OF_SERVICE pos
ON p.PERSON_ID = pos.PERSON_ID
AND pos.PRIMARY_FLAG = 'Y'
WHERE TRUNC(SYSDATE) BETWEEN p.EFFECTIVE_START_DATE AND p.EFFECTIVE_END_DATE;
Related Tables
PER_ALL_PEOPLE_F is the hub that connects to these key tables via PERSON_ID:
Common Pitfalls
- Forgetting the date filter — You will get inflated row counts and duplicate records. Always filter on
EFFECTIVE_START_DATEandEFFECTIVE_END_DATE. - Looking for names in PER_ALL_PEOPLE_F — Names are in
PER_PERSON_NAMES_F, not here. - Not filtering by NAME_TYPE — When joining to PER_PERSON_NAMES_F, always include
NAME_TYPE = 'GLOBAL'(or the specific type you need). - Not filtering by ASSIGNMENT_TYPE — PER_ALL_ASSIGNMENTS_F contains multiple assignment types (E=Employee, C=Contingent Worker, O=Offer, etc.). Filter to avoid cross-type duplicates.
- Using PERSON_ID as a unique key — It is unique per person but not per row. The true primary key is
PERSON_ID+EFFECTIVE_START_DATE.
OTBI / BIP Report Tips
When building Oracle Transactional Business Intelligence (OTBI) or BI Publisher (BIP) reports:
- Prefer the subject areas (e.g., "Workforce Management - Person Real Time") over direct table queries when possible
- Use PER_ALL_PEOPLE_VL for views that automatically join translation data
- In BIP data models, always include the date-effective filter as a bind parameter
- For headcount reports, use the assignment table's
ASSIGNMENT_STATUS_TYPE = 'ACTIVE'rather than trying to derive it from PER_ALL_PEOPLE_F
Explore PER_ALL_PEOPLE_F Schema
View all 60+ columns with data types, lengths, and nullable flags.
View Full Table Schema