6.9 KiB
Views
sqlgen supports creating and managing database views, which are virtual tables based on the result set of a SQL statement. Views provide a way to simplify complex queries, present data in a specific format, or restrict access to certain columns.
Defining Views
To define a view in sqlgen, create a struct with the is_view flag:
struct EmployeeView {
static constexpr bool is_view = true;
std::string first_name;
std::string last_name;
std::string department;
int salary;
};
Custom View Names
You can optionally specify a custom name for your view using either viewname or tablename:
struct EmployeeView {
static constexpr const char* viewname = "EMPLOYEE_SUMMARY"; // or tablename = "EMPLOYEE_SUMMARY"
static constexpr bool is_view = true;
std::string first_name;
std::string last_name;
std::string department;
int salary;
};
Both viewname and tablename work identically for views - they are just syntactic sugar.
Creating Views
Basic View Creation
Create a view from a SELECT query:
using namespace sqlgen;
using namespace sqlgen::literals;
const auto employee_query = select_from<Employee>("first_name"_c, "last_name"_c, "department"_c, "salary"_c);
const auto create_view = create_as<EmployeeView>(employee_query);
create_view(conn).value();
This generates:
CREATE VIEW "EmployeeView" AS SELECT "first_name", "last_name", "department", "salary" FROM "Employee";
Create or Replace View
Use create_or_replace_view_as to create a view or replace it if it already exists:
const auto employee_query = select_from<Employee>("first_name"_c, "last_name"_c, "department"_c, "salary"_c);
const auto create_view = create_or_replace_view_as<EmployeeView>(employee_query);
create_view(conn).value();
This generates different SQL depending on the database:
- MySQL/PostgreSQL:
CREATE OR REPLACE VIEW "EMPLOYEE_SUMMARY" AS SELECT ... - SQLite:
CREATE VIEW IF NOT EXISTS "EmployeeView" AS SELECT ...(automatically translated)
Important: SQLite does not support CREATE OR REPLACE VIEW. When you use create_or_replace_view_as with SQLite, sqlgen automatically translates this to CREATE VIEW IF NOT EXISTS syntax.
View Creation with if_not_exists
For SQLite compatibility, you can also use the if_not_exists modifier:
const auto employee_query = select_from<Employee>("first_name"_c, "last_name"_c, "department"_c, "salary"_c);
const auto create_view = create_as<EmployeeView>(employee_query) | if_not_exists;
create_view(conn).value();
This generates different SQL depending on the database:
- SQLite:
CREATE VIEW IF NOT EXISTS "EmployeeView" AS SELECT ... - MySQL/PostgreSQL:
CREATE VIEW "EmployeeView" AS SELECT ...(if_not_exists ignored for views)
Complex View Examples
Filtered Views
Create views with WHERE clauses:
struct HighSalaryEmployees {
static constexpr bool is_view = true;
std::string first_name;
std::string last_name;
int salary;
};
const auto high_salary_query = select_from<Employee>("first_name"_c, "last_name"_c, "salary"_c)
.where("salary"_c > 50000);
const auto create_high_salary_view = create_or_replace_view_as<HighSalaryEmployees>(high_salary_query);
Joined Views
Create views from joined tables:
struct EmployeeDepartment {
static constexpr bool is_view = true;
std::string first_name;
std::string last_name;
std::string department_name;
std::string location;
};
const auto joined_query = select_from<Employee>("first_name"_c, "last_name"_c)
.inner_join<Department>("department_id"_c == "id"_c)
.select("department_name"_c, "location"_c);
const auto create_joined_view = create_or_replace_view_as<EmployeeDepartment>(joined_query);
Aggregated Views
Create views with aggregations:
struct DepartmentStats {
static constexpr bool is_view = true;
std::string department;
int employee_count;
double avg_salary;
int total_salary;
};
const auto stats_query = select_from<Employee>("department"_c)
.select(count("id"_c).as("employee_count"_c),
avg("salary"_c).as("avg_salary"_c),
sum("salary"_c).as("total_salary"_c))
.group_by("department"_c);
const auto create_stats_view = create_or_replace_view_as<DepartmentStats>(stats_query);
Working with Views
Reading from Views
Views can be used just like regular tables:
// Read all data from the view
const auto employees = sqlgen::read<std::vector<EmployeeView>>(conn).value();
// Query the view with conditions
const auto high_earners = sqlgen::read<std::vector<EmployeeView>>(
conn.and_then(select_from<EmployeeView>().where("salary"_c > 75000))
).value();
Dropping Views
Drop views using the same drop interface:
using namespace sqlgen;
const auto result = drop<EmployeeView>(conn);
// or with if_exists
const auto result = (drop<EmployeeView> | if_exists)(conn);
Database-Specific Behavior
MySQL and PostgreSQL
Both MySQL and PostgreSQL support CREATE OR REPLACE VIEW:
CREATE OR REPLACE VIEW "EMPLOYEE_SUMMARY" AS SELECT ...;
SQLite
SQLite has different view creation behavior:
-- SQLite does NOT support CREATE OR REPLACE VIEW
-- Instead, it uses CREATE VIEW IF NOT EXISTS
CREATE VIEW IF NOT EXISTS "EmployeeView" AS SELECT ...;
Important: SQLite does not support CREATE OR REPLACE VIEW. When you use create_or_replace_view_as with SQLite, sqlgen automatically translates this to the appropriate SQLite syntax. For better SQLite compatibility, use create_as<ViewType>(query) | if_not_exists instead.
Best Practices
- Naming Convention: Use descriptive names for views that indicate their purpose
- Column Selection: Only include columns that are actually needed in the view
- Performance: Views with complex joins or aggregations may impact query performance
- Maintenance: Keep views simple and focused on a single purpose
- Documentation: Document the purpose and expected data of each view
- Database Compatibility: Be aware of database-specific differences in view creation syntax
- SQLite Users: Use
create_as<ViewType>(query) | if_not_existsfor better compatibility
Notes
- Views are read-only by default in most databases
- Views can be used in other queries just like regular tables
- The
is_view = trueflag is required to distinguish views from tables - Custom view names using
viewnameortablenameare optional but recommended for clarity - Views can be created from any valid SELECT query, including complex joins, aggregations, and subqueries
- SQLite Limitation: Use
create_as<ViewType>(query) | if_not_existsinstead ofcreate_or_replace_view_asfor better compatibility - MySQL/PostgreSQL: Both work well with
create_or_replace_view_as