SQLab Hub
Lesson 34 · Stored Procedures
Lesson 34 · Advanced

SQL Stored Procedures: Syntax, Parameters and Examples

A stored procedure is a named set of SQL statements saved in the database, which you run with CALL or EXEC instead of sending the statements yourself. Procedures take parameters, can contain logic and transactions, and let every application run the same code in the same way.

CREATE PROCEDURE · CALL · EXEC Parameters · error handling 🧪 4 practice exercises 📝 40-question quiz ⏱ ~25 min

What Is a Stored Procedure?

Say several applications need "the orders of one customer, newest first". Each could carry its own copy of the query, or the database can hold it once as a procedure that takes the customer id as a parameter:

SQL · MySQL
-- MySQL
DELIMITER //
CREATE PROCEDURE get_customer_orders(IN p_user_id INT)
BEGIN
  SELECT id, order_date, status, total
  FROM orders
  WHERE user_id = p_user_id
  ORDER BY order_date DESC;
END //
DELIMITER ;

CALL get_customer_orders(77);

Calling the procedure runs its query with 77 in place of p_user_id. You can see exactly what it returns by running the same SELECT with the value filled in:

SQLite · runs in the Playground
SELECT id, order_date, status, total
FROM orders
WHERE user_id = 77
ORDER BY order_date DESC;

▶ Run this query in the SQL Playground

What get_customer_orders(77) returns
idorder_datestatustotal
992024-08-16processing3268.65
1842024-01-11cancelled1613.31
1102023-06-03delivered345.48
1642023-03-18delivered4770.01
242022-12-15processing4595.72
✓ 5 rows · real output from the Playground database
SQLite, which runs the SQLabHub Playground, has no stored procedures, and the Playground only accepts SELECT and WITH. The procedures on this page are written for MySQL, PostgreSQL or SQL Server, and each runnable example shows the query inside a procedure with its parameters filled in, which is exactly the result the procedure produces.

CREATE PROCEDURE in Each Database

SQL · SQL Server
-- SQL Server
CREATE PROCEDURE get_customer_orders
    @user_id INT
AS
BEGIN
    SET NOCOUNT ON;
    SELECT id, order_date, status, total
    FROM orders
    WHERE user_id = @user_id
    ORDER BY order_date DESC;
END;

EXEC get_customer_orders @user_id = 77;
SQL · PostgreSQL
-- PostgreSQL 11+: procedures perform actions; a function is used to return rows
CREATE FUNCTION get_customer_orders(p_user_id INT)
RETURNS TABLE (id INT, order_date DATE, status TEXT, total NUMERIC)
LANGUAGE sql AS $$
  SELECT id, order_date, status, total
  FROM orders
  WHERE user_id = p_user_id
  ORDER BY order_date DESC;
$$;

SELECT * FROM get_customer_orders(77);
MySQLSQL ServerPostgreSQL
CreateCREATE PROCEDURECREATE PROCEDURE (or PROC)CREATE PROCEDURE (for actions) or CREATE FUNCTION (to return rows)
RunCALL name(...)EXEC name ...CALL name(...) / SELECT * FROM fn(...)
ParametersIN, OUT, INOUT@name, OUTPUTIN, OUT, INOUT
ChangeDROP then CREATEALTER PROCEDURE / CREATE OR ALTERCREATE OR REPLACE
RemoveDROP PROCEDURE nameDROP PROCEDURE nameDROP PROCEDURE name

MySQL needs DELIMITER in command-line tools so that the semicolons inside the body do not end the CREATE statement early.

Input and Output Parameters

Input parameters pass values in; output parameters hand values back. This procedure returns a department's headcount and average salary through two output parameters:

SQL · MySQL
-- MySQL
CREATE PROCEDURE department_stats(
  IN  p_department VARCHAR(50),
  OUT p_headcount  INT,
  OUT p_avg_salary DECIMAL(10,2)
)
BEGIN
  SELECT COUNT(*), AVG(salary)
  INTO p_headcount, p_avg_salary
  FROM employees
  WHERE department = p_department;
END;

CALL department_stats('Engineering', @n, @avg);
SELECT @n, @avg;
SQLite · runs in the Playground
SELECT COUNT(*) AS headcount,
       ROUND(AVG(salary), 2) AS avg_salary
FROM employees
WHERE department = 'Engineering';

▶ Run this query in the SQL Playground

What department_stats('Engineering') puts into its output parameters
headcountavg_salary
11131028
✓ 1 row · real output from the Playground database

Parameters are also the main defence against SQL injection: the value is always treated as data, never as part of the SQL text.

Variables, IF and Loops

Procedures can declare variables and branch. This one gives a raise only within a limit, and reports what it did:

