sql-blocks 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sql_blocks/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
from sql_blocks import *
|
sql_blocks/sql_blocks.py
ADDED
@@ -0,0 +1,434 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
|
3
|
+
KEYWORD = {
|
4
|
+
'SELECT': (',{}', 'SELECT *'),
|
5
|
+
'FROM': ('{}', ''),
|
6
|
+
'WHERE': ('{}AND ', ''),
|
7
|
+
'GROUP BY': (',{}', ''),
|
8
|
+
'ORDER BY': (',{}', ''),
|
9
|
+
'LIMIT': (' ', ''),
|
10
|
+
}
|
11
|
+
SELECT, FROM, WHERE, GROUP_BY, ORDER_BY, LIMIT = KEYWORD.keys()
|
12
|
+
USUAL_KEYS = [SELECT, WHERE, GROUP_BY, ORDER_BY]
|
13
|
+
|
14
|
+
|
15
|
+
class SQLObject:
|
16
|
+
def __init__(self, table_name: str=''):
|
17
|
+
self.alias = ''
|
18
|
+
self.values = {}
|
19
|
+
self.key_field = ''
|
20
|
+
self.set_table(table_name)
|
21
|
+
|
22
|
+
def set_table(self, table_name: str):
|
23
|
+
if not table_name:
|
24
|
+
return
|
25
|
+
if ' ' in table_name:
|
26
|
+
table_name, self.alias = table_name.split()
|
27
|
+
elif '_' in table_name:
|
28
|
+
self.alias = ''.join(
|
29
|
+
word[0].lower()
|
30
|
+
for word in table_name.split('_')
|
31
|
+
)
|
32
|
+
else:
|
33
|
+
self.alias = table_name.lower()[:3]
|
34
|
+
self.values.setdefault(FROM, []).append(f'{table_name} {self.alias}')
|
35
|
+
|
36
|
+
@property
|
37
|
+
def table_name(self) -> str:
|
38
|
+
return self.values[FROM][0].split()[0]
|
39
|
+
|
40
|
+
def delete(self, search: str, keys: list=USUAL_KEYS):
|
41
|
+
for key in keys:
|
42
|
+
result = []
|
43
|
+
for item in self.values.get(key, []):
|
44
|
+
if search not in item:
|
45
|
+
result.append(item)
|
46
|
+
self.values[key] = result
|
47
|
+
|
48
|
+
|
49
|
+
class Function:
|
50
|
+
...
|
51
|
+
|
52
|
+
class Field:
|
53
|
+
prefix = ''
|
54
|
+
|
55
|
+
@classmethod
|
56
|
+
def format(cls, name: str, main: SQLObject) -> str:
|
57
|
+
if name == '_':
|
58
|
+
name = '*'
|
59
|
+
else:
|
60
|
+
name = f'{main.alias}.{name}'
|
61
|
+
if Function in cls.__bases__:
|
62
|
+
name = f'{cls.__name__}({name})'
|
63
|
+
return f'{cls.prefix}{name}'
|
64
|
+
|
65
|
+
@classmethod
|
66
|
+
def add(cls, name: str, main: SQLObject):
|
67
|
+
main.values.setdefault(SELECT, []).append(
|
68
|
+
cls.format(name, main)
|
69
|
+
)
|
70
|
+
|
71
|
+
|
72
|
+
class Avg(Function, Field):
|
73
|
+
...
|
74
|
+
class Min(Function, Field):
|
75
|
+
...
|
76
|
+
class Max(Function, Field):
|
77
|
+
...
|
78
|
+
class Sum(Function, Field):
|
79
|
+
...
|
80
|
+
class Count(Function, Field):
|
81
|
+
...
|
82
|
+
|
83
|
+
class Distinct(Field):
|
84
|
+
prefix = 'DISTINCT '
|
85
|
+
|
86
|
+
|
87
|
+
class NamedField:
|
88
|
+
def __init__(self, alias: str, class_type = Field):
|
89
|
+
self.alias = alias
|
90
|
+
if class_type not in [Field] + Field.__subclasses__():
|
91
|
+
raise TypeError('class_type must be a Field (sub)class.')
|
92
|
+
self.class_type = class_type
|
93
|
+
|
94
|
+
def add(self, name: str, main: SQLObject):
|
95
|
+
main.values.setdefault(SELECT, []).append(
|
96
|
+
'{} as {}'.format(
|
97
|
+
self.class_type.format(name, main),
|
98
|
+
self.alias
|
99
|
+
)
|
100
|
+
)
|
101
|
+
|
102
|
+
|
103
|
+
class Table:
|
104
|
+
def __init__(self, fields: list | str=[]):
|
105
|
+
if isinstance(fields, str):
|
106
|
+
fields = [f.strip() for f in fields.split(',')]
|
107
|
+
self.fields = fields
|
108
|
+
|
109
|
+
def add(self, name: str, main: SQLObject):
|
110
|
+
main.set_table(name)
|
111
|
+
for field in self.fields:
|
112
|
+
Field.add(field, main)
|
113
|
+
|
114
|
+
|
115
|
+
class PrimaryKey:
|
116
|
+
@staticmethod
|
117
|
+
def add(name: str, main: SQLObject):
|
118
|
+
main.key_field = name
|
119
|
+
|
120
|
+
|
121
|
+
class ForeignKey:
|
122
|
+
references = {}
|
123
|
+
|
124
|
+
def __init__(self, table_name: str):
|
125
|
+
self.table_name = table_name
|
126
|
+
|
127
|
+
def add(self, name: str, main: SQLObject):
|
128
|
+
key = f'{main.table_name}_{self.table_name}'
|
129
|
+
ForeignKey.references[key] = name
|
130
|
+
|
131
|
+
@classmethod
|
132
|
+
def find(cls, obj1: SQLObject, obj2: SQLObject) -> list:
|
133
|
+
key = f'{obj1.table_name}_{obj2.table_name}'
|
134
|
+
return cls.references.get(key, '').split('_')
|
135
|
+
|
136
|
+
|
137
|
+
def quoted(value) -> str:
|
138
|
+
if isinstance(value, str):
|
139
|
+
value = f"'{value}'"
|
140
|
+
return str(value)
|
141
|
+
|
142
|
+
|
143
|
+
class Where:
|
144
|
+
prefix = ''
|
145
|
+
|
146
|
+
def __init__(self, expr: str):
|
147
|
+
self.expr = f'{self.prefix}{expr}'
|
148
|
+
|
149
|
+
@classmethod
|
150
|
+
def __constructor(cls, operator: str, value):
|
151
|
+
return cls(expr=f'{operator} {quoted(value)}')
|
152
|
+
|
153
|
+
@classmethod
|
154
|
+
def eq(cls, value):
|
155
|
+
return cls.__constructor('=', value)
|
156
|
+
|
157
|
+
@classmethod
|
158
|
+
def like(cls, value: str):
|
159
|
+
return cls(f"LIKE '%{value}%'")
|
160
|
+
|
161
|
+
@classmethod
|
162
|
+
def gt(cls, value):
|
163
|
+
return cls.__constructor('>', value)
|
164
|
+
|
165
|
+
@classmethod
|
166
|
+
def gte(cls, value):
|
167
|
+
return cls.__constructor('>=', value)
|
168
|
+
|
169
|
+
@classmethod
|
170
|
+
def lt(cls, value):
|
171
|
+
return cls.__constructor('<', value)
|
172
|
+
|
173
|
+
@classmethod
|
174
|
+
def lte(cls, value):
|
175
|
+
return cls.__constructor('<=', value)
|
176
|
+
|
177
|
+
@classmethod
|
178
|
+
def is_null(cls):
|
179
|
+
return cls('IS NULL')
|
180
|
+
|
181
|
+
@classmethod
|
182
|
+
def list(cls, values):
|
183
|
+
if isinstance(values, list):
|
184
|
+
values = ','.join(quoted(v) for v in values)
|
185
|
+
return cls(f'IN ({values})')
|
186
|
+
|
187
|
+
def add(self, name: str, main: SQLObject):
|
188
|
+
main.values.setdefault(WHERE, []).append('{} {}'.format(
|
189
|
+
Field.format(name, main), self.expr
|
190
|
+
))
|
191
|
+
|
192
|
+
|
193
|
+
class Not(Where):
|
194
|
+
prefix = 'NOT '
|
195
|
+
|
196
|
+
@classmethod
|
197
|
+
def eq(cls, value):
|
198
|
+
return Where.__constructor('<>', value)
|
199
|
+
|
200
|
+
|
201
|
+
class Case:
|
202
|
+
def __init__(self, field: str):
|
203
|
+
self.__conditions = {}
|
204
|
+
self.default = None
|
205
|
+
self.field = field
|
206
|
+
|
207
|
+
def when(self, condition: Where, result: str):
|
208
|
+
self.__conditions[result] = condition
|
209
|
+
return self
|
210
|
+
|
211
|
+
def else_value(self, default: str):
|
212
|
+
self.default = default
|
213
|
+
return self
|
214
|
+
|
215
|
+
def add(self, name: str, main: SQLObject):
|
216
|
+
field = Field.format(self.field, main)
|
217
|
+
default = quoted(self.default)
|
218
|
+
name = 'CASE \n{}\n\tEND AS {}'.format(
|
219
|
+
'\n'.join(
|
220
|
+
f'\t\tWHEN {field} {cond.expr} THEN {quoted(res)}'
|
221
|
+
for res, cond in self.__conditions.items()
|
222
|
+
) + f'\n\t\tELSE {default}' if default else '',
|
223
|
+
name
|
224
|
+
)
|
225
|
+
main.values.setdefault(SELECT, []).append(name)
|
226
|
+
|
227
|
+
|
228
|
+
class Options:
|
229
|
+
def __init__(self, **values):
|
230
|
+
self.__children: dict = values
|
231
|
+
|
232
|
+
def add(self, logical_separator: str, main: SQLObject):
|
233
|
+
"""
|
234
|
+
`logical_separator` must be AND or OR
|
235
|
+
"""
|
236
|
+
conditions: list[str] = []
|
237
|
+
child: Where
|
238
|
+
for field, child in self.__children.items():
|
239
|
+
conditions.append(' {} {} '.format(
|
240
|
+
Field.format(field, main), child.expr
|
241
|
+
))
|
242
|
+
main.values.setdefault(WHERE, []).append(
|
243
|
+
'(' + logical_separator.join(conditions) + ')'
|
244
|
+
)
|
245
|
+
|
246
|
+
|
247
|
+
class Between:
|
248
|
+
def __init__(self, start, end):
|
249
|
+
if start > end:
|
250
|
+
start, end = end, start
|
251
|
+
self.start = start
|
252
|
+
self.end = end
|
253
|
+
|
254
|
+
def add(self, name: str, main:SQLObject):
|
255
|
+
Where.gte(self.start).add(name, main),
|
256
|
+
Where.lte(self.end).add(name, main)
|
257
|
+
|
258
|
+
|
259
|
+
class SortType(Enum):
|
260
|
+
ASC = ''
|
261
|
+
DESC = ' DESC'
|
262
|
+
|
263
|
+
class OrderBy:
|
264
|
+
sort: SortType = SortType.ASC
|
265
|
+
@classmethod
|
266
|
+
def add(cls, name: str, main: SQLObject):
|
267
|
+
if main.alias:
|
268
|
+
name = f'{main.alias}.{name}'
|
269
|
+
main.values.setdefault(ORDER_BY, []).append(name + cls.sort.value)
|
270
|
+
|
271
|
+
|
272
|
+
class GroupBy:
|
273
|
+
@staticmethod
|
274
|
+
def add(name: str, main: SQLObject):
|
275
|
+
main.values.setdefault(GROUP_BY, []).append(f'{main.alias}.{name}')
|
276
|
+
|
277
|
+
|
278
|
+
class Having:
|
279
|
+
def __init__(self, function: Function, condition: Where):
|
280
|
+
self.function = function
|
281
|
+
self.condition = condition
|
282
|
+
|
283
|
+
def add(self, name: str, main:SQLObject):
|
284
|
+
main.values[GROUP_BY][-1] += ' HAVING {} {}'.format(
|
285
|
+
self.function.format(name, main), self.condition.expr
|
286
|
+
)
|
287
|
+
|
288
|
+
@classmethod
|
289
|
+
def avg(cls, condition: Where):
|
290
|
+
return cls(Avg, condition)
|
291
|
+
|
292
|
+
@classmethod
|
293
|
+
def min(cls, condition: Where):
|
294
|
+
return cls(Min, condition)
|
295
|
+
|
296
|
+
@classmethod
|
297
|
+
def max(cls, condition: Where):
|
298
|
+
return cls(Max, condition)
|
299
|
+
|
300
|
+
@classmethod
|
301
|
+
def sum(cls, condition: Where):
|
302
|
+
return cls(Sum, condition)
|
303
|
+
|
304
|
+
@classmethod
|
305
|
+
def count(cls, condition: Where):
|
306
|
+
return cls(Count, condition)
|
307
|
+
|
308
|
+
|
309
|
+
class JoinType(Enum):
|
310
|
+
INNER = ''
|
311
|
+
LEFT = 'LEFT '
|
312
|
+
RIGHT = 'RIGHT '
|
313
|
+
FULL = 'FULL '
|
314
|
+
|
315
|
+
class Select(SQLObject):
|
316
|
+
join_type: JoinType = JoinType.INNER
|
317
|
+
REGEX = None
|
318
|
+
|
319
|
+
def __init__(self, table_name: str='', **values):
|
320
|
+
super().__init__(table_name)
|
321
|
+
self.__call__(**values)
|
322
|
+
self.break_lines = True
|
323
|
+
|
324
|
+
def add(self, name: str, main: SQLObject):
|
325
|
+
def update_values(key: str, new_values: list):
|
326
|
+
for value in new_values:
|
327
|
+
old_values = main.values.get(key, [])
|
328
|
+
if value not in old_values:
|
329
|
+
main.values[key] = old_values + [value]
|
330
|
+
update_values(
|
331
|
+
FROM, [
|
332
|
+
'{jt}JOIN {tb} {a2} ON ({a1}.{f1} = {a2}.{f2})'.format(
|
333
|
+
jt=self.join_type.value,
|
334
|
+
tb=self.table_name,
|
335
|
+
a1=main.alias, f1=name,
|
336
|
+
a2=self.alias, f2=self.key_field
|
337
|
+
)
|
338
|
+
] + self.values[FROM][1:]
|
339
|
+
)
|
340
|
+
for key in USUAL_KEYS:
|
341
|
+
update_values(key, self.values.get(key, []))
|
342
|
+
|
343
|
+
def __add__(self, other: SQLObject):
|
344
|
+
foreign_field, *primary_key = ForeignKey.find(self, other)
|
345
|
+
if not foreign_field:
|
346
|
+
foreign_field, *primary_key = ForeignKey.find(other, self)
|
347
|
+
if foreign_field:
|
348
|
+
if primary_key:
|
349
|
+
PrimaryKey.add(primary_key[0], self)
|
350
|
+
self.add(foreign_field, other)
|
351
|
+
return other
|
352
|
+
raise ValueError(f'No relationship found between {self.table_name} and {other.table_name}.')
|
353
|
+
elif primary_key:
|
354
|
+
PrimaryKey.add(primary_key[0], other)
|
355
|
+
other.add(foreign_field, self)
|
356
|
+
return self
|
357
|
+
|
358
|
+
def __str__(self) -> str:
|
359
|
+
TABULATION = '\n\t' if self.break_lines else ' '
|
360
|
+
LINE_BREAK = '\n' if self.break_lines else ' '
|
361
|
+
DEFAULT = lambda key: KEYWORD[key][1]
|
362
|
+
FMT_SEP = lambda key: KEYWORD[key][0].format(TABULATION)
|
363
|
+
select, _from, where, groupBy, orderBy, limit = [
|
364
|
+
DEFAULT(key) if not self.values.get(key) else "{}{}{}{}".format(
|
365
|
+
LINE_BREAK, key, TABULATION, FMT_SEP(key).join(self.values[key])
|
366
|
+
) for key in KEYWORD
|
367
|
+
]
|
368
|
+
return f'{select}{_from}{where}{groupBy}{orderBy}{limit}'.strip()
|
369
|
+
|
370
|
+
def __call__(self, **values):
|
371
|
+
to_list = lambda x: x if isinstance(x, list) else [x]
|
372
|
+
for name, params in values.items():
|
373
|
+
for obj in to_list(params):
|
374
|
+
obj.add(name, self)
|
375
|
+
return self
|
376
|
+
|
377
|
+
def __eq__(self, other: SQLObject) -> bool:
|
378
|
+
def sorted_values(obj: SQLObject, key: str) -> list:
|
379
|
+
return sorted(obj.values.get(key, []))
|
380
|
+
for key in KEYWORD:
|
381
|
+
if sorted_values(self, key) != sorted_values(other, key):
|
382
|
+
return False
|
383
|
+
return True
|
384
|
+
|
385
|
+
def limit(self, row_count: int, offset: int=0):
|
386
|
+
result = [str(row_count)]
|
387
|
+
if offset > 0:
|
388
|
+
result.append(f'OFFSET {offset}')
|
389
|
+
self.values.setdefault(LIMIT, result)
|
390
|
+
return self
|
391
|
+
|
392
|
+
@classmethod
|
393
|
+
def parse(cls, txt: str) -> list[SQLObject]:
|
394
|
+
import re
|
395
|
+
txt = re.sub(' +', ' ', re.sub('\n|\t', ' ', txt))
|
396
|
+
if not cls.REGEX:
|
397
|
+
keywords = '|'.join(KEYWORD)
|
398
|
+
cls.REGEX = re.compile(f'({keywords})', re.IGNORECASE)
|
399
|
+
tokens = [t.strip() for t in cls.REGEX.split(txt) if t.strip()]
|
400
|
+
values = {k: v for k, v in zip(tokens[::2], tokens[1::2])}
|
401
|
+
tables = [t.strip() for t in re.split('JOIN|LEFT|RIGHT|ON', values[FROM]) if t.strip()]
|
402
|
+
result = {}
|
403
|
+
for item in tables:
|
404
|
+
if '=' in item:
|
405
|
+
a1, f1, a2, f2 = [r.strip() for r in re.split('[().=]', item) if r]
|
406
|
+
obj1: SQLObject = result[a1]
|
407
|
+
obj2:SQLObject = result[a2]
|
408
|
+
PrimaryKey.add(f2, obj2)
|
409
|
+
ForeignKey(obj2.table_name).add(f1, obj1)
|
410
|
+
else:
|
411
|
+
obj = cls(item)
|
412
|
+
for key in USUAL_KEYS:
|
413
|
+
if not key in values:
|
414
|
+
continue
|
415
|
+
separator = KEYWORD[key][0].format(
|
416
|
+
' ' if key == WHERE else ''
|
417
|
+
)
|
418
|
+
fields = [
|
419
|
+
fld.strip() for fld in re.split(
|
420
|
+
separator, values[key]
|
421
|
+
) if len(tables) == 1
|
422
|
+
or re.findall(f'^[( ]*{obj.alias}\.', fld)
|
423
|
+
]
|
424
|
+
obj.values[key] = [
|
425
|
+
f'{obj.alias}.{f}' if not '.' in f else f for f in fields
|
426
|
+
]
|
427
|
+
result[obj.alias] = obj
|
428
|
+
return list( result.values() )
|
429
|
+
|
430
|
+
|
431
|
+
class SubSelect(Select):
|
432
|
+
def add(self, name: str, main: SQLObject):
|
433
|
+
self.break_lines = False
|
434
|
+
Where.list(self).add(name, main)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2018 The Python Packaging Authority
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
@@ -0,0 +1,296 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: sql_blocks
|
3
|
+
Version: 0.0.2
|
4
|
+
Summary: Allows you to create objects for parts of SQL query commands. Also to combine these objects by joining them, adding or removing parts...
|
5
|
+
Home-page: https://github.com/julio-cascalles/sql_blocks
|
6
|
+
Author: Júlio Cascalles
|
7
|
+
Author-email: Julio Cascalles <julio.cascalles@outlook.com>
|
8
|
+
Project-URL: Homepage, https://github.com/julio-cascalles/sql_blocks
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
11
|
+
Classifier: Operating System :: OS Independent
|
12
|
+
Requires-Python: >=3.8
|
13
|
+
Description-Content-Type: text/markdown
|
14
|
+
License-File: LICENSE
|
15
|
+
|
16
|
+
# SQL_Blocks
|
17
|
+
|
18
|
+
### 1 - You can assemble a simple object that will then be converted into an SQL command:
|
19
|
+
|
20
|
+
> a = Select('Actor') # --> SELECT * FROM Actor act
|
21
|
+
|
22
|
+
_Note that an alias "act" has been added._
|
23
|
+
|
24
|
+
You can specify your own alias: `a = Select('Actor a')`
|
25
|
+
|
26
|
+
---
|
27
|
+
### 2 - You can also add a field, like this...
|
28
|
+
|
29
|
+
* a = Select('Actor a', **name=Field**)
|
30
|
+
|
31
|
+
* Here are another ways to add a field:
|
32
|
+
- Select('Actor a', name=Distinct )
|
33
|
+
|
34
|
+
- Select('Actor a', name=NamedField('actors_name'))
|
35
|
+
|
36
|
+
- Select(
|
37
|
+
'Actor a',
|
38
|
+
name=NamedField('actors_name', Distinct)
|
39
|
+
)
|
40
|
+
|
41
|
+
---
|
42
|
+
|
43
|
+
### 3 - To set conditions, use **Where**:
|
44
|
+
* For example, `a = Select(... age=Where.gt(45) )`
|
45
|
+
|
46
|
+
Some possible conditions:
|
47
|
+
* field=Where.eq(value) - ...the field is EQUAL to the value;
|
48
|
+
* field=Where.gt(value) - ...the field is GREATER than the value;
|
49
|
+
* field=Where.lt(value) - ...the field is LESS than the value;
|
50
|
+
|
51
|
+
3.1 -- If you want to filter the field on a range of values:
|
52
|
+
|
53
|
+
`a = Select( 'Actor a', age=Between(45, 69) )`
|
54
|
+
|
55
|
+
3.2 -- Sub-queries:
|
56
|
+
|
57
|
+
```
|
58
|
+
query = Select('Movie m', title=Field,
|
59
|
+
id=SubSelect(
|
60
|
+
'Review r',
|
61
|
+
rate=Where.gt(4.5),
|
62
|
+
movie_id=Distinct
|
63
|
+
)
|
64
|
+
)
|
65
|
+
```
|
66
|
+
|
67
|
+
**>> print(query)**
|
68
|
+
|
69
|
+
SELECT
|
70
|
+
m.title
|
71
|
+
FROM
|
72
|
+
Movie m
|
73
|
+
WHERE
|
74
|
+
m.id IN (
|
75
|
+
SELECT DISTINCT r.movie
|
76
|
+
FROM Review r WHERE r.rate > 4.5
|
77
|
+
)
|
78
|
+
|
79
|
+
3.3 -- Optional conditions:
|
80
|
+
```
|
81
|
+
OR=Options(
|
82
|
+
genre=Where.eq("Sci-Fi"),
|
83
|
+
awards=Where.like("Oscar")
|
84
|
+
)
|
85
|
+
```
|
86
|
+
> Could be AND=Options(...)
|
87
|
+
|
88
|
+
---
|
89
|
+
### 4 - A field can be two things at the same time:
|
90
|
+
|
91
|
+
* m = Select('Movie m' release_date=[Field, OrderBy])
|
92
|
+
- This means that the field will appear in the results and also that the query will be ordered by that field.
|
93
|
+
* Applying **GROUP BY** to item 3.2, it would look like this:
|
94
|
+
```
|
95
|
+
SubSelect(
|
96
|
+
'Review r', movie=[GroupBy, Distinct],
|
97
|
+
rate=Having.avg(Where.gt(4.5))
|
98
|
+
)
|
99
|
+
```
|
100
|
+
---
|
101
|
+
### 5 - Relationships:
|
102
|
+
```
|
103
|
+
query = Select('Actor a', name=Field,
|
104
|
+
cast=Select('Cast c', id=PrimaryKey)
|
105
|
+
)
|
106
|
+
```
|
107
|
+
**>> print(query)**
|
108
|
+
```
|
109
|
+
SELECT
|
110
|
+
a.name
|
111
|
+
FROM
|
112
|
+
Actor a
|
113
|
+
JOIN Cast c ON (a.cast = c.id)
|
114
|
+
```
|
115
|
+
|
116
|
+
---
|
117
|
+
### 6 - The reverse process (parse):
|
118
|
+
```
|
119
|
+
text = """
|
120
|
+
SELECT
|
121
|
+
cas.role,
|
122
|
+
m.title,
|
123
|
+
m.release_date,
|
124
|
+
a.name as actors_name
|
125
|
+
FROM
|
126
|
+
Actor a
|
127
|
+
LEFT JOIN Cast cas ON (a.cast = cas.id)
|
128
|
+
LEFT JOIN Movie m ON (cas.movie = m.id)
|
129
|
+
WHERE
|
130
|
+
(
|
131
|
+
m.genre = 'Sci-Fi'
|
132
|
+
OR
|
133
|
+
m.awards LIKE '%Oscar%'
|
134
|
+
)
|
135
|
+
AND a.age <= 69 AND a.age >= 45
|
136
|
+
ORDER BY
|
137
|
+
m.release_date DESC
|
138
|
+
"""
|
139
|
+
```
|
140
|
+
|
141
|
+
`a, c, m = Select.parse(text)`
|
142
|
+
|
143
|
+
**6.1 --- print(a)**
|
144
|
+
```
|
145
|
+
SELECT
|
146
|
+
a.name as actors_name
|
147
|
+
FROM
|
148
|
+
Actor a
|
149
|
+
WHERE
|
150
|
+
a.age <= 69
|
151
|
+
AND a.age >= 45
|
152
|
+
```
|
153
|
+
|
154
|
+
**6.2 --- print(c)**
|
155
|
+
|
156
|
+
SELECT
|
157
|
+
c.role
|
158
|
+
FROM
|
159
|
+
Cast c
|
160
|
+
|
161
|
+
**6.3 --- print(m)**
|
162
|
+
|
163
|
+
SELECT
|
164
|
+
m.title,
|
165
|
+
m.release_date
|
166
|
+
FROM
|
167
|
+
Movie m
|
168
|
+
WHERE
|
169
|
+
( m.genre = 'Sci-Fi' OR m.awards LIKE '%Oscar%' )
|
170
|
+
ORDER BY
|
171
|
+
m.release_date DESC
|
172
|
+
|
173
|
+
|
174
|
+
|
175
|
+
**6.4 --- print(a+c)**
|
176
|
+
|
177
|
+
SELECT
|
178
|
+
a.name as actors_name,
|
179
|
+
cas.role
|
180
|
+
FROM
|
181
|
+
Actor a
|
182
|
+
JOIN Cast cas ON (a.cast = cas.id)
|
183
|
+
WHERE
|
184
|
+
a.age >= 45
|
185
|
+
AND a.age <= 69
|
186
|
+
|
187
|
+
**6.5 --- print(c+m)**
|
188
|
+
> `... or print(m+c)`
|
189
|
+
|
190
|
+
SELECT
|
191
|
+
cas.role,
|
192
|
+
m.title,
|
193
|
+
m.release_date,
|
194
|
+
m.director
|
195
|
+
FROM
|
196
|
+
Cast cas
|
197
|
+
JOIN Movie m ON (cas.movie = m.id)
|
198
|
+
WHERE
|
199
|
+
( m.genre = 'Sci-Fi' OR m.awards LIKE '%Oscar%' )
|
200
|
+
AND m.director LIKE '%Coppola%'
|
201
|
+
ORDER BY
|
202
|
+
m.release_date,
|
203
|
+
m.director
|
204
|
+
---
|
205
|
+
|
206
|
+
### 7 - You can add or delete attributes directly in objects:
|
207
|
+
* a(gender=Field)
|
208
|
+
* m.delete('director')
|
209
|
+
|
210
|
+
---
|
211
|
+
|
212
|
+
### 8 - Defining relationship on separate objects:
|
213
|
+
```
|
214
|
+
a = Select...
|
215
|
+
c = Select...
|
216
|
+
m = Select...
|
217
|
+
```
|
218
|
+
`a + c => ERROR: "No relationship found between Actor and Cast"`
|
219
|
+
|
220
|
+
8.1 - But...
|
221
|
+
|
222
|
+
a( cast=ForeignKey('Cast') )
|
223
|
+
c(id=PrimaryKey)
|
224
|
+
|
225
|
+
**a + c => Ok!**
|
226
|
+
|
227
|
+
8.2
|
228
|
+
|
229
|
+
c( movie=ForeignKey('Movie') )
|
230
|
+
m(id=PrimaryKey)
|
231
|
+
|
232
|
+
> **c + m => Ok!**
|
233
|
+
>> **m + c => Ok!**
|
234
|
+
|
235
|
+
|
236
|
+
---
|
237
|
+
|
238
|
+
### 9 - Comparing objects
|
239
|
+
|
240
|
+
9.1
|
241
|
+
```
|
242
|
+
a1 = Select.parse('''
|
243
|
+
SELECT gender, max(age) FROM Actor act
|
244
|
+
WHERE act.age <= 69 AND act.age >= 45
|
245
|
+
GROUP BY gender
|
246
|
+
''')[0]
|
247
|
+
|
248
|
+
a2 = Select('Actor',
|
249
|
+
age=Between(45, 69), gender=GroupBy,
|
250
|
+
gender=[GroupBy, Field]
|
251
|
+
)
|
252
|
+
```
|
253
|
+
> **a1 == a2 # --- True!**
|
254
|
+
|
255
|
+
|
256
|
+
|
257
|
+
9.2
|
258
|
+
```
|
259
|
+
m1 = Select.parse("""
|
260
|
+
SELECT title, release_date FROM Movie m ORDER BY release_date
|
261
|
+
WHERE m.genre = 'Sci-Fi' AND m.awards LIKE '%Oscar%'
|
262
|
+
""")[0]
|
263
|
+
|
264
|
+
m2 = Select.parse("""
|
265
|
+
SELECT release_date, title
|
266
|
+
FROM Movie m
|
267
|
+
WHERE m.awards LIKE '%Oscar%' AND m.genre = 'Sci-Fi'
|
268
|
+
ORDER BY release_date
|
269
|
+
""")[0]
|
270
|
+
```
|
271
|
+
|
272
|
+
**m1 == m2 # --- True!**
|
273
|
+
|
274
|
+
|
275
|
+
9.3
|
276
|
+
```
|
277
|
+
best_movies = SubSelect(
|
278
|
+
Review=Table('role'),
|
279
|
+
rate=[GroupBy, Having.avg(Where.gt(4.5))]
|
280
|
+
)
|
281
|
+
m1 = Select(
|
282
|
+
Movie=Table('title,release_date),
|
283
|
+
id=best_movies
|
284
|
+
)
|
285
|
+
|
286
|
+
sql = "SELECT rev.role FROM Review rev GROUP BY rev.rate HAVING Avg(rev.rate) > 4.5"
|
287
|
+
m2 = Select(
|
288
|
+
'Movie', release_date=Field, title=Field,
|
289
|
+
id=Where(f"IN ({sql})")
|
290
|
+
)
|
291
|
+
```
|
292
|
+
**m1 == m2 # --- True!**
|
293
|
+
|
294
|
+
---
|
295
|
+
---
|
296
|
+
|
@@ -0,0 +1,7 @@
|
|
1
|
+
sql_blocks/__init__.py,sha256=TodC5q-UEdYEz9v1RRoogVqqRcsKnZRY1WDGinrI2zo,26
|
2
|
+
sql_blocks/sql_blocks.py,sha256=V5WKBZg90emr0-jPpFEyX9rtU4VR3JlQKKu1qhlaiH0,13193
|
3
|
+
sql_blocks-0.0.2.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
+
sql_blocks-0.0.2.dist-info/METADATA,sha256=stJ7wQ6iRmHNvf4nJBVr_r5W13Z-ey1ZAllXd8kV9bw,6604
|
5
|
+
sql_blocks-0.0.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
+
sql_blocks-0.0.2.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
+
sql_blocks-0.0.2.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
sql_blocks
|