{"id":24527198,"url":"https://github.com/StephenKevin/zugh","last_synced_at":"2025-10-03T04:30:32.938Z","repository":{"id":57478611,"uuid":"195609705","full_name":"StephenKevin/zugh","owner":"StephenKevin","description":"[WIP] Empowering you to use complex SQL, but without writing them directly.","archived":false,"fork":false,"pushed_at":"2020-08-15T10:25:23.000Z","size":81,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-01-09T08:59:33.935Z","etag":null,"topics":["mysql","python3","sql"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/zugh/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/StephenKevin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-07-07T04:29:04.000Z","updated_at":"2020-08-15T10:15:51.000Z","dependencies_parsed_at":"2022-08-30T02:31:04.573Z","dependency_job_id":null,"html_url":"https://github.com/StephenKevin/zugh","commit_stats":null,"previous_names":["stephenkwen/zugh"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StephenKevin%2Fzugh","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StephenKevin%2Fzugh/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StephenKevin%2Fzugh/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StephenKevin%2Fzugh/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/StephenKevin","download_url":"https://codeload.github.com/StephenKevin/zugh/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":234862665,"owners_count":18898395,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mysql","python3","sql"],"created_at":"2025-01-22T06:17:09.022Z","updated_at":"2025-10-03T04:30:27.652Z","avatar_url":"https://github.com/StephenKevin.png","language":"Python","readme":"# Zugh\n\n**[WIP] Access to database in pythonic way**\n\n- [Zugh](#zugh)\n  - [Status](#status)\n  - [Required](#required)\n  - [Licence](#licence)\n  - [Install](#install)\n- [Usage](#usage)\n  - [Connection](#connection)\n    - [Config](#config)\n    - [Pool](#pool)\n  - [Database](#database)\n  - [Table](#table)\n  - [Query Object](#query-object)\n  - [Insert](#insert)\n    - [Insert a Row](#insert-a-row)\n    - [Insert Ignore](#insert-ignore)\n    - [Insert Or Update](#insert-or-update)\n    - [Insert Multi Rows](#insert-multi-rows)\n  - [Select](#select)\n    - [Filter](#filter)\n      - [Logic Express](#logic-express)\n      - [Compare](#compare)\n    - [Alias](#alias)\n    - [Sort](#sort)\n    - [Limit](#limit)\n    - [Aggregate](#aggregate)\n    - [Distinct](#distinct)\n    - [Subquery](#subquery)\n    - [Join In](#join-in)\n    - [Union](#union)\n  - [Update](#update)\n    - [F Object](#f-object)\n  - [Delete](#delete)\n  - [Decorator](#decorator)\n    - [db.query.query](#dbqueryquery)\n    - [db.query.transaction](#dbquerytransaction)\n  - [String](#string)\n    - [S Object](#s-object)\n  - [Math](#math)\n\n**Zugh** is a tool for generating SQL and accessing databases flexibly in pythonic way. It empower you to use complex SQL, but didn't need to write them directly.\n\n## Status\n\nWork in progress.\n\nNow we support MySQL only.\n\n## Required\n\n- Python \u003e= 3.6\n- PyMySQL \u003e= 0.9.3\n\n## Licence\n\nMIT.\n\n## Install\n\nUse pip:\n\n```sh\npip install zugh\n```\n\n# Usage\n\n\u003eNote !\\\n\u003eThe time of writing each part of this document is out of order. So the results before and after the execution of SQL may not match. Nevertheless, I recommend that you start reading from scratch and try the code.\n\n## Connection\n\n### Config\n\n```py\n\u003e\u003e\u003e from zugh.db.connection import connect_config\n\u003e\u003e\u003e conn_config = connect_config('localhost', 'your_username', 'your_password')\n# You can use conn_config dict to configure connection for DdataBase object\n# or initial a connection pool\n```\n\n### Pool\n\n```py\n\u003e\u003e\u003e from zugh.db.pool import ConnectionPool\n\u003e\u003e\u003e pool = ConnectionPool(conn_config)\n```\n\n## Database\n\nCreate a database:\n\n```py\n\u003e\u003e\u003e from zugh.schema.db import DataBase\n\u003e\u003e\u003e db = DataBase('zugh', conn_config=conn_config)\n# or db = DataBase('zugh', pool=pool)\n\u003e\u003e\u003e db.create()\n```\n\n## Table\n\nCreate a table.\n\nWe haven't implemented those APIs to create a table yet, so just execute SQL with a connection:\n\n```py\n\u003e\u003e\u003e from zugh.db import connect\n\u003e\u003e\u003e sql = \"\"\"\nCREATE TABLE zugh.users (\n  `id` int(11) NOT NULL AUTO_INCREMENT,\n  `age` int(11) NOT NULL,\n  `score` int(11) NOT NULL DEFAULT '0',\n  PRIMARY KEY (`id`)\n) Engine=InnoDB DEFAULT CHARSET=utf8mb4;\n\"\"\"\n\u003e\u003e\u003e conn = connect(conn_config) # return a connection context\n\u003e\u003e\u003e with conn as cn:\n        with cn.cursor() as cursor:\n            cursor.execute(sql)\n```\n\nInitial a `Table` object:\n\n```py\n\u003e\u003e\u003e from zugh.schema.table import Table\n\u003e\u003e\u003e tb = Table('users', db)\n```\n\n## Query Object\n\n`zugh.query.core.QueryBase` provide a base class for Query class below:\n\n- `zugh.query.core.SelectBase`\n- `zugh.query.core.Update`\n- `zugh.query.core.Insert`\n- `zugh.query.core.Delete`\n- or subclass of above class\n\n`Query object` is a instance of above class. If they were printed, a string of SQL statement would output.If configure properly, they can call `.exe()` method to execute. Usually, you don't use them\ndirectly.\n\nMostly, you would initial a `zugh.schema.table.Table` instance and call relative method, then will return a new `Query object`.\n\nDangerous queries, such as `update`, `delete` or similar mothed are expose after `Table.where()` method.\n\n```py\n\u003e\u003e\u003e q_1 = tb.where().select()\n\u003e\u003e\u003e type(q_1)\n\u003cclass 'zugh.query.core.Select'\u003e\n\u003e\u003e\u003e q_2 = tb.where().update(age=10)\n\u003e\u003e\u003e type(q_2)\n\u003cclass 'zugh.query.core.Update'\u003e\n\u003e\u003e\u003e q_3 = tb.where().delete()\n\u003e\u003e\u003e type(q_3)\n\u003cclass 'zugh.query.core.Delete'\u003e\n\u003e\u003e\u003e q_4 = tb.insert(dict(age=10, score=18))\n\u003e\u003e\u003e type(q_4)\n\u003cclass 'zugh.query.core.Insert'\u003e\n\u003e\u003e\u003e q_5 = q_1.order_by()\n\u003e\u003e type(q_5)\n\u003cclass 'zugh.query.core.OrderBy'\u003e\n```\n\n## Insert\n\n### Insert a Row\n\n```py\n\u003e\u003e\u003e q1 = tb.insert(age=16, score=7)\n\u003e\u003e\u003e print(q1)\nINSERT INTO zugh.users (age, score) VALUES (16, 7)\n\u003e\u003e\u003e q2 = tb.where().select()\n\u003e\u003e\u003e print(q2)\nSELECT * FROM zugh.users\n\u003e\u003e\u003e q2.exe() # execute q2\n((), 0)\n\u003e\u003e\u003e q1.exe() # execute q1\n1\n\u003e\u003e\u003e q2.exe() # execute q2 again\n(((1, 16, 7),), 1)\n```\n\n### Insert Ignore\n\n```py\n\u003e\u003e\u003e q3 = tb.insert_ignore(id=1, age=16, score=7)\n\u003e\u003e\u003e print(q3)\nINSERT IGNORE INTO zugh.users (id, age, score) VALUES (1, 16, 7)\n\u003e\u003e\u003e q3.exe() # would show a duplicate key warning\n```\n\n### Insert Or Update\n\nYou can use `F` object or `values` object to complete complex query.\n\n```py\nfrom zugh.query.others import F, values\n\u003e\u003e\u003e row = dict(id=1, age=16, score=7)\n\u003e\u003e\u003e q4 = tb.upsert(row, dict(age=9))\n\u003e\u003e\u003e print(q4)\nINSERT INTO zugh.users (id, age, score) VALUES (1, 16, 7) ON DUPLICATE KEY UPDATE age = 9\n\u003e\u003e\u003e update_fv = dict(age=F('age')-1, score=values('age')+1)\n\u003e\u003e\u003e q5 = tb.upsert(row, update_fv=update_fv)\n\u003e\u003e\u003e print(q5)\nINSERT INTO zugh.users (id, age, score) VALUES (1, 16, 7) ON DUPLICATE KEY UPDATE age = age - 1, score = VALUES(age) + 1\n```\n\n### Insert Multi Rows\n\n```py\n\u003e\u003e\u003e rows = [\n    dict(age=9, score=8),\n    dict(age=7, score=9),\n    dict(age=17, score=7),\n    dict(age=23, score=7),\n  ]\n\n\u003e\u003e\u003e q6 = tb.insert_multi(rows)\n\u003e\u003e\u003e print(q6)\nINSERT INTO zugh.users (age, score) VALUES (9, 8), (7, 9), (17, 7), (23, 7)\n\u003e\u003e\u003e q6.exe() # execute q6\n4\n\u003e\u003e\u003e q2.exe()\n(((1, 16, 7), (2, 9, 8), (3, 7, 9), (4, 17, 7), (5, 23, 7)), 5)\n```\n\n## Select\n\n```py\n\u003e\u003e\u003e q7 = tb.where(id=3).select()\n\u003e\u003e\u003e print(q7)\nSELECT * FROM zugh.users WHERE id = 3\n\u003e\u003e\u003e q7.exe()\n(((3, 7, 9),), 1)\n\u003e\u003e\u003e q8 = tb.where().select('id', 'age')\n\u003e\u003e\u003e print(q8)\nSELECT id, age FROM zugh.users\n\u003e\u003e\u003e q8.exe()\n(((1, 16), (2, 9), (3, 7), (4, 17), (5, 23)), 5)\n```\n\nBy default,  when call `.exe()` of Select Query, it will return a `tuple` which contain queryset and number of rows.\nThe format of queryset format is still `tuple`. If you prefer a dict-format queryset, you should pass a parameter `cursorclass=pymysql.cursors.DictCursor` to configure connection. For more infomation, please refer docs of `PyMySQL`.\n\n### Filter\n\nthe `Table.where()` method of Table instance act as a filter.\n\nIf don't need to filter table, You can call `Table.select()` directly. It is a shortup of `Table.where().select()`.\n\n#### Logic Express\n\n```py\n\u003e\u003e\u003e from zugh.query.logic import AND, OR, L\n\u003e\u003e\u003e q9 = tb.where('id\u003e3', 'id\u003c7').select()\n\u003e\u003e\u003e print(q9)\nSELECT * FROM zugh.users WHERE id\u003e3 AND id\u003c7\n\u003e\u003e\u003e q9.exe()\n(((4, 17, 7), (5, 23, 7)), 2)\n\n\u003e\u003e\u003e w1 = L('id\u003c3')|L('id\u003e7') # equal to OR('id\u003c3', 'id\u003e7')\n\u003e\u003e\u003e w2 = OR('id\u003c3', 'id\u003e7')\n\u003e\u003e\u003e w1\nOR(L(id\u003c3),L(id\u003e7))\n\u003e\u003e\u003e w2\nOR(L(id\u003c3),L(id\u003e7))\n\u003e\u003e\u003e print(w1)\nid\u003c3 OR id\u003e7\n\u003e\u003e\u003e print(w2)\nid\u003c3 OR id\u003e7\n\n\u003e\u003e\u003e q10 = tb.where(w2).select()\n\u003e\u003e\u003e print(q10)\nSELECT * FROM zugh.users WHERE (id\u003c3 OR id\u003e7)\n\u003e\u003e\u003e q10.exe()\n(((1, 16, 7), (2, 9, 8)), 2)\n\n# you can combine complex Logic object use L, OR and AND\n\n\u003e\u003e\u003e w3 = L('id\u003e3') \u0026 L('id\u003c7') # equal to AND('id\u003e3', 'id\u003c7')\n\u003e\u003e\u003e print(w3)\nid\u003e3 AND id\u003c7\n\u003e\u003e\u003e w4 = L('age\u003e3') \u0026 L('age\u003c20')\n\u003e\u003e\u003e print(w4)\nage\u003e3 AND age\u003c20\n\n\u003e\u003e\u003e print(OR(w3, w4))\n(id\u003e3 AND id\u003c7) OR (age\u003e3 AND age\u003c20)\n\u003e\u003e\u003e print(w3|w4)\n(id\u003e3 AND id\u003c7) OR (age\u003e3 AND age\u003c20)\n```\n\n#### Compare\n\nWe use class or their instance to deal with compare express.You can find them in `zugh.query.condition` module.\n\n| SQL Operator | Python Class/Instance/Operator |           example            |\n| ------------ | ------------------------------ | ---------------------------- |\n| =            | `eq`, `=`                      | .where(name='lisa')          |\n| !=           | `ne`                           | .where(name=ne('lisa'))      |\n| \u003e            | `gt`                           | .where(amount=gt(9))         |\n| \u003e=           | `ge`                           | .where(amount=ge(9))         |\n| \u003c            | `lt`                           | .where(amount=lt(6))         |\n| \u003c=           | `le`                           | .where(amount=le(5))         |\n| IN           | `In`                           | .where(id=In(1,2,3,4))       |\n| NOT IN       | `NIn`                          | .where(id=NIn(98,34,2))      |\n| LIKE         | `like`                         | .where(name=like('lisa%'))   |\n| NOT LIKE     | `unlike`                       | .where(name=unlike('john%')) |\n| IS NULL      | `NULL`                         | .where(age=NULL)             |\n| IS NOT NULL  | `NOT_NULL`                     | .where(age=NOT_NULL)         |\n\nThough works, `eq` is meaningless. For convenience, you would always use `=` .\n\n```py\n\u003e\u003e\u003e from zugh.query.condition import NOT_NULL, NULL, In, NIn, ge, gt, le, like, lt, ne, unlike\n\u003e\u003e\u003e q11 = tb.where(id=gt(3)).select()\n\u003e\u003e\u003e print(q11)\nSELECT * FROM zugh.users WHERE id \u003e 3\n\n\u003e\u003e\u003e q12 = tb.where(id=gt(3), age=lt(18)).select()\n\u003e\u003e\u003e print(q12)\nSELECT * FROM zugh.users WHERE id \u003e 3 AND age \u003c 18\n\u003e\u003e\u003e q12.exe()\n(((4, 17, 7),), 1)\n\n\u003e\u003e\u003e q13 = tb.where(id=In(1,3,5,7,9)).select('id', 'score')\n\u003e\u003e\u003e print(q13)\nSELECT id, score FROM zugh.users WHERE id IN (1,3,5,7,9)\n\u003e\u003e\u003e q13.exe()\n(((1, 7), (3, 9), (5, 7)), 3)\n\n\u003e\u003e\u003e q14 = tb.where(score=NULL).select()\n\u003e\u003e\u003e print(q14)\nSELECT * FROM zugh.users WHERE score IS NULL\n```\n\n### Alias\n\n```py\n\u003e\u003e\u003e from zugh.query.others import As\n\u003e\u003e\u003e from zugh.query.aggregate import Max\n\u003e\u003e\u003e qa = tb.where().select(max_age=Max('age'))\n\u003e\u003e\u003e print(qa)\nSELECT max(age) AS max_age FROM zugh.users\n\u003e\u003e\u003e print(tb.where().select(As(Max('age'), 'max_age')))\nSELECT max(age) AS max_age FROM zugh.users\n```\n\nWe support alias, but the default `cursorclass` of PyMySQL will return query set in tuple. In this case, alias is useless.\nIf you want to return dict, you need to configure connection parameter `cursorclass=pymysql.cursors.DictCursor`. For more information, please refer PyMySQL's documents.\n\n### Sort\n\n```py\n\u003e\u003e\u003e q15 = tb.where().select().order_by('age')\n\u003e\u003e\u003e print(q15)\nSELECT * FROM zugh.users ORDER BY age\n\u003e\u003e\u003e q15.exe()\n(((3, 7, 9), (2, 9, 8), (1, 16, 7), (4, 17, 7), (5, 23, 7)), 5)\n\n# You can use prefix '-' to sort reverse.\n\u003e\u003e\u003e q16 = tb.where().select().order_by('-age', 'score')\n\u003e\u003e\u003e print(q16)\nSELECT * FROM zugh.users ORDER BY age DESC, score\n\u003e\u003e\u003e q16.exe()\n(((5, 23, 7), (4, 17, 7), (1, 16, 7), (2, 9, 8), (3, 7, 9)), 5)\n```\n\n### Limit\n\nWe use a magical `slice` to act limit/offset, `Select Query`' slice will return a instance of\n`zugh.query.core.Limit`, which is a subclass of `zugh.query.core.SelectBase`.\n\n```py\n\u003e\u003e\u003e qm = tb.where().select()\n\u003e\u003e\u003e qm1 = qm[:3] # fetch frist three\n\u003e\u003e\u003e print(qm1)\nSELECT * FROM zugh.users LIMIT 3\n\u003e\u003e\u003e qm2 = qm[2:4]\n\u003e\u003e\u003e print(qm2)\nSELECT * FROM zugh.users LIMIT 2, 2\n\u003e\u003e\u003e qm3 = qm[2:]\n\u003e\u003e\u003e print(qm3)\nSELECT * FROM zugh.users LIMIT 2, 18446744073709551614\n```\n\nExcept for instances of `Limit`, all the others instance of `SelectBase` could use slice to return a instance of `Limit`.\n\nThe slice here don't accept negative numbers.\n\n### Aggregate\n\nWe provide some aggregation functions in `zugh.query.aggregate` moulde.\n\n```py\n\u003e\u003e\u003e from zugh.query.aggregate import Avg, Count, Max, Min, Sum\n\u003e\u003e\u003e q17 = tb.where().select(Avg('age'))\n\u003e\u003e\u003e print(q17)\nSELECT avg(age) FROM zugh.users\n\u003e\u003e\u003e q17.exe()\n(((Decimal('14.4000'),),), 1)\n\n\u003e\u003e\u003e q18 = tb.where().select('score', Count('id')).group_by('score')\n\u003e\u003e\u003e print(q18)\nSELECT score, count(id) FROM zugh.users GROUP BY score\n\u003e\u003e\u003e q18.exe()\n(((7, 3), (8, 1), (9, 1)), 3)\n```\n\nYou can also use write 'raw' functions as long as you like it, such as:\n\n```py\nq17 = tb.where().select('avg(age)')\n```\n\n### Distinct\n\n```py\nfrom zugh.query.others import distinct\n\u003e\u003e\u003e q19 = tb.where().select('distinct age', 'score')\n\u003e\u003e\u003e print(q15)\nSELECT distinct age, score FROM zugh.users\n\u003e\u003e\u003e q19.exe()\n(((16, 7), (9, 8), (7, 9), (17, 7), (23, 7)), 5)\n\n\u003e\u003e\u003e q20 = tb.where().select(distinct('age'), 'score')\n\u003e\u003e\u003e print(q20)\nSELECT DISTINCT age, score FROM zugh.users\n\n\u003e\u003e\u003e q21 = tb.where().select(Count(distinct('age')))\n\u003e\u003e\u003e print(q21)\nSELECT count(DISTINCT age) FROM zugh.users\n```\n\n### Subquery\n\nAt present, `In` and `NIn` express support subquery, it could accept a instance of `SelectBase` as a parameter.But they can not accept instance of `zugh.schema.table.TempTable` as a parameter.\n\n`TempTable` accept a `Select Query` as frist parameter and a alias string as second parameter. It would act like a normal read-only table, you could join it with others, or query data as a new instance `TempTable`.\n\n```py\n\u003e\u003e\u003e from zugh.schema.table import TempTable\n\u003e\u003e\u003e q22 = tb.where().select(Max('age'))\n\u003e\u003e\u003e print(q22)\nSELECT max(age) FROM zugh.users\n\n\u003e\u003e\u003e q23 = tb.where(age=In(q22)).select()\n\u003e\u003e\u003e print(q23)\nSELECT * FROM zugh.users WHERE age IN (SELECT max(age) FROM zugh.users)\n\u003e\u003e\u003e q23.exe()\n(((5, 23, 7),), 1)\n\n\u003e\u003e\u003e q_t = tb.where(id=gt(2)).select()\n\u003e\u003e\u003e tb_t1 = q_t.as_table('ak') # equal to tb_t1 = TempTable(q_t, 'ak')\n\u003e\u003e\u003e tb_t1\nTempTable(SELECT * FROM zugh.users WHERE id \u003e 2)\n\u003e\u003e\u003e\n\u003e\u003e\u003e sql2  = \"\"\"\nCREATE TABLE zugh.account (\n  `id` int(11) NOT NULL AUTO_INCREMENT,\n  `user_id` int(11) NOT NULL,\n  `amount` decimal(11,2) NOT NULL DEFAULT '0',\n  PRIMARY KEY (`id`)\n) Engine=InnoDB DEFAULT CHARSET=utf8mb4;\n\"\"\"\n\u003e\u003e\u003e with conn as cn:\n        with cn.cursor() as cursor:\n            cursor.execute(sql2)\n\u003e\u003e\u003e tb2 = Table('account', db, alias='a') # If tend to join table, alias is necessary\n\u003e\u003e\u003e rows2 = (\n  dict(user_id=1, amount='99.89'),\n  dict(user_id=2, amount='292.2'),\n  dict(user_id=3, amount='299.89'),\n  dict(user_id=4, amount='192.1'),\n  dict(user_id=5, amount='183.7'),\n)\n\u003e\u003e\u003e tb2.insert_multi(rows2).exe()\n\u003e\u003e\u003e\n\u003e\u003e\u003e tb_t2 = tb_t1.inner_join(tb2, on='a.user_id = ak.id')\n\u003e\u003e\u003e q_t2 = tb_t2.select()\n\u003e\u003e\u003e print(q_t2)\nSELECT * FROM (SELECT * FROM zugh.users WHERE id \u003e 2) AS ak INNER JOIN zugh.account AS a ON a.user_id = ak.id\n\u003e\u003e\u003e q_t2.exe()\n(((3, 7, 8, 3, 3, Decimal('299.89')), (4, 17, 8, 4, 4, Decimal('192.10'))), 2)\n```\n\n### Join In\n\nLet's add a new table and query from the join table:\n\n```py\n\u003e\u003e\u003e tb3 = Table('users', db, alias='b') # If tend to join table, alias is necessary\n\u003e\u003e\u003e tb_i = tb2.inner_join(tb3, on='a.user_id=b.id')\n\u003e\u003e\u003e q_i = tb_i.where(a__id=gt(2)).select('a.id', 'a.user_id', 'a.amount', 'b.score', 'b.age')\n\u003e\u003e\u003e print(q_i)\nSELECT a.id, a.user_id, a.amount, b.score, b.age FROM zugh.account AS a INNER JOIN zugh.users AS b ON a.user_id=b.id WHERE a.id \u003e 2\n\u003e\u003e\u003e q_i.exe()\n(((3, 3, Decimal('299.89'), 8, 7), (4, 4, Decimal('192.10'), 8, 17)), 2)\n```\n\nIf two underscores are used in the keyword of `where` method, the two underscores will be replaced by solid point.\nFor example, `a__id` will be replaced with `a.id`. This idea was copied from the Django project.\n\nWe provide `Table.inner_join()`, `Table.left_join()` and `Table.right_join()` methods to support Table join.\n\n### Union\n\nYou can call `union` or `union_all` method from `Select object`. For example:\n\n```py\n\u003e\u003e\u003e from zugh.query.condition import gt, lt\n\u003e\u003e\u003e q_u1 = tb.where(id=lt(5)).select()\n\u003e\u003e\u003e q_u2 = tb.where(age=gt(20)).select()\n\u003e\u003e\u003e q_u = q_u1.union_all(q_u2)\n\u003e\u003e\u003e print(q_u)\nSELECT * FROM zugh.users WHERE id \u003c 5 UNION ALL SELECT * FROM zugh.users WHERE age \u003e 20\n```\n\n## Update\n\n```py\n\u003e\u003e\u003e tb.where(id=1).select().exe()\n(((1, 16, 7),), 1)\n\u003e\u003e\u003e q24 = tb.where(id=1).update(age=28)\n\u003e\u003e\u003e print(q24)\nUPDATE zugh.users SET age = 28 WHERE id = 1\n\u003e\u003e\u003e q24.exe()\n1\n\u003e\u003e\u003e tb.where(id=1).select().exe()\n(((1, 28, 7),), 1)\n```\n\n### F Object\n\nF object means that it is a field, not a string. It could be used in `Table.where()` or `Where.update()`.\nF class is a subclass of `ArithmeticBase`. So F objects can perform mathematical operations, and it will return a new `ArithmeticBase` instance.\n\nSee the following examples:\n\n```py\n\u003e\u003e\u003e from zugh.query.others import F\n\u003e\u003e\u003e tb.where(id=1).select().exe()\n(((1, 28, 7),), 1)\n\n\u003e\u003e\u003e q25 = tb.where(id=1).update(age=F('age') - 2, score=F('score') + 6)\n\u003e\u003e\u003e print(q25)\nUPDATE zugh.users SET age = age - 2, score = score + 6 WHERE id = 1\n\u003e\u003e\u003e q25.exe()\n1\n\u003e\u003e\u003e tb.where(id=1).select().exe()\n(((1, 26, 13),), 1)\n\n# F object also use in filter\n\u003e\u003e\u003e q26 = tb.where(score=gt(F('age')*2)).select()\n\u003e\u003e\u003e print(q26)\nSELECT * FROM zugh.users WHERE score \u003e age * 2\n\u003e\u003e\u003e q26.exe()\n((), 0)\n```\n\n## Delete\n\n```py\n\u003e\u003e\u003e tb.where(id=5).select().exe()\n(((5, 23, 7),), 1)\n\u003e\u003e\u003e q23 = tb.where(id=5).delete()\n\u003e\u003e\u003e print(q23)\nDELETE FROM zugh.users WHERE id = 5\n\u003e\u003e\u003e q23.exe()\n1\n\u003e\u003e\u003e tb.where(id=5).select().exe()\n((), 0)\n```\n\n## Decorator\n\n### db.query.query\n\n`query` decorator wraps a function which return a Query object. When call the wrapped function,\nit would execute a Query object. For example:\n\n```py\n\u003e\u003e\u003e from zugh.query.aggregate import Max\n\u003e\u003e\u003e from zugh.db.query import query\n\n\u003e\u003e\u003e @query()\n    def query_max_score():\n      q1 = tb.where().select(Max('score'))\n      q2 = tb.where(score=In(q1)).select()\n      return q2\n\n\u003e\u003e\u003e query_max_score()\n(((1, 26, 13),), 1)\n```\n\n`query` accept 2 parameters: `conn_config` and `conn_pool`. If the Query object returned don't\nconfigure connection, you can pass a `conn_config` dict  or a connection pool to it.\n\n### db.query.transaction\n\n`transaction` decorator wrap a function which return a list of `Query ojbect`. When call the wrapped\nfunction, it would execute the list of `Query object` as a transaction. If transaction succeed, return True, otherwise return False.\n\nFor example:\n\n```py\nfrom zugh.db.query import transaction\nfrom zugh.query.others import F\n\n@transaction(conn_pool=pool)\ndef mv_score():\n    q1 = tb.where(id=3).update(score=F('score') - 1)\n    q2 = tb.where(id=4).update(score=F('score') + 1)\n    return (q1, q2)\n\n\u003e\u003e\u003e mv_score()\nTrue\n```\n\n## String\n\nSome string function awailable in `zugh.query.string` module.\n\nconcat 2 field:\n\n```py\nfrom zugh.query.string import Concat, S, Substring\n\u003e\u003e\u003e q24 = tb.where().select(Concat('age', 'score'))\n\u003e\u003e\u003e print(q24)\nSELECT concat(age, score) FROM zugh.users\n```\n\n### S Object\n\nIn string functions, str meaning field name instead of string. You should use `S object` to represent string.\n\n```py\nfrom zugh.query.string import Concat, S, Substring\n\u003e\u003e\u003e q25 = tb.where().select(Concat(S('PRI-'), 'age'))\n\u003e\u003e\u003e print(q25)\nSELECT concat('PRI-', age) FROM zugh.users\n\u003e\u003e\u003e q25.exe()\n((('PRI-26',), ('PRI-9',), ('PRI-7',), ('PRI-17',)), 4)\n\n\u003e\u003e\u003e print(tb.where().select(Substring('age', 2)))\nSELECT substring(age, 2) FROM zugh.users\n```\n\n## Math\n\nSome math functions are awailable in `zugh.query.math` module.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FStephenKevin%2Fzugh","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FStephenKevin%2Fzugh","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FStephenKevin%2Fzugh/lists"}