Python library for ease of writing SQL queries. For now only for database MySQL & PostgreSQL.
- Python 2.6 or later.
Installation of Python module sqlpuzzle is very simple by PyPI:
$ sudo pip install sqlpuzzle
Or you can install development version by extracting archive and run:
$ sudo make install
Note: there is also camelCase methods which are only aliases to methods described here. It's only for backward compatiblity with version 0.x. It will be removed soon.
import sqlpuzzleRight now sqlpuzzle support only two databases – MySQL and PostgreSQL. By default sqlpuzzle generate syntax in plain SQL, if you want to change it, you have to call configure somewhere on start of your app.
sqlpuzzle.configure('mysql')
# or
sqlpuzzle.configure('postgresql')Probably base query. Lets look at how simply we can do it with sqlpuzzle.
sql = sqlpuzzle.select_from('table').where(id=42)
# SELECT * FROM "table" WHERE "id" = 42sql = sqlpuzzle.select_from('table')
sql.distinct()
sql.sql_calc_found_rows()
# SELECT SQL_CALC_FOUND_ROWS DISTINCT * FROM "table"You can also use:
sql_cachesql_no_cachealldistinctdistinctrowsql_small_resultsql_big_resultsql_buffer_resultsql_calc_found_rowsstraight_joinhigh_priority
All these methods have one param allow by which you can turn off that select option. Useful when you want in some condition turn it off.
sql = sqlpuzzle.select_from('table')
sql.distinct()
# SELECT DISTINCT * FROM "table"
sql.distinct(allow=False)
# SELECT * FROM "table"Note that not all of them works for every database. For example SQL_NO_CACHE is only for MySQL.
For now we fetch always all columns. That's not good idea, better is explicitly say what we want.
sql = sqlpuzzle.select('id', 'name').from_('table')
# SELECT "id", "name" FROM "table"Look closer to method from_. There have to be underscore because from is keyword.
If you need add some columns, you can do that by method columns.
sql.columns('email', 'country')
# SELECT "id", "name", "email", "country" FROM "table"How to make aliases is described in own section.
Sometimes we need to fetch data from more tables. sqlpuzzle supports both cartesian product and joins.
sqlpuzzle.select_from('t1', 't2')
# SELECT * FROM "t1", "t2"
sqlpuzzle.select_from('t1').left_join('t2').on('t1.id', 't2.id')
# SELECT * FROM "t1" LEFT JOIN "t2" ON "t1"."id" = "t2"."id"You can also use join, right_join, inner_join or straight_join.
We already saw condition in first select. But there is more to show.
sql.where(id=42, type='answer')
# SELECT * FROM "t" WHERE "type" = 'answer' AND "id" = 42As you can see, you can pass more conditions. By default is always used AND. How to do something more complex?
sql.where((sqlpuzzle.Q(age=20) | sqlpuzzle.Q(age=30)) & sqlpuzzle.Q(country='cz')
# SELECT * FROM "t" WHERE ("age" = 20 OR "age" = 30) AND "country" = 'cz'Ok. What about relations – IN, LIKE and others?
sql.where(id=range(1, 4))
# SELECT * FROM "t" WHERE "id" IN (1, 2, 3)
sql.where(age=sqlpuzzle.relations.GE(21))
# SELECT * FROM "t" WHERE "age" > 21As you can see, if you pass list, it automatically use relation IN by default. But you can use relation which you want (if it's supported for that type; you can't use for example integer with IN).
List of all supported relations:
EQ(and aliasEQUAL_TO)NE(and aliasNOT_EQUAL_TO)GT(and aliasGRATHER_THAN)GE(and aliasGRATHER_THAN_OR_EQUAL_TO)LT(and aliasLESS_THAN)LE(and aliasLESS_THAN_OR_EQUAL_TO)LIKEREGEXPIN(default for list-like variables)NOT_INIS(default forNone)IS_NOT
Grouping is very similar to ordering.
Having is very similar to conditions.
sql.having(id=42)
# SELECT * FROM "t" HAVING "id" = 42sql.group_by('name', 'age')
# SELECT * FROM "t" GROUP BY "name", "age"Change order is same as making aliases:
sql.group_by('name', ('age', 'desc'))
# SELECT * FROM "t" GROUP BY "name", "age" DESCsqlpuzzle.select_from('t').limit(10)
# SELECT * FROM "t" LIMIT 10
sqlpuzzle.select_from('t').offset(5)
# SELECT * FROM "t" OFFSET 5
sqlpuzzle.select_from('t').limit(10, 5)
# SELECT * FROM "t" LIMIT 10 OFFSET 5sql.into_outfile('/tmp/out')
# SELECT * FROM "t" INTO OUTFILE '/tmp/out'And also you can use methods fields_terminated_by, lines_terminated_by and optionally_enclosed_by.
sql.for_update()
# SELECT * FROM "t" FOR UPDATEThis method also have param allow as methods for select options by which you can deactivate SELECT FOR UPDATE.
I think that it's now straightforward how base insert will look like.
sqlpuzzle.insert_into('table').values(name='Michael')
# INSERT INTO "table" ("name") VALUES ('Michael')Just call more times method values with different item.
sql = sqlpuzzle.insert_into('table')
sql.values(name='Tom')
sql.values(name='Jerry')
# INSERT INTO "table" ("name") VALUES ('Tom'), ('Jerry')You doesn't have to be worried about order of columns or that you always have to pass all columns.
sql = sqlpuzzle.insert_into('table')
sql.values(a=1)
sql.values(b='a')
INSERT INTO "table" ("a", "b") VALUES (1, NULL), (NULL, 'a')sqlpuzzle.insert_into('t').values(a=2).on_duplicate_key_update(b=4)
# INSERT INTO "t" ("a") VALUES (2) ON DUPLICATE KEY UPDATE "b" = 4sqlpuzzle.update('table').set(name='admin').where(id=1)
# UPDATE "table" SET "name" = 'admin' WHERE "id" = 1There is protection before updating all rows which you can turn on or off by methods allow_update_all or forbid_update_all.
sqlpuzzle.update('table').set(name='admin')
# raises ConfirmUpdateAllException
sqlpuzzle.update('table').set(name='admin').allow_update_all()
# UPDATE "table" SET "name" = 'admin'sqlpuzzle.delete_from('table').where(id=1)
# UPDATE "table" SET "name" = 'admin' WHERE "id" = 1It's same as for update, just methods and exception have different names.
sqlpuzzle.delete_from('t')
# raises ConfirmDeleteAllException
sqlpuzzle.delete_from('t').allow_delete_all()
# DELETE FROM "t"When you have two select query, you can use | or & to make UNION or UNION ALL.
s1 = sqlpuzzle.select_from('t')
s2 = sqlpuzzle.select_from('u')
s1 | s2
# SELECT * FROM "t" UNION SELECT * FROM "u"
s1 & s2
# SELECT * FROM "t" UNION ALL SELECT * FROM "u"Databases have some agregate functions. For example COUNT. You could use customsql for it, but there is better way for some common functions.
sqlpuzzle.select(sqlpuzzle.count()).from_('users')
# SELECT COUNT(*) FROM `users`
sqlpuzzle.select(sqlpuzzle.avg('age')).from_('users')
# SELECT AVG(`age`) FROM `users`List of all supported functions:
avgoravg_distinctcountmaxormax_distinctminormin_distinctsumorsum_distinctconcatgroup_contactconvert
This function have little bit more methods which you can use.
sqlpuzzle.group_contact('name').order_by('name').separator(',')
# GROUP_CONCAT(`name` ORDER BY `age` SEPARATOR ',')Second parameter of this function have to be some database type. There is no validation, because it's easier to allow everything and keep validation for database.
sqlpuzzle.convert('col', 'unsigned')
# CONVERT(`a`, UNSIGNED)Sometimes you have really complex part of query which you can't write with sqlpuzzle. That doesn't mean you have to write that whole query by yourself. You can use customsql or just C.
sqlpuzzle.select_from('t').where(sqlpuzzle.C('a=5'))
# SELECT * FROM "t" WHERE a=5You can pass customsql everywhere. But be aware – customsql does not provide escaping. Maybe better choice will be sqlvalue.
For example conditions for joins uses by default reference on both sides.
sqlpuzzle.select_from('t').left_join('u').on('t.id', 'u.id')
# SELECT * FROM "t" LEFT JOIN "u" ON "t"."id" = "u"."id"When you need use SQL value instead, use explicitly sqlvalue or just V.
sqlpuzzle.select_from('t').left_join('u').on('t.id', sqlpuzzle.V('hi'))
# SELECT * FROM "t" LEFT JOIN "u" ON "t"."id" = 'hi'Same for SQL references with sqlreference or just R.
sqlpuzzle.select_from('t', 'u').where('t.id', 'u.id')
# SELECT * FROM "t", "u" WHERE "t"."id" = 'u.id'
sqlpuzzle.select_from('t', 'u').where('t.id', sqlpuzzle.R('u'))
# SELECT * FROM "t", "u" WHERE "t"."id" = "u"."id"Everywhere, where aliases makes sense, you can use this.
sqlpuzzle.select('id', ('name', 'users name'), ('age', 'how old user is'))
# SELECT "id", "name" AS "users name", "age" AS "how old user is"
sqlpuzzle.select({'name': 'users name'})
# SELECT "name" AS "users name"
sqlpuzzle.select(name='users name')
# SELECT "name" AS "users name"Note: every column / table is put only once. Only if you have always different alias is same column / table putted more than once.
sqlpuzzle.select('a', 'a')
# SELECT "a"
sqlpuzzle.select('a', ('a', 'b'))
# SELECT "a", "a" AS "b"That means you can do this:
sqlpuzzle.select('name').from_('table').where(id=42)Instead of:
sql = sqlpuzzle.select('name')
sql.from_('table')
sql.where(id=42)You doesn't have to do escaping by yourself, sqlpuzzle already do this.
sqlpuzzle.select_from('db.table').where('table.column', "some 'value'")
# SELECT * FROM "db"."table" WHERE "table"."column" = 'some \'value\''As you can see, also dot is recognized. But you can force it.
sqlpuzzle.select_from('"db.table"')
# SELECT * FROM "db.table"It's easy to use dictionary for aliases or for change of order, but don't forget that dictionary doesn't have always same order.
sql.order_by({'b': 'desc', 'a': 'asc'})
# SELECT * FROM "t" ORDER BY "a", "b" DESCFor ORDER BY or GROUP BY is better to use list.
When you need do some base query for two queries, you can use method copy to clone that query.
sql = sqlpuzzle.select_from('t')
s1 = sql.copy()
s1.where(id=42)
s2 = sql.copy()
s2.where(id=24)There could be situation when you need to know if for example distinct is used.
sql = sqlpuzzle.select_from('t').distinct()
sql.has('distinct')
# TrueThis method have two param. First is for specifing part of query (for example 'where') and second is for searching in that part of query. Searching is not so stupid, it looks for words.
sql = sqlpuzzle.select_from('t')
sql.has('where')
# False
sql = sqlpuzzle.select_from('t').where(name='Michael')
sql.has('where')
# True
sql.has('where', 'name')
# True
sql = sqlpuzzle.select_from('t').where(surname='Michael')
sql.has('where', 'name')
# FalseIn first param you can use:
select_options(in fact you can type here already some select options as above)columnstableswheregroup_byhavingorder_bylimitinto_outfileselect_for_updatevalueson_duplicate_key_update
Enjoy!