|
| 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 │ SYMBOL │ SYMBOL │ 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 │ 3 │ 108333.33 │ 120000 │ |
| 115 | +│ sales │ 2 │ 77500.00 │ 80000 │ |
| 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. |
0 commit comments