Loading learning content...
When Charles Bachman delivered his 1973 Turing Award lecture, he titled it "The Programmer as Navigator." This phrase captured the essential nature of network database programming: the programmer doesn't simply describe what data they want—they explicitly navigate to it, step by step, through the network of records and sets.
This paradigm stands in stark contrast to modern declarative query languages like SQL. In SQL, you specify what you want ("find all employees in department D100 with salary > 50000") and the database optimizer decides how to retrieve it. In network database programming, the programmer specifies how: "Find department D100, then traverse to its first employee, check the salary, process if matching, move to the next employee, repeat until finished."
Understanding navigation isn't merely historical curiosity. The navigational paradigm represents a fundamental choice in database interface design that resurfaces in modern technologies: graph database traversal languages, ORM lazy-loading patterns, and cursor-based iteration all echo navigational principles.
By the end of this page, you will understand: (1) The currency indicator system that tracks position during navigation, (2) FIND operations for locating records without retrieval, (3) GET operations for retrieving record data, (4) Navigation patterns for complex queries across multiple sets, (5) The advantages and disadvantages of navigational access, (6) How modern systems echo navigational concepts.
The foundation of navigational programming is the currency indicator system. The DBMS maintains multiple "current record" pointers that track where the program is positioned within the database structure. These currencies are automatically updated by navigation operations and can be explicitly saved or restored.
Types of Currency Indicators:
Current of Run-Unit: The single most recently accessed record of any type. Every successful FIND or GET updates this.
Current of Record Type: For each record type in the subschema, the most recently accessed record of that type. Accessing an EMPLOYEE updates current of EMPLOYEE but not current of DEPARTMENT.
Current of Set Type: For each set type in the subschema, the most recently accessed record (owner or member) within that set context. This enables resuming navigation within a set.
Current of Area (Realm): For each database area (a physical grouping of records), the most recently accessed record in that area.
1234567891011121314151617181920212223242526272829303132
/* Currency indicator behavior during navigation */ Database State:DEPARTMENT "Engineering" owns: EMPLOYEE "Alice", "Bob", "Charlie"DEPARTMENT "Marketing" owns: EMPLOYEE "Diana", "Eve" Program Operation Currency Status After───────────────── ───────────────────── FIND ANY DEPARTMENT USING Current of Run-Unit: Engineering DeptCode = "ENG". Current of DEPARTMENT: Engineering Current of DEPT_EMPLOYEE: Engineering Current of EMPLOYEE: (unchanged) FIND FIRST EMPLOYEE WITHIN Current of Run-Unit: Alice DEPT_EMPLOYEE. Current of DEPARTMENT: Engineering Current of DEPT_EMPLOYEE: Alice Current of EMPLOYEE: Alice FIND NEXT EMPLOYEE WITHIN Current of Run-Unit: Bob DEPT_EMPLOYEE. Current of DEPARTMENT: Engineering Current of DEPT_EMPLOYEE: Bob Current of EMPLOYEE: Bob FIND OWNER WITHIN DEPT_EMPLOYEE. Current of Run-Unit: Engineering Current of DEPARTMENT: Engineering Current of DEPT_EMPLOYEE: Engineering Current of EMPLOYEE: Bob (unchanged!) /* NOTE: FIND OWNER changed current of set to the owner, but did NOT change current of EMPLOYEE - that still points to Bob. This allows returning to where we were. */Currency Preservation and Restoration:
Experienced network programmers leverage currency behavior carefully. Key patterns:
Resume Navigation: After navigating to an owner, you can continue with FIND NEXT using current of record type (which wasn't changed).
Save/Restore Currency: Some systems allow explicit currency saving for later restoration:
OBTAIN CURRENCY STATUS FOR EMPLOYEE INTO saved-key.
... navigate elsewhere ...
FIND EMPLOYEE USING saved-key.
Suppressing Currency Updates: Some operations can be performed without updating currency, preserving position for subsequent operations.
Currency management is both powerful and error-prone. Failing to understand which currencies a FIND operation updates can lead to subtle bugs—navigating to unexpected records, losing position in a set, or infinite loops. Debugging currency issues requires understanding the complete state of all currency indicators at each program point.
FIND operations locate records in the database without retrieving their data into the user work area (UWA). They update currency indicators, positioning for subsequent operations. Finding is cheap; fetching data is an additional step.
Categories of FIND Operations:
| FIND Type | Syntax Pattern | Description |
|---|---|---|
| Direct Access | FIND ANY rec USING key | Hash lookup via CALC key—O(1) |
| Set Sequential | FIND FIRST/NEXT/PRIOR/LAST rec WITHIN set | Traverse set occurrence members |
| Owner Access | FIND OWNER WITHIN set | Navigate from member to owner |
| Duplicate Key | FIND DUPLICATE rec USING key | Find next record with same key |
| Current | FIND CURRENT rec | Re-establish currency to current record |
| DB-Key Access | FIND rec USING db-key | Direct access via database address |
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
/* ================================================================ FIND ANY - Direct Access via CALC Key ================================================================ */ -- O(1) hash-based lookupFIND ANY CUSTOMER USING CustomerID = "C1001".IF DB-STATUS = "0000000" -- Customer found, is now currentELSE IF DB-STATUS = "0502100" -- Record not foundEND-IF. /* ================================================================ FIND FIRST/NEXT/PRIOR/LAST - Set Traversal ================================================================ */ -- Find first member in set occurrenceFIND FIRST ORDER WITHIN CUSTOMER_ORDERS.IF DB-STATUS NOT = "0502100" -- End of set indicator PERFORM UNTIL DB-STATUS = "0502100" GET ORDER. -- process order FIND NEXT ORDER WITHIN CUSTOMER_ORDERS. END-PERFORM.END-IF. -- Find last member (for reverse traversal)FIND LAST ORDER WITHIN CUSTOMER_ORDERS.PERFORM UNTIL DB-STATUS = "0502100" GET ORDER. FIND PRIOR ORDER WITHIN CUSTOMER_ORDERS.END-PERFORM. /* ================================================================ FIND OWNER - Navigate Up to Parent ================================================================ */ -- From an order, find its customerFIND ANY ORDER USING OrderID = "ORD-5001".FIND OWNER WITHIN CUSTOMER_ORDERS.GET CUSTOMER.DISPLAY CustomerName. /* ================================================================ FIND DUPLICATE - Records with Same Key ================================================================ */ -- Find all customers in "New York" (assuming City is non-unique CALC key)MOVE "New York" TO City IN CUSTOMER.FIND ANY CUSTOMER USING City.PERFORM UNTIL DB-STATUS = "0502100" GET CUSTOMER. DISPLAY CustomerName. FIND DUPLICATE CUSTOMER USING City.END-PERFORM. /* ================================================================ FIND WITHIN SET USING - Keyed Access Within Set ================================================================ */ -- Find specific order by OrderDate within customer's ordersFIND ANY CUSTOMER USING CustomerID = "C1001".FIND ORDER WITHIN CUSTOMER_ORDERS USING OrderDate = "2024-01-15".-- Finds order with that date in C1001's set (if exists)Error Handling for FIND:
Every FIND operation sets a DB-STATUS code indicating success or the nature of failure:
| Status Code | Meaning |
|---|---|
| 0000000 | Success |
| 0502100 | End of set (no more members) |
| 0502400 | Record not found |
| 0502500 | Current of set undefined |
| 0504100 | Area (realm) not open |
Robust programs check DB-STATUS after every FIND operation. Ignoring status can lead to processing stale currency or missing data entirely.
The OBTAIN verb combines FIND and GET in one operation: 'OBTAIN NEXT EMPLOYEE WITHIN DEPT_EMPLOYEE' both locates and retrieves. Use OBTAIN when you always need the data, but use separate FIND/GET when you might only conditionally need the data (e.g., find, check a condition via GET of specific field, then GET remaining fields only if needed).
GET operations retrieve data from the current record into the User Work Area (UWA)—a memory structure in the application program corresponding to the record layout. FIND locates; GET retrieves.
GET Syntax Variations:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
/* ================================================================ GET - Retrieve Entire Record ================================================================ */ FIND ANY EMPLOYEE USING EmployeeID = "E1001".GET EMPLOYEE.-- All data items from EMPLOYEE now in UWADISPLAY EmployeeID IN EMPLOYEE.DISPLAY Name IN EMPLOYEE.DISPLAY Salary IN EMPLOYEE.DISPLAY HireDate IN EMPLOYEE. /* ================================================================ GET - Retrieve Specific Data Items ================================================================ */ FIND ANY EMPLOYEE USING EmployeeID = "E1001".GET EMPLOYEE; Name; Salary.-- Only Name and Salary retrieved-- EmployeeID and HireDate contain previous/garbage values /* Efficiency: If you only need Name, don't GET the entire record. Retrieving specific items can save I/O, especially for records with large text fields or many items. */ /* ================================================================ GET Without Prior FIND (using currency) ================================================================ */ -- If currency is established, GET uses current recordFIND FIRST EMPLOYEE WITHIN DEPT_EMPLOYEE.GET EMPLOYEE. -- Gets the first employee FIND NEXT EMPLOYEE WITHIN DEPT_EMPLOYEE.GET. -- Gets current of run-unit (implicit record type) /* ================================================================ UWA (User Work Area) Concept ================================================================ */ -- The subschema DDL generates UWA record layouts: 01 EMPLOYEE-RECORD. 02 EmployeeID PIC 9(8). 02 Name PIC X(50). 02 Salary PIC 9(10)V99. 02 HireDate PIC X(10). 02 DeptCode PIC X(10). -- GET populates these COBOL data areas-- Program operates on UWA data, not directly on databaseRecord Locking and GET:
Most CODASYL implementations integrate record locking with GET operations:
This automatic locking prevents lost updates but can cause contention. Programs should minimize the duration between GET FOR UPDATE and MODIFY.
The FIND/GET separation originated partly from performance optimization. FIND traverses pointer chains (often in memory or cached). GET triggers actual data retrieval from disk. Separating them allows positioning through many records while only fetching data for those that match criteria—an early form of 'lazy loading.'
Complex queries in network databases are constructed by combining navigation primitives. Let's examine common patterns that emerge in real-world programming.
Pattern 1: Simple Set Traversal
Process all members of a set occurrence—the most basic pattern:
1234567891011121314
/* Pattern 1: Process all employees in a department */ FIND ANY DEPARTMENT USING DeptCode = "ENG".FIND FIRST EMPLOYEE WITHIN DEPT_EMPLOYEE.PERFORM UNTIL DB-STATUS = "0502100" -- End of set GET EMPLOYEE. DISPLAY Name IN EMPLOYEE, Salary IN EMPLOYEE. ADD 1 TO employee-count. FIND NEXT EMPLOYEE WITHIN DEPT_EMPLOYEE.END-PERFORM.DISPLAY "Total employees: " employee-count. /* Time Complexity: O(n) where n = number of employees in department Each FIND NEXT follows one pointer - very efficient */Pattern 2: Owner-to-Multiple-Sets
Navigate from an owner to members across multiple set types:
123456789101112131415161718192021222324
/* Pattern 2: List all projects and assignments for a department */ FIND ANY DEPARTMENT USING DeptCode = "ENG".GET DEPARTMENT.DISPLAY "Department: " DeptName IN DEPARTMENT. -- First, traverse DEPT_PROJECT setDISPLAY "Projects:".FIND FIRST PROJECT WITHIN DEPT_PROJECT.PERFORM UNTIL DB-STATUS = "0502100" GET PROJECT. DISPLAY " - " ProjectName IN PROJECT. FIND NEXT PROJECT WITHIN DEPT_PROJECT.END-PERFORM. -- Then, traverse DEPT_EMPLOYEE set -- Note: DEPT_PROJECT currency is lost, but that's okayDISPLAY "Employees:".FIND FIRST EMPLOYEE WITHIN DEPT_EMPLOYEE.PERFORM UNTIL DB-STATUS = "0502100" GET EMPLOYEE. DISPLAY " - " Name IN EMPLOYEE. FIND NEXT EMPLOYEE WITHIN DEPT_EMPLOYEE.END-PERFORM.Pattern 3: Member-to-Owner Navigation
Navigate upward from a member to its owner:
123456789101112
/* Pattern 3: Find which department an employee belongs to */ FIND ANY EMPLOYEE USING EmployeeID = "E1001".GET EMPLOYEE.DISPLAY "Employee: " Name IN EMPLOYEE. FIND OWNER WITHIN DEPT_EMPLOYEE.GET DEPARTMENT.DISPLAY "Department: " DeptName IN DEPARTMENT. /* This is O(1) - follows single owner pointer No search required - member stores pointer to owner */Pattern 4: Cross-Set Navigation (Join Equivalent)
Navigate through an intermediate record to connect two entities—this is how many-to-many relationships are traversed:
123456789101112131415161718192021222324252627282930313233343536
/* Pattern 4: List all suppliers for a specific part Schema: SUPPLIER --[SUPPLIER_SUPPLY]--> SUPPLY <--[PART_SUPPLY]-- PART SUPPLY is the intersection record for M:N relationship*/ FIND ANY PART USING PartNumber = "P001".GET PART.DISPLAY "Part: " Description IN PART.DISPLAY "Suppliers:". -- Navigate to all SUPPLY records for this partFIND FIRST SUPPLY WITHIN PART_SUPPLY.PERFORM UNTIL DB-STATUS = "0502100" GET SUPPLY. -- From each SUPPLY, navigate to its SUPPLIER owner FIND OWNER WITHIN SUPPLIER_SUPPLY. GET SUPPLIER. DISPLAY " - " SupplierName IN SUPPLIER, " Price: $" UnitPrice IN SUPPLY. -- Return to PART_SUPPLY set and continue -- (PART_SUPPLY currency was preserved!) FIND NEXT SUPPLY WITHIN PART_SUPPLY.END-PERFORM. /* This implements what in SQL would be: SELECT S.SupplierName, SU.UnitPrice FROM Part P JOIN Supply SU ON P.PartNumber = SU.PartNumber JOIN Supplier S ON SU.SupplierID = S.SupplierID WHERE P.PartNumber = 'P001' Complexity: O(n) where n = number of suppliers for the part Each SUPPLY has O(1) access to its SUPPLIER owner */In Pattern 4, the critical insight is that navigating to SUPPLIER owner doesn't lose the PART_SUPPLY currency. The 'FIND NEXT SUPPLY WITHIN PART_SUPPLY' continues from where we left off. Understanding which operations affect which currencies is essential for correct navigation.
Pattern 5: Nested Set Traversal
Process hierarchical data by nesting traversals:
1234567891011121314151617181920212223242526272829
/* Pattern 5: List all departments, their projects, and project tasks */ FIND FIRST DEPARTMENT WITHIN ALL_DEPARTMENTS. -- System-owned setPERFORM UNTIL DB-STATUS = "0502100" GET DEPARTMENT. DISPLAY "Department: " DeptName IN DEPARTMENT. -- Nested: all projects in this department FIND FIRST PROJECT WITHIN DEPT_PROJECT. PERFORM UNTIL DB-STATUS = "0502100" GET PROJECT. DISPLAY " Project: " ProjectName IN PROJECT. -- Double-nested: all tasks in this project FIND FIRST TASK WITHIN PROJECT_TASK. PERFORM UNTIL DB-STATUS = "0502100" GET TASK. DISPLAY " Task: " TaskDescription IN TASK. FIND NEXT TASK WITHIN PROJECT_TASK. END-PERFORM. FIND NEXT PROJECT WITHIN DEPT_PROJECT. END-PERFORM. FIND NEXT DEPARTMENT WITHIN ALL_DEPARTMENTS.END-PERFORM. /* Note: Each inner loop completes before outer loop continues. Currency for outer set types is preserved. */Pattern 6: Conditional Navigation
Use record data to decide navigation paths:
1234567891011121314151617181920212223242526272829303132
/* Pattern 6: Find high-value orders and their customer details */ FIND FIRST ORDER WITHIN ALL_ORDERS. -- System-owned setPERFORM UNTIL DB-STATUS = "0502100" GET ORDER; TotalAmount; Status. -- Conditionally navigate based on data values IF TotalAmount > 10000 AND Status = "COMPLETED" GET ORDER. -- Get remaining fields DISPLAY "High-value order: " OrderID IN ORDER. -- Navigate to customer FIND OWNER WITHIN CUSTOMER_ORDER. GET CUSTOMER. DISPLAY " Customer: " CustomerName IN CUSTOMER. -- Navigate to order items FIND FIRST ORDERITEM WITHIN ORDER_ITEMS. PERFORM UNTIL DB-STATUS = "0502100" GET ORDERITEM. DISPLAY " Item: " ProductName IN ORDERITEM. FIND NEXT ORDERITEM WITHIN ORDER_ITEMS. END-PERFORM. END-IF. -- Continue to next order (ORDER currency preserved) FIND NEXT ORDER WITHIN ALL_ORDERS.END-PERFORM. /* Optimization: The initial GET retrieves only TotalAmount and Status. Full record retrieval only happens for qualifying orders. This minimizes I/O for non-matching records. */Pattern 7: Bidirectional Traversal
Navigate both directions within a set:
123456789101112131415161718192021222324252627282930313233
/* Pattern 7: Find an order and display surrounding orders */ FIND ANY ORDER USING OrderID = "ORD-5000".GET ORDER.DISPLAY "Current order: " OrderID IN ORDER. -- Save current positionOBTAIN CURRENCY STATUS FOR ORDER INTO saved-order-key. -- Navigate backwardDISPLAY "Previous orders:".FIND PRIOR ORDER WITHIN CUSTOMER_ORDER.PERFORM 3 TIMES -- Show up to 3 previous IF DB-STATUS NOT = "0502100" GET ORDER. DISPLAY " " OrderID IN ORDER, " " OrderDate IN ORDER. FIND PRIOR ORDER WITHIN CUSTOMER_ORDER. END-IF.END-PERFORM. -- Restore position and navigate forwardFIND ORDER USING saved-order-key.DISPLAY "Following orders:".FIND NEXT ORDER WITHIN CUSTOMER_ORDER.PERFORM 3 TIMES -- Show up to 3 following IF DB-STATUS NOT = "0502100" GET ORDER. DISPLAY " " OrderID IN ORDER, " " OrderDate IN ORDER. FIND NEXT ORDER WITHIN CUSTOMER_ORDER. END-IF.END-PERFORM. /* Use case: Pagination, context display, windowed views */The navigational paradigm of CODASYL and the declarative paradigm of SQL represent fundamentally different philosophies. Understanding this contrast illuminates why relational databases ultimately dominated.
The Core Difference:
Let's compare equivalent queries:
123456789101112131415161718
-- CODASYL: Find engineers earning > 80000 FIND ANY DEPARTMENT USING DeptCode = "ENG".IF DB-STATUS = "0000000" FIND FIRST EMPLOYEE WITHIN DEPT_EMPLOYEE. PERFORM UNTIL DB-STATUS = "0502100" GET EMPLOYEE. IF Salary > 80000 DISPLAY Name, Salary. END-IF. FIND NEXT EMPLOYEE WITHIN DEPT_EMPLOYEE. END-PERFORM.END-IF. -- ~15 lines of explicit navigation1234567891011121314
-- SQL: Same query SELECT e.Name, e.SalaryFROM Employee eJOIN Department d ON e.DeptCode = d.DeptCodeWHERE d.DeptCode = 'ENG' AND e.Salary > 80000; -- 6 lines declaring what we want-- Optimizer decides how to execute| Aspect | Navigational | Declarative |
|---|---|---|
| Code volume | More verbose | Concise |
| Schema dependency | Tight coupling—programs mirror structure | Loose coupling—optimizer adapts |
| Performance control | Programmer has full control | Optimizer decides (sometimes suboptimal) |
| Performance predictability | Highly predictable | Can vary with statistics/version |
| Learning curve | Steeper—must understand structure | Lower—specify what, not how |
| Ad-hoc queries | Requires program | Interactive SQL console |
| Schema changes | Program rewrites often needed | Programs often unaffected |
| Optimization effort | Programmer responsibility | Automated by optimizer |
The relational model's declarative approach won primarily because of programmer productivity. Writing and maintaining navigational code requires deep schema knowledge and careful currency management. SQL's abstraction allowed faster development, easier maintenance, and ad-hoc querying without programming. As relational query optimizers improved, the performance gap narrowed, eliminating navigation's main advantage.
Although pure CODASYL-style navigation is rare today, the paradigm resurfaces in modern technologies:
1. Graph Database Traversals:
Graph databases like Neo4j use navigational patterns in their query languages:
123456789101112131415161718
// Neo4j Cypher: Traverse relationships explicitly MATCH (dept:Department {code: 'ENG'}) -[:EMPLOYS]-> (emp:Employee)WHERE emp.salary > 80000RETURN emp.name, emp.salary // The path pattern -[:EMPLOYS]-> is navigational!// You're specifying HOW to traverse the graph // More complex traversal:MATCH path = (start:Person)-[:KNOWS*1..6]->(end:Person)WHERE start.name = 'Alice' AND end.name = 'Bob'RETURN length(path), nodes(path) // This navigates through up to 6 KNOWS relationships// Pure navigational thinking in modern syntax2. ORM Lazy Loading:
Object-Relational Mappers implement navigational access through lazy-loaded relationships:
12345678910111213141516
# Python SQLAlchemy: ORM Navigation # Fetch departmentdept = session.query(Department).filter_by(code='ENG').first() # Navigate to employees - lazy load triggers queryfor emp in dept.employees: # This is navigation! if emp.salary > 80000: print(emp.name, emp.salary) # Navigate further - another lazy load for review in emp.performance_reviews: print(f" Review: {review.rating}") # The object traversal mirrors CODASYL navigation# dept → employees → reviews follows set relationships3. Cursor-Based Iteration:
Database cursors provide navigational access within result sets:
1234567891011121314151617181920212223
-- SQL Cursor: Navigate through result set DECLARE emp_cursor CURSOR FOR SELECT EmployeeID, Name, Salary FROM Employee WHERE DeptCode = 'ENG'; OPEN emp_cursor; FETCH NEXT FROM emp_cursor INTO @id, @name, @salary;WHILE @@FETCH_STATUS = 0BEGIN IF @salary > 80000 PRINT @name + ': ' + CAST(@salary AS VARCHAR); FETCH NEXT FROM emp_cursor INTO @id, @name, @salary;END; CLOSE emp_cursor;DEALLOCATE emp_cursor; -- FETCH NEXT is essentially FIND NEXT-- Currency (position) is maintained in the cursorModern systems often combine paradigms. Graph databases use declarative pattern matching with navigational traversal. ORMs provide object navigation backed by declarative SQL generation. The goal is programmer-friendly interfaces with efficient execution—Bachman's navigation with Codd's abstraction.
Navigational programming defined an era of database access, placing the programmer directly in control of data traversal through explicit pointer-following operations.
What's Next:
With a thorough understanding of the network model's structure and navigation, we're prepared to compare it with its predecessor—the hierarchical model. We'll analyze their differences, trade-offs, and the contexts where each approach excels or falls short. This comparison provides perspective on the evolution of data modeling paradigms.
You now understand navigational database programming—the defining characteristic of network model systems. While declarative SQL ultimately prevailed for most applications, navigational concepts persist in graph databases, ORMs, and cursor-based processing. Understanding navigation provides insight into the evolution of data access paradigms. Next, we'll compare network and hierarchical models directly.