sql-blocks 1.25.13__tar.gz → 1.25.47__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sql_blocks
3
- Version: 1.25.13
3
+ Version: 1.25.47
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
@@ -52,6 +52,12 @@ You can specify your own alias: `a = Select('Actor a')`
52
52
  ...should return:
53
53
  **SELECT extract(year from due_date) as YEAR_ref...**
54
54
 
55
+ Possible tags in ExpressionField:
56
+ * {f} - The field name;
57
+ * {af} - The field name preceded by the table alias;
58
+ > Can be written as {a.f} or %
59
+ * {t} - The table name;
60
+ * {a} - Only the table alias.
55
61
  ---
56
62
 
57
63
  ### 3 - To set conditions, use **Where**:
@@ -67,6 +73,16 @@ You can specify your own alias: `a = Select('Actor a')`
67
73
  3.1 -- If you want to filter the field on a range of values:
68
74
 
69
75
  `a = Select( 'Actor a', age=Between(45, 69) )`
76
+ ...but if it is a time slot within the same day, you can do it like this:
77
+ `Select(..., event_date=SameDay("2024-10-03"))`
78
+ This results in
79
+ ```
80
+ SELECT ...
81
+ WHERE
82
+ event_date >= '2024-10-03 00:00:00' AND
83
+ event_date <= '2024-10-03 23:59:59'
84
+ ```
85
+ ---
70
86
 
71
87
  3.2 -- Sub-queries:
72
88
  ```
@@ -97,8 +113,10 @@ query = Select('Movie m', title=Field,
97
113
  genre=eq("Sci-Fi"),
98
114
  awards=contains("Oscar")
99
115
  )
116
+ AND=Options(
117
+ ..., name=startswith('Chris')
118
+ )
100
119
  ```
101
- > Could be AND=Options(...)
102
120
 
103
121
  3.4 -- Negative conditions use the _Not_ class instead of _Where_
104
122
  ```
@@ -110,6 +128,16 @@ based_on_book=Not.is_null()
110
128
  hash_tag=inside(['space', 'monster', 'gore'])
111
129
  ```
112
130
 
131
+ 3.6 -- Combining ExpressionField with Where condition:
132
+ * The **formula** method allows you to write an expression as a condition:
133
+ ```
134
+ query=Select(
135
+ 'Folks f2',
136
+ id=Where.formula('({af} = a.father OR {af} = a.mother)')
137
+ )
138
+ ```
139
+ > Results: `WHERE...f2.id = a.father OR f2.id = a.mother`
140
+
113
141
  ---
114
142
  ### 4 - A field can be two things at the same time:
115
143
 
@@ -138,6 +166,35 @@ FROM
138
166
  JOIN Cast c ON (a.cast = c.id)
139
167
  ```
140
168
 
169
+ ---
170
+ **5.1 Multiple tables without JOIN**
171
+ > Warning: This is **NOT** recommended! ⛔
172
+
173
+
174
+ #### Example:
175
+ singer = Select(
176
+ "Singer artist", id=PrimaryKey,
177
+ name=NamedField('artist_name')
178
+ )
179
+ album = Select (
180
+ "Album album",
181
+ name=NamedField('album_name'),
182
+ artist_id=Where.join(singer), # <===== 👀
183
+ )
184
+ **>> print(query)**
185
+
186
+ SELECT
187
+ album.name as album_name,
188
+ artist.name as artist_name,
189
+ album.year_recorded
190
+ FROM
191
+ Album album
192
+ ,Singer artist
193
+ WHERE
194
+ (album.artist_id = artist.id)
195
+
196
+
197
+
141
198
  ---
142
199
  ### 6 - The reverse process (parse):
143
200
  ```
@@ -258,6 +315,30 @@ m = Select...
258
315
  >> **m + c => Ok!**
259
316
 
260
317
 
318
+ ---
319
+ **8.3 Difference between queries**
320
+ ```
321
+ STATUS_DELIVERED_OK = 93
322
+ orders = Select('orders',
323
+ customer_id=ForeignKey('customers'),
324
+ status=eq(STATUS_DELIVERED_OK)
325
+ )
326
+ customers = Select('customers'
327
+ id=PrimaryKey, name=Field
328
+ )
329
+ gap = orders - customers
330
+ ```
331
+ return _customers without orders_:
332
+
333
+ SELECT
334
+ c.name
335
+ FROM
336
+ customers c
337
+ WHERE
338
+ NOT c.id IN (
339
+ SELECT o.customer_id FROM orders o
340
+ WHERE o.status = 93
341
+ )
261
342
  ---
262
343
 
263
344
  ### 9 - Comparing objects
