Skip to content

Commit 0eaebbf

Browse files
feat(sql) Add SQL support for SELECT
1 parent 193e7bb commit 0eaebbf

File tree

5 files changed

+750
-0
lines changed

5 files changed

+750
-0
lines changed
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# :material-database-search: SQL Query Support
2+
3+
Rayforce-Py provides SQL query support, allowing you to query [:octicons-table-24: Tables](../data-types/table/overview.md) using familiar SQL syntax.
4+
5+
## Installation
6+
7+
The SQL integration requires the `sqlglot` library. Install it with:
8+
9+
```bash
10+
pip install rayforce-py[sql]
11+
```
12+
13+
Or install sqlglot directly:
14+
15+
```bash
16+
pip install sqlglot
17+
```
18+
19+
## Basic Usage
20+
21+
Use the `Table.sql()` method to execute SQL queries. Reference the table as `self` in your queries:
22+
23+
```python
24+
>>> from rayforce import Table, Vector, I64, F64, Symbol
25+
26+
>>> employees = Table({
27+
... "id": Vector([1, 2, 3, 4, 5], ray_type=I64),
28+
... "name": Vector(["Alice", "Bob", "Charlie", "Diana", "Eve"], ray_type=Symbol),
29+
... "dept": Vector(["eng", "sales", "eng", "sales", "eng"], ray_type=Symbol),
30+
... "salary": Vector([120000, 75000, 95000, 80000, 110000], ray_type=I64),
31+
... })
32+
33+
>>> result = employees.sql("SELECT * FROM self WHERE salary > 90000")
34+
>>> print(result)
35+
┌─────┬─────────┬────────┬────────┐
36+
id │ name │ dept │ salary │
37+
│ I64 │ SYMBOLSYMBOL │ I64 │
38+
├─────┼─────────┼────────┼────────┤
39+
1 │ Alice │ eng │ 120000
40+
3 │ Charlie │ eng │ 95000
41+
5 │ Eve │ eng │ 110000
42+
└─────┴─────────┴────────┴────────┘
43+
```
44+
45+
## Supported SQL Features
46+
47+
### SELECT Columns
48+
49+
Select specific columns or all columns with `*`:
50+
51+
```python
52+
# Select all columns
53+
employees.sql("SELECT * FROM self")
54+
55+
# Select specific columns
56+
employees.sql("SELECT name, salary FROM self")
57+
58+
# Select with aliases
59+
employees.sql("SELECT name AS employee_name, salary AS annual_salary FROM self")
60+
```
61+
62+
### WHERE Clause
63+
64+
Filter rows using comparison operators and logical conditions:
65+
66+
```python
67+
# Basic comparisons: =, !=, >, >=, <, <=
68+
employees.sql("SELECT * FROM self WHERE salary > 100000")
69+
employees.sql("SELECT * FROM self WHERE dept = 'eng'")
70+
71+
# Logical operators: AND, OR, NOT
72+
employees.sql("SELECT * FROM self WHERE dept = 'eng' AND salary > 100000")
73+
employees.sql("SELECT * FROM self WHERE dept = 'eng' OR dept = 'sales'")
74+
75+
# Parentheses for grouping
76+
employees.sql("SELECT * FROM self WHERE (dept = 'eng' OR dept = 'sales') AND salary > 90000")
77+
78+
# IN clause
79+
employees.sql("SELECT * FROM self WHERE dept IN ('eng', 'hr', 'sales')")
80+
employees.sql("SELECT * FROM self WHERE id IN (1, 3, 5)")
81+
```
82+
83+
### Aggregations
84+
85+
Use aggregate functions with or without GROUP BY:
86+
87+
```python
88+
# Without GROUP BY (aggregate entire table)
89+
employees.sql("SELECT COUNT(id) AS total, AVG(salary) AS avg_salary FROM self")
90+
91+
# Supported aggregations: COUNT, SUM, AVG, MIN, MAX, FIRST, LAST, MEDIAN
92+
employees.sql("SELECT MIN(salary) AS min_sal, MAX(salary) AS max_sal FROM self")
93+
```
94+
95+
### GROUP BY
96+
97+
Group rows and apply aggregations:
98+
99+
```python
100+
>>> result = employees.sql("""
101+
... SELECT
102+
... dept,
103+
... COUNT(id) AS headcount,
104+
... AVG(salary) AS avg_salary,
105+
... MAX(salary) AS max_salary
106+
... FROM self
107+
... GROUP BY dept
108+
... """)
109+
>>> print(result)
110+
┌────────┬───────────┬────────────┬────────────┐
111+
│ dept │ headcount │ avg_salary │ max_salary │
112+
SYMBOL │ I64 │ F64 │ I64 │
113+
├────────┼───────────┼────────────┼────────────┤
114+
│ eng │ 3108333.33120000
115+
│ sales │ 277500.0080000
116+
└────────┴───────────┴────────────┴────────────┘
117+
```
118+
119+
### ORDER BY
120+
121+
Sort results in ascending or descending order:
122+
123+
```python
124+
# Ascending (default)
125+
employees.sql("SELECT * FROM self ORDER BY salary")
126+
127+
# Descending
128+
employees.sql("SELECT * FROM self ORDER BY salary DESC")
129+
130+
# Combined with other clauses
131+
employees.sql("""
132+
SELECT dept, AVG(salary) AS avg_sal
133+
FROM self
134+
GROUP BY dept
135+
ORDER BY avg_sal DESC
136+
""")
137+
```
138+
139+
### Computed Columns
140+
141+
Use arithmetic expressions in SELECT:
142+
143+
```python
144+
# Arithmetic: +, -, *, /, %
145+
employees.sql("SELECT name, salary, salary * 0.1 AS bonus FROM self")
146+
employees.sql("SELECT name, salary / 12 AS monthly_salary FROM self")
147+
employees.sql("SELECT name, salary + 5000 AS adjusted_salary FROM self")
148+
```
149+
150+
## Limitations
151+
152+
The current SQL implementation supports common query patterns but has some limitations:
153+
154+
- Only `SELECT` statements are supported (no `INSERT`, `UPDATE`, `DELETE`)
155+
- `JOIN` operations are not yet supported via SQL (use the native `.inner_join()`, `.left_join()` methods)
156+
- Subqueries are not supported
157+
- `HAVING` clause is not supported
158+
- `LIMIT` and `OFFSET` are not supported (use `.take()` on the result)
159+
160+
For complex operations not supported by SQL, use the native Rayforce query API which provides full functionality.

docs/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ nav:
138138
- IPC: content/documentation/IPC.md
139139
- Websocket: content/documentation/websocket.md
140140
- Plugins:
141+
- SQL: content/documentation/plugins/sql.md
141142
- Parquet: content/documentation/plugins/parquet.md
142143
- Polars: content/documentation/plugins/polars.md
143144
- Pandas: content/documentation/plugins/pandas.md

0 commit comments

Comments
 (0)