Oracle HCM Person vs Assignment vs Work Relationship Data Model Explained

Understanding Oracle Fusion HCM's core data model is fundamental for any HCM consultant, developer, or analyst. The three foundational entities—Person, Assignment, and Work Relationship—form the backbone of how Oracle stores and manages worker information. This comprehensive guide explains the differences, relationships, and practical usage patterns for each entity.

Many HCM professionals struggle with when to query PER_ALL_PEOPLE_F versus PER_ALL_ASSIGNMENTS_F versus PER_WORK_RELATIONSHIPS. Each table serves distinct purposes and contains different types of information, making the choice critical for accurate reporting and data analysis.

Core Data Model Overview

Oracle HCM's worker data model is built on three primary entities that work together to provide a complete view of an individual's employment relationship:

PERSON
Who they are
→
WORK RELATIONSHIP
Employment contract
→
ASSIGNMENT
What they do

This hierarchical relationship reflects real-world employment scenarios:

Person Entity (PER_ALL_PEOPLE_F)

The Person entity represents the individual as a human being, independent of their employment status. It contains biographical information, personal details, and characteristics that remain consistent across employment relationships.

What's Stored in PER_ALL_PEOPLE_F

Data Category Key Columns Description
Identity PERSON_ID, PERSON_NUMBER Unique identifiers for the person
Name Information FIRST_NAME, LAST_NAME, MIDDLE_NAMES Legal and display names
Demographics DATE_OF_BIRTH, GENDER, ETHNICITY Personal characteristics
Documentation NATIONAL_IDENTIFIER, PASSPORT_NUMBER Government identification
Contact EMAIL_ADDRESS, PHONE_NUMBER Communication details
Status CURRENT_EMPLOYEE_FLAG, CURRENT_NPW_FLAG Current employment status

When to Use PER_ALL_PEOPLE_F

Query the Person entity when you need:

Sample Query: Employee Directory

-- Get active employee directory with contact information
SELECT 
    p.PERSON_NUMBER,
    p.FIRST_NAME,
    p.LAST_NAME,
    p.DISPLAY_NAME,
    p.EMAIL_ADDRESS,
    p.PHONE_NUMBER,
    p.DATE_OF_BIRTH,
    p.GENDER
FROM PER_ALL_PEOPLE_F p
WHERE p.CURRENT_EMPLOYEE_FLAG = 'Y'
  AND TRUNC(SYSDATE) BETWEEN p.EFFECTIVE_START_DATE 
                          AND p.EFFECTIVE_END_DATE
ORDER BY p.LAST_NAME, p.FIRST_NAME;

Key Considerations for Person Data

Date-Effective Behavior: PER_ALL_PEOPLE_F is date-effective, meaning person information changes over time create new rows. Always include the date filter unless you specifically need historical data.

Important points about Person entity:

Work Relationship Entity (PER_WORK_RELATIONSHIPS)

The Work Relationship entity represents the legal employment contract between an individual and the organization. It defines the terms and conditions of employment, including start date, employment category, and termination details.

What's Stored in PER_WORK_RELATIONSHIPS

Data Category Key Columns Description
Identity WORK_RELATIONSHIP_ID, PERSON_ID Unique identifiers linking to person
Employment Dates START_DATE, TERMINATION_DATE Contract period
Employment Terms WORKER_TYPE, LEGAL_EMPLOYER_NAME Contract classification
Status PRIMARY_FLAG, SUSPENDED_FLAG Relationship status indicators
Business Context ENTERPRISE_NAME, LEGISLATION_CODE Legal and business entity information

When to Use PER_WORK_RELATIONSHIPS

Query the Work Relationship entity when you need:

Sample Query: Employment History

-- Get employment history with work relationship details
SELECT 
    p.PERSON_NUMBER,
    p.DISPLAY_NAME,
    wr.WORK_RELATIONSHIP_ID,
    wr.START_DATE,
    wr.TERMINATION_DATE,
    wr.WORKER_TYPE,
    wr.LEGAL_EMPLOYER_NAME,
    wr.PRIMARY_FLAG,
    CASE 
        WHEN wr.TERMINATION_DATE IS NULL THEN 'Active'
        ELSE 'Terminated'
    END as EMPLOYMENT_STATUS
FROM PER_ALL_PEOPLE_F p
JOIN PER_WORK_RELATIONSHIPS wr 
  ON p.PERSON_ID = wr.PERSON_ID