@@ -363,6 +444,35 @@ m2 = Select(
363
444
 
364
445
  > The method allows you to select which rules you want to apply in the optimization...Or define your own rules!
365
446
 
447
+ >> NOTE: When a joined table is used only as a filter, it is possible that it can be changed to a sub-query:
448
+
449
+ query = Select(
450
+ 'Installments i', due_date=Field, customer=Select(
451
+ 'Customer c', id=PrimaryKey,
452
+ name=endswith('Smith')
453
+ )
454
+ )
455
+ print(query)
456
+ print('-----')
457
+ query.optimize([RuleReplaceJoinBySubselect])
458
+ print(query)
459
+ ```
460
+ SELECT
461
+ i.due_date
462
+ FROM
463
+ Installments i
464
+ JOIN Customer c ON (i.customer = c.id)
465
+ WHERE
466
+ c.name LIKE '%Smith'
467
+ -----
468
+ SELECT
469
+ i.due_date
470
+ FROM
471
+ Installments i
472
+ WHERE
473
+ i.customer IN (SELECT c.id FROM Customer c WHERE c.name LIKE '%Smith')
474
+ ```
475
+
366
476
  ---
367
477
 
368
478
  ### 12 - Adding multiple fields at once
@@ -370,7 +480,7 @@ m2 = Select(
370
480
  query = Select('post p')
371
481
  query.add_fields(
372
482
  'user_id, created_at',
373
- order_by=True, group_by=True
483
+ [OrderBy, GroupBy]
374
484
  )
375
485
  ```
376
486
  ...is the same as...
@@ -419,6 +529,7 @@ ORDER BY
419
529
  * `^` Put the field in the ORDER BY clause
420
530
  * `@` Immediately after the table name, it indicates the grouping field.
421
531
  * `$` For SQL functions like **avg**$_field_, **sum**$_field_, **count**$_field_...
532
+ * `*` Sets the primary key field.
422
533
 
423
534
 
424
535
  ---
@@ -520,12 +631,18 @@ It consists of the inverse process of parsing: From a Select object, it returns
520
631
  ---
521
632
  ### 14 - Window Function
522
633
 
523
- Aggregation functions (Avg, Min, Max, Sum, Count) have the **over** method...
634
+ Aggregation functions (Avg, Min, Max, Sum, Count) -- or Window functions (Lead, Lag, Row_Number, Rank) -- have the **over** method...
524
635
 
525
636
  query=Select(
526
637
  'Enrollment e',
527
638
  payment=Sum().over(
528
- partition='student_id', order='due_date'
639
+ student_id=Partition, due_date=OrderBy,
640
+ # _=Rows(Current(), Following(5)),
641
+ # ^^^-------> ROWS BETWEEN CURRENT ROW AND 5 FOLLOWING
642
+ # _=Rows(Preceding(3), Following()),
643
+ # ^^^-------> ROWS BETWEEN 3 PRECEDING AND UNBOUNDED FOLLOWING
644
+ # _=Rows(Preceding(3))
645
+ # ^^^-------> ROWS 3 PRECEDING
529
646
  ).As('sum_per_student')
530
647
  )
531
648
 
@@ -567,3 +684,176 @@ GROUP BY
567
684
  ORDER BY
568
685
  customer_count
569
686
  ```
687
+ ---
688
+ ### 16 - Function classes
689
+ You may use this functions:
690
+ * SubString
691
+ * Round
692
+ * DateDiff
693
+ * Year
694
+ * Current_Date
695
+ * Avg
696
+ * Min
697
+ * Max
698
+ * Sum
699
+ * Count
700
+ * Lag
701
+ * Lead
702
+ * Row_Number
703
+ * Rank
704
+ * Coalesce
705
+ * Cast
706
+ > Some of these functions may vary in syntax depending on the database.
707
+ For example, if your query is going to run on Oracle, do the following:
708
+
709
+ `Function.dialect = Dialect.ORACLE`
710
+
711
+
712
+ > Most of this functions you can use nested inside each other.
713
+ *Example:*
714
+ ```
715
+ Select(...
716
+ event_date=Substring(
717
+ Cast("CHAR"), 12, 19
718
+ ).As('time')
719
+ )
720
+ ```
721
+ Results...
722
+ ```
723
+ SELECT ...
724
+ SubString(Cast(event_date As char), 12, 19) as time
725
+ ```
726
+
727
+ >> `Function.auto_convert` option (default: True)
728
+
729
+ - Put Cast(...) when there is a difference between the types of the parameter and the return of the nested function
730
+ ```
731
+ birth=Round( DateDiff(Current_Date()) ).As('age')
732
+ ```
733
+ ...Returns...
734
+ ```
735
+ SELECT
736
+ Round(
737
+ Cast(Current_Date() - p.birth As FLOAT)
738
+ /* ^^^ */
739
+ ) as age
740
+ ...
741
+ ```
742
+ ---
743
+
744
+ ### 17 - CTE and Recursive classes
745
+
746
+ * **17.1 - _CTE class_**
747
+ ```
748
+ query = Select(
749
+ 'SocialMedia s', post=Count, reaction=Sum, user=GroupBy
750
+ )
751
+ print( CTE('Metrics', [query]) )
752
+ ```
753
+ The result is...
754
+ ```
755
+ WITH Metrics AS (
756
+ SELECT Count(s.post), Sum(s.reaction) FROM SocialMedia s GROUP BY user
757
+ )SELECT * FROM Metrics
758
+ ```
759
+
760
+ * **17.2 - _Recursive class_**
761
+ ```
762
+ q1 = Select(
763
+ 'SocialMedia me', name=[ eq(MY_NAME), Field ]
764
+ )
765
+ q2 = Select(
766
+ 'SocialMedia you' name=Field, id=Where.formula('{af} = n.friend')
767
+ )
768
+ print( Recursive('Network', [q1, q2]) )
769
+ ```
770
+ The result is...
771
+ ```
772
+ WITH RECURSIVE Network AS (
773
+ SELECT me.name FROM SocialMedia me WHERE
774
+ me.name = 'Júlio Cascalles'
775
+ UNION ALL
776
+ SELECT you.name FROM SocialMedia you , Network n
777
+ WHERE you.id = n.friend
778
+ )SELECT * FROM Network
779
+ ```
780
+
781
+ * **17.2.1 - The `create` method** ... parameters :
782
+ - name: The name of the CTE
783
+ - pattern: A cypher script that defines the tables used
784
+ - formula: The format for `Where.formula` method _(*)_
785
+ - init_value: The value for the condition in the first table
786
+ - format (optional): If tables are files or internet hiperlinks, you may especify the extension and/or folder...
787
+ > Example:
788
+ ```
789
+ R = Recursive.create(
790
+ 'Route R', 'Flyght(departure, arrival)',
791
+ '[2] = R.[1]', 'JFK', format='.csv'
792
+ ) # ^^^--- Flyghts from JFK airport
793
+ ```
794
+ _...Creates a recursive CTE called Route, using Flyght table, where the recursivity condition is Flyght.arrival equals to Route.departure_
795
+ >> (*) -- Note that [1] and [2] refers to first field and second field. 😉
796
+
797
+ Result:
798
+
799
+ WITH RECURSIVE Route AS (
800
+ SELECT f1.departure, f1.arrival
801
+ FROM Flyght.csv f1
802
+ WHERE f1.departure = 'JFK'
803
+ UNION ALL
804
+ SELECT f2.departure, f2.arrival
805
+ FROM Flyght.csv f2
806
+ , Route R
807
+ WHERE f2.arrival = R.departure
808
+ )SELECT * FROM Route R
809
+
810
+ **17.2.2 - The `join` method**
811
+
812
+ In the previous example, if you add this code...
813
+ `R.join('Airport(*id,name)', 'departure, arrival', format='.csv')`
814
+
815
+ ...The result would be:
816
+
817
+ WITH RECURSIVE Route AS (
818
+ SELECT f1.departure, f1.arrival
819
+ FROM Flyght.csv f1
820
+ WHERE f1.departure = 'JFK'
821
+ UNION ALL
822
+ SELECT f2.departure, f2.arrival
823
+ FROM Flyght.csv f2
824
+ , Route R
825
+ WHERE f2.arrival = R.departure
826
+ )SELECT
827
+ a1.name, a2.name
828
+ FROM
829
+ Route R
830
+ JOIN Airport.csv a2 ON (R.arrival = a2.id)
831
+ JOIN Airport.csv a1 ON (R.departure = a1.id)
832
+
833
+
834
+ **17.2.3 - The `counter` method**
835
+ Adds an increment field in queries inside CTE:
836
+ > Examples:
837
+ * `R.counter('stops', 0)` # -- counter starts with 0 and increment +1
838
+ * `R2.counter('generation', 5, '- 1')` # -- for the code below...
839
+ ```
840
+ R2 = Recursive.create(
841
+ 'Ancestors a', 'People(id,name,father,mother,birth)',
842
+ '(% = a.father OR % = a.mother)', 32630, '.parquet'
843
+ )
844
+ ```
845
+ ...Results:
846
+
847
+ WITH RECURSIVE Ancestors AS (
848
+ SELECT p1.id, p1.name, p1.father, p1.mother, p1.birth,
849
+ 5 AS generation /* <<---- Most current generation ------------*/
850
+ FROM People.parquet p1 WHERE p1.id = 32630
851
+ UNION ALL
852
+ SELECT p2.id, p2.name, p2.father, p2.mother, p2.birth,
853
+ (generation- 1) AS generation /* <<-- Previous generation -----*/
854
+ FROM People.parquet p2 , Ancestors a WHERE (p2.id = a.father OR p2.id = a.mother)
855
+ )SELECT * FROM Ancestors a
856
+
857
+
858
+ >> Note: Comments added later.
859
+ ---