SQL · MySQL
-- MySQL
CREATE PROCEDURE give_raise(IN p_employee_id INT, IN p_pct DECIMAL(5,2))
BEGIN
  DECLARE v_salary DECIMAL(12,2);

  SELECT salary INTO v_salary FROM employees WHERE id = p_employee_id;

  IF v_salary IS NULL THEN
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'employee not found';
  ELSEIF p_pct > 10 THEN
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'raise above 10% needs approval';
  ELSE
    UPDATE employees SET salary = ROUND(salary * (1 + p_pct / 100))
    WHERE id = p_employee_id;
  END IF;
END;

Before calling it for real, you can preview the effect with a SELECT, here for a 5% raise for employee 4:

SQLite · runs in the Playground
SELECT id, first_name, last_name, salary AS current_salary,
       ROUND(salary * 1.05) AS salary_after_raise
FROM employees
WHERE id = 4;

▶ Run this query in the SQL Playground

Preview of give_raise(4, 5)
idfirst_namelast_namecurrent_salarysalary_after_raise
4MelissaSmith5146554038
✓ 1 row · real output from the Playground database

Procedures can also loop (WHILE, LOOP, cursors), but a single set-based UPDATE or INSERT ... SELECT is almost always faster than processing rows one at a time.

Error Handling and Transactions

A procedure that changes several tables should do it inside a transaction and undo everything if any step fails:

SQL · SQL Server
-- SQL Server
CREATE PROCEDURE place_order
    @user_id INT, @product_id INT, @quantity INT
AS
BEGIN
    SET NOCOUNT ON;
    BEGIN TRY
        BEGIN TRANSACTION;

        DECLARE @price DECIMAL(10,2) =
            (SELECT price FROM products WHERE id = @product_id);
        DECLARE @order_id INT;

        INSERT INTO orders (user_id, total, status, order_date)
        VALUES (@user_id, @price * @quantity, 'pending', CAST(GETDATE() AS date));
        SET @order_id = SCOPE_IDENTITY();

        INSERT INTO order_items (order_id, product_id, quantity, unit_price, subtotal)
        VALUES (@order_id, @product_id, @quantity, @price, @price * @quantity);

        UPDATE products SET stock = stock - @quantity WHERE id = @product_id;

        COMMIT;
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0 ROLLBACK;
        THROW;   -- pass the error on to the caller
    END CATCH;
END;

Without the transaction, a failure after the first INSERT would leave an order with no items. Without THROW (or RESIGNAL in MySQL), the caller would never learn that anything went wrong.

Dynamic SQL and SQL Injection

Dynamic SQL builds the statement text at run time, for example to sort by a column the caller chooses. Concatenating caller input into SQL is how injection happens:

SQL · SQL Server
-- SQL Server: unsafe
DECLARE @sql NVARCHAR(400) =
  N'SELECT * FROM products WHERE category = ''' + @category + N'''';
EXEC (@sql);   -- @category = ''' OR 1=1 --'' returns every row

-- safe: the value stays a parameter
EXEC sp_executesql
  N'SELECT * FROM products WHERE category = @cat',
  N'@cat NVARCHAR(50)', @cat = @category;

-- column names cannot be parameters: check them against a fixed list
IF @sort_column NOT IN ('name', 'price', 'rating')
    THROW 50001, 'invalid sort column', 1;

Rule of thumb: values go in as parameters; identifiers such as column names are checked against an allow-list before they are put into the text.

Procedures vs Functions, Views and Triggers

Stored procedureFunctionViewTrigger
Runs whenCalled with CALL / EXECUsed inside a querySelected fromA row changes
ParametersYesYesNoNo (OLD / NEW)
Can change dataYesUsually not (not in SQL Server)Only if updatableYes
Use in SELECTNoYesYesNo
Transactions insideYes (MySQL, SQL Server, PostgreSQL procedures)NoNoRuns inside the caller's

A view is a saved SELECT without parameters; a trigger runs automatically on a data change; a CTE lasts for one statement only.

Performance and Security

Benefit or riskDetails
Fewer round tripsSeveral statements run in one call instead of several requests from the application
Plan reuseSQL Server and MySQL cache the plan for a procedure's statements; parameterized queries from an application get the same benefit
Parameter sniffingSQL Server builds the plan for the first parameter value it sees; a very different value later can run slowly. OPTION (RECOMPILE) or local variables are the usual fixes
PermissionsGRANT EXECUTE lets a role run the procedure without direct access to the tables
Hidden logicLogic in procedures is outside the application's code base, so version-control and test it like code

Common Stored Procedure Mistakes

Five errors and how to avoid them.

1. Concatenating input into dynamic SQL

