Skip to main content

Course Progress

Loading...

SQL Views - Virtual Tables

Duration: 85 minutes
Module 3: Database Objects

Learning Objectives

  • Understand views as virtual tables
  • Create and manage simple and complex views
  • Implement updatable views with restrictions
  • Use views for security and access control
  • Apply views for data abstraction
  • Understand materialized views concepts
  • Optimize view performance
  • Handle view dependencies and maintenance

Understanding Database Views

Views are virtual tables that present data from one or more tables through a predefined query. They simplify complex queries and enhance security! 🔍

💡
View Key Concepts
  • Virtual Table:View doesn't store data, executes query on access
  • Abstraction:Hides complex JOINs and calculations
  • Security:Restrict access to specific columns/rows
  • Consistency:Centralizes business logic
  • Updateable:Some views allow INSERT/UPDATE/DELETE
  • Performance:Can be slower than direct queries

Creating and Managing Views

graph TB VIEW[Database Views] --> SIMPLE[Simple Views] VIEW --> COMPLEX[Complex Views] VIEW --> UPDATE[Updatable Views] VIEW --> MAT[Materialized Views] SIMPLE --> S1[Single table] SIMPLE --> S2[No aggregates] COMPLEX --> C1[Multiple tables] COMPLEX --> C2[Aggregates/Groups] UPDATE --> U1[Restrictions apply] UPDATE --> U2[WITH CHECK OPTION] MAT --> M1[Stored results] MAT --> M2[Refresh needed] style VIEW fill:#dcfce7 style SIMPLE fill:#dbeafe style COMPLEX fill:#fef3c7 style UPDATE fill:#fee2e2

View Creation Examples

-- ========================================
-- SIMPLE VIEWS
-- ========================================

-- Basic view creation
CREATE VIEW active_customers AS
SELECT 
    customer_id,
    customer_name,
    email,
    registration_date
FROM customers
WHERE is_active = TRUE;

-- Using the view
SELECT * FROM active_customers;
SELECT * FROM active_customers WHERE registration_date >= '2024-01-01';

-- View with column aliases
CREATE VIEW product_inventory AS
SELECT 
    p.product_id AS id,
    p.product_name AS name,
    p.price AS unit_price,
    i.quantity_available AS stock,
    p.price * i.quantity_available AS inventory_value
FROM products p
JOIN inventory i ON p.product_id = i.product_id;

-- ========================================
-- COMPLEX VIEWS
-- ========================================

-- View with aggregations
CREATE VIEW customer_statistics AS
SELECT 
    c.customer_id,
    c.customer_name,
    c.customer_segment,
    COUNT(DISTINCT o.order_id) AS total_orders,
    SUM(o.amount) AS lifetime_value,
    AVG(o.amount) AS avg_order_value,
    MAX(o.order_date) AS last_order_date,
    DATEDIFF(CURDATE(), MAX(o.order_date)) AS days_since_last_order
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id;

-- Multi-table complex view
CREATE VIEW order_details_view AS
SELECT 
    o.order_id,
    o.order_date,
    c.customer_name,
    c.email AS customer_email,
    p.product_name,
    oi.quantity,
    oi.unit_price,
    oi.quantity * oi.unit_price AS line_total,
    o.status AS order_status
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id;

-- ========================================
-- REPLACE/ALTER VIEWS
-- ========================================

-- Replace existing view
CREATE OR REPLACE VIEW active_customers AS
SELECT 
    customer_id,
    customer_name,
    email,
    phone,  -- Added phone
    registration_date,
    last_login
FROM customers
WHERE is_active = TRUE
    AND last_login >= DATE_SUB(CURDATE(), INTERVAL 90 DAY);

-- ALTER VIEW (MySQL 5.0.1+)
ALTER VIEW product_inventory AS
SELECT 
    p.product_id,
    p.product_name,
    p.category,  -- Added category
    p.price,
    i.quantity_available,
    i.reorder_level  -- Added reorder level
FROM products p
JOIN inventory i ON p.product_id = i.product_id
WHERE p.is_discontinued = FALSE;

-- ========================================
-- VIEW METADATA
-- ========================================

-- Show all views in database
SHOW FULL TABLES WHERE Table_type = 'VIEW';

