Using raw SQL

Pony allows using raw SQL in your queries. There are two options on how you can use raw SQL:

  1. Use the raw_sql() function in order to write only a part of a generator or lambda query using raw SQL.

  2. Write a complete SQL query using the Entity.select_by_sql() or Entity.get_by_sql() methods.

Using the raw_sql() function

Let’s explore examples of using the raw_sql() function. Here is the schema and initial data that we’ll use for our examples:

  1. from datetime import date
  2. from pony.orm import *
  3. db = Database('sqlite', ':memory:')
  4. class Person(db.Entity):
  5. id = PrimaryKey(int)
  6. name = Required(str)
  7. age = Required(int)
  8. dob = Required(date)
  9. db.generate_mapping(create_tables=True)
  10. with db_session:
  11. Person(id=1, name='John', age=30, dob=date(1986, 1, 1))
  12. Person(id=2, name='Mike', age=32, dob=date(1984, 5, 20))
  13. Person(id=3, name='Mary', age=20, dob=date(1996, 2, 15))

The raw_sql() result can be treated as a logical expression:

  1. select(p for p in Person if raw_sql('abs("p"."age") > 25'))

The raw_sql() result can be used for a comparison:

  1. q = Person.select(lambda x: raw_sql('abs("x"."age")') > 25)
  2. print(q.get_sql())
  3. SELECT "x"."id", "x"."name", "x"."age", "x"."dob"
  4. FROM "Person" "x"
  5. WHERE abs("x"."age") > 25

Also, in the example above we use raw_sql() in a lambda query and print out the resulting SQL. As you can see the raw SQL part becomes a part of the whole query.

The raw_sql() can accept $parameters:

  1. x = 25
  2. select(p for p in Person if raw_sql('abs("p"."age") > $x'))

You can change the content of the raw_sql() function dynamically and still use parameters inside:

  1. x = 1
  2. s = 'p.id > $x'
  3. select(p for p in Person if raw_sql(s))

Another way of using dynamic raw SQL content:

  1. x = 1
  2. cond = raw_sql('p.id > $x')
  3. select(p for p in Person if cond)

You can use various types inside the raw SQL query:

  1. x = date(1990, 1, 1)
  2. select(p for p in Person if raw_sql('p.dob < $x'))

Parameters inside the raw SQL part can be combined:

  1. x = 10
  2. y = 15
  3. select(p for p in Person if raw_sql('p.age > $(x + y)'))

You can even call Python functions inside:

  1. select(p for p in Person if raw_sql('p.dob < $date.today()'))

The raw_sql() function can be used not only in the condition part, but also in the part which returns the result of the query:

  1. names = select(raw_sql('UPPER(p.name)') for p in Person)[:]
  2. print(names)
  3. ['JOHN', 'MIKE', 'MARY']

But when you return data using the raw_sql() function, you might need to specify the type of the result, because Pony has no idea on what the result type is:

  1. dates = select(raw_sql('(p.dob)') for p in Person)[:]
  2. print(dates)
  3. ['1985-01-01', '1983-05-20', '1995-02-15']

If you want to get the result as a list of dates, you need to specify the result_type:

  1. dates = select(raw_sql('(p.dob)', result_type=date) for p in Person)[:]
  2. print(dates)
  3. [datetime.date(1986, 1, 1), datetime.date(1984, 5, 20), datetime.date(1996, 2, 15)]

The raw_sql() function can be used in a Query.filter() too:

  1. x = 25
  2. select(p for p in Person).filter(lambda p: p.age > raw_sql('$x'))

It can be used inside the Query.filter() without lambda. In this case you have to use the first letter of entity name in lower case as the alias:

  1. x = 25
  2. Person.select().filter(raw_sql('p.age > $x'))

You can use several raw_sql() expressions in a single query:

  1. x = '123'
  2. y = 'John'
  3. Person.select(lambda p: raw_sql("UPPER(p.name) || $x")
  4. == raw_sql("UPPER($y || '123')"))

The same parameter names can be used several times with different types and values:

  1. x = 10
  2. y = 31
  3. q = select(p for p in Person if p.age > x and p.age < raw_sql('$y'))
  4. x = date(1980, 1, 1)
  5. y = 'j'
  6. q = q.filter(lambda p: p.dob > x and p.name.startswith(raw_sql('UPPER($y)')))
  7. persons = q[:]

You can use raw_sql() in Query.order_by() section:

  1. x = 9
  2. Person.select().order_by(lambda p: raw_sql('SUBSTR(p.dob, $x)'))

Or without lambda, if you use the same alias, that you used in previous filters. In this case we use the default alias - the first letter of the entity name:

  1. x = 9
  2. Person.select().order_by(raw_sql('SUBSTR(p.dob, $x)'))

Using the select_by_sql() and get_by_sql() methods

Although Pony can translate almost any condition written in Python to SQL, sometimes the need arises to use raw SQL, for example - in order to call a stored procedure or to use a dialect feature of a specific database system. In this case, Pony allows the user to write a query in a raw SQL, by placing it inside the function Entity.select_by_sql() or Entity.get_by_sql() as a string:

  1. Product.select_by_sql("SELECT * FROM Products")

Unlike the method Entity.select(), the method Entity.select_by_sql() does not return the Query object, but a list of entity instances.

Parameters are passed using the following syntax: “$name_variable” or “$(expression in Python)”. For example:

  1. x = 1000
  2. y = 500
  3. Product.select_by_sql("SELECT * FROM Product WHERE price > $x OR price = $(y * 2)")

When Pony encounters a parameter within a raw SQL query, it gets the variable value from the current frame (from globals and locals) or from the dictionaries which can be passed as parameters:

  1. Product.select_by_sql("SELECT * FROM Product WHERE price > $x OR price = $(y * 2)",
  2. globals={'x': 100}, locals={'y': 200})

Variables and more complex expressions specified after the $ sign, will be automatically calculated and transferred into the query as parameters, which makes SQL-injection impossible. Pony automatically replaces $x in the query string with “?”, “%S” or with other paramstyle, used in your database.

If you need to use the $ sign in the query (for example, in the name of a system table), you have to write two $ signs in succession: $$.