Back to Stored Procedures, Views & Triggers
Q2Question 2 of 6

(Scenario) Stored Procedure: Two IN Params, Fetch-Into-Variable, Update, Return Status

Key Concepts
  • The procedure takes two IN parameters and one OUT or INOUT status parameter.
  • A local variable holds the value fetched with SELECT ... INTO from the source table.
  • The two IN parameters appear in the WHERE clause to filter the source row.
  • The fetched variable is used in the SET clause of an UPDATE on the target table.
  • The entire read-then-update runs inside a transaction so the operation is atomic.
  • Error handling catches no-row and too-many-rows conditions and sets a precise failure status.
  • After the UPDATE, the affected row count is checked to detect an update that matched nothing.

Definition

Take two inputs, SELECT ... INTO a local variable from a source table, use it to UPDATE a target table inside a transaction with error handling, and return a success status.
  • Two IN parameters – used in the WHERE clause to locate the source row (e.g., account number and product code).
  • SELECT ... INTO – fetches a single column from the source table into a local variable; syntax varies by engine.
  • Local variable – holds the fetched value (e.g., v_rate) for use in the subsequent UPDATE.
  • UPDATE with variable – applies the fetched value to the target table's SET clause.
  • Transaction – wraps the read-then-update so the operation is atomic; commits on success, rolls back on failure.
  • OUT status – returns a precise result like SUCCESS, FAILED_SOURCE_NOT_FOUND, or FAILED_UPDATE_NO_ROWS.

A National Bank of India clerk is told an account number and a date (the two inputs). They look up that account's interest rate from the rate book (fetch one value into a note), then write that rate onto the account's ledger card (update the target table), and finally stamp the form "done" or "failed" (the status). If anything goes wrong mid-way, they tear up the partial work so the records are never left half-changed.

Understanding the Concepts

What does this scenario procedure do?

The procedure accepts two IN parameters and one INOUT status. Inside the body, it declares a local variable, runs a SELECT ... INTO using the two inputs in the WHERE clause, uses the fetched value to UPDATE a target table, and returns a success or failure status.

SQL
-- PostgreSQL: complete scenario procedure
CREATE OR REPLACE PROCEDURE sp_apply_product_rate(
    IN  p_account_id   VARCHAR,
    IN  p_product_code VARCHAR,
    INOUT p_status     VARCHAR DEFAULT 'PENDING'
)
LANGUAGE plpgsql
AS $$
DECLARE
    v_rate NUMERIC(5,2);
    v_rows_updated INT;
BEGIN
    -- Step 1: Fetch the rate from product_rates into a local variable
    SELECT rate INTO STRICT v_rate
    FROM product_rates
    WHERE product_code = p_product_code
      AND effective_date <= CURRENT_DATE
    ORDER BY effective_date DESC
    LIMIT 1;

    -- Step 2: Update the target table with the fetched rate
    UPDATE account_interest
    SET current_rate = v_rate,
        updated_at   = NOW()
    WHERE account_id = p_account_id;

    GET DIAGNOSTICS v_rows_updated = ROW_COUNT;

    IF v_rows_updated = 0 THEN
        p_status := 'FAILED_ACCOUNT_NOT_UPDATED';
        RETURN;
    END IF;

    p_status := 'SUCCESS';

EXCEPTION
    WHEN NO_DATA_FOUND THEN
        p_status := 'FAILED_PRODUCT_NOT_FOUND';
    WHEN TOO_MANY_ROWS THEN
        p_status := 'FAILED_MULTIPLE_RATES';
    WHEN OTHERS THEN
        p_status := 'FAILED_' || SQLERRM;
END;
$$;

How does SELECT ... INTO work across different engines?

The syntax for assigning a query result into a local variable varies by database engine. The concept is the same: fetch one value from a row into a variable.

SQL
-- PostgreSQL PL/pgSQL
SELECT rate INTO STRICT v_rate
FROM product_rates
WHERE product_code = p_product_code;

-- Oracle PL/SQL
SELECT rate INTO v_rate
FROM product_rates
WHERE product_code = p_product_code;

-- SQL Server T-SQL
SELECT @v_rate = rate
FROM product_rates
WHERE product_code = @p_product_code;

-- Alternative T-SQL: SET with subquery
SET @v_rate = (
    SELECT rate FROM product_rates
    WHERE product_code = @p_product_code
);

| Engine | Syntax | No-row behavior | Too-many-rows behavior |
|---|---|---|---|
| PostgreSQL (STRICT) | SELECT col INTO STRICT var | Raises NO_DATA_FOUND | Raises TOO_MANY_ROWS |
| Oracle | SELECT col INTO var | Raises NO_DATA_FOUND | Raises TOO_MANY_ROWS |
| SQL Server | SELECT @var = col | @var stays NULL | Takes last row silently |

How do you handle the no-row and too-many-rows cases?