WHERE TRUNC(SYSDATE) BETWEEN p.EFFECTIVE_START_DATE 
                          AND p.EFFECTIVE_END_DATE
ORDER BY p.PERSON_NUMBER, wr.START_DATE;

Work Relationship Patterns

Understanding common work relationship patterns is crucial for accurate reporting:

Scenario Person Rows Work Relationship Rows Example
Standard Employee 1 1 Regular full-time employee
Rehire 1 2+ Employee who left and returned
Concurrent Employment 1 2+ Working for multiple legal entities
Contractor to Employee 1 2 Conversion from contractor to FTE

Important: PER_WORK_RELATIONSHIPS is NOT date-effective. Each work relationship is a separate row with distinct start and end dates. This differs from the date-effective pattern used in Person and Assignment tables.

Assignment Entity (PER_ALL_ASSIGNMENTS_F)

The Assignment entity represents the specific job, role, and organizational position within a work relationship. It contains operational details about what the person does, where they work, and how they're positioned in the organization.

What's Stored in PER_ALL_ASSIGNMENTS_F

Data Category Key Columns Description
Identity ASSIGNMENT_ID, ASSIGNMENT_NUMBER Unique assignment identifiers
Job Information JOB_ID, JOB_CODE, POSITION_ID Role and position details
Organization ORGANIZATION_ID, DEPARTMENT_ID Reporting structure
Management MANAGER_ID, SUPERVISOR_ID Reporting relationships
Location LOCATION_ID, WORK_AT_HOME_FLAG Work location details
Employment Terms EMPLOYMENT_CATEGORY, FTE Work arrangement details
Compensation PAYROLL_ID, SALARY_BASIS_ID Pay processing information

When to Use PER_ALL_ASSIGNMENTS_F

Query the Assignment entity when you need:

Sample Query: Current Assignments with Hierarchy

-- Get current assignments with manager and organizational details
SELECT 
    p.PERSON_NUMBER as EMPLOYEE_NUMBER,
    p.DISPLAY_NAME as EMPLOYEE_NAME,
    a.ASSIGNMENT_NUMBER,
    j.JOB_CODE,
    j.NAME as JOB_TITLE,
    d.NAME as DEPARTMENT_NAME,
    l.LOCATION_NAME,
    mgr.DISPLAY_NAME as MANAGER_NAME,
    a.EMPLOYMENT_CATEGORY,
    a.ASSIGNMENT_STATUS_TYPE
FROM PER_ALL_ASSIGNMENTS_F a
JOIN PER_ALL_PEOPLE_F p 
  ON a.PERSON_ID = p.PERSON_ID
  AND TRUNC(SYSDATE) BETWEEN p.EFFECTIVE_START_DATE 
                          AND p.EFFECTIVE_END_DATE
LEFT JOIN PER_JOBS j 
  ON a.JOB_ID = j.JOB_ID
  AND TRUNC(SYSDATE) BETWEEN j.EFFECTIVE_START_DATE 
                          AND j.EFFECTIVE_END_DATE
LEFT JOIN HR_ALL_ORGANIZATION_UNITS d 
  ON a.ORGANIZATION_ID = d.ORGANIZATION_ID
LEFT JOIN HR_LOCATIONS_ALL l 
  ON a.LOCATION_ID = l.LOCATION_ID
LEFT JOIN PER_ALL_PEOPLE_F mgr 
  ON a.MANAGER_ID = mgr.PERSON_ID
  AND TRUNC(SYSDATE) BETWEEN mgr.EFFECTIVE_START_DATE 
                          AND mgr.EFFECTIVE_END_DATE
WHERE a.ASSIGNMENT_STATUS_TYPE = 'ACTIVE'
  AND a.PRIMARY_FLAG = 'Y'
  AND TRUNC(SYSDATE) BETWEEN a.EFFECTIVE_START_DATE 
                          AND a.EFFECTIVE_END_DATE
ORDER BY d.NAME, p.LAST_NAME;

Assignment Types and Patterns

Assignments can have various patterns based on organizational needs:

Assignment Type Description Use Case PRIMARY_FLAG
Primary Assignment Main job responsibility Regular employment Y
Secondary Assignment Additional responsibilities Matrix management, special projects N
Global Assignment International assignment Expatriate assignments Varies
Temporary Assignment Short-term role change Coverage, special projects N

Entity Relationships and Data Integrity

Understanding the relationships between these entities is crucial for accurate data queries and reporting:

