|
From: <sub...@co...> - 2005-08-01 05:55:57
|
Author: ianb
Date: 2005-08-01 05:55:51 +0000 (Mon, 01 Aug 2005)
New Revision: 875
Modified:
trunk/SQLObject/docs/SQLObject.txt
Log:
Finished transition back to single document
Modified: trunk/SQLObject/docs/SQLObject.txt
===================================================================
--- trunk/SQLObject/docs/SQLObject.txt 2005-08-01 05:55:31 UTC (rev 874)
+++ trunk/SQLObject/docs/SQLObject.txt 2005-08-01 05:55:51 UTC (rev 875)
@@ -624,7 +624,7 @@
that's name starts with ``_set_``, ``_get_``, ``_del_``, or ``_doc_``,
it will be used to create a property. So, for instance, say you have
images stored under the ID of the person in the ``/var/people/images``
-directory:
+directory::
class Person(SQLObject):
# ...
@@ -683,7 +683,7 @@
To deal with this, SQLObject creates two methods for each getter and
setter, for example: ``_set_lastName`` and ``_SO_set_lastName``. So
-to intercept all changes to ``lastName``:
+to intercept all changes to ``lastName``::
class Person(SQLObject):
lastName = StringCol()
@@ -694,11 +694,31 @@
self._SO_set_lastName(value)
Or perhaps you want to constrain a phone numbers to be actual
-digits, and of proper length, and make the formatting nice:
+digits, and of proper length, and make the formatting nice::
-.. raw:: html
- :file: ../examples/snippets/phonenumber_magicoverride.html
+ import re
+ class PhoneNumber(SQLObject):
+ phoneNumber = StringCol(length=30)
+
+ _garbageCharactersRE = re.compile(r'[\-\.\(\) ]')
+ _phoneNumberRE = re.compile(r'^[0-9]+$')
+ def _set_phoneNumber(self, value):
+ value = self._garbageCharactersRE.sub('', value)
+ if not len(value) >= 10:
+ raise ValueError(
+ 'Phone numbers must be at least 10 digits long')
+ if not self._phoneNumberRE.match(value):
+ raise ValueError, 'Phone numbers can contain only digits'
+ self._SO_set_phoneNumber(value)
+
+ def _get_phoneNumber(self):
+ value = self._SO_get_phoneNumber()
+ number = '(%s) %s-%s' % (value[0:3], value[3:6], value[6:10])
+ if len(value) > 10:
+ number += ' ext.%s' % value[10:]
+ return number
+
.. note::
You should be a little cautious when modifying data that gets set
@@ -709,9 +729,6 @@
out. One advantage of methods (as opposed to attribute access) is
that the programmer is more likely to expect this disconnect.
-
-.. include:: SQLObjectCustomization.txt
-
Reference
=========
@@ -719,18 +736,752 @@
be useful for many situations. Now we'll show how to specify the
class more completely.
-.. include:: SQLObjectCol.txt
-.. include:: SQLObjectClasses.txt
-.. include:: SQLObjectRelationships.txt
-.. include:: SQLObjectTransactions.txt
-.. include:: SQLObjectGeneration.txt
-.. include:: SQLObjectDynamic.txt
-.. include:: SQLObjectLegacy.txt
-.. include:: SQLObjectDBConnection.txt
-.. include:: SQLObjectExported.txt
-.. include:: SQLObjectJoins.txt
-.. include:: SQLObjectSubqueries.txt
+Col Class: Specifying Columns
+-----------------------------
+The list of columns is a list of `Col` objects. These objects don't
+have functionality in themselves, but give you a way to specify the
+column.
+
+`dbName`:
+ This is the name of the column in the database. If you don't
+ give a name, your Pythonic name will be converted from
+ mixed-case to underscore-separated.
+`default`:
+ The default value for this column. Used when creating a new row.
+ If you give a callable object or function, the function will be
+ called, and the return value will be used. So you can give
+ ``DateTime.now`` to make the default value be the current time.
+ Or you can use ``SQLBuilder.func.NOW()`` to have the database use
+ the ``NOW()`` function internally. If you don't give a default
+ there will be an exception if this column isn't specified in the
+ call to `new`.
+`alternateID`:
+ This boolean (default False) indicates if the column can be used
+ as an ID for the field (for instance, a username), though it is
+ not a primary key. If so a class method will be added, like
+ ``byUsername`` which will return that object. Use
+ `alternateMethodName` if you don't like the ``by*`` name
+ (e.g. ``alternateMethodName="username"``).
+
+ The column should be declared ``UNIQUE`` in your table schema.
+`unique`:
+ If true, when SQLObject creates a table it will declare this
+ column to be ``UNIQUE``.
+`notNone`:
+ If true, None/``NULL`` is not allowed for this column. Useful if
+ you are using SQLObject to create your tables.
+`sqlType`:
+ The SQL type for this column (like ``INT``, ``BOOLEAN``, etc).
+ You can use classes (defined below) for this, but if those don't
+ work it's sometimes easiest just to use `sqlType`. Only necessary
+ if SQLObject is creating your tables.
+
+Subclasses of Col
+~~~~~~~~~~~~~~~~~
+
+The `ForeignKey` class should be used instead of `Col` when the column
+is a reference to another table/class. It is generally used like
+``ForeignKey('Role')``, in this instance to create a reference to a
+table `Role`. This is largely equivalent to ``Col(foreignKey='Role',
+sqlType='INT')``. Two attributes will generally be created, ``role``,
+which returns a `Role` instance, and ``roleID``, which returns an
+integer ID for the related role.
+
+There are some other subclasses of `Col`. These are used to indicate
+different types of columns, when SQLObject creates your tables.
+
+`BoolCol`:
+ Will create a ``BOOLEAN`` column in Postgres, or ``INT`` in other
+ databses. It will also convert values to ``"t"/"f"`` or ``0/1``
+ according to the database backend.
+
+`CurrencyCol`:
+ Equivalent to ``DecimalCol(size=10, precision=2)``.
+
+`DateTimeCol`:
+ A date and time (usually returned as an mxDateTime object).
+
+`DecimalCol`:
+ Base-10, precise number. Uses the keyword arguments `size` for
+ number of digits stored, and `precision` for the number of digits
+ after the decimal point.
+
+`EnumCol`:
+ One of several string values -- give the possible strings as a
+ list, with the `enumValues` keyword argument. MySQL has a native
+ ``ENUM`` type, but will work with other databases too (storage
+ just won't be as efficient).
+
+`FloatCol`:
+ Floats.
+
+`ForeignKey`:
+ A key to another table/class. Use like ``user =
+ ForeignKey('User')``.
+
+`IntCol`:
+ Integers.
+
+`StringCol`:
+ A string (character) column. Extra keywords:
+
+ `length`:
+ If given, the type will be something like ``VARCHAR(length)``.
+ If not given, then ``TEXT`` is assumed (i.e., lengthless).
+ `varchar`:
+ A boolean; if you have a length, differentiates between
+ ``CHAR`` and ``VARCHAR``, default True, i.e., use
+ ``VARCHAR``.
+
+`UnicodeCol`:
+ A subclass of `StringCol`. Also accepts a dbEncoding keyword
+ argument, which defaults to ``"UTF-8"``. Values coming in and
+ out from the database will be encoded and decoded. **Note**:
+ parameters in queries will not be automatically encoded, so if
+ you do a query matching a UnicodeCol column you must apply the
+ encoding yourself.
+
+
+SQLObject Class: Specifying Your Class
+--------------------------------------
+
+In addition to the columns, there are a number of other special
+attributes you can set in your class.
+
+`_connection`:
+ The connection object to use, from `DBConnection`. You can also
+ set the variable `__connection__` in the enclosing module and it
+ will be picked up (be sure to define `__connection__` before you
+ class). You can also pass a connection object in at instance
+ creation time, as described in transactions_.
+
+`_table`:
+ The database name of the table for this class. If you don't give
+ a name, then the standard ``MixedCase`` to ``mixed_case``
+ translation is performed.
+
+`_joins`:
+ A list of `Join` objects. This is covered below.
+
+`_cacheValues`:
+ If set to ``False`` then values for attributes from the database
+ won't be cached. So everytime you access an attribute in the
+ object the database will be queried for a value. If you want to
+ handle concurrent access to the database from multiple processes
+ then this is probably the way to do so. You should also use
+ it with transactions_ (it is not implied).
+
+.. _idName:
+
+`_idName`:
+ The name of the primary key column (default ``id``).
+
+`_style`:
+ A style object -- this object allows you to use other algorithms
+ for translating between Python attribute and class names, and the
+ database's column and table names. See `Changing the Naming
+ Style`_ for more.
+
+.. Relationships_:
+
+Relationships Between Classes/Tables
+------------------------------------
+
+You can use the `ForeignKey` to handle foreign references in a table,
+but for back references and many-to-many relationships you'll use
+joins.
+
+MultipleJoin: One-to-Many
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+See `One-to-Many Relationships`_ for an example of one-to-many
+relationships.
+
+Several keyword arguments are allowed to the `MultipleJoin` constructor:
+
+.. _`Multiple Join Keywords`:
+
+`joinColumn`:
+ The column name of the key that points to this table. So, if you have
+ a table ``Product``, and another table has a column ``ProductNo`` that
+ points to this table, then you'd use ``joinColumn="ProductNo"``.
+`orderBy`:
+ Like the `orderBy`_ argument to `select()`, you can specify
+ the order that the joined objects should be returned in. `_defaultOrder`
+ will be used if not specified; ``None`` forces unordered results.
+`joinMethodName`:
+ When adding joins dynamically (using the class method `addJoin`_),
+ you can give the name of the accessor for the join. It can also be
+ created automatically, and is normally implied (i.e., ``addresses =
+ MultipleJoin(...)`` implies ``joinMethodName="addresses"``).
+
+RelatedJoin: Many-to-Many
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+See `Many-to-Many Relationships`_ for examples of using many-to-many joins.
+
+`RelatedJoin` has all the keyword arguments of `MultipleJoin`__, plus:
+
+__ `Multiple Join Keywords`_
+
+`otherColumn`:
+ Similar to `joinColumn`, but referring to the joined class.
+`intermediateTable`:
+ The name of the intermediate table which references both classes.
+`addRemoveName`:
+ In the `user/role example`__, the methods `addRole(role)` and
+ `removeRole(role)` are created. The ``Role`` portion of these
+ method names can be changed by giving a string value here.
+
+__ `Many-to-Many Relationships`_
+
+An example schema that requires the use of `joinColumn`, `otherColumn`,
+and `intermediateTable`::
+
+ CREATE TABLE person (
+ id SERIAL,
+ username VARCHAR(100) NOT NULL UNIQUE
+ );
+
+ CREATE TABLE role (
+ id SERIAL,
+ name VARCHAR(50) NOT NULL UNIQUE
+ );
+
+ CREATE TABLE assigned_roles (
+ person INT NOT NULL,
+ role INT NOT NULL
+ );
+
+Then the usage in a class::
+
+ class Person(SQLObject):
+ username = StringCol(length=100, alternateID=True)
+ roles = RelatedJoin('Role', joinColumn='person', otherColumn='role',
+ intermediateTable='assigned_roles')
+ class Role(SQLObject):
+ name = StringCol(length=50, alternateID=True)
+ roles = RelatedJoin('Person', joinColumn='role', otherColumn='person',
+ intermediateTable='assigned_roles')
+
+SingleJoin: One-to-One
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Similar to `MultipleJoin`, but returns just one object, not a list.
+
+
+Transactions
+------------
+
+Transaction support in SQLObject is left to the database.
+Transactions can be used like::
+
+ conn = DBConnection.PostgresConnection('yada')
+ trans = conn.transaction()
+ p = Person.get(1, trans)
+ p.firstName = 'Bob'
+ trans.commit()
+ p.firstName = 'Billy'
+ trans.rollback()
+
+The ``trans`` object here is essentially a wrapper around a single
+database connection, and `commit` and `rollback` just pass that
+message to the `psycopg` connection.
+
+If you want to use transactions you should also turn `_cacheValues`
+off, like:
+
+ class Person(SQLObject):
+ _cacheValue = False
+ # ...
+
+Automatic Schema Generation
+---------------------------
+
+All the connections support creating and droping tables based on the
+class definition. First you have to prepare your class definition,
+which means including type information in your columns.
+
+Columns Types
+~~~~~~~~~~~~~
+
+A column type is indicated by using a subclass of `Col`:
+
+`StringCol`:
+ StringCol represents ``CHAR``, ``VARCHAR``, and ``TEXT``. The
+ `length` keyword argument indicates the ``CHAR`` or ``VARCHAR``
+ length -- if not given, then ``TEXT`` is assumed. If you use
+ ``varchar=False`` then ``CHAR`` will be used, otherwise
+ ``VARCHAR`` is the default.
+`IntCol`:
+ The ``INT`` type.
+`FloatCol`:
+ The ``FLOAT`` type.
+`DecimalCol`:
+ The ``DECIMAL`` SQL type, i.e., base 10 number. The keyword
+ arguments `size` and `precision` indicate the scope. So
+ ``DecimalCol(size=5, precision=2)`` is a number like ###.##,
+ i.e., 5 digits, two of them past the decimal point.
+`CurrencyCol`:
+ Like ``DecimalCol(size=10, precision=2)``.
+`EnumCol`:
+ A MySQL ``ENUM``, i.e., one of a finite number of strings.
+ For other databases this will be a ``VARCHAR``.
+`DateTimeCol`:
+ A moment in time. ``TIMESTAMP`` in Postgres, and ``DATETIME``
+ in MySQL. Note the names of these columns match the *Python*
+ type names, not the SQL names.
+`ForeignKey`:
+ This is a reference to another table. You typically need to
+ only give the name of the foreign class that is referenced.
+ `ForeignKey` implies an ``INT`` column.
+
+Indexes
+~~~~~~~
+
+You can also define indexes for your tables, which is only meaningful
+when creating your tables through SQLObject (SQLObject relies on the
+database to implement the indexes). You do this again with attribute
+assignment, like::
+
+ firstLastIndex = DatabaseIndex('firstName', 'lastName')
+
+This creates an index on two columns, useful if you are selecting a
+particular name. Of course, you can give a single column, and you can
+give the column object (``firstName``) instead of the string name.
+Note that if you use ``unique`` or ``alternateID`` (which implies
+``unique``) the database may make an index for you, and primary keys
+are always indexed.
+
+If you give the keyword argument ``unique`` to `DatabaseIndex` you'll
+create a unique index -- the combination of columns must be unique.
+
+You can also use dictionaries in place of the column names, to add
+extra options. E.g.::
+
+ lastNameIndex = DatabaseIndex({'expression': 'lower(last_name)'})
+
+In that case, the index will be on the lower-case version of the
+column. It seems that only PostgreSQL supports this. You can also
+do::
+
+ lastNameIndex = DatabaseIndex({'column': lastName, 'length': 10})
+
+Which asks the database to only pay attention to the first ten
+characters. Only MySQL supports this, but it is ignored in other
+databases.
+
+Creating and Dropping Tables
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To create a table call `createTable`. It takes two arguments:
+
+`ifNotExists`:
+ If the table already exists, then don't try to create it. Default
+ False.
+`createJoinTables`:
+ If you used `Many-to-Many relationships`_, then the intermediate tables
+ will be created (but only for one of the two involved classes).
+ Default True.
+
+`dropTable` takes arguments `ifExists` and `dropJoinTables`,
+self-explanatory.
+
+Dynamic Classes
+===============
+
+SQLObject classes can be manipulated dynamically. This leaves open
+the possibility of constructing SQLObject classes from an XML file,
+from database introspection, or from a graphical interface.
+
+Automatic Class Generation
+---------------------------
+
+SQLObject can read the table description from the database, and fill
+in the class columns (as would normally be described in the `_columns`
+attribute). Do this like::
+
+ class Person(SQLObject):
+
+ _fromDatabase = True
+
+You can still specify columns (in `_columns`), and only missing
+columns will be added.
+
+*This is not supported in SQLite*
+
+Runtime Column Changes
+----------------------
+
+*SQLite does not support this feature*
+
+You can add and remove columns to your class at runtime. Such changes
+will effect all instances, since changes are made inplace to the
+class. There are two methods, `addColumn` and `delColumn`, both of
+which take a `Col` object (or subclass) as an argument. There's also
+an option argument `changeSchema` which, if True, will add or drop the
+column from the database (typically with an ``ALTER`` command).
+
+When adding columns, you must pass the name as part of the column
+constructor, like ``StringCol("username", length=20)``. When removing
+columns, you can either use the Col object (as found in `_columns`, or
+which you used in `addColumn`), or you can use the column name (like
+``MyClass.delColumn("username")``).
+
+.. _addJoin:
+
+You can also add Joins__, like
+``MyClass.addJoin(MultipleJoin("MyOtherClass"))``, and remove joins with
+`delJoin`. `delJoin` does not take strings, you have to get the join
+object out of the `_joins` attribute.
+
+__ Relationships_:
+
+Legacy Database Schemas
+=======================
+
+Often you will have a database that already exists, and does not use
+the naming conventions that SQLObject expects, or does not use any
+naming convention at all.
+
+SQLObject requirements
+----------------------
+
+While SQLObject tries not to make too many requirements on your
+schema, some assumptions are made. Some of these may be relaxed in
+the future.
+
+All tables that you want to turn into a class need to have an integer
+primary key. That key should be defined like:
+
+MySQL:
+ ``INT PRIMARY KEY AUTO_INCREMENT``
+Postgres:
+ ``SERIAL PRIMARY KEY``
+SQLite:
+ ``INTEGER PRIMARY KEY``
+
+SQLObject does not support non-integer keys (that may change). It
+does not support sequences in Postgres (that will change -- ``SERIAL``
+uses an implicit sequence). It does not support primary keys made up
+of multiple columns (that probably won't change). It does not
+generally support tables with primary keys with business meaning --
+i.e., primary keys are assumed to be immutable (that won't change).
+
+At the moment foreign key column names must end in ``"ID"``
+(case-insensitive). This restriction will probably be removed in the
+next release.
+
+Changing the Naming Style
+-------------------------
+
+By default names in SQLObject are expected to be mixed case in Python
+(like ``mixedCase``), and underscore-separated in SQL (like
+``mixed_case``). This applies to table and column names. The primary
+key is assumed to be simply ``id``.
+
+Other styles exist. A typical one is mixed case column names, and a
+primary key that includes the table name, like ``ProductID``. You can
+use a different "Style" object to indicate a different naming
+convention. For instance::
+
+ class Person(SQLObject):
+ _style = MixedCaseStyle(longID=True)
+
+ firstName = StringCol()
+ lastName = StringCol()
+
+If you use ``Person.createTable()``, you'll get::
+
+ CREATE TABLE Person (
+ PersonID INT PRIMARY KEY,
+ FirstName Text,
+ LastName Text
+ )
+
+The `MixedCaseStyle` object handles the initial capitalization of
+words, but otherwise leaves them be. By using ``longID=True``, we
+indicate that the primary key should look like a normal reference
+(``PersonID`` for `MixedCaseStyle`, or ``person_id`` for the default
+style).
+
+If you wish to change the style globally, assign the style to the
+connection, like::
+
+ __connection__.style = MixedCaseStyle(longID=True)
+
+Irregular Naming
+----------------
+
+While naming conventions are nice, they are not always present. You
+can control most of the names that SQLObject uses, independent of the
+Python names (so at least you don't have to propagate the
+irregularity to your brand-spanking new Python code).
+
+Here's a simple example::
+
+ class User(SQLObject):
+ _table = "user_table"
+ _idName = "userid"
+
+ username = StringCol(length=20, dbName='name')
+
+The attribute `_table` overrides the table name. `_idName` provides
+an alternative to ``id``. The ``dbName`` keyword argument gives the
+column name.
+
+Non-Integer Keys
+----------------
+
+While not strictly a legacy database issue, this fits into the
+category of "irregularities". If you use non-integer keys, all
+primary key management is up to you. You must create the table
+yourself, and when you create instances you must pass a ``id`` keyword
+argument into constructor (like ``Person(id='555-55-5555', ...)``).
+
+DBConnection: Database Connections
+==================================
+
+The `DBConnection` module currently has four external classes,
+`MySQLConnection`, `PostgresConnection`, `SQLiteConnection`,
+`SybaseConnection`, and `MaxdbConnection`.
+
+You can pass the keyword argument `debug` to any connector. If set to
+true, then any SQL sent to the database will also be printed to the
+console.
+
+MySQL
+-----
+
+`MySQLConnection` takes the keyword arguments `host`, `db`, `user`,
+and `passwd`, just like `MySQLdb.connect` does.
+
+MySQLConnection supports all the features, though MySQL only supports
+transactions_ when using the InnoDB backend, and SQLObject currently
+does not have support for explicitly defining the backend when using
+``createTable``.
+
+Postgres
+--------
+
+`PostgresConnection` takes a single connection string, like
+``"dbname=something user=some_user"``, just like `psycopg.connect`.
+You can also use the same keyword arguments as for `MySQLConnection`,
+and a dsn string will be constructed.
+
+PostgresConnection supports transactions and all other features.
+
+SQLite
+------
+
+`SQLiteConnection` takes the a single string, which is the path to the
+database file.
+
+SQLite puts all data into one file, with a journal file that is opened
+in the same directory during operation (the file is deleted when the
+program quits). SQLite does not restrict the types you can put in a
+column -- strings can go in integer columns, dates in integers, etc.
+
+SQLiteConnection doesn't support `automatic class generation`_ and
+SQLite does not support `runtime column changes`_.
+
+SQLite may have concurrency issues, depending on your usage in a
+multi-threaded environment.
+
+Firebird
+--------
+
+`FirebirdConnection` takes the arguments `host`, `db`, `user` (default
+``"sysdba"``), `passwd` (default ``"masterkey"``).
+
+Firebird supports all the features. Support is still young, so there
+may be some issues, especially with concurrent access, and especially
+using lazy selects. Try ``list(MyClass.select())`` to avoid
+concurrent cursors if you have problems (using ``list()`` will
+pre-fetch all the results of a select).
+
+Firebird support uses the kinterbasdb_ Python library.
+
+.. _kinterbasdb: http://kinterbasdb.sourceforge.net/
+
+If you are using indexes and get an error like *key size exceeds
+implementation restriction for index*, see `this page`_ to understand
+the restrictions on your indexing.
+
+.. _this page: http://www.volny.cz/iprenosil/interbase/ip_ib_indexcalculator.htm
+
+SybaseConnection
+----------------
+
+`SybaseConnection` takes the arguments `host`, `db`, `user`, and
+`passwd`. It also takes the extra boolean argument `locking` (default
+True), which is passed through when performing a connection. You may
+use a False value for `locking` if you are not using multiple threads,
+for a slight performance boost.
+
+It uses the Sybase_ module.
+
+.. _Sybase: http://www.object-craft.com.au/projects/sybase/
+
+MAX DB
+------
+
+MAX DB, also known as SAP DB, is available from a partnership of SAP
+and MySQL. It takes the typical arguments: `host`, `database`,
+`user`, `password`. It also takes the arguments `sqlmode` (default
+``"internal"``), `isolation`, and `timeout`, which are passed through
+when creating the connection to the database.
+
+It uses the sapdb_ module.
+
+.. _sapdb: http://www.sapdb.org/sapdbPython.html
+
+Exported Symbols
+================
+
+You can use ``from sqlobject import *``, though you don't have to. It
+exports a minimal number of symbols. The symbols exported:
+
+From `sqlobject.main`:
+
+* `NoDefault`
+* `SQLObject`
+* `getID`
+* `getObject`
+
+From `sqlobject.col`:
+* `Col`
+* `StringCol`
+* `IntCol`
+* `FloatCol`
+* `KeyCol`
+* `ForeignKey`
+* `EnumCol`
+* `DateTimeCol`
+* `DecimalCol`
+* `CurrencyCol`
+
+From `sqlobject.joins`:
+* `MultipleJoin`
+* `RelatedJoin`
+
+From `sqlobject.styles`:
+* `Style`
+* `MixedCaseUnderscoreStyle`
+* `DefaultStyle`
+* `MixedCaseStyle`
+
+From `sqlobject.sqlbuilder`:
+
+* `AND`
+* `OR`
+* `NOT`
+* `IN`
+* `LIKE`
+* `DESC`
+* `CONTAINSSTRING`
+* `const`
+* `func`
+
+LEFT JOIN and other JOINs
+-------------------------
+
+First look in the FAQ_, question "How can I do a LEFT JOIN?"
+
+Still here? Well. To perform a JOIN use one of the JOIN helpers from
+sqlobject.sqlbuilder. Pass an instance of the helper to .select()
+method. For example::
+
+ from sqlobject.sqlbuilder import LEFTJOINOn
+ MyTable.select(
+ join=LEFTJOINOn(Table1, Table2,
+ Table1.q.name == Table2.q.value))
+
+will generate the query::
+
+ SELECT * FROM my_table, table1
+ LEFT JOIN table2 ON table1.name = table2.value;
+
+.. _FAQ: FAQ.html#how-can-i-do-a-left-join
+
+If you want to join with the primary table - leave the first table
+None::
+
+ MyTable.select(
+ join=LEFTJOINOn(None, Table1,
+ MyTable.q.name == Table1.q.value))
+
+will generate the query::
+
+ SELECT * FROM my_table
+ LEFT JOIN table2 ON my_table.name = table1.value;
+
+The join argument for .select() can be a JOIN() or a list/tuples of
+JOIN()s.
+
+
+How can I join a table with itself?
+-----------------------------------
+
+Use Alias from sqlobject.sqlbuilder. Example::
+
+ from sqlobject.sqlbuilder import Alias
+ alias = Alias(MyTable, "my_table_alias")
+ MyTable.select(MyTable.q.name == alias.q.value)
+
+will generate the query::
+
+ SELECT * FROM my_table, my_table AS my_table_alias
+ WHERE my_table.name = my_table_alias.value;
+
+Can I use LEFTJOIN() with aliases?
+----------------------------------
+
+Sure! That's a situation the JOINs and aliases were primary developed
+for. Code::
+
+ from sqlobject.sqlbuilder import LEFTJOINOn, Alias
+ alias = Alias(OtherTable, "other_table_alias")
+ MyTable.select(MyTable.q.name == OtherTable.q.value,
+ join=LEFTJOINOn(MyTable, alias, MyTable.col1 == alias.q.col2))
+
+will result in the query::
+
+ SELECT * FROM other_table,
+ my_table LEFT JOIN other_table AS other_table_alias
+ WHERE my_table.name == other_table.value AND
+ my_table.col1 = other_table_alias.col2.
+
+Subqueries (subselects)
+-----------------------
+
+You can run queries with subqueries (subselects) on those DBMS that can do
+subqueries (MySQL supports subqueries from version 4.1).
+
+Use corresponding classess and functions from sqlbuilder::
+
+ from sqlobject.sqlbuilder import EXISTS, Select
+ select = Test1.select(EXISTS(Select(Test2.q.col2, where=(Outer(Test1).q.col1 == Test2.q.col2))))
+
+generates the query::
+
+ SELECT test1.id, test1.col1 FROM test1 WHERE
+ EXISTS (SELECT test2.col2 FROM test2 WHERE (test1.col1 = test2.col2))
+
+Note the usage of Outer - this is the helper to allow refering to a table
+in the outer query.
+
+Select() is used instead of .select() because you need to control what
+columns and in what order the inner query returns.
+
+Avalable queries are IN(), NOTIN(), EXISTS(), NOTEXISTS(), SOME(), ANY()
+and ALL(). The last 3 are used with comparison operators, like this:
+"somevalue = ANY(Select(...))".
+
+SQLBuilder
+----------
+
For more information on SQLBuilder, read the `SQLBuilder
Documentation`_.
|