-- Show view definition
SHOW CREATE VIEW customer_statistics;

-- View information from information_schema
SELECT 
    TABLE_NAME,
    VIEW_DEFINITION,
    CHECK_OPTION,
    IS_UPDATABLE,
    DEFINER,
    SECURITY_TYPE
FROM information_schema.VIEWS
WHERE TABLE_SCHEMA = DATABASE();

-- Check view columns
DESCRIBE customer_statistics;

-- ========================================
-- DROP VIEWS
-- ========================================

-- Drop single view
DROP VIEW IF EXISTS customer_statistics;

-- Drop multiple views
DROP VIEW IF EXISTS view1, view2, view3;

Updatable Views

Working with Updatable Views

-- ========================================
-- UPDATABLE VIEW EXAMPLES
-- ========================================

-- Simple updatable view
CREATE VIEW editable_products AS
SELECT 
    product_id,
    product_name,
    price,
    category,
    is_active
FROM products
WHERE category = 'Electronics';

-- INSERT through view
INSERT INTO editable_products (product_name, price, category, is_active)
VALUES ('New Laptop', 999.99, 'Electronics', TRUE);

-- UPDATE through view
UPDATE editable_products
SET price = price * 1.1
WHERE product_id = 123;

-- DELETE through view
DELETE FROM editable_products
WHERE product_id = 456;

-- ========================================
-- WITH CHECK OPTION
-- ========================================

