documentation/concepts/views.md
A view is a virtual table defined by a SQL SELECT statement. Views do not
store data themselves; instead, their defining query is executed as a sub-query
whenever the view is referenced.
Views provide several benefits:
-- Create a view
CREATE VIEW hourly_summary AS (
SELECT ts, symbol, sum(quantity) as volume
FROM trades
SAMPLE BY 1h
);
-- Query the view like a table
SELECT * FROM hourly_summary WHERE symbol = 'AAPL';
Use CREATE VIEW to define a new view:
CREATE VIEW daily_prices AS (
SELECT ts, symbol, last(price) as closing_price
FROM trades
SAMPLE BY 1d
)
To avoid errors when the view already exists:
CREATE VIEW IF NOT EXISTS price_view AS (
SELECT symbol, last(price) as price FROM trades SAMPLE BY 1h
)
To update an existing view or create it if it doesn't exist:
CREATE OR REPLACE VIEW price_view AS (
SELECT symbol, last(price) as price, ts FROM trades SAMPLE BY 1h
)
For full syntax details, see CREATE VIEW.
Views are queried exactly like tables:
SELECT * FROM my_view
SELECT ts, price FROM my_view WHERE symbol = 'AAPL'
SELECT v1.ts, v2.value
FROM view1 v1
JOIN view2 v2 ON v1.id = v2.id
Views in QuestDB are fully transparent to the query optimizer. When you query a view, the optimizer treats it exactly as if you had written the view's query inline as a sub-query. This means views benefit from the complete suite of query optimizations:
-- View definition
CREATE VIEW trades_view AS (
SELECT ts, symbol, price, quantity FROM trades WHERE price > 0
)
-- This query is optimized as if written inline
SELECT ts, price FROM trades_view WHERE symbol = 'AAPL' ORDER BY ts
-- Optimizer sees: SELECT ts, price FROM trades WHERE price > 0 AND symbol = 'AAPL' ORDER BY ts
-- Only ts and price columns are read, filters applied at scan, ordering uses index
Use EXPLAIN to see how the optimizer processes view queries:
EXPLAIN SELECT * FROM trades_view WHERE symbol = 'AAPL'
There is no performance penalty for using views compared to writing equivalent sub-queries directly.
Views support the DECLARE statement to define parameters with default values.
Use OVERRIDABLE to allow users to change parameter values at query time.
CREATE VIEW filtered_trades AS (
DECLARE OVERRIDABLE @min_price := 100
SELECT ts, symbol, price FROM trades WHERE price >= @min_price
)
SELECT * FROM filtered_trades
-- Uses default @min_price = 100
DECLARE @min_price := 500 SELECT * FROM filtered_trades
-- Overrides @min_price to 500
By default, parameters are non-overridable. Use OVERRIDABLE to allow
override at query time:
CREATE VIEW price_range AS (
DECLARE OVERRIDABLE @lo := 100, OVERRIDABLE @hi := 1000
SELECT ts, symbol, price FROM trades WHERE price >= @lo AND price <= @hi
)
-- Query with custom range
DECLARE @lo := 50, @hi := 200 SELECT * FROM price_range
Parameters without OVERRIDABLE cannot be changed at query time, providing
security for sensitive filters:
CREATE VIEW secure_view AS (
DECLARE @min_value := 0
SELECT * FROM trades WHERE value >= @min_value
)
-- This will fail with "variable is not overridable: @min_value"
DECLARE @min_value := -100 SELECT * FROM secure_view
Combine overridable and non-overridable parameters:
CREATE VIEW mixed_params AS (
DECLARE @fixed_filter := 'active', OVERRIDABLE @limit := 100
SELECT * FROM data WHERE status = @fixed_filter LIMIT @limit
)
-- @limit can be overridden, @fixed_filter cannot
DECLARE @limit := 50 SELECT * FROM mixed_params
Views can reference other views, tables, and materialized views:
-- Level 1: Raw data filtering
CREATE VIEW valid_trades AS (
SELECT * FROM trades WHERE price > 0 AND quantity > 0
)
-- Level 2: Aggregation
CREATE VIEW hourly_stats AS (
SELECT ts, symbol, sum(quantity) as volume
FROM valid_trades
SAMPLE BY 1h
)
-- Level 3: Derived metrics
CREATE VIEW hourly_vwap AS (
SELECT ts, symbol, volume, turnover / volume as vwap
FROM hourly_stats
WHERE volume > 0
)
:::tip
Keep view hierarchies shallow (3-4 levels maximum) for better query planning and maintainability.
:::
SELECT * FROM views()
Returns:
| Column | Description |
|---|---|
view_name | Name of the view |
view_sql | The SQL definition |
view_table_dir_name | Internal directory name |
invalidation_reason | Error message if view is invalid |
view_status | valid or invalid |
view_status_update_time | Timestamp of last status change |
SHOW CREATE VIEW my_view
Returns the CREATE VIEW statement that would recreate the view.
SHOW COLUMNS FROM my_view
To modify an existing view's definition:
ALTER VIEW my_view AS (SELECT col1, col2 FROM my_table WHERE col1 > 0)
For full syntax, see ALTER VIEW.
DROP VIEW my_view
-- Or safely:
DROP VIEW IF EXISTS my_view
For full syntax, see DROP VIEW.
Views are automatically invalidated when their dependencies change:
| Operation | Effect |
|---|---|
DROP TABLE | View becomes invalid |
RENAME TABLE | View becomes invalid |
DROP COLUMN | View becomes invalid if column is referenced |
RENAME COLUMN | View becomes invalid if column is referenced |
| Column type change | View metadata is updated |
Views are automatically revalidated when the invalidating condition is reversed:
SELECT view_name, view_status, invalidation_reason
FROM views()
WHERE view_status = 'invalid'
Views appear in the tables() function with table_type = 'V':
SELECT table_name, table_type FROM tables()
| table_type | Description |
|---|---|
T | Regular table |
V | View |
M | Materialized view |
Understanding when to use each type is important for performance:
| Feature | View | Materialized View |
|---|---|---|
| Data storage | None (virtual) | Physical storage |
| Query execution | On every access | Pre-computed |
| Data freshness | Always current | Depends on refresh |
| Performance | Query-time cost | Read-time benefit |
| Storage cost | Zero | Proportional to result size |
DECLAREFor detailed comparisons and examples, see Materialized Views.
Views provide a security boundary between users and underlying data.
Views use a definer security model. When a view is created, the creator's
permissions are captured. Users querying the view only need SELECT permission
on the view itself - they do not need permissions on the underlying tables.
-- Admin creates a view on sensitive table
CREATE VIEW public_summary AS (
SELECT date, region, sum(sales) as total FROM sensitive_sales GROUP BY date, region
);
-- Grant SELECT on the view to analysts
GRANT SELECT ON public_summary TO analyst_role;
-- Analysts can query the view without access to sensitive_sales
SELECT * FROM public_summary; -- Works!
SELECT * FROM sensitive_sales; -- Access denied!
The view's definer permissions are preserved even if the creator's account is later disabled or deleted.
Unlike tables, views do not support column-level permissions. You can only grant or revoke permissions on the entire view:
-- This works: grant SELECT on entire view
GRANT SELECT ON my_view TO user1;
-- Column-level permissions are NOT supported for views
-- Use separate views to expose different column subsets
To provide different column access to different users, create multiple views with different column selections.
Views enable several security patterns:
-- Base table with sensitive data
CREATE TABLE employees (
id LONG,
name VARCHAR,
salary DOUBLE, -- Sensitive
department VARCHAR,
hire_date TIMESTAMP
);
-- View exposing only non-sensitive columns
CREATE VIEW employees_public AS (
SELECT id, name, department, hire_date
FROM employees
);
-- Grant access to public view only
GRANT SELECT ON employees_public TO analyst_role;
-- View for specific trading desk
CREATE VIEW desk_a_trades AS (
SELECT * FROM trades WHERE trader_id IN (101, 102, 103)
);
GRANT SELECT ON desk_a_trades TO desk_a_users;
For more details on permissions, see Role-Based Access Control (RBAC).
Every query against a view executes the underlying query. For expensive aggregations accessed frequently, consider materialized views.
Create indexes on base table columns used in view filters:
ALTER TABLE trades ALTER COLUMN symbol ADD INDEX
Always examine query plans when optimizing:
EXPLAIN SELECT * FROM my_view WHERE symbol = 'AAPL'
RENAME TABLE) on
viewsSQL Commands
CREATE VIEW: Create a new viewALTER VIEW: Modify a view definitionDROP VIEW: Remove a viewRelated Concepts