❌ SQL injection
SQL
SET @sql = 'SELECT * FROM users WHERE email = ''' + @email + '''';
EXEC (@sql);
✅ Parameterize
SQL
EXEC sp_executesql
  N'SELECT * FROM users WHERE email = @e',
  N'@e NVARCHAR(200)', @e = @email;

Input such as ' OR 1=1 -- becomes part of the SQL. A parameter is always just a value.

2. Multi-step changes without a transaction

❌ Half an order on failure
SQL
INSERT INTO orders ...;
INSERT INTO order_items ...;   -- fails
UPDATE products ...;
✅ All or nothing
SQL
BEGIN TRY BEGIN TRANSACTION;
  INSERT ...; INSERT ...; UPDATE ...;
COMMIT; END TRY
BEGIN CATCH ROLLBACK; THROW; END CATCH;

Each statement commits on its own otherwise.

3. Swallowing errors

❌ Caller thinks it worked
SQL
BEGIN CATCH
  ROLLBACK;
END CATCH;
✅ Re-raise after cleanup
SQL
BEGIN CATCH
  IF @@TRANCOUNT > 0 ROLLBACK;
  THROW;
END CATCH;

Rolling back without re-raising hides the failure from the application.

4. A DELETE procedure without a required key

❌ Can empty the table
SQL
CREATE PROCEDURE remove_inactive AS
DELETE FROM users;
✅ Always filter by the key
SQL
CREATE PROCEDURE remove_user @id INT AS
DELETE FROM users WHERE id = @id;

A missing WHERE in a procedure runs on every call. Many teams prefer a soft delete (setting a status) for user data.

5. Looping row by row

❌ Thousands of statements
SQL
-- cursor over employees, UPDATE one row per loop
✅ One set-based statement
SQL
UPDATE employees
SET salary = ROUND(salary * 1.03)
WHERE department = 'Engineering';

A single statement lets the database do the work in one pass.

Practice Stored Procedures Queries

4 exercises on the real Playground tables.

Write each query yourself first. Every solution has been run against the Playground database.

  1. 1Easy A procedure order_count_by_status(p_status) returns the number of orders with a status. Write its query for the status "shipped". Table: orders

    Show solution
    SQLite · runs in the Playground
    SELECT COUNT(*) AS orders
    FROM orders
    WHERE status = 'shipped';

    ▶ Run this query in the SQL Playground

    Inside the procedure, 'shipped' would be the parameter p_status.

  2. 2Medium Write the query for a procedure monthly_revenue(p_year) for the year 2024: revenue per month, excluding cancelled and refunded orders. Table: orders

    Show solution
    SQLite · runs in the Playground
    SELECT strftime('%m', order_date) AS month,
           ROUND(SUM(total), 2) AS revenue
    FROM orders
    WHERE strftime('%Y', order_date) = '2024'
      AND status NOT IN ('cancelled', 'refunded')
    GROUP BY month
    ORDER BY month;

    ▶ Run this query in the SQL Playground

    A reporting procedure keeps this rule in one place for every dashboard.

  3. 3Medium A procedure low_stock(p_threshold) lists products below a stock level. Write its query for a threshold of 50. Table: products

    Show solution
    SQLite · runs in the Playground
    SELECT id, name, stock
    FROM products
    WHERE stock < 50
    ORDER BY stock;

    ▶ Run this query in the SQL Playground

    The threshold is a parameter, so the same procedure serves every warehouse rule.

  4. 4Medium Preview a procedure that would give every Design employee a 3% raise: show current and new salary without changing anything. Table: employees

    Show solution
    SQLite · runs in the Playground
    SELECT first_name, last_name, salary,
           ROUND(salary * 1.03) AS new_salary
    FROM employees
    WHERE department = 'Design'
    ORDER BY salary DESC;

    ▶ Run this query in the SQL Playground

    Running the SELECT version first is a cheap check before an UPDATE in a procedure changes real data.

📝 Quiz — Stored Procedures

Test your understanding · 0/40 answered

Question 1 of 40

What is a stored procedure?

Question 2 of 40

Which statement creates a procedure?

Question 3 of 40

Which keyword runs a procedure in MySQL and PostgreSQL?

Question 4 of 40

What is an input parameter for?

Question 5 of 40

What is an output parameter for?

Question 6 of 40

What is the key difference between a procedure and a function?

Question 7 of 40

Can a procedure be called inside a SELECT?

Question 8 of 40

What does DECLARE do in a procedure?

Question 9 of 40

What is TRY ... CATCH for?

Question 10 of 40

Why wrap multi-step writes in a transaction?

Question 11 of 40

What is dynamic SQL?

Question 12 of 40

How can dynamic SQL become an injection risk?

Question 13 of 40

Why does a parameterized procedure resist SQL injection?

Question 14 of 40

What is parameter sniffing?

Question 15 of 40

What is a common fix for parameter sniffing in SQL Server?

Question 16 of 40

How do procedures help security?

Question 17 of 40

What is the difference between a procedure and a view?

Question 18 of 40

What is the difference between a procedure and a trigger?

Question 19 of 40

What is the difference between a procedure and a CTE?

Question 20 of 40

What is a nested procedure call?

Question 21 of 40

Why are recursive procedures usually discouraged for hierarchies?

Question 22 of 40

What does SET NOCOUNT ON do in SQL Server?

Question 23 of 40

Why should a DELETE procedure require an identifying parameter?

Question 24 of 40

What is a soft delete in a delete procedure?

Question 25 of 40

Why are plans cached for procedures?

Question 26 of 40

Why are cursors in procedures often discouraged?

Question 27 of 40

What is a temporary table inside a procedure for?

Question 28 of 40

How can a procedure reduce network traffic?

Question 29 of 40

What is the risk of building a WHERE clause by string concatenation?

Question 30 of 40

What does GRANT EXECUTE ON PROCEDURE ... TO a_role do?

Question 31 of 40

How are output parameters declared?

Question 32 of 40

What is the safest way to let a caller choose the sort column?

Question 33 of 40

Why split one giant procedure into several focused ones?

Question 34 of 40

Why re-raise an error (THROW, RESIGNAL) in an error handler?

Question 35 of 40

Why do companies put payroll or refund logic in procedures?

Question 36 of 40

How do procedures support scheduled reports?

Question 37 of 40

What is the main trade-off of dynamic SQL?

Question 38 of 40

Why can copying a parameter into a local variable help with parameter sniffing?

Question 39 of 40

A call fails with "procedure does not exist". What do you check first?

Question 40 of 40

How do procedures relate to duplicated application code?

Stored Procedures Interview Questions

Q1. What is the difference between a stored procedure and a function?

A function returns a value or a table and can be used inside a query; in SQL Server it cannot change data. A procedure is called on its own with CALL or EXEC, can change data, manage transactions and return several result sets.

Q2. How do stored procedures protect against SQL injection?

By taking values as parameters, which are never interpreted as SQL. A procedure that concatenates input into dynamic SQL is still vulnerable.

Q3. What is parameter sniffing and how do you fix it?

SQL Server compiles a procedure's plan for the first parameter values it sees and reuses it. If later values need a very different plan, performance drops. Fixes include OPTION (RECOMPILE), OPTIMIZE FOR, or copying parameters into local variables.

Q4. How do you handle errors in a procedure that changes several tables?

Put the changes in a transaction inside TRY ... CATCH (SQL Server) or with an EXIT HANDLER (MySQL); in the handler roll back if a transaction is open, then re-raise the error.

Q5. When would you not use a stored procedure?

For logic that changes often and belongs with the application, for simple queries an ORM or parameterized query handles well, and where moving between database products is likely, because procedure languages differ.

Frequently Asked Questions

What is a stored procedure in SQL?

A stored procedure is a named set of SQL statements saved in the database. Any application, script or user with permission can run it by name, with CALL or EXEC, instead of sending the statements themselves.

Why use SQL stored procedures?

To write important logic once and reuse it everywhere, to control access (callers need only permission to run the procedure, not access to the tables), and to run several statements in one call.

How do stored procedures improve performance?

Mainly by running several statements in one round trip to the server and, in databases such as SQL Server and MySQL, by reusing cached plans. Parameterized queries from an application get similar plan reuse, so the gain is not automatic.

What are parameters in a stored procedure?

Placeholders declared in CREATE PROCEDURE. Input parameters pass values in, and output parameters (OUT or OUTPUT) hand values back to the caller.

What is the difference between a stored procedure and a function in SQL?

A function returns a single value or a table and can be used inside a SELECT. A procedure is run on its own, can change data and manage transactions, and can return zero, one or several result sets.

Can stored procedures prevent SQL injection?

Yes, when they take input as parameters: parameter values are treated as data, never as SQL. A procedure that builds SQL text from input with string concatenation is still vulnerable.

How do companies use stored procedures in real business systems?

For operations that must behave the same way from every application, such as order processing, refunds, payroll runs, data loads and standard reports, with permissions and transactions controlled in one place.

Are stored procedures important in SQL interviews?

Yes, especially for roles that work with SQL Server or MySQL. Common questions cover CREATE PROCEDURE syntax, input and output parameters, procedures versus functions, error handling, dynamic SQL and injection, and transactions.