Cardinality Rules

Relationship Cardinality Description Business Example
Person → Work Relationship 1 : Many One person can have multiple employment contracts Rehires, concurrent employment
Work Relationship → Assignment 1 : Many One contract can have multiple assignments Primary + secondary assignments
Person → Assignment 1 : Many One person can have multiple assignments across relationships Complex organizational structures

Common Join Patterns

Here are the most frequently used join patterns when working with these entities:

-- Pattern 1: Person + Current Primary Assignment (Most Common)
SELECT p.*, a.*
FROM PER_ALL_PEOPLE_F p
JOIN PER_ALL_ASSIGNMENTS_F a 
  ON p.PERSON_ID = a.PERSON_ID
  AND a.PRIMARY_FLAG = 'Y'
  AND a.ASSIGNMENT_STATUS_TYPE = 'ACTIVE'
WHERE p.CURRENT_EMPLOYEE_FLAG = 'Y'
  AND TRUNC(SYSDATE) BETWEEN p.EFFECTIVE_START_DATE AND p.EFFECTIVE_END_DATE
  AND TRUNC(SYSDATE) BETWEEN a.EFFECTIVE_START_DATE AND a.EFFECTIVE_END_DATE;

-- Pattern 2: Full Three-Entity Join
SELECT p.*, wr.*, a.*
FROM PER_ALL_PEOPLE_F p
JOIN PER_WORK_RELATIONSHIPS wr 
  ON p.PERSON_ID = wr.PERSON_ID
  AND wr.PRIMARY_FLAG = 'Y'
JOIN PER_ALL_ASSIGNMENTS_F a 
  ON wr.WORK_RELATIONSHIP_ID = a.WORK_RELATIONSHIP_ID
  AND a.PRIMARY_FLAG = 'Y'
  AND a.ASSIGNMENT_STATUS_TYPE = 'ACTIVE'
WHERE TRUNC(SYSDATE) BETWEEN p.EFFECTIVE_START_DATE AND p.EFFECTIVE_END_DATE
  AND TRUNC(SYSDATE) BETWEEN a.EFFECTIVE_START_DATE AND a.EFFECTIVE_END_DATE;

-- Pattern 3: Person with All Work Relationships (Historical View)
SELECT p.*, wr.*, 
       CASE WHEN wr.TERMINATION_DATE IS NULL THEN 'Active' ELSE 'Terminated' END
FROM PER_ALL_PEOPLE_F p
JOIN PER_WORK_RELATIONSHIPS wr 
  ON p.PERSON_ID = wr.PERSON_ID
WHERE TRUNC(SYSDATE) BETWEEN p.EFFECTIVE_START_DATE AND p.EFFECTIVE_END_DATE
ORDER BY p.PERSON_NUMBER, wr.START_DATE;

Performance Tip: When joining these large tables, always include appropriate date filters and consider using indexes on PERSON_ID, ASSIGNMENT_STATUS_TYPE, and PRIMARY_FLAG columns.

Practical Usage Scenarios

Let's explore common business scenarios and which entity to use for each:

Scenario 1: Employee Headcount Report

Requirement: Count of active employees by department

Best Entity: Assignment (PER_ALL_ASSIGNMENTS_F)

Rationale: Department is an assignment-level attribute

-- Employee headcount by department
SELECT 
    org.NAME as DEPARTMENT,
    COUNT(*) as EMPLOYEE_COUNT
FROM PER_ALL_ASSIGNMENTS_F a
JOIN HR_ALL_ORGANIZATION_UNITS org 
  ON a.ORGANIZATION_ID = org.ORGANIZATION_ID
WHERE a.ASSIGNMENT_STATUS_TYPE = 'ACTIVE'
  AND a.PRIMARY_FLAG = 'Y'
  AND TRUNC(SYSDATE) BETWEEN a.EFFECTIVE_START_DATE AND a.EFFECTIVE_END_DATE
GROUP BY org.NAME
ORDER BY org.NAME;

Scenario 2: Diversity Demographics

Requirement: Gender and ethnicity breakdown

Best Entity: Person (PER_ALL_PEOPLE_F)

Rationale: Demographics are person-level attributes

-- Gender and ethnicity breakdown for active employees
SELECT 
    p.GENDER,
    p.ETHNICITY,
    COUNT(*) as EMPLOYEE_COUNT