-- LOCAL check (only this view's WHERE)
CREATE VIEW premium_products AS
SELECT * FROM products
WHERE price > 1000
WITH LOCAL CHECK OPTION;

-- Cascaded check (all underlying views)
CREATE VIEW electronics_premium AS
SELECT * FROM premium_products
WHERE category = 'Electronics'
WITH CASCADED CHECK OPTION;

-- This INSERT will fail (price <= 1000)
-- INSERT INTO premium_products (product_name, price)
-- VALUES ('Cheap Item', 50.00);  -- Error!

-- ========================================
-- VIEW WITH DEFAULT VALUES
-- ========================================

CREATE VIEW customer_orders AS
SELECT 
    o.order_id,
    o.customer_id,
    COALESCE(o.status, 'pending') AS status,
    COALESCE(o.amount, 0) AS amount,
    o.order_date
FROM orders o;

-- ========================================
-- INSTEAD OF TRIGGERS (Not in MySQL)
-- ========================================

-- MySQL doesn't support INSTEAD OF triggers
-- Alternative: Use stored procedures for complex updates

DELIMITER $$

CREATE PROCEDURE update_complex_view(
    IN p_customer_id INT,
    IN p_new_email VARCHAR(255),
    IN p_new_phone VARCHAR(20)
)
BEGIN
    -- Update customer table
    UPDATE customers
    SET email = p_new_email,
        phone = p_new_phone
    WHERE customer_id = p_customer_id;
    
    -- Log the change
    INSERT INTO audit_log (table_name, action, user, timestamp)
    VALUES ('customers', 'UPDATE', USER(), NOW());
END$$

DELIMITER ;

-- ========================================
-- CHECK VIEW UPDATABILITY
-- ========================================

-- Check if view is updatable
SELECT 
    TABLE_NAME,
    IS_UPDATABLE,
    CHECK_OPTION
FROM information_schema.VIEWS
WHERE TABLE_SCHEMA = DATABASE()
    AND TABLE_NAME = 'editable_products';

-- Test updatability
-- Try to update and check for errors
BEGIN;
    UPDATE editable_products SET price = 100 WHERE product_id = 1;
    -- If no error, view is updatable
ROLLBACK;

Views for Security and Access Control

🔒
Security Through Views
Views provide row-level and column-level security by exposing only necessary data to users.

Security Implementation with Views

-- ========================================
-- COLUMN-LEVEL SECURITY
-- ========================================

-- Hide sensitive columns
CREATE VIEW employee_public AS
SELECT 
    employee_id,
    first_name,
    last_name,
    department,
    job_title
    -- Excludes: salary, ssn, birth_date, personal info
FROM employees;

-- Grant access to view, not table
GRANT SELECT ON employee_public TO 'app_user'@'localhost';
-- Don't grant on employees table

-- ========================================
-- ROW-LEVEL SECURITY
-- ========================================

-- Department-specific views
CREATE VIEW sales_team_view AS
SELECT * FROM employees
WHERE department = 'Sales';

CREATE VIEW hr_employee_view AS
SELECT * FROM employees
WHERE department = 'HR' 
   OR employee_id = SUBSTRING_INDEX(USER(), '@', 1);

-- Regional data access
CREATE VIEW regional_sales_west AS
SELECT * FROM sales
WHERE region = 'West';

-- ========================================
-- DATA MASKING
-- ========================================

CREATE VIEW customer_masked AS
SELECT 
    customer_id,
    CONCAT(LEFT(customer_name, 1), REPEAT('*', LENGTH(customer_name) - 2), 
           RIGHT(customer_name, 1)) AS customer_name,
    CONCAT(LEFT(email, 3), '***@***', 
           SUBSTRING_INDEX(email, '.', -1)) AS email,
    CONCAT('***-***-', RIGHT(phone, 4)) AS phone,
    registration_date
FROM customers;

-- ========================================
-- MULTI-TENANT SECURITY
-- ========================================

-- Create tenant-specific views
CREATE VIEW tenant_1_data AS
SELECT * FROM multi_tenant_table
WHERE tenant_id = 1;

CREATE VIEW tenant_2_data AS
SELECT * FROM multi_tenant_table
WHERE tenant_id = 2;

-- User can only see their tenant's data
GRANT SELECT ON tenant_1_data TO 'tenant1_user'@'localhost';

-- ========================================
-- AUDIT VIEWS
-- ========================================

CREATE VIEW user_activity_summary AS
SELECT 
    user_id,
    COUNT(*) AS total_actions,
    MAX(action_timestamp) AS last_activity,
    COUNT(CASE WHEN action_type = 'LOGIN' THEN 1 END) AS login_count,
    COUNT(CASE WHEN action_type = 'ERROR' THEN 1 END) AS error_count
FROM audit_log
GROUP BY user_id;

-- ========================================
-- DEFINER vs INVOKER Security
-- ========================================

-- DEFINER security (default) - runs with creator's privileges
CREATE DEFINER = 'admin'@'localhost'
SQL SECURITY DEFINER
VIEW sensitive_data AS
SELECT * FROM confidential_table;

-- INVOKER security - runs with caller's privileges
CREATE SQL SECURITY INVOKER
VIEW public_data AS
SELECT * FROM public_table;

-- ========================================
-- ROLE-BASED ACCESS VIEWS
-- ========================================

-- Manager view - sees team data
CREATE VIEW manager_dashboard AS
SELECT 
    e.employee_id,
    e.employee_name,
    e.performance_score,
    d.department_budget,
    d.department_goals
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE e.manager_id = (
    SELECT employee_id 
    FROM employees 
    WHERE user_account = CURRENT_USER()
);

-- Executive view - sees aggregate data
CREATE VIEW executive_summary AS
SELECT 
    department,
    COUNT(*) AS employee_count,
    AVG(salary) AS avg_salary,
    SUM(budget) AS total_budget
FROM departments d
JOIN employees e ON d.department_id = e.department_id
GROUP BY department;

Advanced View Patterns

Complex View Patterns and Optimization

-- ========================================
-- MATERIALIZED VIEW SIMULATION IN MySQL
-- ========================================

-- MySQL doesn't have native materialized views
-- Simulate with tables and triggers

-- Create materialized view table
CREATE TABLE mv_daily_sales_summary (
    sale_date DATE PRIMARY KEY,
    total_orders INT,
    total_revenue DECIMAL(10,2),
    unique_customers INT,
    avg_order_value DECIMAL(10,2),
    last_refreshed TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_revenue (total_revenue)
);

-- Refresh procedure
DELIMITER $$

CREATE PROCEDURE refresh_daily_sales_summary()
BEGIN
    TRUNCATE TABLE mv_daily_sales_summary;
    
    INSERT INTO mv_daily_sales_summary (
        sale_date,
        total_orders,
        total_revenue,
        unique_customers,
        avg_order_value
    )
    SELECT 
        DATE(order_date) AS sale_date,
        COUNT(*) AS total_orders,
        SUM(amount) AS total_revenue,
        COUNT(DISTINCT customer_id) AS unique_customers,
        AVG(amount) AS avg_order_value
    FROM orders
    WHERE status = 'completed'
    GROUP BY DATE(order_date);
END$$

DELIMITER ;

-- Schedule refresh (using events)
CREATE EVENT refresh_sales_summary
ON SCHEDULE EVERY 1 HOUR
DO CALL refresh_daily_sales_summary();

-- ========================================
-- NESTED VIEWS
-- ========================================

-- Base view
CREATE VIEW order_basics AS
SELECT 
    order_id,
    customer_id,
    order_date,
    amount,
    status
FROM orders;

-- View built on another view
CREATE VIEW recent_orders AS
SELECT * FROM order_basics
WHERE order_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY);

