sql-blocks 1.25.13__tar.gz → 1.25.51__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.51
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
@@ -15,6 +15,10 @@ License-File: LICENSE
15
15
 
16
16
  # SQL_Blocks
17
17
 
18
+ ## _SQL_Blocks_ is useful for building complex SQL commands through smaller query blocks:
19
+
20
+ ---
21
+
18
22
  ### 1 - You can assemble a simple object that will then be converted into an SQL command:
19
23
 
20
24
  > a = Select('Actor') # --> SELECT * FROM Actor act
@@ -52,6 +56,12 @@ You can specify your own alias: `a = Select('Actor a')`
52
56
  ...should return:
53
57
  **SELECT extract(year from due_date) as YEAR_ref...**
54
58
 
59
+ Possible tags in ExpressionField:
60
+ * {f} - The field name;
61
+ * {af} - The field name preceded by the table alias;
62
+ > Can be written as {a.f} or %
63
+ * {t} - The table name;
64
+ * {a} - Only the table alias.
55
65
  ---
56
66
 
57
67
  ### 3 - To set conditions, use **Where**:
@@ -67,6 +77,16 @@ You can specify your own alias: `a = Select('Actor a')`
67
77
  3.1 -- If you want to filter the field on a range of values:
68
78
 
69
79
  `a = Select( 'Actor a', age=Between(45, 69) )`
80
+ ...but if it is a time slot within the same day, you can do it like this:
81
+ `Select(..., event_date=SameDay("2024-10-03"))`
82
+ This results in
83
+ ```
84
+ SELECT ...
85
+ WHERE
86
+ event_date >= '2024-10-03 00:00:00' AND
87
+ event_date <= '2024-10-03 23:59:59'
88
+ ```
89
+ ---
70
90
 
71
91
  3.2 -- Sub-queries:
72
92
  ```
@@ -97,8 +117,10 @@ query = Select('Movie m', title=Field,
97
117
  genre=eq("Sci-Fi"),
98
118
  awards=contains("Oscar")
99
119
  )
120
+ AND=Options(
121
+ ..., name=startswith('Chris')
122
+ )
100
123
  ```
101
- > Could be AND=Options(...)
102
124
 
103
125
  3.4 -- Negative conditions use the _Not_ class instead of _Where_
104
126
  ```
@@ -110,6 +132,16 @@ based_on_book=Not.is_null()
110
132
  hash_tag=inside(['space', 'monster', 'gore'])
111
133
  ```
112
134
 
135
+ 3.6 -- Combining ExpressionField with Where condition:
136
+ * The **formula** method allows you to write an expression as a condition:
137
+ ```
138
+ query=Select(
139
+ 'Folks f2',
140
+ id=Where.formula('({af} = a.father OR {af} = a.mother)')
141
+ )
142
+ ```
143
+ > Results: `WHERE...f2.id = a.father OR f2.id = a.mother`
144
+
113
145
  ---
114
146
  ### 4 - A field can be two things at the same time:
115
147
 
@@ -138,6 +170,35 @@ FROM
138
170
  JOIN Cast c ON (a.cast = c.id)
139
171
  ```
140
172
 
173
+ ---
174
+ **5.1 Multiple tables without JOIN**
175
+ > Warning: This is **NOT** recommended! ⛔
176
+
177
+
178
+ #### Example:
179
+ singer = Select(
180
+ "Singer artist", id=PrimaryKey,
181
+ name=NamedField('artist_name')
182
+ )
183
+ album = Select (
184
+ "Album album",
185
+ name=NamedField('album_name'),
186
+ artist_id=Where.join(singer), # <===== 👀
187
+ )
188
+ **>> print(query)**
189
+
190
+ SELECT
191
+ album.name as album_name,
192
+ artist.name as artist_name,
193
+ album.year_recorded
194
+ FROM
195
+ Album album
196
+ ,Singer artist
197
+ WHERE
198
+ (album.artist_id = artist.id)
199
+
200
+
201
+
141
202
  ---
142
203
  ### 6 - The reverse process (parse):
143
204
  ```