FROM PER_ALL_PEOPLE_F p
WHERE p.CURRENT_EMPLOYEE_FLAG = 'Y'
  AND TRUNC(SYSDATE) BETWEEN p.EFFECTIVE_START_DATE AND p.EFFECTIVE_END_DATE
GROUP BY p.GENDER, p.ETHNICITY
ORDER BY p.GENDER, p.ETHNICITY;

Scenario 3: Turnover Analysis

Requirement: Employees who terminated in the last year

Best Entity: Work Relationship (PER_WORK_RELATIONSHIPS)

Rationale: Termination dates are stored at the work relationship level

-- Turnover analysis - employees terminated in last 12 months
SELECT 
    p.PERSON_NUMBER,
    p.DISPLAY_NAME,
    wr.START_DATE,
    wr.TERMINATION_DATE,
    wr.LEGAL_EMPLOYER_NAME,
    ROUND((wr.TERMINATION_DATE - wr.START_DATE) / 365, 2) as TENURE_YEARS
FROM PER_WORK_RELATIONSHIPS wr
JOIN PER_ALL_PEOPLE_F p 
  ON wr.PERSON_ID = p.PERSON_ID
  AND TRUNC(SYSDATE) BETWEEN p.EFFECTIVE_START_DATE AND p.EFFECTIVE_END_DATE
WHERE wr.TERMINATION_DATE BETWEEN ADD_MONTHS(TRUNC(SYSDATE), -12) AND TRUNC(SYSDATE)
  AND wr.PRIMARY_FLAG = 'Y'
ORDER BY wr.TERMINATION_DATE DESC;

Scenario 4: Manager-Employee Relationships

Requirement: Current reporting structure

Best Entity: Assignment (PER_ALL_ASSIGNMENTS_F)

Rationale: Manager relationships are assignment-specific

-- Current manager-employee relationships
SELECT 
    emp.PERSON_NUMBER as EMPLOYEE_NUMBER,
    emp.DISPLAY_NAME as EMPLOYEE_NAME,
    mgr.PERSON_NUMBER as MANAGER_NUMBER,
    mgr.DISPLAY_NAME as MANAGER_NAME,
    j.NAME as JOB_TITLE,
    org.NAME as DEPARTMENT
FROM PER_ALL_ASSIGNMENTS_F a
JOIN PER_ALL_PEOPLE_F emp 
  ON a.PERSON_ID = emp.PERSON_ID
  AND TRUNC(SYSDATE) BETWEEN emp.EFFECTIVE_START_DATE AND emp.EFFECTIVE_END_DATE
LEFT JOIN PER_ALL_PEOPLE_F mgr 
  ON a.MANAGER_ID = mgr.PERSON_ID
  AND TRUNC(SYSDATE) BETWEEN mgr.EFFECTIVE_START_DATE AND mgr.EFFECTIVE_END_DATE
LEFT JOIN PER_JOBS j 
  ON a.JOB_ID = j.JOB_ID
  AND TRUNC(SYSDATE) BETWEEN j.EFFECTIVE_START_DATE AND j.EFFECTIVE_END_DATE
LEFT JOIN HR_ALL_ORGANIZATION_UNITS org 
  ON a.ORGANIZATION_ID = org.ORGANIZATION_ID
WHERE a.ASSIGNMENT_STATUS_TYPE = 'ACTIVE'
  AND a.PRIMARY_FLAG = 'Y'
  AND TRUNC(SYSDATE) BETWEEN a.EFFECTIVE_START_DATE AND a.EFFECTIVE_END_DATE
ORDER BY org.NAME, mgr.DISPLAY_NAME, emp.DISPLAY_NAME;

Date-Effective vs Non Date-Effective

Understanding the date-effective pattern differences is crucial:

Entity Date-Effective? Date Columns Data Pattern
Person âś… Yes EFFECTIVE_START_DATE, EFFECTIVE_END_DATE Multiple rows for historical changes
Work Relationship ❌ No START_DATE, TERMINATION_DATE One row per employment contract
Assignment âś… Yes EFFECTIVE_START_DATE, EFFECTIVE_END_DATE Multiple rows for historical changes

Critical: Always include date filters for Person and Assignment queries. Work Relationship queries should filter on START_DATE and TERMINATION_DATE based on your specific needs.

Performance Considerations

When working with these large tables, consider these performance optimization strategies:

Indexing Strategy

Oracle HCM provides standard indexes on these key columns:

Query Optimization Tips

