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.
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:
-- 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:
SELECT id, order_date, status, total
FROM orders
WHERE user_id = 77
ORDER BY order_date DESC;
▶ Run this query in the SQL Playground
| id | order_date | status | total |
|---|---|---|---|
| 99 | 2024-08-16 | processing | 3268.65 |
| 184 | 2024-01-11 | cancelled | 1613.31 |
| 110 | 2023-06-03 | delivered | 345.48 |
| 164 | 2023-03-18 | delivered | 4770.01 |
| 24 | 2022-12-15 | processing | 4595.72 |
CREATE PROCEDURE in Each Database
-- 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;
-- 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);
| MySQL | SQL Server | PostgreSQL | |
|---|---|---|---|
| Create | CREATE PROCEDURE | CREATE PROCEDURE (or PROC) | CREATE PROCEDURE (for actions) or CREATE FUNCTION (to return rows) |
| Run | CALL name(...) | EXEC name ... | CALL name(...) / SELECT * FROM fn(...) |
| Parameters | IN, OUT, INOUT | @name, OUTPUT | IN, OUT, INOUT |
| Change | DROP then CREATE | ALTER PROCEDURE / CREATE OR ALTER | CREATE OR REPLACE |
| Remove | DROP PROCEDURE name | DROP PROCEDURE name | DROP 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:
-- 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;
SELECT COUNT(*) AS headcount,
ROUND(AVG(salary), 2) AS avg_salary
FROM employees
WHERE department = 'Engineering';
▶ Run this query in the SQL Playground
| headcount | avg_salary |
|---|---|
| 11 | 131028 |
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:
-- 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:
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
| id | first_name | last_name | current_salary | salary_after_raise |
|---|---|---|---|---|
| 4 | Melissa | Smith | 51465 | 54038 |
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 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 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 procedure | Function | View | Trigger | |
|---|---|---|---|---|
| Runs when | Called with CALL / EXEC | Used inside a query | Selected from | A row changes |
| Parameters | Yes | Yes | No | No (OLD / NEW) |
| Can change data | Yes | Usually not (not in SQL Server) | Only if updatable | Yes |
| Use in SELECT | No | Yes | Yes | No |
| Transactions inside | Yes (MySQL, SQL Server, PostgreSQL procedures) | No | No | Runs 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 risk | Details |
|---|---|
| Fewer round trips | Several statements run in one call instead of several requests from the application |
| Plan reuse | SQL Server and MySQL cache the plan for a procedure's statements; parameterized queries from an application get the same benefit |
| Parameter sniffing | SQL 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 |
| Permissions | GRANT EXECUTE lets a role run the procedure without direct access to the tables |
| Hidden logic | Logic 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
SET @sql = 'SELECT * FROM users WHERE email = ''' + @email + '''';
EXEC (@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
INSERT INTO orders ...;
INSERT INTO order_items ...; -- fails
UPDATE products ...;
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
BEGIN CATCH
ROLLBACK;
END CATCH;
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
CREATE PROCEDURE remove_inactive AS
DELETE FROM users;
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
-- cursor over employees, UPDATE one row per loop
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.
-
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:
ordersShow solution
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.
-
2Medium Write the query for a procedure monthly_revenue(p_year) for the year 2024: revenue per month, excluding cancelled and refunded orders. Table:
ordersShow solution
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.
-
3Medium A procedure low_stock(p_threshold) lists products below a stock level. Write its query for a threshold of 50. Table:
productsShow solution
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.
-
4Medium Preview a procedure that would give every Design employee a 3% raise: show current and new salary without changing anything. Table:
employeesShow solution
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
What is a stored procedure?
Run it with CALL or EXEC.
Which statement creates a procedure?
SQL Server also accepts CREATE PROC.
Which keyword runs a procedure in MySQL and PostgreSQL?
SQL Server uses EXEC.
What is an input parameter for?
For example the customer id to look up.
What is an output parameter for?
OUT in MySQL and PostgreSQL, OUTPUT in SQL Server.
What is the key difference between a procedure and a function?
The details vary by database.
Can a procedure be called inside a SELECT?
Use a function when you need a value inside a query.
What does DECLARE do in a procedure?
The variable exists only while the procedure runs.
What is TRY ... CATCH for?
SQL Server syntax; MySQL uses DECLARE ... HANDLER.
Why wrap multi-step writes in a transaction?
No half-finished orders.
What is dynamic SQL?
Useful, but handle input carefully.
How can dynamic SQL become an injection risk?
Use parameters instead.
Why does a parameterized procedure resist SQL injection?
The value cannot change the statement.
What is parameter sniffing?
A SQL Server issue mostly.
What is a common fix for parameter sniffing in SQL Server?
Copying parameters into local variables is another.
How do procedures help security?
Callers can only do what the procedure does.
What is the difference between a procedure and a view?
Different tools for different jobs.
What is the difference between a procedure and a trigger?
A trigger can call a procedure.
What is the difference between a procedure and a CTE?
A CTE can appear inside a procedure.
What is a nested procedure call?
Common for splitting work into steps.
Why are recursive procedures usually discouraged for hierarchies?
SQL Server allows 32 nesting levels.
What does SET NOCOUNT ON do in SQL Server?
Less chatter sent back to the client.
Why should a DELETE procedure require an identifying parameter?
Always filter by a key.
What is a soft delete in a delete procedure?
Keeps history.
Why are plans cached for procedures?
Parameterized application queries can be cached too.
Why are cursors in procedures often discouraged?
Use them only when set-based SQL cannot do the job.
What is a temporary table inside a procedure for?
It disappears when the session ends.
How can a procedure reduce network traffic?
Matters when the application is far from the database.
What is the risk of building a WHERE clause by string concatenation?
Bind values as parameters.
What does GRANT EXECUTE ON PROCEDURE ... TO a_role do?
Least privilege.
How are output parameters declared?
The caller provides a variable to receive the value.
What is the safest way to let a caller choose the sort column?
Column names cannot be bound as parameters.
Why split one giant procedure into several focused ones?
Like functions in any program.
Why re-raise an error (THROW, RESIGNAL) in an error handler?
Otherwise the failure is silent.
Why do companies put payroll or refund logic in procedures?
Consistency and control.
How do procedures support scheduled reports?
One definition of each number.
What is the main trade-off of dynamic SQL?
Use it when static SQL cannot express the query.
Why can copying a parameter into a local variable help with parameter sniffing?
A SQL Server technique.
A call fails with "procedure does not exist". What do you check first?
Most often a schema or name mismatch.
How do procedures relate to duplicated application code?
One place to fix bugs.
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.