sql-blocks 1.25.1901__py3-none-any.whl → 1.25.1021229__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/sql_blocks.py +172 -32
- {sql_blocks-1.25.1901.dist-info → sql_blocks-1.25.1021229.dist-info}/METADATA +161 -6
- sql_blocks-1.25.1021229.dist-info/RECORD +7 -0
- sql_blocks-1.25.1901.dist-info/RECORD +0 -7
- {sql_blocks-1.25.1901.dist-info → sql_blocks-1.25.1021229.dist-info}/LICENSE +0 -0
- {sql_blocks-1.25.1901.dist-info → sql_blocks-1.25.1021229.dist-info}/WHEEL +0 -0
- {sql_blocks-1.25.1901.dist-info → sql_blocks-1.25.1021229.dist-info}/top_level.txt +0 -0
sql_blocks/sql_blocks.py
CHANGED
@@ -42,23 +42,34 @@ class SQLObject:
|
|
42
42
|
if not table_name:
|
43
43
|
return
|
44
44
|
cls = SQLObject
|
45
|
+
is_file_name = any([
|
46
|
+
'/' in table_name, '.' in table_name
|
47
|
+
])
|
48
|
+
ref = table_name
|
49
|
+
if is_file_name:
|
50
|
+
ref = table_name.split('/')[-1].split('.')[0]
|
45
51
|
if cls.ALIAS_FUNC:
|
46
|
-
self.__alias = cls.ALIAS_FUNC(
|
52
|
+
self.__alias = cls.ALIAS_FUNC(ref)
|
47
53
|
elif ' ' in table_name.strip():
|
48
54
|
table_name, self.__alias = table_name.split()
|
49
|
-
elif '_' in
|
55
|
+
elif '_' in ref:
|
50
56
|
self.__alias = ''.join(
|
51
57
|
word[0].lower()
|
52
|
-
for word in
|
58
|
+
for word in ref.split('_')
|
53
59
|
)
|
54
60
|
else:
|
55
|
-
self.__alias =
|
61
|
+
self.__alias = ref.lower()[:3]
|
56
62
|
self.values.setdefault(FROM, []).append(f'{table_name} {self.alias}')
|
57
63
|
|
58
64
|
@property
|
59
65
|
def table_name(self) -> str:
|
60
66
|
return self.values[FROM][0].split()[0]
|
61
67
|
|
68
|
+
def set_file_format(self, pattern: str):
|
69
|
+
if '{' not in pattern:
|
70
|
+
pattern = '{}' + pattern
|
71
|
+
self.values[FROM][0] = pattern.format(self.aka())
|
72
|
+
|
62
73
|
@property
|
63
74
|
def alias(self) -> str:
|
64
75
|
if self.__alias:
|
@@ -360,15 +371,20 @@ class ExpressionField:
|
|
360
371
|
class FieldList:
|
361
372
|
separator = ','
|
362
373
|
|
363
|
-
def __init__(self, fields: list=[], class_types = [Field]):
|
374
|
+
def __init__(self, fields: list=[], class_types = [Field], ziped: bool=False):
|
364
375
|
if isinstance(fields, str):
|
365
376
|
fields = [
|
366
377
|
f.strip() for f in fields.split(self.separator)
|
367
378
|
]
|
368
379
|
self.fields = fields
|
369
380
|
self.class_types = class_types
|
381
|
+
self.ziped = ziped
|
370
382
|
|
371
383
|
def add(self, name: str, main: SQLObject):
|
384
|
+
if self.ziped: # --- One class per field...
|
385
|
+
for field, class_type in zip(self.fields, self.class_types):
|
386
|
+
class_type.add(field, main)
|
387
|
+
return
|
372
388
|
for field in self.fields:
|
373
389
|
for class_type in self.class_types:
|
374
390
|
class_type.add(field, main)
|
@@ -423,23 +439,23 @@ class Position(Enum):
|
|
423
439
|
class Where:
|
424
440
|
prefix = ''
|
425
441
|
|
426
|
-
def __init__(self,
|
427
|
-
self.
|
442
|
+
def __init__(self, content: str):
|
443
|
+
self.content = content
|
428
444
|
|
429
445
|
@classmethod
|
430
446
|
def __constructor(cls, operator: str, value):
|
431
|
-
return cls(
|
447
|
+
return cls(f'{operator} {quoted(value)}')
|
432
448
|
|
433
449
|
@classmethod
|
434
450
|
def eq(cls, value):
|
435
451
|
return cls.__constructor('=', value)
|
436
452
|
|
437
453
|
@classmethod
|
438
|
-
def contains(cls,
|
454
|
+
def contains(cls, text: str, pos: Position = Position.Middle):
|
439
455
|
return cls(
|
440
456
|
"LIKE '{}{}{}'".format(
|
441
457
|
'%' if pos != Position.StartsWith else '',
|
442
|
-
|
458
|
+
text,
|
443
459
|
'%' if pos != Position.EndsWith else ''
|
444
460
|
)
|
445
461
|
)
|
@@ -470,14 +486,47 @@ class Where:
|
|
470
486
|
values = ','.join(quoted(v) for v in values)
|
471
487
|
return cls(f'IN ({values})')
|
472
488
|
|
489
|
+
@classmethod
|
490
|
+
def formula(cls, formula: str):
|
491
|
+
where = cls( ExpressionField(formula) )
|
492
|
+
where.add = where.add_expression
|
493
|
+
return where
|
494
|
+
|
495
|
+
def add_expression(self, name: str, main: SQLObject):
|
496
|
+
self.content = self.content.format(name, main)
|
497
|
+
main.values.setdefault(WHERE, []).append('{} {}'.format(
|
498
|
+
self.prefix, self.content
|
499
|
+
))
|
500
|
+
|
501
|
+
@classmethod
|
502
|
+
def join(cls, query: SQLObject):
|
503
|
+
where = cls(query)
|
504
|
+
where.add = where.add_join
|
505
|
+
return where
|
506
|
+
|
507
|
+
def add_join(self, name: str, main: SQLObject):
|
508
|
+
query = self.content
|
509
|
+
main.values[FROM].append(f',{query.table_name} {query.alias}')
|
510
|
+
for key in USUAL_KEYS:
|
511
|
+
main.update_values(key, query.values.get(key, []))
|
512
|
+
main.values.setdefault(WHERE, []).append('({a1}.{f1} = {a2}.{f2})'.format(
|
513
|
+
a1=main.alias, f1=name,
|
514
|
+
a2=query.alias, f2=query.key_field
|
515
|
+
))
|
516
|
+
|
473
517
|
def add(self, name: str, main: SQLObject):
|
474
518
|
func_type = FUNCTION_CLASS.get(name.lower())
|
519
|
+
exists = any(
|
520
|
+
main.is_named_field(fld, SELECT)
|
521
|
+
for fld in main.values.get(SELECT, [])
|
522
|
+
if name in fld
|
523
|
+
)
|
475
524
|
if func_type:
|
476
525
|
name = func_type.format('*', main)
|
477
|
-
|
526
|
+
elif not exists:
|
478
527
|
name = Field.format(name, main)
|
479
528
|
main.values.setdefault(WHERE, []).append('{}{} {}'.format(
|
480
|
-
self.prefix, name, self.
|
529
|
+
self.prefix, name, self.content
|
481
530
|
))
|
482
531
|
|
483
532
|
|
@@ -485,6 +534,10 @@ eq, contains, gt, gte, lt, lte, is_null, inside = (
|
|
485
534
|
getattr(Where, method) for method in
|
486
535
|
('eq', 'contains', 'gt', 'gte', 'lt', 'lte', 'is_null', 'inside')
|
487
536
|
)
|
537
|
+
startswith, endswith = [
|
538
|
+
lambda x: contains(x, Position.StartsWith),
|
539
|
+
lambda x: contains(x, Position.EndsWith)
|
540
|
+
]
|
488
541
|
|
489
542
|
|
490
543
|
class Not(Where):
|
@@ -492,7 +545,7 @@ class Not(Where):
|
|
492
545
|
|
493
546
|
@classmethod
|
494
547
|
def eq(cls, value):
|
495
|
-
return Where(
|
548
|
+
return Where(f'<> {quoted(value)}')
|
496
549
|
|
497
550
|
|
498
551
|
class Case:
|
@@ -533,7 +586,7 @@ class Options:
|
|
533
586
|
child: Where
|
534
587
|
for field, child in self.__children.items():
|
535
588
|
conditions.append(' {} {} '.format(
|
536
|
-
Field.format(field, main), child.
|
589
|
+
Field.format(field, main), child.content
|
537
590
|
))
|
538
591
|
main.values.setdefault(WHERE, []).append(
|
539
592
|
'(' + logical_separator.join(conditions) + ')'
|
@@ -561,7 +614,7 @@ class Clause:
|
|
561
614
|
found = re.findall(r'^_\d', name)
|
562
615
|
if found:
|
563
616
|
name = found[0].replace('_', '')
|
564
|
-
elif main.alias and not is_function():
|
617
|
+
elif '.' not in name and main.alias and not is_function():
|
565
618
|
name = f'{main.alias}.{name}'
|
566
619
|
return name
|
567
620
|
|
@@ -632,7 +685,7 @@ class Having:
|
|
632
685
|
|
633
686
|
def add(self, name: str, main:SQLObject):
|
634
687
|
main.values[GROUP_BY][-1] += ' HAVING {} {}'.format(
|
635
|
-
self.function.format(name, main), self.condition.
|
688
|
+
self.function.format(name, main), self.condition.content
|
636
689
|
)
|
637
690
|
|
638
691
|
@classmethod
|
@@ -1003,8 +1056,11 @@ class SQLParser(Parser):
|
|
1003
1056
|
if not key in values:
|
1004
1057
|
continue
|
1005
1058
|
separator = self.class_type.get_separator(key)
|
1059
|
+
cls = {
|
1060
|
+
ORDER_BY: OrderBy, GROUP_BY: GroupBy
|
1061
|
+
}.get(key, Field)
|
1006
1062
|
obj.values[key] = [
|
1007
|
-
|
1063
|
+
cls.format(fld, obj)
|
1008
1064
|
for fld in re.split(separator, values[key])
|
1009
1065
|
if (fld != '*' and len(tables) == 1) or obj.match(fld, key)
|
1010
1066
|
]
|
@@ -1066,7 +1122,11 @@ class CypherParser(Parser):
|
|
1066
1122
|
if token in self.TOKEN_METHODS:
|
1067
1123
|
return
|
1068
1124
|
class_list = [Field]
|
1069
|
-
if '
|
1125
|
+
if '*' in token:
|
1126
|
+
token = token.replace('*', '')
|
1127
|
+
self.queries[-1].key_field = token
|
1128
|
+
return
|
1129
|
+
elif '$' in token:
|
1070
1130
|
func_name, token = token.split('$')
|
1071
1131
|
if func_name == 'count':
|
1072
1132
|
if not token:
|
@@ -1090,10 +1150,13 @@ class CypherParser(Parser):
|
|
1090
1150
|
def add_foreign_key(self, token: str, pk_field: str=''):
|
1091
1151
|
curr, last = [self.queries[i] for i in (-1, -2)]
|
1092
1152
|
if not pk_field:
|
1093
|
-
if
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1153
|
+
if last.key_field:
|
1154
|
+
pk_field = last.key_field
|
1155
|
+
else:
|
1156
|
+
if not last.values.get(SELECT):
|
1157
|
+
raise IndexError(f'Primary Key not found for {last.table_name}.')
|
1158
|
+
pk_field = last.values[SELECT][-1].split('.')[-1]
|
1159
|
+
last.delete(pk_field, [SELECT], exact=True)
|
1097
1160
|
if '{}' in token:
|
1098
1161
|
foreign_fld = token.format(
|
1099
1162
|
last.table_name.lower()
|
@@ -1411,6 +1474,88 @@ class NotSelectIN(SelectIN):
|
|
1411
1474
|
condition_class = Not
|
1412
1475
|
|
1413
1476
|
|
1477
|
+
class CTE(Select):
|
1478
|
+
prefix = ''
|
1479
|
+
|
1480
|
+
def __init__(self, table_name: str, query_list: list[Select]):
|
1481
|
+
super().__init__(table_name)
|
1482
|
+
for query in query_list:
|
1483
|
+
query.break_lines = False
|
1484
|
+
self.query_list = query_list
|
1485
|
+
self.break_lines = False
|
1486
|
+
|
1487
|
+
def __str__(self) -> str:
|
1488
|
+
# ---------------------------------------------------------
|
1489
|
+
def justify(query: Select) -> str:
|
1490
|
+
result, line = [], ''
|
1491
|
+
keywords = '|'.join(KEYWORD)
|
1492
|
+
for word in re.split(fr'({keywords}|AND|OR|,)', str(query)):
|
1493
|
+
if len(line) >= 65:
|
1494
|
+
result.append(line)
|
1495
|
+
line = ''
|
1496
|
+
line += word
|
1497
|
+
if line:
|
1498
|
+
result.append(line)
|
1499
|
+
return '\n '.join(result)
|
1500
|
+
# ---------------------------------------------------------
|
1501
|
+
return 'WITH {}{} AS (\n {}\n){}'.format(
|
1502
|
+
self.prefix, self.table_name,
|
1503
|
+
'\nUNION ALL\n '.join(
|
1504
|
+
justify(q) for q in self.query_list
|
1505
|
+
), super().__str__()
|
1506
|
+
)
|
1507
|
+
|
1508
|
+
class Recursive(CTE):
|
1509
|
+
prefix = 'RECURSIVE '
|
1510
|
+
|
1511
|
+
def __str__(self) -> str:
|
1512
|
+
if len(self.query_list) > 1:
|
1513
|
+
self.query_list[-1].values[FROM].append(
|
1514
|
+
f', {self.table_name} {self.alias}')
|
1515
|
+
return super().__str__()
|
1516
|
+
|
1517
|
+
@classmethod
|
1518
|
+
def create(cls, name: str, pattern: str, formula: str, init_value, format: str=''):
|
1519
|
+
SQLObject.ALIAS_FUNC = None
|
1520
|
+
def get_field(obj: SQLObject, pos: int) -> str:
|
1521
|
+
return obj.values[SELECT][pos].split('.')[-1]
|
1522
|
+
t1, t2 = detect(
|
1523
|
+
pattern*2, join_queries=False, format=format
|
1524
|
+
)
|
1525
|
+
pk_field = get_field(t1, 0)
|
1526
|
+
foreign_key = ''
|
1527
|
+
for num in re.findall(r'\[(\d+)\]', formula):
|
1528
|
+
num = int(num)
|
1529
|
+
if not foreign_key:
|
1530
|
+
foreign_key = get_field(t2, num-1)
|
1531
|
+
formula = formula.replace(f'[{num}]', '%')
|
1532
|
+
else:
|
1533
|
+
formula = formula.replace(f'[{num}]', get_field(t2, num-1))
|
1534
|
+
Where.eq(init_value).add(pk_field, t1)
|
1535
|
+
Where.formula(formula).add(foreign_key or pk_field, t2)
|
1536
|
+
return cls(name, [t1, t2])
|
1537
|
+
|
1538
|
+
def join(self, pattern: str, fields: list | str, format: str):
|
1539
|
+
if isinstance(fields, str):
|
1540
|
+
count = len( fields.split(',') )
|
1541
|
+
else:
|
1542
|
+
count = len(fields)
|
1543
|
+
queries = detect(
|
1544
|
+
pattern*count, join_queries=False, format=format
|
1545
|
+
)
|
1546
|
+
FieldList(fields, queries, ziped=True).add('', self)
|
1547
|
+
self.break_lines = True
|
1548
|
+
|
1549
|
+
def counter(self, name: str, start, increment: str='+1'):
|
1550
|
+
for i, query in enumerate(self.query_list):
|
1551
|
+
if i == 0:
|
1552
|
+
Field.add(f'{start} AS {name}', query)
|
1553
|
+
else:
|
1554
|
+
Field.add(f'({name}{increment}) AS {name}', query)
|
1555
|
+
|
1556
|
+
|
1557
|
+
# ----- Rules -----
|
1558
|
+
|
1414
1559
|
class RulePutLimit(Rule):
|
1415
1560
|
@classmethod
|
1416
1561
|
def apply(cls, target: Select):
|
@@ -1525,7 +1670,7 @@ def parser_class(text: str) -> Parser:
|
|
1525
1670
|
return None
|
1526
1671
|
|
1527
1672
|
|
1528
|
-
def detect(text: str, join_queries: bool = True) -> Select:
|
1673
|
+
def detect(text: str, join_queries: bool = True, format: str='') -> Select | list[Select]:
|
1529
1674
|
from collections import Counter
|
1530
1675
|
parser = parser_class(text)
|
1531
1676
|
if not parser:
|
@@ -1536,11 +1681,14 @@ def detect(text: str, join_queries: bool = True) -> Select:
|
|
1536
1681
|
continue
|
1537
1682
|
pos = [ f.span() for f in re.finditer(fr'({table})[(]', text) ]
|
1538
1683
|
for begin, end in pos[::-1]:
|
1539
|
-
new_name = f'{table}_{count}' # See set_table (line
|
1684
|
+
new_name = f'{table}_{count}' # See set_table (line 55)
|
1540
1685
|
Select.EQUIVALENT_NAMES[new_name] = table
|
1541
1686
|
text = text[:begin] + new_name + '(' + text[end:]
|
1542
1687
|
count -= 1
|
1543
1688
|
query_list = Select.parse(text, parser)
|
1689
|
+
if format:
|
1690
|
+
for query in query_list:
|
1691
|
+
query.set_file_format(format)
|
1544
1692
|
if not join_queries:
|
1545
1693
|
return query_list
|
1546
1694
|
result = query_list[0]
|
@@ -1549,11 +1697,3 @@ def detect(text: str, join_queries: bool = True) -> Select:
|
|
1549
1697
|
return result
|
1550
1698
|
|
1551
1699
|
|
1552
|
-
if __name__ == '__main__':
|
1553
|
-
query = Select(
|
1554
|
-
'Tips t',
|
1555
|
-
tip=[Field, Lag().over(day=OrderBy).As('last')],
|
1556
|
-
diff=ExpressionField('Round(tip-last, 2) as {f}'),
|
1557
|
-
Row_Number=gt(1)
|
1558
|
-
)
|
1559
|
-
print(query)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sql_blocks
|
3
|
-
Version: 1.25.
|
3
|
+
Version: 1.25.1021229
|
4
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
5
|
Home-page: https://github.com/julio-cascalles/sql_blocks
|
6
6
|
Author: Júlio Cascalles
|
@@ -98,10 +98,7 @@ query = Select('Movie m', title=Field,
|
|
98
98
|
awards=contains("Oscar")
|
99
99
|
)
|
100
100
|
AND=Options(
|
101
|
-
..., name=
|
102
|
-
'Chris',
|
103
|
-
Position.StartsWith
|
104
|
-
)
|
101
|
+
..., name=startswith('Chris')
|
105
102
|
)
|
106
103
|
```
|
107
104
|
|
@@ -115,6 +112,16 @@ based_on_book=Not.is_null()
|
|
115
112
|
hash_tag=inside(['space', 'monster', 'gore'])
|
116
113
|
```
|
117
114
|
|
115
|
+
3.6 -- Combining ExpressionField with Where condition:
|
116
|
+
* The **formula** method allows you to write an expression as a condition:
|
117
|
+
```
|
118
|
+
query=Select(
|
119
|
+
'Folks f2',
|
120
|
+
id=Where.formula('({af} = a.father OR {af} = a.mother)')
|
121
|
+
)
|
122
|
+
```
|
123
|
+
> Results: `WHERE...f2.id = a.father OR f2.id = a.mother`
|
124
|
+
|
118
125
|
---
|
119
126
|
### 4 - A field can be two things at the same time:
|
120
127
|
|
@@ -143,6 +150,35 @@ FROM
|
|
143
150
|
JOIN Cast c ON (a.cast = c.id)
|
144
151
|
```
|
145
152
|
|
153
|
+
---
|
154
|
+
**5.1 Multiple tables without JOIN**
|
155
|
+
> Warning: This is **NOT** recommended! ⛔
|
156
|
+
|
157
|
+
|
158
|
+
#### Example:
|
159
|
+
singer = Select(
|
160
|
+
"Singer artist", id=PrimaryKey,
|
161
|
+
name=NamedField('artist_name')
|
162
|
+
)
|
163
|
+
album = Select (
|
164
|
+
"Album album",
|
165
|
+
name=NamedField('album_name'),
|
166
|
+
artist_id=Where.join(singer), # <===== 👀
|
167
|
+
)
|
168
|
+
**>> print(query)**
|
169
|
+
|
170
|
+
SELECT
|
171
|
+
album.name as album_name,
|
172
|
+
artist.name as artist_name,
|
173
|
+
album.year_recorded
|
174
|
+
FROM
|
175
|
+
Album album
|
176
|
+
,Singer artist
|
177
|
+
WHERE
|
178
|
+
(album.artist_id = artist.id)
|
179
|
+
|
180
|
+
|
181
|
+
|
146
182
|
---
|
147
183
|
### 6 - The reverse process (parse):
|
148
184
|
```
|
@@ -373,7 +409,7 @@ m2 = Select(
|
|
373
409
|
query = Select(
|
374
410
|
'Installments i', due_date=Field, customer=Select(
|
375
411
|
'Customer c', id=PrimaryKey,
|
376
|
-
name=
|
412
|
+
name=endswith('Smith')
|
377
413
|
)
|
378
414
|
)
|
379
415
|
print(query)
|
@@ -453,6 +489,7 @@ ORDER BY
|
|
453
489
|
* `^` Put the field in the ORDER BY clause
|
454
490
|
* `@` Immediately after the table name, it indicates the grouping field.
|
455
491
|
* `$` For SQL functions like **avg**$_field_, **sum**$_field_, **count**$_field_...
|
492
|
+
* `*` Sets the primary key field.
|
456
493
|
|
457
494
|
|
458
495
|
---
|
@@ -631,3 +668,121 @@ For example, if your query is going to run on Oracle, do the following:
|
|
631
668
|
|
632
669
|
`Function.dialect = Dialect.ORACLE`
|
633
670
|
|
671
|
+
---
|
672
|
+
|
673
|
+
### 17 - CTE and Recursive classes
|
674
|
+
|
675
|
+
* **17.1 - _CTE class_**
|
676
|
+
```
|
677
|
+
query = Select(
|
678
|
+
'SocialMedia s', post=Count, reaction=Sum, user=GroupBy
|
679
|
+
)
|
680
|
+
print( CTE('Metrics', [query]) )
|
681
|
+
```
|
682
|
+
The result is...
|
683
|
+
```
|
684
|
+
WITH Metrics AS (
|
685
|
+
SELECT Count(s.post), Sum(s.reaction) FROM SocialMedia s GROUP BY user
|
686
|
+
)SELECT * FROM Metrics
|
687
|
+
```
|
688
|
+
|
689
|
+
* **17.2 - _Recursive class_**
|
690
|
+
```
|
691
|
+
q1 = Select(
|
692
|
+
'SocialMedia me', name=[ eq(MY_NAME), Field ]
|
693
|
+
)
|
694
|
+
q2 = Select(
|
695
|
+
'SocialMedia you' name=Field, id=Where.formula('{af} = n.friend')
|
696
|
+
)
|
697
|
+
print( Recursive('Network', [q1, q2]) )
|
698
|
+
```
|
699
|
+
The result is...
|
700
|
+
```
|
701
|
+
WITH RECURSIVE Network AS (
|
702
|
+
SELECT me.name FROM SocialMedia me WHERE
|
703
|
+
me.name = 'Júlio Cascalles'
|
704
|
+
UNION ALL
|
705
|
+
SELECT you.name FROM SocialMedia you , Network n
|
706
|
+
WHERE you.id = n.friend
|
707
|
+
)SELECT * FROM Network
|
708
|
+
```
|
709
|
+
|
710
|
+
* **17.2.1 - The `create` method** ... parameters :
|
711
|
+
- name: The name of the CTE
|
712
|
+
- pattern: A cypher script that defines the tables used
|
713
|
+
- formula: The format for `Where.formula` method _(*)_
|
714
|
+
- init_value: The value for the condition in the first table
|
715
|
+
- format (optional): If tables are files or internet hiperlinks, you may especify the extension and/or folder...
|
716
|
+
> Example:
|
717
|
+
```
|
718
|
+
R = Recursive.create(
|
719
|
+
'Route R', 'Flyght(departure, arrival)',
|
720
|
+
'[2] = R.[1]', 'JFK', format='.csv'
|
721
|
+
) # ^^^--- Flyghts from JFK airport
|
722
|
+
```
|
723
|
+
_...Creates a recursive CTE called Route, using Flyght table, where the recursivity condition is Flyght.arrival equals to Route.departure_
|
724
|
+
>> (*) -- Note that [1] and [2] refers to first field and second field. 😉
|
725
|
+
|
726
|
+
Result:
|
727
|
+
|
728
|
+
WITH RECURSIVE Route AS (
|
729
|
+
SELECT f1.departure, f1.arrival
|
730
|
+
FROM Flyght.csv f1
|
731
|
+
WHERE f1.departure = 'JFK'
|
732
|
+
UNION ALL
|
733
|
+
SELECT f2.departure, f2.arrival
|
734
|
+
FROM Flyght.csv f2
|
735
|
+
, Route R
|
736
|
+
WHERE f2.arrival = R.departure
|
737
|
+
)SELECT * FROM Route R
|
738
|
+
|
739
|
+
**17.2.2 - The `join` method**
|
740
|
+
|
741
|
+
In the previous example, if you add this code...
|
742
|
+
`R.join('Airport(*id,name)', 'departure, arrival', format='.csv')`
|
743
|
+
|
744
|
+
...The result would be:
|
745
|
+
|
746
|
+
WITH RECURSIVE Route AS (
|
747
|
+
SELECT f1.departure, f1.arrival
|
748
|
+
FROM Flyght.csv f1
|
749
|
+
WHERE f1.departure = 'JFK'
|
750
|
+
UNION ALL
|
751
|
+
SELECT f2.departure, f2.arrival
|
752
|
+
FROM Flyght.csv f2
|
753
|
+
, Route R
|
754
|
+
WHERE f2.arrival = R.departure
|
755
|
+
)SELECT
|
756
|
+
a1.name, a2.name
|
757
|
+
FROM
|
758
|
+
Route R
|
759
|
+
JOIN Airport.csv a2 ON (R.arrival = a2.id)
|
760
|
+
JOIN Airport.csv a1 ON (R.departure = a1.id)
|
761
|
+
|
762
|
+
|
763
|
+
**17.2.3 - The `counter` method**
|
764
|
+
Adds an increment field in queries inside CTE:
|
765
|
+
> Examples:
|
766
|
+
* `R.counter('stops', 0)` # -- counter starts with 0 and increment +1
|
767
|
+
* `R2.counter('generation', 5, '- 1')` # -- for the code below...
|
768
|
+
```
|
769
|
+
R2 = Recursive.create(
|
770
|
+
'Ancestors a', 'People(id,name,father,mother,birth)',
|
771
|
+
'(% = a.father OR % = a.mother)', 32630, '.parquet'
|
772
|
+
)
|
773
|
+
```
|
774
|
+
...Results:
|
775
|
+
|
776
|
+
WITH RECURSIVE Ancestors AS (
|
777
|
+
SELECT p1.id, p1.name, p1.father, p1.mother, p1.birth,
|
778
|
+
5 AS generation /* <<---- Most current generation ------------*/
|
779
|
+
FROM People.parquet p1 WHERE p1.id = 32630
|
780
|
+
UNION ALL
|
781
|
+
SELECT p2.id, p2.name, p2.father, p2.mother, p2.birth,
|
782
|
+
(generation- 1) AS generation /* <<-- Previous generation -----*/
|
783
|
+
FROM People.parquet p2 , Ancestors a WHERE (p2.id = a.father OR p2.id = a.mother)
|
784
|
+
)SELECT * FROM Ancestors a
|
785
|
+
|
786
|
+
|
787
|
+
>> Note: Comments added later.
|
788
|
+
---
|
@@ -0,0 +1,7 @@
|
|
1
|
+
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
+
sql_blocks/sql_blocks.py,sha256=VW8n2nWu7RREnns8dmYruhlvzGBCA3EeuBKiXCjClmE,56975
|
3
|
+
sql_blocks-1.25.1021229.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
+
sql_blocks-1.25.1021229.dist-info/METADATA,sha256=XXScfi4okZQLPPHvVvi2bd6YsvQQLb5ry2_mmOLThU4,19573
|
5
|
+
sql_blocks-1.25.1021229.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
+
sql_blocks-1.25.1021229.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
+
sql_blocks-1.25.1021229.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
-
sql_blocks/sql_blocks.py,sha256=U2bcs8SnXcGOlsY9rhB3lpPzzb-W4MsG64N7r0QjKEI,51822
|
3
|
-
sql_blocks-1.25.1901.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
-
sql_blocks-1.25.1901.dist-info/METADATA,sha256=MCkLq9JFvLiVAEJc_IhDLd3rExUKeVa04EtC9IwJwsA,15027
|
5
|
-
sql_blocks-1.25.1901.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
-
sql_blocks-1.25.1901.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
-
sql_blocks-1.25.1901.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|