-- Good: Filter early and use specific criteria
SELECT p.PERSON_NUMBER, a.ASSIGNMENT_NUMBER
FROM PER_ALL_PEOPLE_F p
JOIN PER_ALL_ASSIGNMENTS_F a 
  ON p.PERSON_ID = a.PERSON_ID
  AND a.PRIMARY_FLAG = 'Y'                    -- Filter in JOIN
  AND a.ASSIGNMENT_STATUS_TYPE = 'ACTIVE'     -- Filter in JOIN
WHERE p.CURRENT_EMPLOYEE_FLAG = 'Y'          -- Filter early
  AND TRUNC(SYSDATE) BETWEEN p.EFFECTIVE_START_DATE AND p.EFFECTIVE_END_DATE
  AND TRUNC(SYSDATE) BETWEEN a.EFFECTIVE_START_DATE AND a.EFFECTIVE_END_DATE;

-- Bad: Filter after joining large result sets
SELECT p.PERSON_NUMBER, a.ASSIGNMENT_NUMBER
FROM PER_ALL_PEOPLE_F p
JOIN PER_ALL_ASSIGNMENTS_F a 
  ON p.PERSON_ID = a.PERSON_ID
WHERE p.CURRENT_EMPLOYEE_FLAG = 'Y'
  AND a.PRIMARY_FLAG = 'Y'                    -- Filter in WHERE (late)
  AND a.ASSIGNMENT_STATUS_TYPE = 'ACTIVE'     -- Filter in WHERE (late)
  AND TRUNC(SYSDATE) BETWEEN p.EFFECTIVE_START_DATE AND p.EFFECTIVE_END_DATE
  AND TRUNC(SYSDATE) BETWEEN a.EFFECTIVE_START_DATE AND a.EFFECTIVE_END_DATE;

Common Data Model Pitfalls

Avoid these common mistakes when working with Oracle HCM's data model:

1. Forgetting Date-Effective Filters

-- Wrong: Missing date filters (returns historical data)
SELECT COUNT(*) FROM PER_ALL_PEOPLE_F WHERE CURRENT_EMPLOYEE_FLAG = 'Y';

-- Correct: Include date filters for current data
SELECT COUNT(*) 
FROM PER_ALL_PEOPLE_F 
WHERE CURRENT_EMPLOYEE_FLAG = 'Y'
  AND TRUNC(SYSDATE) BETWEEN EFFECTIVE_START_DATE AND EFFECTIVE_END_DATE;

2. Mixing Entity Levels

-- Wrong: Trying to get department from Person table
SELECT p.PERSON_NUMBER, p.DEPARTMENT_NAME  -- DEPARTMENT_NAME not in Person!
FROM PER_ALL_PEOPLE_F p;

-- Correct: Get department from Assignment
SELECT p.PERSON_NUMBER, org.NAME as DEPARTMENT_NAME
FROM PER_ALL_PEOPLE_F p
JOIN PER_ALL_ASSIGNMENTS_F a ON p.PERSON_ID = a.PERSON_ID
JOIN HR_ALL_ORGANIZATION_UNITS org ON a.ORGANIZATION_ID = org.ORGANIZATION_ID;

3. Incorrect Primary Assignment Logic

-- Wrong: Not filtering for primary assignments
SELECT p.PERSON_NUMBER, COUNT(a.ASSIGNMENT_ID) as ASSIGNMENT_COUNT
FROM PER_ALL_PEOPLE_F p
JOIN PER_ALL_ASSIGNMENTS_F a ON p.PERSON_ID = a.PERSON_ID
GROUP BY p.PERSON_NUMBER;  -- May return multiple assignments per person

-- Correct: Focus on primary assignments for headcount
SELECT p.PERSON_NUMBER, a.ASSIGNMENT_NUMBER
FROM PER_ALL_PEOPLE_F p
JOIN PER_ALL_ASSIGNMENTS_F a 
  ON p.PERSON_ID = a.PERSON_ID
  AND a.PRIMARY_FLAG = 'Y';

Best Practices Summary

Follow these best practices when working with Oracle HCM's core data model:

Mastering these three core entities is fundamental to successful Oracle HCM development and reporting. Whether you're building REST API integrations, creating OTBI reports, or writing custom SQL queries, understanding the Person-Work Relationship-Assignment model will make your work more accurate and efficient.

Remember that this data model reflects real-world employment complexity—people can be rehired, hold multiple positions, and change roles over time. The three-entity model provides the flexibility to handle these scenarios while maintaining data integrity and auditability.