PostgreSQL's STRICT keyword makes SELECT INTO raise an exception if the query returns zero or more than one row. Oracle raises these exceptions by default. SQL Server needs explicit checks.

SQL
-- PostgreSQL: STRICT enforces exactly one row
BEGIN
    SELECT rate INTO STRICT v_rate
    FROM product_rates
    WHERE product_code = p_product_code;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RAISE NOTICE 'No rate found for product %', p_product_code;
        p_status := 'FAILED_PRODUCT_NOT_FOUND';
    WHEN TOO_MANY_ROWS THEN
        RAISE NOTICE 'Multiple rates for product %', p_product_code;
        p_status := 'FAILED_MULTIPLE_RATES';
END;
SQL
-- SQL Server: explicit check after SELECT
SELECT @v_rate = rate
FROM product_rates
WHERE product_code = @p_product_code;

IF @v_rate IS NULL
BEGIN
    SET @p_status = 'FAILED_PRODUCT_NOT_FOUND';
    RETURN;
END;

How do you check the UPDATE affected row count?

After the UPDATE, always verify that at least one row was modified. If the target account does not exist, the UPDATE silently matches zero rows.

SQL
-- PostgreSQL: GET DIAGNOSTICS
UPDATE account_interest
SET current_rate = v_rate
WHERE account_id = p_account_id;

GET DIAGNOSTICS v_rows_updated = ROW_COUNT;

IF v_rows_updated = 0 THEN
    p_status := 'FAILED_ACCOUNT_NOT_UPDATED';
    RETURN;
END IF;
SQL
-- Oracle: SQL%ROWCOUNT
UPDATE account_interest
SET current_rate = v_rate
WHERE account_id = p_account_id;

IF SQL%ROWCOUNT = 0 THEN
    p_status := 'FAILED_ACCOUNT_NOT_UPDATED';
    ROLLBACK;
    RETURN;
END IF;
SQL
-- SQL Server: @@ROWCOUNT
UPDATE account_interest
SET current_rate = @v_rate
WHERE account_id = @p_account_id;

IF @@ROWCOUNT = 0
BEGIN
    SET @p_status = 'FAILED_ACCOUNT_NOT_UPDATED';
    ROLLBACK TRANSACTION;
    RETURN;
END;

How does error handling work in this scenario?

The error handling block catches exceptions and sets a precise failure status. In PostgreSQL and Oracle, the EXCEPTION block catches specific conditions. In SQL Server, BEGIN TRY / BEGIN CATCH wraps the logic.

SQL
-- SQL Server T-SQL: complete scenario with TRY/CATCH
CREATE PROCEDURE sp_apply_product_rate
    @p_account_id   VARCHAR(20),
    @p_product_code VARCHAR(20),
    @p_status       VARCHAR(50) OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @v_rate DECIMAL(5,2);

    BEGIN TRY
        BEGIN TRANSACTION;

        SELECT @v_rate = rate
        FROM product_rates
        WHERE product_code = @p_product_code;

        IF @v_rate IS NULL
        BEGIN
            SET @p_status = 'FAILED_PRODUCT_NOT_FOUND';
            ROLLBACK TRANSACTION;
            RETURN;
        END;

        UPDATE account_interest
        SET current_rate = @v_rate,
            updated_at   = GETDATE()
        WHERE account_id = @p_account_id;

        IF @@ROWCOUNT = 0
        BEGIN
            SET @p_status = 'FAILED_ACCOUNT_NOT_UPDATED';
            ROLLBACK TRANSACTION;
            RETURN;
        END;

        COMMIT TRANSACTION;
        SET @p_status = 'SUCCESS';
    END TRY
    BEGIN CATCH
        IF XACT_STATE() <> 0
            ROLLBACK TRANSACTION;
        SET @p_status = 'FAILED_' + ERROR_MESSAGE();
    END CATCH;
END;

How do you call this procedure from Java?

Rajesh calls the procedure from Java using a CallableStatement, passing the two IN parameters and reading the INOUT status.

JAVA
package com.bank.service;

import java.sql.*;
import java.util.logging.Logger;

public class RateApplicationService {

    private static final Logger LOG = Logger.getLogger(
        RateApplicationService.class.getName());

    private final Connection connection;

    public RateApplicationService(Connection connection) {
        this.connection = connection;
    }

    public String applyProductRate(String accountId, String productCode)
            throws SQLException {
        String sql = "CALL sp_apply_product_rate(?, ?, NULL)";
        try (CallableStatement cs = connection.prepareCall(sql)) {
            cs.setString(1, accountId);
            cs.setString(2, productCode);
            cs.execute();
            ResultSet rs = cs.getResultSet();
            if (rs.next()) {
                String status = rs.getString(1);
                LOG.info("Rate application status: " + status);
                return status;
            }
            return "UNKNOWN";
        }
    }
}
TEXT
Output:
Rate application status: SUCCESS