@@ -258,6 +319,30 @@ m = Select...
258
319
  >> **m + c => Ok!**
259
320
 
260
321
 
322
+ ---
323
+ **8.3 Difference between queries**
324
+ ```
325
+ STATUS_DELIVERED_OK = 93
326
+ orders = Select('orders',
327
+ customer_id=ForeignKey('customers'),
328
+ status=eq(STATUS_DELIVERED_OK)
329
+ )
330
+ customers = Select('customers'
331
+ id=PrimaryKey, name=Field
332
+ )
333
+ gap = orders - customers
334
+ ```
335
+ return _customers without orders_:
336
+
337
+ SELECT
338
+ c.name
339
+ FROM
340
+ customers c
341
+ WHERE
342
+ NOT c.id IN (
343
+ SELECT o.customer_id FROM orders o
344
+ WHERE o.status = 93
345
+ )
261
346
  ---
262
347
 
263
348
  ### 9 - Comparing objects
@@ -330,6 +415,31 @@ m2 = Select(
330
415
  )
331
416
  )
332
417
 
418
+ 10.1 - If the labels used in the CASE are based on ranges of values ​​in sequence, you can use the **Range class**:
419
+
420
+ query = Select(
421
+ 'People p',
422
+ age_group=Range('age',{ # <<----------
423
+ 'adult': 50,
424
+ 'teenager': 17,
425
+ 'child': 10,
426
+ 'elderly': 70,
427
+ 'young': 21,
428
+ })
429
+ )
430
+ is equivalent to...
431
+ ```
432
+ SELECT
433
+ CASE
434
+ WHEN p.age BETWEEN 0 AND 10 THEN 'child'
435
+ WHEN p.age BETWEEN 11 AND 17 THEN 'teenager'
436
+ WHEN p.age BETWEEN 18 AND 21 THEN 'young'
437
+ WHEN p.age BETWEEN 22 AND 50 THEN 'adult'
438
+ WHEN p.age BETWEEN 51 AND 70 THEN 'elderly'
439
+ END AS age_group
440
+ FROM
441
+ People p
442
+ ```
333
443
  ---
334
444
 
335
445
  ### 11 - optimize method