-- Another level of nesting
CREATE VIEW recent_large_orders AS
SELECT * FROM recent_orders
WHERE amount > 1000;

-- ========================================
-- UNION VIEWS
-- ========================================

CREATE VIEW all_transactions AS
SELECT 
    'Order' AS transaction_type,
    order_id AS transaction_id,
    customer_id,
    amount,
    order_date AS transaction_date
FROM orders
WHERE status = 'completed'

UNION ALL

SELECT 
    'Refund' AS transaction_type,
    refund_id AS transaction_id,
    customer_id,
    -amount AS amount,
    refund_date AS transaction_date
FROM refunds

UNION ALL

SELECT 
    'Credit' AS transaction_type,
    credit_id AS transaction_id,
    customer_id,
    amount,
    credit_date AS transaction_date
FROM customer_credits;

-- ========================================
-- PARAMETERIZED VIEW WORKAROUND
-- ========================================

-- MySQL views don't support parameters
-- Use session variables as workaround

SET @customer_id = 123;
SET @start_date = '2024-01-01';
SET @end_date = '2024-12-31';

CREATE VIEW parameterized_orders AS
SELECT *
FROM orders
WHERE customer_id = @customer_id
    AND order_date BETWEEN @start_date AND @end_date;

-- Better approach: Use stored procedure
DELIMITER $$

CREATE PROCEDURE get_customer_orders(
    IN p_customer_id INT,
    IN p_start_date DATE,
    IN p_end_date DATE
)
BEGIN
    SELECT *
    FROM orders
    WHERE customer_id = p_customer_id
        AND order_date BETWEEN p_start_date AND p_end_date;
END$$

DELIMITER ;

-- ========================================
-- VIEW DEPENDENCIES
-- ========================================

-- Find all views that depend on a table
SELECT 
    TABLE_NAME AS view_name,
    VIEW_DEFINITION
FROM information_schema.VIEWS
WHERE TABLE_SCHEMA = DATABASE()
    AND VIEW_DEFINITION LIKE '%products%';

-- Find view hierarchy
WITH RECURSIVE view_deps AS (
    SELECT 
        TABLE_NAME AS view_name,
        VIEW_DEFINITION,
        1 AS level
    FROM information_schema.VIEWS
    WHERE TABLE_SCHEMA = DATABASE()
        AND VIEW_DEFINITION NOT LIKE '%information_schema.VIEWS%'
    
    UNION ALL
    
    SELECT 
        v.TABLE_NAME,
        v.VIEW_DEFINITION,
        vd.level + 1
    FROM information_schema.VIEWS v
    JOIN view_deps vd ON v.VIEW_DEFINITION LIKE CONCAT('%', vd.view_name, '%')
    WHERE v.TABLE_SCHEMA = DATABASE()
        AND vd.level < 5
)
SELECT DISTINCT view_name, level
FROM view_deps
ORDER BY level, view_name;

View Performance Optimization

⚠️
Performance Considerations
Views execute their underlying query each time they're accessed. Complex views can impact performance significantly.
-- ========================================
-- VIEW PERFORMANCE OPTIMIZATION
-- ========================================

-- Analyze view performance
EXPLAIN SELECT * FROM customer_statistics;

-- Create indexes for view performance
-- Identify columns used in view's WHERE/JOIN
CREATE INDEX idx_view_support 
ON orders(customer_id, order_date, status);

-- ========================================
-- OPTIMIZE COMPLEX VIEWS
-- ========================================

