PL/SQL Variables — Hard-Coded VARCHAR2 Overflows Batch
- Variables store temporary data; constants enforce immutability – use the right tool for the job.
- Always use %TYPE for variables that hold column values – it prevents schema-migration-induced failures.
- Uninitialized variables are NULL – explicitly initialize everything to avoid silent NULL propagation.
- PL/SQL Variables: named memory locations that hold values that can change during program execution
- Constants: fixed values declared with CONSTANT — must be initialized and cannot change
- Data Types: define the kind of data (scalar, composite, reference, LOB) and the operations allowed
- %TYPE anchors a variable to a column's data type — schema changes propagate automatically
- %ROWTYPE holds an entire row — reduces code maintenance when table structure changes
- Production trap: hard-coded VARCHAR2(20) breaks when the column expands to VARCHAR2(50) — ORA-06502
Quick Debug Cheat Sheet: PL/SQL Variable and Data Type Errors
ORA-06502 on variable assignment
SET SERVEROUTPUT ON;
EXEC DBMS_OUTPUT.PUT_LINE('Value: ' || v_some_variable);SELECT data_length FROM all_tab_columns WHERE table_name = 'SUPPLIERS' AND column_name = 'SUPPLIER_NAME';NULL result from arithmetic with uninitialized variable
SELECT DUMP(v_counter) FROM DUAL; -- shows NULL if uninitializedCheck package body for package-level variable declarations.PLS-00302: variable name ambiguous with column
SHOW ERRORS; -- displays exact line and columnSELECT text FROM user_source WHERE name = 'PROCEDURE_NAME' AND line = [error_line];Production Incident
Production Debug GuideQuick symptom-to-action reference for common variable-related failures
PL/SQL Variables, Constants and Data Types is a fundamental concept in Database development. In PL/SQL, every piece of information must be held in a named storage location with a specific format. By mastering these building blocks, you can create programs that are not only functional but also highly maintainable and less prone to data-related errors.
In this guide we'll break down exactly what PL/SQL Variables, Constants and Data Types is, why it was designed this way to ensure strict data integrity within the Oracle engine, and how to use it correctly in real projects.
By the end you'll have both the conceptual understanding and practical code examples to use PL/SQL Variables, Constants and Data Types with confidence.
What Is PL/SQL Variables, Constants and Data Types and Why Does It Exist?
PL/SQL Variables, Constants and Data Types is a core feature of PL/SQL. It was designed to solve the problem of temporary data storage during procedural execution. Unlike standard SQL, which processes sets of data, PL/SQL needs a way to hold individual values, calculation results, and database records in memory. Variables allow for dynamic changes, while Constants protect values that must remain fixed (like a tax rate or API version). Data types (Scalar, Large Object, Composite, and Reference) exist to tell the compiler how much memory to allocate and what operations are valid on that data.
This may seem obvious, but the real power lies in how PL/SQL enforces data integrity. A variable's type determines what you can do with it – you cannot accidentally store a date where a number is expected. The compiler catches these mismatches at compile time, not at 3 AM in production.
-- io.thecodeforge: Standard Variable and Constant Declaration DECLARE -- Scalar Variable v_employee_name VARCHAR2(100) := 'TheCodeForge Developer'; -- Constant: Must be initialized and cannot be changed gc_tax_rate CONSTANT NUMBER(3,2) := 0.05; -- Anchored Type: Dynamically matches the column type in the DB v_project_id forge_projects.project_id%TYPE; -- Rowtype: Holds an entire row from a table v_project_rec forge_projects%ROWTYPE; BEGIN v_project_id := 101; -- Selective Assignment SELECT * INTO v_project_rec FROM forge_projects WHERE project_id = v_project_id; DBMS_OUTPUT.PUT_LINE('Processing: ' || v_project_rec.project_name); END;
Common Mistakes and How to Avoid Them
When learning PL/SQL Variables, Constants and Data Types, most developers hit the same set of gotchas. A frequent mistake is hard-coding variable lengths (e.g., VARCHAR2(20)) instead of using %TYPE. If the database column later expands to 50 characters, your code will throw a 'Value Error'. Another common error is failing to initialize variables, leading to unexpected NULL logic in calculations.
Knowing these in advance saves hours of debugging production 'Buffer Overflow' and 'Constraint Violation' errors.
But there's a deeper one: using package-level global variables when local variables would suffice. These globals persist across calls within the same session, and if your code relies on their previous values, you're in for a debugging nightmare. A change in one procedure can affect another unintentionally.
-- io.thecodeforge: Avoiding hard-coded data types -- WRONG: Risk of ORA-06502 if the DB column expands -- v_name VARCHAR2(10); -- CORRECT: Using %TYPE to stay synchronized DECLARE v_forge_user_name forge_users.username%TYPE; v_current_total NUMBER(10,2) := 0.00; -- Initialize to avoid NULL arithmetic BEGIN SELECT username INTO v_forge_user_name FROM forge_users WHERE user_id = 1; v_current_total := v_current_total + 150.50; END;
Variables vs Constants – When to Use Each
Choosing between a variable and a constant is straightforward: use a constant when the value must never change. That sounds obvious, but developers often use variables for values that are logically fixed – like a maximum retry count, a cutoff date, or a conversion factor.
Constants improve readability and prevent accidental modification. They also help the compiler optimise memory usage. In PL/SQL, a constant must be declared with the CONSTANT keyword and initialized immediately. After that, any attempt to assign a new value raises a compilation error. That's a good thing – it's a contract your code makes with itself.
But there's a nuance: a constant can hold a value fetched from the database at declaration time, but that value becomes fixed for the duration of the block. If the underlying data changes, the constant still holds the old value. Use constants for truly static values, not for session-level configuration you might want to refresh.
-- io.thecodeforge: Proper use of constants vs variables DECLARE -- Constant: tax rate never changes mid-calculation gc_tax_rate CONSTANT NUMBER(3,2) := 0.07; -- Variable: running total changes v_gross_amount NUMBER(10,2) := 0.00; v_net_amount NUMBER(10,2) := 0.00; BEGIN v_gross_amount := 1150.00; v_net_amount := v_gross_amount * (1 - gc_tax_rate); DBMS_OUTPUT.PUT_LINE('Net: ' || v_net_amount); -- This would fail to compile: gc_tax_rate := 0.06; END;
Anchored Types: %TYPE and %ROWTYPE for Schema Resilience
Anchored types are PL/SQL's solution to the schema coupling problem. When you declare a variable with %TYPE, you tell the compiler: 'Find the data type of this column and use that.' If the column later changes, your code automatically adapts – no manual edits needed.
%ROWTYPE goes further: it declares a variable that holds an entire row. You can SELECT INTO a %ROWTYPE variable and access each column by name. This eliminates the need to list all columns individually. When you add a column to the table, your %ROWTYPE variables include it automatically (though you may need to adjust SELECT * to include the new column).
The performance cost is negligible – the compiler resolves the type at compile time, not runtime. The real cost saved is maintenance hours. A 200-line procedure that uses %ROWTYPE instead of 20 individual variable declarations is vastly easier to maintain and less error-prone.
But there's a catch: %ROWTYPE cannot be used for columns from joins – it only maps to a single table or cursor. For joins, you need to define a custom record type or use individual variables.
-- io.thecodeforge: Using %TYPE and %ROWTYPE for resilience DECLARE -- %TYPE: single column v_order_total forge_orders.total_amount%TYPE; -- %ROWTYPE: entire row v_customer_rec forge_customers%ROWTYPE; BEGIN SELECT total_amount INTO v_order_total FROM forge_orders WHERE order_id = 1001; SELECT * INTO v_customer_rec FROM forge_customers WHERE customer_id = 202; DBMS_OUTPUT.PUT_LINE('Customer: ' || v_customer_rec.name || ' - Order Total: ' || v_order_total); END;
- %TYPE is a compile-time placeholder that resolves to the column's actual data type.
- If the column changes from VARCHAR2(30) to VARCHAR2(60), your variable changes too – no code change.
- %ROWTYPE bundles all columns into a single record – like a struct that auto-updates.
- Use %ROWTYPE for SELECT * INTO statements; never use individual variables for every column.
Data Type Categories and Their Memory Implications
PL/SQL data types fall into four categories: Scalar, Composite, Reference, and LOB (Large Object). Each has distinct memory and performance characteristics.
Scalar types (NUMBER, VARCHAR2, DATE, BOOLEAN) are the simplest – they hold a single value. A VARCHAR2 variable can hold up to 32,767 bytes in PL/SQL, but only 4,000 bytes in SQL. That's a common tripwire: you can build a large string in PL/SQL, but passing it to a SQL context truncates it silently. Use CLOB for larger text.
Composite types (records, collections) consume memory proportional to their elements. A collection of 100,000 NUMBER items can use hundreds of kilobytes. If you declare such a collection at the package level, it persists for the session and can lead to PGA memory exhaustion under high concurrency.
LOB types (CLOB, BLOB) are stored outside the PGA, with a locator in the variable. They're efficient for large data but have slower access than scalars. Reference types (REF CURSOR, REF) hold pointers to data – useful for passing result sets between programs.
Knowing these memory implications helps you avoid performance surprises. A naive variable choice can turn a fast procedure into a memory hog.
-- io.thecodeforge: Understanding data type memory DECLARE -- Scalar: small, fast v_small VARCHAR2(100) := 'Hi'; -- Composite: collection can be large TYPE num_table IS TABLE OF NUMBER INDEX BY PLS_INTEGER; v_big num_table; -- LOB: large data, uses locator v_clob CLOB := 'This is large text...'; -- Reference: pointer to cursor v_cursor SYS_REFCURSOR; BEGIN -- Fill collection with 10000 numbers FOR i IN 1..10000 LOOP v_big(i) := i; END LOOP; DBMS_OUTPUT.PUT_LINE('Count: ' || v_big.COUNT); -- Open ref cursor OPEN v_cursor FOR 'SELECT * FROM dual'; END;
Scope, Lifetime, and Variable Visibility
A variable's scope determines where it can be accessed; its lifetime determines how long it exists. In PL/SQL, these depend on where you declare the variable.
Local variables inside a BEGIN-END block exist only during that block's execution. They are created when the block starts and destroyed when it ends. This is the safest scope – no unintended side effects.
Package-level variables declared in a package specification are visible to any caller. They live for the duration of the user session. If you modify a package variable in one procedure, any subsequent call to another procedure in the same session sees the modified value. This is both a feature (for session context like user ID) and a danger (for accidental state corruption).
Package body variables (declared in the body but not in the spec) are private – only procedures within the package can see them. That's a good middle ground: session-persistent but encapsulated.
The golden rule: declare variables at the smallest possible scope. Start with local. If you need to share state, use package body variables. Use package spec variables only when external callers need to read or set them.
-- io.thecodeforge: Scope and lifetime examples CREATE OR REPLACE PACKAGE forge_session_ctx IS -- Package spec variable: visible to all callers, session lifetime g_user_id NUMBER; g_user_role VARCHAR2(30); END forge_session_ctx; / CREATE OR REPLACE PACKAGE BODY forge_session_ctx IS -- Private package body variable: only visible inside this package g_last_access TIMESTAMP; PROCEDURE set_context(p_user_id NUMBER, p_role VARCHAR2) IS -- Local variable: only visible within this procedure v_local_counter NUMBER := 0; BEGIN g_user_id := p_user_id; g_user_role := p_role; g_last_access := SYSTIMESTAMP; v_local_counter := v_local_counter + 1; END; END forge_session_ctx;
| Feature | Variable | Constant | Anchored Type (%TYPE) |
|---|---|---|---|
| Value Modification | Can be updated anytime | Immutable once declared | Depends on usage |
| Initialization | Optional (Defaults to NULL) | Mandatory | Optional |
| Maintenance | Manual updates required | Single point of change | Automatic via Schema |
| Scope | Local or Global | Local or Global | Linked to DB Object |
| Best Use Case | Counters, flags, temp data | Configuration, Pi, Tax rates | Database data retrieval |
🎯 Key Takeaways
- Variables store temporary data; constants enforce immutability – use the right tool for the job.
- Always use %TYPE for variables that hold column values – it prevents schema-migration-induced failures.
- Uninitialized variables are NULL – explicitly initialize everything to avoid silent NULL propagation.
- Package-level variables persist across calls – use them intentionally and clear session context between requests.
- PL/SQL VARCHAR2 max (32767) != SQL VARCHAR2 max (4000) – mind the difference when mixing contexts.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QWhat is the difference between %TYPE and %ROWTYPE?JuniorReveal
- QWhy should you use a CONSTANT instead of a regular variable for fixed values?JuniorReveal
- QHow does PL/SQL handle uninitialized variables by default?JuniorReveal
- QWhat are the benefits and trade-offs of using anchored data types (%TYPE, %ROWTYPE)?Mid-levelReveal
- QWhat is the maximum size of a VARCHAR2 variable in PL/SQL and how does it differ from SQL?SeniorReveal
Frequently Asked Questions
Is it mandatory to initialize a constant in PL/SQL?
Yes. A constant must be initialized at declaration. You cannot assign a value later. Example: gc_rate CONSTANT NUMBER := 0.05; is required.
Can I change a variable declared with %TYPE after declaration?
Yes, %TYPE affects only the data type, not the mutability. You can assign new values to the variable as long as they are compatible with the anchored type.
What happens if I try to assign a value larger than the variable's size?
You get ORA-06502 (PL/SQL numeric or value error). The runtime raises this exception. It can be caught with an exception handler, but it's better to avoid it by using %TYPE or ensuring sizes are adequate.
How do I declare a variable that holds a row from a join?
You cannot use %ROWTYPE for joins directly. Instead, create a custom record type: TYPE join_rec IS RECORD (col1 table1.col1%TYPE, col2 table2.col2%TYPE); v_rec join_rec; Then SELECT into v_rec.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.