@@ -363,6 +473,35 @@ m2 = Select(
363
473
 
364
474
  > The method allows you to select which rules you want to apply in the optimization...Or define your own rules!
365
475
 
476
+ >> NOTE: When a joined table is used only as a filter, it is possible that it can be changed to a sub-query:
477
+
478
+ query = Select(
479
+ 'Installments i', due_date=Field, customer=Select(
480
+ 'Customer c', id=PrimaryKey,
481
+ name=endswith('Smith')
482
+ )
483
+ )
484
+ print(query)
485
+ print('-----')
486
+ query.optimize([RuleReplaceJoinBySubselect])
487
+ print(query)
488
+ ```
489
+ SELECT
490
+ i.due_date
491
+ FROM
492
+ Installments i
493
+ JOIN Customer c ON (i.customer = c.id)
494
+ WHERE
495
+ c.name LIKE '%Smith'
496
+ -----
497
+ SELECT
498
+ i.due_date
499
+ FROM
500
+ Installments i
501
+ WHERE
502
+ i.customer IN (SELECT c.id FROM Customer c WHERE c.name LIKE '%Smith')
503
+ ```
504
+
366
505
  ---
367
506
 
368
507
  ### 12 - Adding multiple fields at once
@@ -370,7 +509,7 @@ m2 = Select(
370
509
  query = Select('post p')
371
510
  query.add_fields(
372
511
  'user_id, created_at',
373
- order_by=True, group_by=True
512
+ [OrderBy, GroupBy]
374
513
  )
375
514
  ```
376
515
  ...is the same as...
@@ -419,6 +558,7 @@ ORDER BY
419
558
  * `^` Put the field in the ORDER BY clause
420
559
  * `@` Immediately after the table name, it indicates the grouping field.
421
560
  * `$` For SQL functions like **avg**$_field_, **sum**$_field_, **count**$_field_...
561
+ * `*` Sets the primary key field.
422
562
 
423
563
 
424
564
  ---
@@ -520,12 +660,18 @@ It consists of the inverse process of parsing: From a Select object, it returns
520
660
  ---
521
661
  ### 14 - Window Function
522
662
 
523
- Aggregation functions (Avg, Min, Max, Sum, Count) have the **over** method...
663
+ Aggregation functions (Avg, Min, Max, Sum, Count) -- or Window functions (Lead, Lag, Row_Number, Rank) -- have the **over** method...
524
664
 
525
665
  query=Select(
526
666
  'Enrollment e',
527
667
  payment=Sum().over(
528
- partition='student_id', order='due_date'
668
+ student_id=Partition, due_date=OrderBy,
669
+ # _=Rows(Current(), Following(5)),
670
+ # ^^^-------> ROWS BETWEEN CURRENT ROW AND 5 FOLLOWING
671
+ # _=Rows(Preceding(3), Following()),
672
+ # ^^^-------> ROWS BETWEEN 3 PRECEDING AND UNBOUNDED FOLLOWING
673
+ # _=Rows(Preceding(3))
674
+ # ^^^-------> ROWS 3 PRECEDING
529
675
  ).As('sum_per_student')
530
676
  )
531
677
 
@@ -567,3 +713,176 @@ GROUP BY
567
713
  ORDER BY
568
714
  customer_count
569
715
  ```
716
+ ---
717
+ ### 16 - Function classes
718
+ You may use this functions:
719
+ * SubString
720
+ * Round
721
+ * DateDiff
722
+ * Year
723
+ * Current_Date
724
+ * Avg
725
+ * Min
726
+ * Max
727
+ * Sum
728
+ * Count
729
+ * Lag
730
+ * Lead
731
+ * Row_Number
732
+ * Rank
733
+ * Coalesce
734
+ * Cast
735
+ > Some of these functions may vary in syntax depending on the database.
736
+ For example, if your query is going to run on Oracle, do the following:
737
+
738
+ `Function.dialect = Dialect.ORACLE`
739
+
740
+
741
+ > Most of this functions you can use nested inside each other.
742
+ *Example:*
743
+ ```
744
+ Select(...
745
+ event_date=Substring(
746
+ Cast("CHAR"), 12, 19
747
+ ).As('time')
748
+ )
749
+ ```
750
+ Results...
751
+ ```
752
+ SELECT ...
753
+ SubString(Cast(event_date As char), 12, 19) as time
754
+ ```
755
+
756
+ >> `Function.auto_convert` option (default: True)
757
+
758
+ - Put Cast(...) when there is a difference between the types of the parameter and the return of the nested function
759
+ ```
760
+ birth=Round( DateDiff(Current_Date()) ).As('age')
761
+ ```
762
+ ...Returns...
763
+ ```
764
+ SELECT
765
+ Round(
766
+ Cast(Current_Date() - p.birth As FLOAT)
767
+ /* ^^^ */
768
+ ) as age
769
+ ...
770
+ ```
771
+ ---
772
+
773
+ ### 17 - CTE and Recursive classes
774
+
775
+ * **17.1 - _CTE class_**
776
+ ```
777
+ query = Select(
778
+ 'SocialMedia s', post=Count, reaction=Sum, user=GroupBy
779
+ )
780
+ print( CTE('Metrics', [query]) )
781
+ ```
782
+ The result is...
783
+ ```
784
+ WITH Metrics AS (
785
+ SELECT Count(s.post), Sum(s.reaction) FROM SocialMedia s GROUP BY user
786
+ )SELECT * FROM Metrics
787
+ ```
788
+
789
+ * **17.2 - _Recursive class_**
790
+ ```
791
+ q1 = Select(
792
+ 'SocialMedia me', name=[ eq(MY_NAME), Field ]
793
+ )
794
+ q2 = Select(
795
+ 'SocialMedia you' name=Field, id=Where.formula('{af} = n.friend')
796
+ )
797
+ print( Recursive('Network', [q1, q2]) )
798
+ ```
799
+ The result is...
800
+ ```
801
+ WITH RECURSIVE Network AS (
802
+ SELECT me.name FROM SocialMedia me WHERE
803
+ me.name = 'Júlio Cascalles'
804
+ UNION ALL
805
+ SELECT you.name FROM SocialMedia you , Network n
806
+ WHERE you.id = n.friend
807
+ )SELECT * FROM Network
808
+ ```
809
+
810
+ * **17.2.1 - The `create` method** ... parameters :
811
+ - name: The name of the CTE
812
+ - pattern: A cypher script that defines the tables used
813
+ - formula: The format for `Where.formula` method _(*)_
814
+ - init_value: The value for the condition in the first table
815
+ - format (optional): If tables are files or internet hiperlinks, you may especify the extension and/or folder...
816
+ > Example:
817
+ ```
818
+ R = Recursive.create(
819
+ 'Route R', 'Flyght(departure, arrival)',
820
+ '[2] = R.[1]', 'JFK', format='.csv'
821
+ ) # ^^^--- Flyghts from JFK airport
822
+ ```
823
+ _...Creates a recursive CTE called Route, using Flyght table, where the recursivity condition is Flyght.arrival equals to Route.departure_
824
+ >> (*) -- Note that [1] and [2] refers to first field and second field. 😉
825
+
826
+ Result:
827
+
828
+ WITH RECURSIVE Route AS (
829
+ SELECT f1.departure, f1.arrival
830
+ FROM Flyght.csv f1
831
+ WHERE f1.departure = 'JFK'
832
+ UNION ALL
833
+ SELECT f2.departure, f2.arrival
834
+ FROM Flyght.csv f2
835
+ , Route R
836
+ WHERE f2.arrival = R.departure
837
+ )SELECT * FROM Route R
838
+
839
+ **17.2.2 - The `join` method**
840
+
841
+ In the previous example, if you add this code...
842
+ `R.join('Airport(*id,name)', 'departure, arrival', format='.csv')`
843
+
844
+ ...The result would be:
845
+
846
+ WITH RECURSIVE Route AS (
847
+ SELECT f1.departure, f1.arrival
848
+ FROM Flyght.csv f1
849
+ WHERE f1.departure = 'JFK'
850
+ UNION ALL
851
+ SELECT f2.departure, f2.arrival
852
+ FROM Flyght.csv f2
853
+ , Route R
854
+ WHERE f2.arrival = R.departure
855
+ )SELECT
856
+ a1.name, a2.name
857
+ FROM
858
+ Route R
859
+ JOIN Airport.csv a2 ON (R.arrival = a2.id)
860
+ JOIN Airport.csv a1 ON (R.departure = a1.id)
861
+
862
+
863
+ **17.2.3 - The `counter` method**
864
+ Adds an increment field in queries inside CTE:
865
+ > Examples:
866
+ * `R.counter('stops', 0)` # -- counter starts with 0 and increment +1
867
+ * `R2.counter('generation', 5, '- 1')` # -- for the code below...
868
+ ```
869
+ R2 = Recursive.create(
870
+ 'Ancestors a', 'People(id,name,father,mother,birth)',
871
+ '(% = a.father OR % = a.mother)', 32630, '.parquet'
872
+ )
873
+ ```
874
+ ...Results:
875
+
876
+ WITH RECURSIVE Ancestors AS (
877
+ SELECT p1.id, p1.name, p1.father, p1.mother, p1.birth,
878
+ 5 AS generation /* <<---- Most current generation ------------*/
879
+ FROM People.parquet p1 WHERE p1.id = 32630
880
+ UNION ALL
881
+ SELECT p2.id, p2.name, p2.father, p2.mother, p2.birth,
882
+ (generation- 1) AS generation /* <<-- Previous generation -----*/
883
+ FROM People.parquet p2 , Ancestors a WHERE (p2.id = a.father OR p2.id = a.mother)
884
+ )SELECT * FROM Ancestors a
885
+
886
+
887
+ >> Note: Comments added later.
888
+ ---