sql-blocks 1.25.1801__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 +179 -37
- {sql_blocks-1.25.1801.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.1801.dist-info/RECORD +0 -7
- {sql_blocks-1.25.1801.dist-info → sql_blocks-1.25.1021229.dist-info}/LICENSE +0 -0
- {sql_blocks-1.25.1801.dist-info → sql_blocks-1.25.1021229.dist-info}/WHEEL +0 -0
- {sql_blocks-1.25.1801.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:
|
@@ -335,6 +346,9 @@ class Cast(Function):
|
|
335
346
|
...
|
336
347
|
|
337
348
|
|
349
|
+
FUNCTION_CLASS = {f.__name__.lower(): f for f in Function.__subclasses__()}
|
350
|
+
|
351
|
+
|
338
352
|
class ExpressionField:
|
339
353
|
def __init__(self, expr: str):
|
340
354
|
self.expr = expr
|
@@ -357,15 +371,20 @@ class ExpressionField:
|
|
357
371
|
class FieldList:
|
358
372
|
separator = ','
|
359
373
|
|
360
|
-
def __init__(self, fields: list=[], class_types = [Field]):
|
374
|
+
def __init__(self, fields: list=[], class_types = [Field], ziped: bool=False):
|
361
375
|
if isinstance(fields, str):
|
362
376
|
fields = [
|
363
377
|
f.strip() for f in fields.split(self.separator)
|
364
378
|
]
|
365
379
|
self.fields = fields
|
366
380
|
self.class_types = class_types
|
381
|
+
self.ziped = ziped
|
367
382
|
|
368
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
|
369
388
|
for field in self.fields:
|
370
389
|
for class_type in self.class_types:
|
371
390
|
class_type.add(field, main)
|
@@ -420,23 +439,23 @@ class Position(Enum):
|
|
420
439
|
class Where:
|
421
440
|
prefix = ''
|
422
441
|
|
423
|
-
def __init__(self,
|
424
|
-
self.
|
442
|
+
def __init__(self, content: str):
|
443
|
+
self.content = content
|
425
444
|
|
426
445
|
@classmethod
|
427
446
|
def __constructor(cls, operator: str, value):
|
428
|
-
return cls(
|
447
|
+
return cls(f'{operator} {quoted(value)}')
|
429
448
|
|
430
449
|
@classmethod
|
431
450
|
def eq(cls, value):
|
432
451
|
return cls.__constructor('=', value)
|
433
452
|
|
434
453
|
@classmethod
|
435
|
-
def contains(cls,
|
454
|
+
def contains(cls, text: str, pos: Position = Position.Middle):
|
436
455
|
return cls(
|
437
456
|
"LIKE '{}{}{}'".format(
|
438
457
|
'%' if pos != Position.StartsWith else '',
|
439
|
-
|
458
|
+
text,
|
440
459
|
'%' if pos != Position.EndsWith else ''
|
441
460
|
)
|
442
461
|
)
|
@@ -467,9 +486,47 @@ class Where:
|
|
467
486
|
values = ','.join(quoted(v) for v in values)
|
468
487
|
return cls(f'IN ({values})')
|
469
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
|
+
|
470
517
|
def add(self, name: str, main: SQLObject):
|
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
|
+
)
|
524
|
+
if func_type:
|
525
|
+
name = func_type.format('*', main)
|
526
|
+
elif not exists:
|
527
|
+
name = Field.format(name, main)
|
471
528
|
main.values.setdefault(WHERE, []).append('{}{} {}'.format(
|
472
|
-
self.prefix,
|
529
|
+
self.prefix, name, self.content
|
473
530
|
))
|
474
531
|
|
475
532
|
|
@@ -477,6 +534,10 @@ eq, contains, gt, gte, lt, lte, is_null, inside = (
|
|
477
534
|
getattr(Where, method) for method in
|
478
535
|
('eq', 'contains', 'gt', 'gte', 'lt', 'lte', 'is_null', 'inside')
|
479
536
|
)
|
537
|
+
startswith, endswith = [
|
538
|
+
lambda x: contains(x, Position.StartsWith),
|
539
|
+
lambda x: contains(x, Position.EndsWith)
|
540
|
+
]
|
480
541
|
|
481
542
|
|
482
543
|
class Not(Where):
|
@@ -484,7 +545,7 @@ class Not(Where):
|
|
484
545
|
|
485
546
|
@classmethod
|
486
547
|
def eq(cls, value):
|
487
|
-
return Where(
|
548
|
+
return Where(f'<> {quoted(value)}')
|
488
549
|
|
489
550
|
|
490
551
|
class Case:
|
@@ -525,7 +586,7 @@ class Options:
|
|
525
586
|
child: Where
|
526
587
|
for field, child in self.__children.items():
|
527
588
|
conditions.append(' {} {} '.format(
|
528
|
-
Field.format(field, main), child.
|
589
|
+
Field.format(field, main), child.content
|
529
590
|
))
|
530
591
|
main.values.setdefault(WHERE, []).append(
|
531
592
|
'(' + logical_separator.join(conditions) + ')'
|
@@ -549,12 +610,11 @@ class Clause:
|
|
549
610
|
def format(cls, name: str, main: SQLObject) -> str:
|
550
611
|
def is_function() -> bool:
|
551
612
|
diff = main.diff(SELECT, [name.lower()], True)
|
552
|
-
FUNCTION_CLASS = {f.__name__.lower(): f for f in Function.__subclasses__()}
|
553
613
|
return diff.intersection(FUNCTION_CLASS)
|
554
614
|
found = re.findall(r'^_\d', name)
|
555
615
|
if found:
|
556
616
|
name = found[0].replace('_', '')
|
557
|
-
elif main.alias and not is_function():
|
617
|
+
elif '.' not in name and main.alias and not is_function():
|
558
618
|
name = f'{main.alias}.{name}'
|
559
619
|
return name
|
560
620
|
|
@@ -625,7 +685,7 @@ class Having:
|
|
625
685
|
|
626
686
|
def add(self, name: str, main:SQLObject):
|
627
687
|
main.values[GROUP_BY][-1] += ' HAVING {} {}'.format(
|
628
|
-
self.function.format(name, main), self.condition.
|
688
|
+
self.function.format(name, main), self.condition.content
|
629
689
|
)
|
630
690
|
|
631
691
|
@classmethod
|
@@ -996,8 +1056,11 @@ class SQLParser(Parser):
|
|
996
1056
|
if not key in values:
|
997
1057
|
continue
|
998
1058
|
separator = self.class_type.get_separator(key)
|
1059
|
+
cls = {
|
1060
|
+
ORDER_BY: OrderBy, GROUP_BY: GroupBy
|
1061
|
+
}.get(key, Field)
|
999
1062
|
obj.values[key] = [
|
1000
|
-
|
1063
|
+
cls.format(fld, obj)
|
1001
1064
|
for fld in re.split(separator, values[key])
|
1002
1065
|
if (fld != '*' and len(tables) == 1) or obj.match(fld, key)
|
1003
1066
|
]
|
@@ -1059,7 +1122,11 @@ class CypherParser(Parser):
|
|
1059
1122
|
if token in self.TOKEN_METHODS:
|
1060
1123
|
return
|
1061
1124
|
class_list = [Field]
|
1062
|
-
if '
|
1125
|
+
if '*' in token:
|
1126
|
+
token = token.replace('*', '')
|
1127
|
+
self.queries[-1].key_field = token
|
1128
|
+
return
|
1129
|
+
elif '$' in token:
|
1063
1130
|
func_name, token = token.split('$')
|
1064
1131
|
if func_name == 'count':
|
1065
1132
|
if not token:
|
@@ -1068,7 +1135,6 @@ class CypherParser(Parser):
|
|
1068
1135
|
Count().As(token, extra_classes).add(pk_field, self.queries[-1])
|
1069
1136
|
return
|
1070
1137
|
else:
|
1071
|
-
FUNCTION_CLASS = {f.__name__.lower(): f for f in Function.__subclasses__()}
|
1072
1138
|
class_list = [ FUNCTION_CLASS[func_name] ]
|
1073
1139
|
class_list += extra_classes
|
1074
1140
|
FieldList(token, class_list).add('', self.queries[-1])
|
@@ -1084,10 +1150,13 @@ class CypherParser(Parser):
|
|
1084
1150
|
def add_foreign_key(self, token: str, pk_field: str=''):
|
1085
1151
|
curr, last = [self.queries[i] for i in (-1, -2)]
|
1086
1152
|
if not pk_field:
|
1087
|
-
if
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
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)
|
1091
1160
|
if '{}' in token:
|
1092
1161
|
foreign_fld = token.format(
|
1093
1162
|
last.table_name.lower()
|
@@ -1405,6 +1474,88 @@ class NotSelectIN(SelectIN):
|
|
1405
1474
|
condition_class = Not
|
1406
1475
|
|
1407
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
|
+
|
1408
1559
|
class RulePutLimit(Rule):
|
1409
1560
|
@classmethod
|
1410
1561
|
def apply(cls, target: Select):
|
@@ -1519,7 +1670,7 @@ def parser_class(text: str) -> Parser:
|
|
1519
1670
|
return None
|
1520
1671
|
|
1521
1672
|
|
1522
|
-
def detect(text: str, join_queries: bool = True) -> Select:
|
1673
|
+
def detect(text: str, join_queries: bool = True, format: str='') -> Select | list[Select]:
|
1523
1674
|
from collections import Counter
|
1524
1675
|
parser = parser_class(text)
|
1525
1676
|
if not parser:
|
@@ -1530,11 +1681,14 @@ def detect(text: str, join_queries: bool = True) -> Select:
|
|
1530
1681
|
continue
|
1531
1682
|
pos = [ f.span() for f in re.finditer(fr'({table})[(]', text) ]
|
1532
1683
|
for begin, end in pos[::-1]:
|
1533
|
-
new_name = f'{table}_{count}' # See set_table (line
|
1684
|
+
new_name = f'{table}_{count}' # See set_table (line 55)
|
1534
1685
|
Select.EQUIVALENT_NAMES[new_name] = table
|
1535
1686
|
text = text[:begin] + new_name + '(' + text[end:]
|
1536
1687
|
count -= 1
|
1537
1688
|
query_list = Select.parse(text, parser)
|
1689
|
+
if format:
|
1690
|
+
for query in query_list:
|
1691
|
+
query.set_file_format(format)
|
1538
1692
|
if not join_queries:
|
1539
1693
|
return query_list
|
1540
1694
|
result = query_list[0]
|
@@ -1543,15 +1697,3 @@ def detect(text: str, join_queries: bool = True) -> Select:
|
|
1543
1697
|
return result
|
1544
1698
|
|
1545
1699
|
|
1546
|
-
if __name__ == '__main__':
|
1547
|
-
for dialect in Dialect:
|
1548
|
-
Function.dialect = dialect
|
1549
|
-
print(f'--------------{dialect.name}--------------')
|
1550
|
-
query = Select(
|
1551
|
-
'Installments',
|
1552
|
-
_=DateDiff(
|
1553
|
-
Current_Date(),
|
1554
|
-
'due_date'
|
1555
|
-
).As('elapsed_time')
|
1556
|
-
).limit(10)
|
1557
|
-
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=iFS3drlQaazFkbBBFIH58RPZt4Lhe6R2Xqi_Ujef7Bg,51893
|
3
|
-
sql_blocks-1.25.1801.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
-
sql_blocks-1.25.1801.dist-info/METADATA,sha256=WnwFnhle4YXNyeC6dBVAnQmUqJiboT1CDhbvRHOmEQY,15027
|
5
|
-
sql_blocks-1.25.1801.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
-
sql_blocks-1.25.1801.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
-
sql_blocks-1.25.1801.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|