1359 lines
37 KiB
Plaintext
1359 lines
37 KiB
Plaintext
Metadata-Version: 2.4
|
|
Name: PyPika
|
|
Version: 0.48.9
|
|
Summary: A SQL query builder API for Python
|
|
Home-page: https://github.com/kayak/pypika
|
|
Author: Timothy Heys
|
|
Author-email: theys@kayak.com
|
|
License: Apache License Version 2.0
|
|
Keywords: pypika python query builder querybuilder sql mysql postgres psql oracle vertica aggregated relational database rdbms business analytics bi data science analysis pandas orm object mapper
|
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
Classifier: Development Status :: 5 - Production/Stable
|
|
Classifier: Intended Audience :: Developers
|
|
Classifier: Programming Language :: Python :: 3
|
|
Classifier: Programming Language :: PL/SQL
|
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
Classifier: Operating System :: POSIX
|
|
Classifier: Operating System :: MacOS :: MacOS X
|
|
Classifier: Operating System :: Microsoft :: Windows
|
|
License-File: LICENSE.txt
|
|
Dynamic: author
|
|
Dynamic: author-email
|
|
Dynamic: classifier
|
|
Dynamic: description
|
|
Dynamic: home-page
|
|
Dynamic: keywords
|
|
Dynamic: license
|
|
Dynamic: license-file
|
|
Dynamic: summary
|
|
|
|
PyPika - Python Query Builder
|
|
=============================
|
|
|
|
.. _intro_start:
|
|
|
|
|BuildStatus| |CoverageStatus| |Codacy| |Docs| |PyPi| |License|
|
|
|
|
Abstract
|
|
--------
|
|
|
|
What is |Brand|?
|
|
|
|
|Brand| is a Python API for building SQL queries. The motivation behind |Brand| is to provide a simple interface for
|
|
building SQL queries without limiting the flexibility of handwritten SQL. Designed with data analysis in mind, |Brand|
|
|
leverages the builder design pattern to construct queries to avoid messy string formatting and concatenation. It is also
|
|
easily extended to take full advantage of specific features of SQL database vendors.
|
|
|
|
What are the design goals for |Brand|?
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
|Brand| is a fast, expressive and flexible way to replace handwritten SQL (or even ORM for the courageous souls amongst you).
|
|
Validation of SQL correctness is not an explicit goal of |Brand|. With such a large number of
|
|
SQL database vendors providing a robust validation of input data is difficult. Instead you are encouraged to check inputs you provide to |Brand| or appropriately handle errors raised from
|
|
your SQL database - just as you would have if you were writing SQL yourself.
|
|
|
|
.. _intro_end:
|
|
|
|
Read the docs: http://pypika.readthedocs.io/en/latest/
|
|
|
|
Installation
|
|
------------
|
|
|
|
.. _installation_start:
|
|
|
|
|Brand| supports python ``3.6+``. It may also work on pypy, cython, and jython, but is not being tested for these versions.
|
|
|
|
To install |Brand| run the following command:
|
|
|
|
.. code-block:: bash
|
|
|
|
pip install pypika
|
|
|
|
|
|
.. _installation_end:
|
|
|
|
|
|
Tutorial
|
|
--------
|
|
|
|
.. _tutorial_start:
|
|
|
|
The main classes in pypika are ``pypika.Query``, ``pypika.Table``, and ``pypika.Field``.
|
|
|
|
.. code-block:: python
|
|
|
|
from pypika import Query, Table, Field
|
|
|
|
|
|
Selecting Data
|
|
^^^^^^^^^^^^^^
|
|
|
|
The entry point for building queries is ``pypika.Query``. In order to select columns from a table, the table must
|
|
first be added to the query. For simple queries with only one table, tables and columns can be references using
|
|
strings. For more sophisticated queries a ``pypika.Table`` must be used.
|
|
|
|
.. code-block:: python
|
|
|
|
q = Query.from_('customers').select('id', 'fname', 'lname', 'phone')
|
|
|
|
To convert the query into raw SQL, it can be cast to a string.
|
|
|
|
.. code-block:: python
|
|
|
|
str(q)
|
|
|
|
Alternatively, you can use the `Query.get_sql()` function:
|
|
|
|
.. code-block:: python
|
|
|
|
q.get_sql()
|
|
|
|
|
|
Tables, Columns, Schemas, and Databases
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
In simple queries like the above example, columns in the "from" table can be referenced by passing string names into
|
|
the ``select`` query builder function. In more complex examples, the ``pypika.Table`` class should be used. Columns can be
|
|
referenced as attributes on instances of ``pypika.Table``.
|
|
|
|
.. code-block:: python
|
|
|
|
from pypika import Table, Query
|
|
|
|
customers = Table('customers')
|
|
q = Query.from_(customers).select(customers.id, customers.fname, customers.lname, customers.phone)
|
|
|
|
Both of the above examples result in the following SQL:
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT id,fname,lname,phone FROM customers
|
|
|
|
An alias for the table can be given using the ``.as_`` function on ``pypika.Table``
|
|
|
|
.. code-block:: sql
|
|
|
|
customers = Table('x_view_customers').as_('customers')
|
|
q = Query.from_(customers).select(customers.id, customers.phone)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT id,phone FROM x_view_customers customers
|
|
|
|
A schema can also be specified. Tables can be referenced as attributes on the schema.
|
|
|
|
.. code-block:: sql
|
|
|
|
from pypika import Table, Query, Schema
|
|
|
|
views = Schema('views')
|
|
q = Query.from_(views.customers).select(customers.id, customers.phone)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT id,phone FROM views.customers
|
|
|
|
Also references to databases can be used. Schemas can be referenced as attributes on the database.
|
|
|
|
.. code-block:: sql
|
|
|
|
from pypika import Table, Query, Database
|
|
|
|
my_db = Database('my_db')
|
|
q = Query.from_(my_db.analytics.customers).select(customers.id, customers.phone)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT id,phone FROM my_db.analytics.customers
|
|
|
|
|
|
Results can be ordered by using the following syntax:
|
|
|
|
.. code-block:: python
|
|
|
|
from pypika import Order
|
|
Query.from_('customers').select('id', 'fname', 'lname', 'phone').orderby('id', order=Order.desc)
|
|
|
|
This results in the following SQL:
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT "id","fname","lname","phone" FROM "customers" ORDER BY "id" DESC
|
|
|
|
Arithmetic
|
|
""""""""""
|
|
|
|
Arithmetic expressions can also be constructed using pypika. Operators such as `+`, `-`, `*`, and `/` are implemented
|
|
by ``pypika.Field`` which can be used simply with a ``pypika.Table`` or directly.
|
|
|
|
.. code-block:: python
|
|
|
|
from pypika import Field
|
|
|
|
q = Query.from_('account').select(
|
|
Field('revenue') - Field('cost')
|
|
)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT revenue-cost FROM accounts
|
|
|
|
Using ``pypika.Table``
|
|
|
|
.. code-block:: python
|
|
|
|
accounts = Table('accounts')
|
|
q = Query.from_(accounts).select(
|
|
accounts.revenue - accounts.cost
|
|
)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT revenue-cost FROM accounts
|
|
|
|
An alias can also be used for fields and expressions.
|
|
|
|
.. code-block:: sql
|
|
|
|
q = Query.from_(accounts).select(
|
|
(accounts.revenue - accounts.cost).as_('profit')
|
|
)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT revenue-cost profit FROM accounts
|
|
|
|
More arithmetic examples
|
|
|
|
.. code-block:: python
|
|
|
|
table = Table('table')
|
|
q = Query.from_(table).select(
|
|
table.foo + table.bar,
|
|
table.foo - table.bar,
|
|
table.foo * table.bar,
|
|
table.foo / table.bar,
|
|
(table.foo+table.bar) / table.fiz,
|
|
)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT foo+bar,foo-bar,foo*bar,foo/bar,(foo+bar)/fiz FROM table
|
|
|
|
|
|
Filtering
|
|
"""""""""
|
|
|
|
Queries can be filtered with ``pypika.Criterion`` by using equality or inequality operators
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
q = Query.from_(customers).select(
|
|
customers.id, customers.fname, customers.lname, customers.phone
|
|
).where(
|
|
customers.lname == 'Mustermann'
|
|
)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT id,fname,lname,phone FROM customers WHERE lname='Mustermann'
|
|
|
|
Query methods such as select, where, groupby, and orderby can be called multiple times. Multiple calls to the where
|
|
method will add additional conditions as
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
q = Query.from_(customers).select(
|
|
customers.id, customers.fname, customers.lname, customers.phone
|
|
).where(
|
|
customers.fname == 'Max'
|
|
).where(
|
|
customers.lname == 'Mustermann'
|
|
)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT id,fname,lname,phone FROM customers WHERE fname='Max' AND lname='Mustermann'
|
|
|
|
Filters such as IN and BETWEEN are also supported
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
q = Query.from_(customers).select(
|
|
customers.id,customers.fname
|
|
).where(
|
|
customers.age[18:65] & customers.status.isin(['new', 'active'])
|
|
)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT id,fname FROM customers WHERE age BETWEEN 18 AND 65 AND status IN ('new','active')
|
|
|
|
Filtering with complex criteria can be created using boolean symbols ``&``, ``|``, and ``^``.
|
|
|
|
AND
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
q = Query.from_(customers).select(
|
|
customers.id, customers.fname, customers.lname, customers.phone
|
|
).where(
|
|
(customers.age >= 18) & (customers.lname == 'Mustermann')
|
|
)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT id,fname,lname,phone FROM customers WHERE age>=18 AND lname='Mustermann'
|
|
|
|
OR
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
q = Query.from_(customers).select(
|
|
customers.id, customers.fname, customers.lname, customers.phone
|
|
).where(
|
|
(customers.age >= 18) | (customers.lname == 'Mustermann')
|
|
)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT id,fname,lname,phone FROM customers WHERE age>=18 OR lname='Mustermann'
|
|
|
|
XOR
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
q = Query.from_(customers).select(
|
|
customers.id, customers.fname, customers.lname, customers.phone
|
|
).where(
|
|
(customers.age >= 18) ^ customers.is_registered
|
|
)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT id,fname,lname,phone FROM customers WHERE age>=18 XOR is_registered
|
|
|
|
|
|
Convenience Methods
|
|
"""""""""""""""""""
|
|
|
|
In the `Criterion` class, there are the static methods `any` and `all` that allow building chains AND and OR expressions with a list of terms.
|
|
|
|
.. code-block:: python
|
|
|
|
from pypika import Criterion
|
|
|
|
customers = Table('customers')
|
|
q = Query.from_(customers).select(
|
|
customers.id,
|
|
customers.fname
|
|
).where(
|
|
Criterion.all([
|
|
customers.is_registered,
|
|
customers.age >= 18,
|
|
customers.lname == "Jones",
|
|
])
|
|
)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT id,fname FROM customers WHERE is_registered AND age>=18 AND lname = "Jones"
|
|
|
|
|
|
Grouping and Aggregating
|
|
""""""""""""""""""""""""
|
|
|
|
Grouping allows for aggregated results and works similar to ``SELECT`` clauses.
|
|
|
|
.. code-block:: python
|
|
|
|
from pypika import functions as fn
|
|
|
|
customers = Table('customers')
|
|
q = Query \
|
|
.from_(customers) \
|
|
.where(customers.age >= 18) \
|
|
.groupby(customers.id) \
|
|
.select(customers.id, fn.Sum(customers.revenue))
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT id,SUM("revenue") FROM "customers" WHERE "age">=18 GROUP BY "id"
|
|
|
|
After adding a ``GROUP BY`` clause to a query, the ``HAVING`` clause becomes available. The method
|
|
``Query.having()`` takes a ``Criterion`` parameter similar to the method ``Query.where()``.
|
|
|
|
.. code-block:: python
|
|
|
|
from pypika import functions as fn
|
|
|
|
payments = Table('payments')
|
|
q = Query \
|
|
.from_(payments) \
|
|
.where(payments.transacted[date(2015, 1, 1):date(2016, 1, 1)]) \
|
|
.groupby(payments.customer_id) \
|
|
.having(fn.Sum(payments.total) >= 1000) \
|
|
.select(payments.customer_id, fn.Sum(payments.total))
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT customer_id,SUM(total) FROM payments
|
|
WHERE transacted BETWEEN '2015-01-01' AND '2016-01-01'
|
|
GROUP BY customer_id HAVING SUM(total)>=1000
|
|
|
|
|
|
Joining Tables and Subqueries
|
|
"""""""""""""""""""""""""""""
|
|
|
|
Tables and subqueries can be joined to any query using the ``Query.join()`` method. Joins can be performed with either
|
|
a ``USING`` or ``ON`` clauses. The ``USING`` clause can be used when both tables/subqueries contain the same field and
|
|
the ``ON`` clause can be used with a criterion. To perform a join, ``...join()`` can be chained but then must be
|
|
followed immediately by ``...on(<criterion>)`` or ``...using(*field)``.
|
|
|
|
|
|
Join Types
|
|
~~~~~~~~~~
|
|
|
|
All join types are supported by |Brand|.
|
|
|
|
.. code-block:: python
|
|
|
|
Query \
|
|
.from_(base_table)
|
|
...
|
|
.join(join_table, JoinType.left)
|
|
...
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
Query \
|
|
.from_(base_table)
|
|
...
|
|
.left_join(join_table) \
|
|
.left_outer_join(join_table) \
|
|
.right_join(join_table) \
|
|
.right_outer_join(join_table) \
|
|
.inner_join(join_table) \
|
|
.outer_join(join_table) \
|
|
.full_outer_join(join_table) \
|
|
.cross_join(join_table) \
|
|
.hash_join(join_table) \
|
|
...
|
|
|
|
See the list of join types here ``pypika.enums.JoinTypes``
|
|
|
|
Example of a join using `ON`
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
history, customers = Tables('history', 'customers')
|
|
q = Query \
|
|
.from_(history) \
|
|
.join(customers) \
|
|
.on(history.customer_id == customers.id) \
|
|
.select(history.star) \
|
|
.where(customers.id == 5)
|
|
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT "history".* FROM "history" JOIN "customers" ON "history"."customer_id"="customers"."id" WHERE "customers"."id"=5
|
|
|
|
As a shortcut, the ``Query.join().on_field()`` function is provided for joining the (first) table in the ``FROM`` clause
|
|
with the joined table when the field name(s) are the same in both tables.
|
|
|
|
Example of a join using `ON`
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
history, customers = Tables('history', 'customers')
|
|
q = Query \
|
|
.from_(history) \
|
|
.join(customers) \
|
|
.on_field('customer_id', 'group') \
|
|
.select(history.star) \
|
|
.where(customers.group == 'A')
|
|
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT "history".* FROM "history" JOIN "customers" ON "history"."customer_id"="customers"."customer_id" AND "history"."group"="customers"."group" WHERE "customers"."group"='A'
|
|
|
|
|
|
Example of a join using `USING`
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
history, customers = Tables('history', 'customers')
|
|
q = Query \
|
|
.from_(history) \
|
|
.join(customers) \
|
|
.using('customer_id') \
|
|
.select(history.star) \
|
|
.where(customers.id == 5)
|
|
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT "history".* FROM "history" JOIN "customers" USING "customer_id" WHERE "customers"."id"=5
|
|
|
|
|
|
Example of a correlated subquery in the `SELECT`
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
history, customers = Tables('history', 'customers')
|
|
last_purchase_at = Query.from_(history).select(
|
|
history.purchase_at
|
|
).where(history.customer_id==customers.customer_id).orderby(
|
|
history.purchase_at, order=Order.desc
|
|
).limit(1)
|
|
q = Query.from_(customers).select(
|
|
customers.id, last_purchase_at.as_('last_purchase_at')
|
|
)
|
|
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT
|
|
"id",
|
|
(SELECT "history"."purchase_at"
|
|
FROM "history"
|
|
WHERE "history"."customer_id" = "customers"."customer_id"
|
|
ORDER BY "history"."purchase_at" DESC
|
|
LIMIT 1) "last_purchase_at"
|
|
FROM "customers"
|
|
|
|
|
|
Unions
|
|
""""""
|
|
|
|
Both ``UNION`` and ``UNION ALL`` are supported. ``UNION DISTINCT`` is synonomous with "UNION`` so |Brand| does not
|
|
provide a separate function for it. Unions require that queries have the same number of ``SELECT`` clauses so
|
|
trying to cast a unioned query to string will throw a ``SetOperationException`` if the column sizes are mismatched.
|
|
|
|
To create a union query, use either the ``Query.union()`` method or `+` operator with two query instances. For a
|
|
union all, use ``Query.union_all()`` or the `*` operator.
|
|
|
|
.. code-block:: python
|
|
|
|
provider_a, provider_b = Tables('provider_a', 'provider_b')
|
|
q = Query.from_(provider_a).select(
|
|
provider_a.created_time, provider_a.foo, provider_a.bar
|
|
) + Query.from_(provider_b).select(
|
|
provider_b.created_time, provider_b.fiz, provider_b.buz
|
|
)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT "created_time","foo","bar" FROM "provider_a" UNION SELECT "created_time","fiz","buz" FROM "provider_b"
|
|
|
|
Intersect
|
|
"""""""""
|
|
|
|
``INTERSECT`` is supported. Intersects require that queries have the same number of ``SELECT`` clauses so
|
|
trying to cast a intersected query to string will throw a ``SetOperationException`` if the column sizes are mismatched.
|
|
|
|
To create a intersect query, use the ``Query.intersect()`` method.
|
|
|
|
.. code-block:: python
|
|
|
|
provider_a, provider_b = Tables('provider_a', 'provider_b')
|
|
q = Query.from_(provider_a).select(
|
|
provider_a.created_time, provider_a.foo, provider_a.bar
|
|
)
|
|
r = Query.from_(provider_b).select(
|
|
provider_b.created_time, provider_b.fiz, provider_b.buz
|
|
)
|
|
intersected_query = q.intersect(r)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT "created_time","foo","bar" FROM "provider_a" INTERSECT SELECT "created_time","fiz","buz" FROM "provider_b"
|
|
|
|
Minus
|
|
"""""
|
|
|
|
``MINUS`` is supported. Minus require that queries have the same number of ``SELECT`` clauses so
|
|
trying to cast a minus query to string will throw a ``SetOperationException`` if the column sizes are mismatched.
|
|
|
|
To create a minus query, use either the ``Query.minus()`` method or `-` operator with two query instances.
|
|
|
|
.. code-block:: python
|
|
|
|
provider_a, provider_b = Tables('provider_a', 'provider_b')
|
|
q = Query.from_(provider_a).select(
|
|
provider_a.created_time, provider_a.foo, provider_a.bar
|
|
)
|
|
r = Query.from_(provider_b).select(
|
|
provider_b.created_time, provider_b.fiz, provider_b.buz
|
|
)
|
|
minus_query = q.minus(r)
|
|
|
|
(or)
|
|
|
|
minus_query = Query.from_(provider_a).select(
|
|
provider_a.created_time, provider_a.foo, provider_a.bar
|
|
) - Query.from_(provider_b).select(
|
|
provider_b.created_time, provider_b.fiz, provider_b.buz
|
|
)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT "created_time","foo","bar" FROM "provider_a" MINUS SELECT "created_time","fiz","buz" FROM "provider_b"
|
|
|
|
EXCEPT
|
|
""""""
|
|
|
|
``EXCEPT`` is supported. Minus require that queries have the same number of ``SELECT`` clauses so
|
|
trying to cast a except query to string will throw a ``SetOperationException`` if the column sizes are mismatched.
|
|
|
|
To create a except query, use the ``Query.except_of()`` method.
|
|
|
|
.. code-block:: python
|
|
|
|
provider_a, provider_b = Tables('provider_a', 'provider_b')
|
|
q = Query.from_(provider_a).select(
|
|
provider_a.created_time, provider_a.foo, provider_a.bar
|
|
)
|
|
r = Query.from_(provider_b).select(
|
|
provider_b.created_time, provider_b.fiz, provider_b.buz
|
|
)
|
|
minus_query = q.except_of(r)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT "created_time","foo","bar" FROM "provider_a" EXCEPT SELECT "created_time","fiz","buz" FROM "provider_b"
|
|
|
|
Date, Time, and Intervals
|
|
"""""""""""""""""""""""""
|
|
|
|
Using ``pypika.Interval``, queries can be constructed with date arithmetic. Any combination of intervals can be
|
|
used except for weeks and quarters, which must be used separately and will ignore any other values if selected.
|
|
|
|
.. code-block:: python
|
|
|
|
from pypika import functions as fn
|
|
|
|
fruits = Tables('fruits')
|
|
q = Query.from_(fruits) \
|
|
.select(fruits.id, fruits.name) \
|
|
.where(fruits.harvest_date + Interval(months=1) < fn.Now())
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT id,name FROM fruits WHERE harvest_date+INTERVAL 1 MONTH<NOW()
|
|
|
|
|
|
Tuples
|
|
""""""
|
|
|
|
Tuples are supported through the class ``pypika.Tuple`` but also through the native python tuple wherever possible.
|
|
Tuples can be used with ``pypika.Criterion`` in **WHERE** clauses for pairwise comparisons.
|
|
|
|
.. code-block:: python
|
|
|
|
from pypika import Query, Tuple
|
|
|
|
q = Query.from_(self.table_abc) \
|
|
.select(self.table_abc.foo, self.table_abc.bar) \
|
|
.where(Tuple(self.table_abc.foo, self.table_abc.bar) == Tuple(1, 2))
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT "foo","bar" FROM "abc" WHERE ("foo","bar")=(1,2)
|
|
|
|
Using ``pypika.Tuple`` on both sides of the comparison is redundant and |Brand| supports native python tuples.
|
|
|
|
.. code-block:: python
|
|
|
|
from pypika import Query, Tuple
|
|
|
|
q = Query.from_(self.table_abc) \
|
|
.select(self.table_abc.foo, self.table_abc.bar) \
|
|
.where(Tuple(self.table_abc.foo, self.table_abc.bar) == (1, 2))
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT "foo","bar" FROM "abc" WHERE ("foo","bar")=(1,2)
|
|
|
|
Tuples can be used in **IN** clauses.
|
|
|
|
.. code-block:: python
|
|
|
|
Query.from_(self.table_abc) \
|
|
.select(self.table_abc.foo, self.table_abc.bar) \
|
|
.where(Tuple(self.table_abc.foo, self.table_abc.bar).isin([(1, 1), (2, 2), (3, 3)]))
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT "foo","bar" FROM "abc" WHERE ("foo","bar") IN ((1,1),(2,2),(3,3))
|
|
|
|
|
|
Strings Functions
|
|
"""""""""""""""""
|
|
|
|
There are several string operations and function wrappers included in |Brand|. Function wrappers can be found in the
|
|
``pypika.functions`` package. In addition, `LIKE` and `REGEX` queries are supported as well.
|
|
|
|
.. code-block:: python
|
|
|
|
from pypika import functions as fn
|
|
|
|
customers = Tables('customers')
|
|
q = Query.from_(customers).select(
|
|
customers.id,
|
|
customers.fname,
|
|
customers.lname,
|
|
).where(
|
|
customers.lname.like('Mc%')
|
|
)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT id,fname,lname FROM customers WHERE lname LIKE 'Mc%'
|
|
|
|
.. code-block:: python
|
|
|
|
from pypika import functions as fn
|
|
|
|
customers = Tables('customers')
|
|
q = Query.from_(customers).select(
|
|
customers.id,
|
|
customers.fname,
|
|
customers.lname,
|
|
).where(
|
|
customers.lname.regex(r'^[abc][a-zA-Z]+&')
|
|
)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT id,fname,lname FROM customers WHERE lname REGEX '^[abc][a-zA-Z]+&';
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
from pypika import functions as fn
|
|
|
|
customers = Tables('customers')
|
|
q = Query.from_(customers).select(
|
|
customers.id,
|
|
fn.Concat(customers.fname, ' ', customers.lname).as_('full_name'),
|
|
)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT id,CONCAT(fname, ' ', lname) full_name FROM customers
|
|
|
|
|
|
Custom Functions
|
|
""""""""""""""""
|
|
|
|
Custom Functions allows us to use any function on queries, as some functions are not covered by PyPika as default, we can appeal
|
|
to Custom functions.
|
|
|
|
.. code-block:: python
|
|
|
|
from pypika import CustomFunction
|
|
|
|
customers = Tables('customers')
|
|
DateDiff = CustomFunction('DATE_DIFF', ['interval', 'start_date', 'end_date'])
|
|
|
|
q = Query.from_(customers).select(
|
|
customers.id,
|
|
customers.fname,
|
|
customers.lname,
|
|
DateDiff('day', customers.created_date, customers.updated_date)
|
|
)
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT id,fname,lname,DATE_DIFF('day',created_date,updated_date) FROM customers
|
|
|
|
Case Statements
|
|
"""""""""""""""
|
|
|
|
Case statements allow fow a number of conditions to be checked sequentially and return a value for the first condition
|
|
met or otherwise a default value. The Case object can be used to chain conditions together along with their output
|
|
using the ``when`` method and to set the default value using ``else_``.
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
from pypika import Case, functions as fn
|
|
|
|
customers = Tables('customers')
|
|
q = Query.from_(customers).select(
|
|
customers.id,
|
|
Case()
|
|
.when(customers.fname == "Tom", "It was Tom")
|
|
.when(customers.fname == "John", "It was John")
|
|
.else_("It was someone else.").as_('who_was_it')
|
|
)
|
|
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT "id",CASE WHEN "fname"='Tom' THEN 'It was Tom' WHEN "fname"='John' THEN 'It was John' ELSE 'It was someone else.' END "who_was_it" FROM "customers"
|
|
|
|
|
|
With Clause
|
|
"""""""""""""""
|
|
|
|
With clause allows give a sub-query block a name, which can be referenced in several places within the main SQL query.
|
|
The SQL WITH clause is basically a drop-in replacement to the normal sub-query.
|
|
|
|
.. code-block:: python
|
|
|
|
from pypika import Table, AliasedQuery, Query
|
|
|
|
customers = Table('customers')
|
|
|
|
sub_query = (Query
|
|
.from_(customers)
|
|
.select('*'))
|
|
|
|
test_query = (Query
|
|
.with_(sub_query, "an_alias")
|
|
.from_(AliasedQuery("an_alias"))
|
|
.select('*'))
|
|
|
|
You can use as much as `.with_()` as you want.
|
|
|
|
.. code-block:: sql
|
|
|
|
WITH an_alias AS (SELECT * FROM "customers") SELECT * FROM an_alias
|
|
|
|
|
|
Inserting Data
|
|
^^^^^^^^^^^^^^
|
|
|
|
Data can be inserted into tables either by providing the values in the query or by selecting them through another query.
|
|
|
|
By default, data can be inserted by providing values for all columns in the order that they are defined in the table.
|
|
|
|
Insert with values
|
|
""""""""""""""""""
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
|
|
q = Query.into(customers).insert(1, 'Jane', 'Doe', 'jane@example.com')
|
|
|
|
.. code-block:: sql
|
|
|
|
INSERT INTO customers VALUES (1,'Jane','Doe','jane@example.com')
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
|
|
q = customers.insert(1, 'Jane', 'Doe', 'jane@example.com')
|
|
|
|
.. code-block:: sql
|
|
|
|
INSERT INTO customers VALUES (1,'Jane','Doe','jane@example.com')
|
|
|
|
Multiple rows of data can be inserted either by chaining the ``insert`` function or passing multiple tuples as args.
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
|
|
q = Query.into(customers).insert(1, 'Jane', 'Doe', 'jane@example.com').insert(2, 'John', 'Doe', 'john@example.com')
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
|
|
q = Query.into(customers).insert((1, 'Jane', 'Doe', 'jane@example.com'),
|
|
(2, 'John', 'Doe', 'john@example.com'))
|
|
|
|
Insert with constraint violation handling
|
|
"""""""""""""""""""""""""""""""""""""""""
|
|
|
|
MySQL
|
|
~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
|
|
q = MySQLQuery.into(customers) \
|
|
.insert(1, 'Jane', 'Doe', 'jane@example.com') \
|
|
.on_duplicate_key_ignore())
|
|
|
|
.. code-block:: sql
|
|
|
|
INSERT INTO `customers` VALUES (1,'Jane','Doe','jane@example.com') ON DUPLICATE KEY IGNORE
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
|
|
q = MySQLQuery.into(customers) \
|
|
.insert(1, 'Jane', 'Doe', 'jane@example.com') \
|
|
.on_duplicate_key_update(customers.email, Values(customers.email))
|
|
|
|
.. code-block:: sql
|
|
|
|
INSERT INTO `customers` VALUES (1,'Jane','Doe','jane@example.com') ON DUPLICATE KEY UPDATE `email`=VALUES(`email`)
|
|
|
|
``.on_duplicate_key_update`` works similar to ``.set`` for updating rows, additionally it provides the ``Values``
|
|
wrapper to update to the value specified in the ``INSERT`` clause.
|
|
|
|
PostgreSQL
|
|
~~~~~~~~~~
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
|
|
q = PostgreSQLQuery.into(customers) \
|
|
.insert(1, 'Jane', 'Doe', 'jane@example.com') \
|
|
.on_conflict(customers.email) \
|
|
.do_nothing()
|
|
|
|
.. code-block:: sql
|
|
|
|
INSERT INTO "customers" VALUES (1,'Jane','Doe','jane@example.com') ON CONFLICT ("email") DO NOTHING
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
|
|
q = PostgreSQLQuery.into(customers) \
|
|
.insert(1, 'Jane', 'Doe', 'jane@example.com') \
|
|
.on_conflict(customers.email) \
|
|
.do_update(customers.email, 'bob@example.com')
|
|
|
|
.. code-block:: sql
|
|
|
|
INSERT INTO "customers" VALUES (1,'Jane','Doe','jane@example.com') ON CONFLICT ("email") DO UPDATE SET "email"='bob@example.com'
|
|
|
|
|
|
Insert from a SELECT Sub-query
|
|
""""""""""""""""""""""""""""""
|
|
|
|
.. code-block:: sql
|
|
|
|
INSERT INTO "customers" VALUES (1,'Jane','Doe','jane@example.com'),(2,'John','Doe','john@example.com')
|
|
|
|
|
|
To specify the columns and the order, use the ``columns`` function.
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
|
|
q = Query.into(customers).columns('id', 'fname', 'lname').insert(1, 'Jane', 'Doe')
|
|
|
|
.. code-block:: sql
|
|
|
|
INSERT INTO customers (id,fname,lname) VALUES (1,'Jane','Doe','jane@example.com')
|
|
|
|
|
|
Inserting data with a query works the same as querying data with the additional call to the ``into`` method in the
|
|
builder chain.
|
|
|
|
.. code-block:: python
|
|
|
|
customers, customers_backup = Tables('customers', 'customers_backup')
|
|
|
|
q = Query.into(customers_backup).from_(customers).select('*')
|
|
|
|
.. code-block:: sql
|
|
|
|
INSERT INTO customers_backup SELECT * FROM customers
|
|
|
|
.. code-block:: python
|
|
|
|
customers, customers_backup = Tables('customers', 'customers_backup')
|
|
|
|
q = Query.into(customers_backup).columns('id', 'fname', 'lname')
|
|
.from_(customers).select(customers.id, customers.fname, customers.lname)
|
|
|
|
.. code-block:: sql
|
|
|
|
INSERT INTO customers_backup SELECT "id", "fname", "lname" FROM customers
|
|
|
|
The syntax for joining tables is the same as when selecting data
|
|
|
|
.. code-block:: python
|
|
|
|
customers, orders, orders_backup = Tables('customers', 'orders', 'orders_backup')
|
|
|
|
q = Query.into(orders_backup).columns('id', 'address', 'customer_fname', 'customer_lname')
|
|
.from_(customers)
|
|
.join(orders).on(orders.customer_id == customers.id)
|
|
.select(orders.id, customers.fname, customers.lname)
|
|
|
|
.. code-block:: sql
|
|
|
|
INSERT INTO "orders_backup" ("id","address","customer_fname","customer_lname")
|
|
SELECT "orders"."id","customers"."fname","customers"."lname" FROM "customers"
|
|
JOIN "orders" ON "orders"."customer_id"="customers"."id"
|
|
|
|
Updating Data
|
|
^^^^^^^^^^^^^^
|
|
PyPika allows update queries to be constructed with or without where clauses.
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
|
|
Query.update(customers).set(customers.last_login, '2017-01-01 10:00:00')
|
|
|
|
Query.update(customers).set(customers.lname, 'smith').where(customers.id == 10)
|
|
|
|
.. code-block:: sql
|
|
|
|
UPDATE "customers" SET "last_login"='2017-01-01 10:00:00'
|
|
|
|
UPDATE "customers" SET "lname"='smith' WHERE "id"=10
|
|
|
|
The syntax for joining tables is the same as when selecting data
|
|
|
|
.. code-block:: python
|
|
|
|
customers, profiles = Tables('customers', 'profiles')
|
|
|
|
Query.update(customers)
|
|
.join(profiles).on(profiles.customer_id == customers.id)
|
|
.set(customers.lname, profiles.lname)
|
|
|
|
.. code-block:: sql
|
|
|
|
UPDATE "customers"
|
|
JOIN "profiles" ON "profiles"."customer_id"="customers"."id"
|
|
SET "customers"."lname"="profiles"."lname"
|
|
|
|
Using ``pypika.Table`` alias to perform the update
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
|
|
customers.update()
|
|
.set(customers.lname, 'smith')
|
|
.where(customers.id == 10)
|
|
|
|
.. code-block:: sql
|
|
|
|
UPDATE "customers" SET "lname"='smith' WHERE "id"=10
|
|
|
|
Using ``limit`` for performing update
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
|
|
customers.update()
|
|
.set(customers.lname, 'smith')
|
|
.limit(2)
|
|
|
|
.. code-block:: sql
|
|
|
|
UPDATE "customers" SET "lname"='smith' LIMIT 2
|
|
|
|
|
|
Parametrized Queries
|
|
^^^^^^^^^^^^^^^^^^^^
|
|
|
|
PyPika allows you to use ``Parameter(str)`` term as a placeholder for parametrized queries.
|
|
|
|
.. code-block:: python
|
|
|
|
customers = Table('customers')
|
|
|
|
q = Query.into(customers).columns('id', 'fname', 'lname')
|
|
.insert(Parameter(':1'), Parameter(':2'), Parameter(':3'))
|
|
|
|
.. code-block:: sql
|
|
|
|
INSERT INTO customers (id,fname,lname) VALUES (:1,:2,:3)
|
|
|
|
This allows you to build prepared statements, and/or avoid SQL-injection related risks.
|
|
|
|
Due to the mix of syntax for parameters, depending on connector/driver, it is required that you specify the
|
|
parameter token explicitly or use one of the specialized Parameter types per [PEP-0249](https://www.python.org/dev/peps/pep-0249/#paramstyle):
|
|
``QmarkParameter()``, ``NumericParameter(int)``, ``NamedParameter(str)``, ``FormatParameter()``, ``PyformatParameter(str)``
|
|
|
|
An example of some common SQL parameter styles used in Python drivers are:
|
|
|
|
PostgreSQL:
|
|
``$number`` OR ``%s`` + ``:name`` (depending on driver)
|
|
MySQL:
|
|
``%s``
|
|
SQLite:
|
|
``?``
|
|
Vertica:
|
|
``:name``
|
|
Oracle:
|
|
``:number`` + ``:name``
|
|
MSSQL:
|
|
``%(name)s`` OR ``:name`` + ``:number`` (depending on driver)
|
|
|
|
You can find out what parameter style is needed for DBAPI compliant drivers here: https://www.python.org/dev/peps/pep-0249/#paramstyle or in the DB driver documentation.
|
|
|
|
Temporal support
|
|
^^^^^^^^^^^^^^^^
|
|
|
|
Temporal criteria can be added to the tables.
|
|
|
|
Select
|
|
""""""
|
|
|
|
Here is a select using system time.
|
|
|
|
.. code-block:: python
|
|
|
|
t = Table("abc")
|
|
q = Query.from_(t.for_(SYSTEM_TIME.as_of('2020-01-01'))).select("*")
|
|
|
|
This produces:
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT * FROM "abc" FOR SYSTEM_TIME AS OF '2020-01-01'
|
|
|
|
You can also use between.
|
|
|
|
.. code-block:: python
|
|
|
|
t = Table("abc")
|
|
q = Query.from_(
|
|
t.for_(SYSTEM_TIME.between('2020-01-01', '2020-02-01'))
|
|
).select("*")
|
|
|
|
This produces:
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT * FROM "abc" FOR SYSTEM_TIME BETWEEN '2020-01-01' AND '2020-02-01'
|
|
|
|
You can also use a period range.
|
|
|
|
.. code-block:: python
|
|
|
|
t = Table("abc")
|
|
q = Query.from_(
|
|
t.for_(SYSTEM_TIME.from_to('2020-01-01', '2020-02-01'))
|
|
).select("*")
|
|
|
|
This produces:
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT * FROM "abc" FOR SYSTEM_TIME FROM '2020-01-01' TO '2020-02-01'
|
|
|
|
Finally you can select for all times:
|
|
|
|
.. code-block:: python
|
|
|
|
t = Table("abc")
|
|
q = Query.from_(t.for_(SYSTEM_TIME.all_())).select("*")
|
|
|
|
This produces:
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT * FROM "abc" FOR SYSTEM_TIME ALL
|
|
|
|
A user defined period can also be used in the following manner.
|
|
|
|
.. code-block:: python
|
|
|
|
t = Table("abc")
|
|
q = Query.from_(
|
|
t.for_(t.valid_period.between('2020-01-01', '2020-02-01'))
|
|
).select("*")
|
|
|
|
This produces:
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT * FROM "abc" FOR "valid_period" BETWEEN '2020-01-01' AND '2020-02-01'
|
|
|
|
Joins
|
|
"""""
|
|
|
|
With joins, when the table object is used when specifying columns, it is
|
|
important to use the table from which the temporal constraint was generated.
|
|
This is because `Table("abc")` is not the same table as `Table("abc").for_(...)`.
|
|
The following example demonstrates this.
|
|
|
|
.. code-block:: python
|
|
|
|
t0 = Table("abc").for_(SYSTEM_TIME.as_of('2020-01-01'))
|
|
t1 = Table("efg").for_(SYSTEM_TIME.as_of('2020-01-01'))
|
|
query = (
|
|
Query.from_(t0)
|
|
.join(t1)
|
|
.on(t0.foo == t1.bar)
|
|
.select("*")
|
|
)
|
|
|
|
This produces:
|
|
|
|
.. code-block:: sql
|
|
|
|
SELECT * FROM "abc" FOR SYSTEM_TIME AS OF '2020-01-01'
|
|
JOIN "efg" FOR SYSTEM_TIME AS OF '2020-01-01'
|
|
ON "abc"."foo"="efg"."bar"
|
|
|
|
Update & Deletes
|
|
""""""""""""""""
|
|
|
|
An update can be written as follows:
|
|
|
|
.. code-block:: python
|
|
|
|
t = Table("abc")
|
|
q = Query.update(
|
|
t.for_portion(
|
|
SYSTEM_TIME.from_to('2020-01-01', '2020-02-01')
|
|
)
|
|
).set("foo", "bar")
|
|
|
|
This produces:
|
|
|
|
.. code-block:: sql
|
|
|
|
UPDATE "abc"
|
|
FOR PORTION OF SYSTEM_TIME FROM '2020-01-01' TO '2020-02-01'
|
|
SET "foo"='bar'
|
|
|
|
Here is a delete:
|
|
|
|
.. code-block:: python
|
|
|
|
t = Table("abc")
|
|
q = Query.from_(
|
|
t.for_portion(t.valid_period.from_to('2020-01-01', '2020-02-01'))
|
|
).delete()
|
|
|
|
This produces:
|
|
|
|
.. code-block:: sql
|
|
|
|
DELETE FROM "abc"
|
|
FOR PORTION OF "valid_period" FROM '2020-01-01' TO '2020-02-01'
|
|
|
|
Creating Tables
|
|
^^^^^^^^^^^^^^^
|
|
|
|
The entry point for creating tables is ``pypika.Query.create_table``, which is used with the class ``pypika.Column``.
|
|
As with selecting data, first the table should be specified. This can be either a
|
|
string or a `pypika.Table`. Then the columns, and constraints. Here's an example
|
|
that demonstrates much of the functionality.
|
|
|
|
.. code-block:: python
|
|
|
|
stmt = Query \
|
|
.create_table("person") \
|
|
.columns(
|
|
Column("id", "INT", nullable=False),
|
|
Column("first_name", "VARCHAR(100)", nullable=False),
|
|
Column("last_name", "VARCHAR(100)", nullable=False),
|
|
Column("phone_number", "VARCHAR(20)", nullable=True),
|
|
Column("status", "VARCHAR(20)", nullable=False, default=ValueWrapper("NEW")),
|
|
Column("date_of_birth", "DATETIME")) \
|
|
.unique("last_name", "first_name") \
|
|
.primary_key("id")
|
|
|
|
This produces:
|
|
|
|
.. code-block:: sql
|
|
|
|
CREATE TABLE "person" (
|
|
"id" INT NOT NULL,
|
|
"first_name" VARCHAR(100) NOT NULL,
|
|
"last_name" VARCHAR(100) NOT NULL,
|
|
"phone_number" VARCHAR(20) NULL,
|
|
"status" VARCHAR(20) NOT NULL DEFAULT 'NEW',
|
|
"date_of_birth" DATETIME,
|
|
UNIQUE ("last_name","first_name"),
|
|
PRIMARY KEY ("id")
|
|
)
|
|
|
|
There is also support for creating a table from a query.
|
|
|
|
.. code-block:: python
|
|
|
|
stmt = Query.create_table("names").as_select(
|
|
Query.from_("person").select("last_name", "first_name")
|
|
)
|
|
|
|
This produces:
|
|
|
|
.. code-block:: sql
|
|
|
|
CREATE TABLE "names" AS (SELECT "last_name","first_name" FROM "person")
|
|
|
|
.. _tutorial_end:
|
|
|
|
|
|
.. _license_start:
|
|
|
|
|
|
License
|
|
-------
|
|
|
|
Copyright 2020 KAYAK Germany, GmbH
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
|
|
Crafted with ♥ in Berlin.
|
|
|
|
.. _license_end:
|
|
|
|
|
|
.. _appendix_start:
|
|
|
|
.. |Brand| replace:: *PyPika*
|
|
|
|
.. _appendix_end:
|
|
|
|
.. _available_badges_start:
|
|
|
|
.. |BuildStatus| image:: https://github.com/kayak/pypika/workflows/Unit%20Tests/badge.svg
|
|
:target: https://github.com/kayak/pypika/actions
|
|
.. |CoverageStatus| image:: https://coveralls.io/repos/kayak/pypika/badge.svg?branch=master
|
|
:target: https://coveralls.io/github/kayak/pypika?branch=master
|
|
.. |Codacy| image:: https://api.codacy.com/project/badge/Grade/6d7e44e5628b4839a23da0bd82eaafcf
|
|
:target: https://www.codacy.com/app/twheys/pypika
|
|
.. |Docs| image:: https://readthedocs.org/projects/pypika/badge/?version=latest
|
|
:target: http://pypika.readthedocs.io/en/latest/
|
|
.. |PyPi| image:: https://img.shields.io/pypi/v/pypika.svg?style=flat
|
|
:target: https://pypi.python.org/pypi/pypika
|
|
.. |License| image:: https://img.shields.io/hexpm/l/plug.svg?maxAge=2592000
|
|
:target: http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
.. _available_badges_end:
|