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.
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;
};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.
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";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 "EmployeeView" AS SELECT ...
Important: SQLite does not support CREATE OR REPLACE VIEW. When you use create_or_replace_view_as with SQLite, sqlgen does not automatically translate this and will result in an error if the view already exists. The recommended way to achieve this is to manually drop the view before creating it:
(drop<EmployeeView> | if_exists)(conn).value();
const auto create_view = create_as<EmployeeView>(employee_query);
create_view(conn).value();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)
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);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, "t1">(
"first_name"_t1 | as<"first_name">,
"last_name"_t1 | as<"last_name">,
"department_name"_t2 | as<"department_name">,
"location"_t2 | as<"location">
) |
inner_join<Department, "t2">("department_id"_t1 == "id"_t2);
const auto create_joined_view = create_or_replace_view_as<EmployeeDepartment>(joined_query);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,
count("id"_c) | as<"employee_count">,
avg("salary"_c) | as<"avg_salary">,
sum("salary"_c) | as<"total_salary">
) | group_by("department"_c);
const auto create_stats_view = create_or_replace_view_as<DepartmentStats>(stats_query);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_query = sqlgen::read<std::vector<EmployeeView>> | where("salary"_c > 75000);
const auto high_earners = high_earners_query(conn).value();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);Both MySQL and PostgreSQL support CREATE OR REPLACE VIEW:
CREATE OR REPLACE VIEW "EMPLOYEE_SUMMARY" AS SELECT ...;SQLite has different view creation behavior. It does not support CREATE OR REPLACE VIEW. Instead, you should manually drop the view if it exists before creating it. See the example in the "Create or Replace View" section.
- 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
- 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