-- Instead of complex view with many JOINs
-- CREATE VIEW complex_report AS
-- SELECT ... FROM t1 JOIN t2 JOIN t3 JOIN t4 ...

-- Use summary tables updated periodically
CREATE TABLE report_summary (
    report_date DATE,
    metric1 DECIMAL(10,2),
    metric2 INT,
    -- ... other columns
    PRIMARY KEY (report_date)
);

-- Update via stored procedure or triggers
DELIMITER $$

CREATE TRIGGER update_report_summary
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
    INSERT INTO report_summary (report_date, metric1, metric2)
    VALUES (DATE(NEW.order_date), NEW.amount, 1)
    ON DUPLICATE KEY UPDATE
        metric1 = metric1 + NEW.amount,
        metric2 = metric2 + 1;
END$$

DELIMITER ;

-- ========================================
-- VIEW CACHING STRATEGIES
-- ========================================

-- Use query cache (deprecated in MySQL 8.0)
-- For older versions:
SET GLOBAL query_cache_type = 1;
SET GLOBAL query_cache_size = 67108864; -- 64MB

-- Application-level caching
-- Cache view results in Redis/Memcached

-- ========================================
-- MONITOR VIEW USAGE
-- ========================================

-- Track slow queries involving views
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;

-- Check view execution frequency
SELECT 
    SUBSTRING_INDEX(
        SUBSTRING_INDEX(argument, 'FROM', -1), 
        ' ', 2
    ) AS view_name,
    COUNT(*) AS execution_count,
    AVG(query_time) AS avg_time,
    MAX(query_time) AS max_time
FROM mysql.general_log
WHERE argument LIKE '%FROM%VIEW%'
GROUP BY view_name;

Practice Exercise

💻
View Challenges
Create views for these scenarios:
  1. Create a view showing top customers by revenue with data masking
  2. Build an updatable view for product management with CHECK OPTION
  3. Design role-based views for different user types
  4. Create a reporting view with multiple aggregations
  5. Implement a view simulating materialized view behavior
  6. Build nested views for hierarchical data
  7. Create a security view hiding sensitive information
  8. Design a performance-optimized view for dashboards
💡 Click for Solutions
-- 1. Top customers with data masking
CREATE VIEW top_customers_masked AS
SELECT 
    customer_id,
    CONCAT(LEFT(customer_name, 2), REPEAT('*', LENGTH(customer_name) - 4), 
           RIGHT(customer_name, 2)) AS customer_name,
    CONCAT(LEFT(email, 3), '****@****.***') AS email,
    total_orders,
    total_revenue,
    CASE 
        WHEN total_revenue > 10000 THEN 'Platinum'
        WHEN total_revenue > 5000 THEN 'Gold'
        WHEN total_revenue > 1000 THEN 'Silver'
        ELSE 'Bronze'
    END AS tier
FROM (
    SELECT 
        c.customer_id,
        c.customer_name,
        c.email,
        COUNT(o.order_id) AS total_orders,
        SUM(o.amount) AS total_revenue
    FROM customers c
    JOIN orders o ON c.customer_id = o.customer_id
    WHERE o.status = 'completed'
    GROUP BY c.customer_id
    ORDER BY total_revenue DESC
    LIMIT 100
) AS customer_summary;

-- 2. Updatable product management view
CREATE VIEW product_management AS
SELECT 
    product_id,
    sku,
    product_name,
    category,
    price,
    is_active,
    last_updated
FROM products
WHERE is_active = TRUE
    AND price > 0
WITH CHECK OPTION;

-- Test updates
UPDATE product_management 
SET price = 99.99 
WHERE product_id = 1;

-- 3. Role-based views
-- Admin view
CREATE VIEW admin_dashboard AS
SELECT 
    DATE(order_date) AS date,
    COUNT(*) AS orders,
    SUM(amount) AS revenue,
    AVG(amount) AS avg_order,
    COUNT(DISTINCT customer_id) AS unique_customers
FROM orders
GROUP BY DATE(order_date);

-- Sales view
CREATE VIEW sales_dashboard AS
SELECT 
    product_name,
    category,
    SUM(quantity) AS units_sold,
    SUM(quantity * unit_price) AS revenue