What are the correctness points to mention in an interview?

The key correctness points are: validate that exactly one source row matched before updating, check the affected row count of the UPDATE, keep the transaction short to limit lock contention, and set the status on both the success and failure paths so the outcome is never ambiguous.

TEXT
Correctness Checklist:
  1. SELECT ... INTO with STRICT (or explicit NULL check)
  2. Handle NO_DATA_FOUND and TOO_MANY_ROWS
  3. Check @@ROWCOUNT / SQL%ROWCOUNT / GET DIAGNOSTICS after UPDATE
  4. COMMIT on success, ROLLBACK on any failure
  5. Set status on EVERY path (success, no source, no target, exception)
  6. Keep the transaction short to reduce lock contention

What should a senior engineer know about this pattern?

This read-then-update pattern is exactly where stored procedures shine because the whole operation is one round trip and the transaction boundary is tight and local. A senior engineer should note that FOR UPDATE on the source row can prevent phantom reads if concurrency is a concern, and that the procedure should be idempotent if retries are possible.

SQL
-- Senior addition: FOR UPDATE to lock the source row
SELECT rate INTO STRICT v_rate
FROM product_rates
WHERE product_code = p_product_code
FOR UPDATE;
-- Now no other transaction can modify this rate row
-- until we COMMIT or ROLLBACK

Visual Overview

DIAGRAM
┌────────────────────────────────────────────────────┐
│   Scenario: Fetch-Into-Variable, Update, Status    │
├────────────────────────────────────────────────────┤
│                                                    │
│  CALL sp_apply_product_rate('SAV2001', 'FD_1YR')   │
│          │                                         │
│          ▼                                         │
│  ┌──────────────────────────────┐                   │
│  │  IN: p_account_id = SAV2001 │                   │
│  │  IN: p_product_code = FD_1YR│                   │
│  └──────────────┬───────────────┘                   │
│                 │                                   │
│                 ▼                                   │
│  ┌──────────────────────────────┐                   │
│  │  SELECT rate INTO v_rate    │                   │
│  │  FROM product_rates         │                   │
│  │  WHERE product_code = FD_1YR│                   │
│  │  ───────────────────────    │                   │
│  │  v_rate = 7.50              │                   │
│  └──────────────┬───────────────┘                   │
│                 │                                   │
│          ┌──────┴──────┐                            │
│          │ v_rate NULL?│                            │
│          └──┬───────┬──┘                            │
│         YES │       │ NO                            │
│             ▼       ▼                               │
│     FAILED_NOT   ┌──────────────────────┐           │
│     _FOUND       │  UPDATE account_     │           │
│                  │  interest SET rate   │           │
│                  │  = 7.50 WHERE       │           │
│                  │  account = SAV2001  │           │
│                  └──────────┬───────────┘           │
│                             │                       │
│                   ┌─────────┴─────────┐             │
│                   │ rows_updated = 0? │             │
│                   └───┬──────────┬────┘             │
│                  YES  │          │ NO               │
│                       ▼          ▼                  │
│             FAILED_ACCOUNT   p_status = SUCCESS     │
│             _NOT_UPDATED                            │
└────────────────────────────────────────────────────┘

Key Takeaway

This scenario procedure takes two IN parameters and an INOUT status, declares a local variable, fetches a single value into it using the two inputs in a WHERE clause, uses the variable to update a target table, and wraps the whole thing in a transaction that commits on success and rolls back on any error, always setting the status.

The fetch-into-a-variable step is the part whose syntax is engine-specific. PostgreSQL PL/pgSQL uses SELECT col INTO STRICT v_var FROM source WHERE ..., where STRICT enforces exactly one row. Oracle PL/SQL uses SELECT col INTO v_var FROM ... and raises NO_DATA_FOUND and TOO_MANY_ROWS by default. SQL Server T-SQL uses SELECT @v_var = col FROM ... or the SET @v = (SELECT ...) subquery form, but silently leaves the variable NULL on no rows, so an explicit check is needed.

At the National Bank of India, Rajesh used this exact pattern to apply a product's current interest rate to a specific account. The procedure took the account number and product code, did a SELECT rate INTO v_rate FROM product_rates WHERE product = p_product, then UPDATE account_interest SET rate = v_rate WHERE account = p_account, and returned SUCCESS or a precise failure like FAILED_PRODUCT_NOT_FOUND when the source lookup found nothing, or FAILED_ACCOUNT_NOT_UPDATED when the update hit zero rows, rolling back so no half-applied change persisted.

The correctness points are to validate exactly one source row before updating, to check the update's affected row count, to keep the transaction short to limit lock contention, and to set the status on both the success and failure paths so the outcome is never ambiguous.

Remember one sentence: take two inputs, SELECT ... INTO a local variable from a source table, use it to UPDATE a target table inside a transaction with error handling, and return a success status.

---

Share
Use ← → arrow keys to navigate