Bind Variables

Summary: in this tutorial, you will learn how to use the bind variables to pass data to and from Oracle Database.

An introduction to bind variables #

If you want to pass data to and from the Oracle database, you use placeholders in the SQL statement as follows:

sql = ''' SELECT customer_id, name
          FROM customers
          WHERE customer_id = :customer_id '''Code language: Python (python)

In this query, the :customer_id is a placeholder. It is also known as a bind variable or bind parameter.

When you execute a query using the Cursor object, you need to pass the value of the bind variable:

cursor.execute(sql, {'customer_id': customer_id})Code language: Python (python)

In this case, the value of the customer_id variable will be used for the :customer_id bind variable in the SQL statement when the query is executed.

Bind variables make the code more secure and help avoid SQL injection security issues because user data is never treated as a part of the executable SQL statement.

If user data is concatenated with a SQL statement, it will be vulnerable to a SQL injection attack:

sql = ''' SELECT customer_id, name
          FROM customers
          WHERE customer_id = ''' + customer_id;Code language: Python (python)

For this reason, it is a good practice to always use bind variables in your query and never concatenate or interpolate user data into an SQL statement like the following:

sql = f''' SELECT customer_id, name
          FROM customers
          WHERE customer_id = {customer_id}''';Code language: Python (python)

Besides the security benefit, bind variables can improve the performance of a query if an SQL statement is executed multiple times with different values because Oracle just needs to parse and cache the SQL statement once.

The following example illustrates how to find the customer’s name by id using bind variables:

from typing import  Optional
import oracledb
import logging
from customer import Customer
from connect import connect


def find_by_id(customer_id: int) -> Optional[Customer]:

    sql = ''' SELECT customer_id, name
              FROM customers
              WHERE customer_id = :customer_id '''

    try:
        with connect() as connection:
            with connection.cursor() as cursor:
                cursor.execute(sql, {'customer_id': customer_id})
                row = cursor.fetchone()
                if row:
                    return Customer(customer_id=row[0], name=row[1])
                return None # Return None if no customer is found
    except oracledb.Error as e:
        logging.error(f"Error executing query to find customer by ID: {e}")
        raise
Code language: Python (python)

Note that here is the config.py module if you haven’t followed the previous tutorial:

username = 'OT'
password = 'oracle'
dsn = '192.168.2.18/freepdb1'Code language: Python (python)

Binding by names #

If bind variables are associated with names, you have named binds. The named binds require the keyword parameter names or key of the dictionary to match the bind variable names. For example:

from typing import  Optional
import oracledb
import logging
from connect import connect

from dataclasses import dataclass


@dataclass(frozen=True)
class Product:
    product_id: int
    product_name: str    
    category_id: int
    list_price: float

def find_products(category_id: int, list_price: float) -> Optional[Product]:

    sql = ''' SELECT product_id, product_name, category_id, list_price
              FROM products
              WHERE category_id = :category_id and list_price > :list_price
              ORDER BY list_price '''

    try:
        with connect() as connection:
            with connection.cursor() as cursor:
                # Pass the parameter using a dictionary
                cursor.execute(sql, {
                    'category_id': category_id, 
                    'list_price': list_price
                })
                
                rows = cursor.fetchall()
                
                if not rows:
                    return None
                
                products = []
                for row in rows:
                    # Create a Customer object from the fetched row
                    product =  Product(
                        product_id=row[0], 
                        product_name=row[1],
                        category_id=row[2],
                        list_price=row[3]
                    )
                    products.append(product)
                return products
                
    except oracledb.Error as e:
        logging.error(f"Error executing query to find customer by ID: {e}")
        raiseCode language: Python (python)

The :list_price and :standard_cost bind variables are named binds:

sql = ''' SELECT product_id, product_name, list_price, standard_cost
          FROM products
          WHERE list_price >= :list_price and standard_cost <= :standard_cost '''        Code language: Python (python)

When executing the SQL statement with named binds, you need to pass the keyword parameter names that match the named binds:

cursor.execute(sql, list_price=15, standard_cost=13)Code language: Python (python)

Alternatively, you can pass parameters as a dictionary instead of as keyword parameters:

cursor.execute(sql, {'list_price': list_price, 'standard_cost': standard_cost})Code language: Python (python)

The binding by names allows you to specify meaningful names and use them freely in any position.

In addition, you just need to specify the name parameters once if a variable is repeated multiple times in a query.

Binding by positions #

A positional bind is performed when a list of bind values is passed to the Cursor.execute() call. For example:

Code language: Python (python)

The order of the bind values must exactly match the order of each bind variable. If you use a bind variable multiple times in the query, you must repeat the bind values.

Bind directions #

Bind variables can be IN or OUT.

The IN bind variables allow you to pass data from Python to Oracle Database while the OUT bind variables allow you to get data back from the Oracle Database to Python.

In the previous examples, you have passed in bind variables to the Oracle Database to query data and used a Cursor to fetch the result.

To have the Oracle Database return data to Python, you need to create a variable by using the Cursor.var() method. See the following example:

import oracledb
from connect import connect

# construct a PL/SQL anonymous block
plsql = '''
    begin 
        select count(*) into :customer_count 
        from customers; 
    end;
'''

try:
    with connect() as connection:
        with connection.cursor() as cursor:
            # create a variable
            customer_count = cursor.var(int)

            # execute the pl/sql anonymous block
            cursor.execute(plsql, customer_count=customer_count)

            # show the value of the variable
            print(f'The number of customers is {customer_count.getvalue()}')
except oracledb.Error as error:
    print(error)Code language: Python (python)

In this example:

First, declare a variable that holds an anonymous PL/SQL block:

plsql = '''
    begin 
        select count(*) into :customer_count 
        from customers; 
    end;
'''Code language: Python (python)

Second, connect to the Oracle Database and create a new Cursor object from the Connection object.

Third, create a variable to hold the returned data from the execution of the anonymous PL/SQL block:

customer_count = cursor.var(int)Code language: Python (python)

Fourth, execute the PL/SQL block and receive the returned data:

cursor.execute(plsql, customer_count=customer_count)Code language: Python (python)

Finally, show the value of the variable by calling the getvalue() method:

print(f'Customer count is {customer_count.getvalue()}')Code language: Python (python)

The output is:

The number of customers is 319Code language: Python (python)

In this tutorial, you have learned how to use bind variables to pass data to and from Oracle Database.

Was this tutorial helpful?