FROM order_items oi
JOIN products p ON oi.product_id = p.product_id
GROUP BY p.product_id;

-- Customer service view
CREATE VIEW customer_service_view AS
SELECT 
    c.customer_id,
    c.customer_name,
    c.email,
    COUNT(o.order_id) AS total_orders,
    MAX(o.order_date) AS last_order,
    COUNT(r.review_id) AS reviews_written
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
LEFT JOIN reviews r ON c.customer_id = r.user_id
GROUP BY c.customer_id;

-- 4. Complex reporting view
CREATE VIEW sales_report AS
SELECT 
    YEAR(o.order_date) AS year,
    QUARTER(o.order_date) AS quarter,
    MONTH(o.order_date) AS month,
    p.category,
    COUNT(DISTINCT o.order_id) AS order_count,
    COUNT(DISTINCT o.customer_id) AS unique_customers,
    SUM(oi.quantity) AS units_sold,
    SUM(oi.quantity * oi.unit_price) AS gross_revenue,
    SUM(oi.quantity * (oi.unit_price - p.cost)) AS gross_profit,
    AVG(oi.unit_price) AS avg_selling_price
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
WHERE o.status = 'completed'
GROUP BY 
    YEAR(o.order_date),
    QUARTER(o.order_date),
    MONTH(o.order_date),
    p.category;

-- 5. Materialized view simulation
-- Create table to store view results
CREATE TABLE mv_product_performance (
    product_id INT PRIMARY KEY,
    product_name VARCHAR(200),
    total_quantity_sold INT,
    total_revenue DECIMAL(10,2),
    avg_rating DECIMAL(3,2),
    review_count INT,
    last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- Procedure to refresh
DELIMITER $$
CREATE PROCEDURE refresh_product_performance()
BEGIN
    TRUNCATE TABLE mv_product_performance;
    
    INSERT INTO mv_product_performance
    SELECT 
        p.product_id,
        p.product_name,
        COALESCE(SUM(oi.quantity), 0),
        COALESCE(SUM(oi.quantity * oi.unit_price), 0),
        COALESCE(AVG(r.rating), 0),
        COUNT(DISTINCT r.review_id),
        NOW()
    FROM products p
    LEFT JOIN order_items oi ON p.product_id = oi.product_id
    LEFT JOIN reviews r ON p.product_id = r.product_id
    GROUP BY p.product_id;
END$$
DELIMITER ;

-- 6. Nested views for hierarchy
CREATE VIEW category_base AS
SELECT 
    category_id,
    category_name,
    parent_category_id
FROM categories;

CREATE VIEW category_with_products AS
SELECT 
    c.*,
    COUNT(p.product_id) AS product_count
FROM category_base c
LEFT JOIN products p ON c.category_id = p.category_id
GROUP BY c.category_id;

CREATE VIEW category_hierarchy AS
SELECT 
    c1.category_name AS main_category,
    c2.category_name AS sub_category,
    c2.product_count
FROM category_with_products c1
JOIN category_with_products c2 ON c1.category_id = c2.parent_category_id;

-- 7. Security view
CREATE VIEW employee_safe_view AS
SELECT 
    employee_id,
    first_name,
    last_name,
    department,
    hire_date,
    CASE 
        WHEN USER() LIKE '%hr%' THEN salary
        ELSE 'RESTRICTED'
    END AS salary_info,
    CASE 
        WHEN USER() LIKE '%hr%' THEN ssn
        ELSE 'XXX-XX-' || RIGHT(ssn, 4)
    END AS ssn_masked
FROM employees;

-- 8. Performance-optimized dashboard view
CREATE VIEW dashboard_metrics AS
SELECT 
    (SELECT COUNT(*) FROM orders WHERE DATE(order_date) = CURDATE()) AS orders_today,
    (SELECT SUM(amount) FROM orders WHERE DATE(order_date) = CURDATE()) AS revenue_today,
    (SELECT COUNT(*) FROM customers WHERE DATE(registration_date) = CURDATE()) AS new_customers_today,
    (SELECT COUNT(*) FROM products WHERE is_active = TRUE) AS active_products,
    (SELECT AVG(rating) FROM reviews WHERE DATE(created_at) = CURDATE()) AS avg_rating_today;

Additional Resources