or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

api

features

charts

charts.mdconditional-formatting.mdvisualizations.md
authorization.mdchangesets.mdcharts-as-code.mdcompiler.mddashboards.mddbt.mdee-features.mdformatting.mdparameters.mdpivot.mdprojects-spaces.mdsql-runner.mdtemplating.mdwarehouse.md
index.md
tile.json

virtual-views.mddocs/api/utilities/specialized/

Virtual View Utilities

Functions for creating virtual explores from SQL queries for use in SQL Runner visualizations.

Capabilities

This module provides the following functionality:

Core Functions

/**
 * Creates a virtual explore from a SQL query for visualization
 * Virtual explores behave like regular explores but are backed by custom SQL
 * @param virtualViewName - Name for the virtual view
 * @param sql - SQL query that defines the view
 * @param columns - Column definitions from the SQL results
 * @param warehouseClient - Warehouse client for SQL generation
 * @param label - Optional display label for the explore
 * @returns Explore object configured as a virtual view
 */
function createVirtualView(
  virtualViewName: string,
  sql: string,
  columns: VizColumn[],
  warehouseClient: WarehouseClient,
  label?: string
): Explore;

/**
 * Creates a temporary virtual view with fake warehouse client for testing
 * Useful for preview/validation without a real warehouse connection
 * @param virtualViewName - Name for the virtual view
 * @param sql - SQL query that defines the view
 * @param columns - Column definitions from the SQL results
 * @returns Explore object configured as a temporary virtual view
 */
function createTemporaryVirtualView(
  virtualViewName: string,
  sql: string,
  columns: VizColumn[]
): Explore;

Examples

Creating Virtual Views from SQL Runner

import { createVirtualView, createTemporaryVirtualView, type VizColumn } from '@lightdash/common';

// Create virtual view from SQL Runner results
const sql = `
  SELECT
    date_trunc('month', order_date) as month,
    customer_segment,
    SUM(revenue) as total_revenue,
    COUNT(*) as order_count
  FROM orders
  GROUP BY 1, 2
`;

const columns: VizColumn[] = [
  { reference: 'month', type: DimensionType.DATE },
  { reference: 'customer_segment', type: DimensionType.STRING },
  { reference: 'total_revenue', type: DimensionType.NUMBER },
  { reference: 'order_count', type: DimensionType.NUMBER },
];

// Create virtual explore for visualization
const virtualExplore = createVirtualView(
  'revenue_by_segment',
  sql,
  columns,
  warehouseClient,
  'Revenue by Segment'
);

// Now you can use this explore for charts
const cartesianDataModel = new CartesianChartDataModel({
  resultsRunner: {
    rows: sqlResults.rows,
    getField: (fieldId) => virtualExplore.tables['revenue_by_segment'].dimensions[fieldId],
  },
  fieldConfig: {
    x: ['month'],
    y: [{ reference: 'total_revenue', aggregation: 'sum' }],
  },
});

Temporary Virtual Views for Testing

import { createTemporaryVirtualView, type VizColumn } from '@lightdash/common';

// Create temporary virtual view for testing/preview
const sql = `
  SELECT
    product_category,
    COUNT(*) as product_count,
    AVG(price) as avg_price
  FROM products
  GROUP BY product_category
`;

const columns: VizColumn[] = [
  { reference: 'product_category', type: DimensionType.STRING },
  { reference: 'product_count', type: DimensionType.NUMBER },
  { reference: 'avg_price', type: DimensionType.NUMBER },
];

const tempExplore = createTemporaryVirtualView(
  'preview_view',
  sql,
  columns
);
// Can be used for validation without warehouse connection

SQL Runner Chart Creation

import { createVirtualView, DimensionType } from '@lightdash/common';

async function createChartFromSqlRunner(
  sqlQuery: string,
  warehouseClient: WarehouseClient
): Promise<ChartConfig> {
  // Execute SQL query
  const results = await warehouseClient.runQuery(sqlQuery);

  // Create column definitions from results
  const columns: VizColumn[] = results.fields.map(field => ({
    reference: field.name,
    type: inferDimensionType(field.type),
  }));

  // Create virtual view
  const virtualExplore = createVirtualView(
    'sql_runner_view',
    sqlQuery,
    columns,
    warehouseClient,
    'SQL Runner Results'
  );

  // Build chart configuration
  return {
    type: 'cartesian',
    explore: virtualExplore,
    dimensions: columns.filter(c => c.type !== DimensionType.NUMBER),
    metrics: columns.filter(c => c.type === DimensionType.NUMBER),
  };
}

function inferDimensionType(sqlType: string): DimensionType {
  const lowerType = sqlType.toLowerCase();
  if (lowerType.includes('int') || lowerType.includes('numeric') || lowerType.includes('decimal')) {
    return DimensionType.NUMBER;
  } else if (lowerType.includes('date') || lowerType.includes('timestamp')) {
    return DimensionType.DATE;
  } else if (lowerType.includes('bool')) {
    return DimensionType.BOOLEAN;
  }
  return DimensionType.STRING;
}

Dashboard Integration

import { createVirtualView } from '@lightdash/common';

async function addSqlRunnerChart(
  dashboardId: string,
  sqlQuery: string,
  chartConfig: ChartConfig
) {
  // Execute SQL
  const results = await executeSqlQuery(sqlQuery);

  // Extract columns from results
  const columns = results.fields.map(field => ({
    reference: field.name,
    type: mapSqlTypeToDimensionType(field.type),
  }));

  // Create virtual explore
  const explore = createVirtualView(
    `sql_chart_${Date.now()}`,
    sqlQuery,
    columns,
    warehouseClient,
    chartConfig.title
  );

  // Create chart using virtual explore
  const chart = await createChart({
    ...chartConfig,
    explore,
    dashboardId,
  });

  return chart;
}

Preview Virtual View

import { createTemporaryVirtualView } from '@lightdash/common';

// Preview SQL query results as a chart without executing
function previewSqlChart(
  sqlQuery: string,
  sampleResults: { fields: Field[]; rows: Row[] }
) {
  const columns = sampleResults.fields.map(field => ({
    reference: field.name,
    type: DimensionType.STRING, // Default for preview
  }));

  const tempExplore = createTemporaryVirtualView(
    'preview',
    sqlQuery,
    columns
  );

  // Use temp explore for preview rendering
  return {
    explore: tempExplore,
    sampleData: sampleResults.rows.slice(0, 10),
  };
}

API Endpoint

import { createVirtualView } from '@lightdash/common';

// API endpoint to create visualization from SQL
app.post('/api/sql-runner/visualize', async (req, res) => {
  const { sql, chartType } = req.body;

  try {
    // Get warehouse client for project
    const warehouseClient = await getWarehouseClient(req.user.projectId);

    // Execute SQL
    const results = await warehouseClient.runQuery(sql);

    // Create columns from results
    const columns: VizColumn[] = results.fields.map(field => ({
      reference: field.name,
      type: inferTypeFromSqlType(field.type),
    }));

    // Create virtual explore
    const explore = createVirtualView(
      'sql_runner_viz',
      sql,
      columns,
      warehouseClient,
      'SQL Runner Visualization'
    );

    // Generate chart based on explore
    const chartData = generateChartData(explore, results.rows, chartType);

    res.json({
      explore,
      chartData,
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

Column Type Mapping

import { DimensionType } from '@lightdash/common';

function createColumnsFromSqlResults(
  results: SqlQueryResults
): VizColumn[] {
  return results.fields.map(field => {
    let type: DimensionType;

    // Map SQL types to Lightdash dimension types
    const sqlType = field.type.toLowerCase();

    if (sqlType.includes('int') || sqlType.includes('numeric') ||
        sqlType.includes('decimal') || sqlType.includes('float') ||
        sqlType.includes('double')) {
      type = DimensionType.NUMBER;
    } else if (sqlType.includes('date') || sqlType.includes('timestamp')) {
      type = DimensionType.DATE;
    } else if (sqlType.includes('bool')) {
      type = DimensionType.BOOLEAN;
    } else {
      type = DimensionType.STRING;
    }

    return {
      reference: field.name,
      type,
    };
  });
}

Testing Virtual Views

import { createTemporaryVirtualView, createVirtualView } from '@lightdash/common';

describe('Virtual views', () => {
  it('should create temporary virtual view', () => {
    const columns = [
      { reference: 'category', type: DimensionType.STRING },
      { reference: 'total', type: DimensionType.NUMBER },
    ];

    const explore = createTemporaryVirtualView(
      'test_view',
      'SELECT category, SUM(amount) as total FROM sales GROUP BY category',
      columns
    );

    expect(explore.name).toBe('test_view');
    expect(explore.tables['test_view']).toBeDefined();
  });

  it('should create virtual view with warehouse client', () => {
    const columns = [
      { reference: 'month', type: DimensionType.DATE },
      { reference: 'revenue', type: DimensionType.NUMBER },
    ];

    const explore = createVirtualView(
      'revenue_view',
      'SELECT * FROM revenue',
      columns,
      mockWarehouseClient,
      'Revenue View'
    );

    expect(explore.label).toBe('Revenue View');
    expect(explore.tables['revenue_view'].dimensions['month'].type).toBe(DimensionType.DATE);
  });
});

Advanced Usage with Aggregations

import { createVirtualView } from '@lightdash/common';

// Create virtual view with pre-aggregated data
async function createAggregatedView(
  baseTable: string,
  groupBy: string[],
  metrics: { name: string; expression: string }[]
) {
  const sql = `
    SELECT
      ${groupBy.join(', ')},
      ${metrics.map(m => `${m.expression} as ${m.name}`).join(', ')}
    FROM ${baseTable}
    GROUP BY ${groupBy.map((_, i) => i + 1).join(', ')}
  `;

  const columns: VizColumn[] = [
    ...groupBy.map(field => ({
      reference: field,
      type: DimensionType.STRING, // Infer from schema in production
    })),
    ...metrics.map(m => ({
      reference: m.name,
      type: DimensionType.NUMBER,
    })),
  ];

  return createVirtualView(
    'aggregated_view',
    sql,
    columns,
    warehouseClient,
    'Aggregated Results'
  );
}

// Usage
const explore = await createAggregatedView(
  'orders',
  ['customer_segment', 'product_category'],
  [
    { name: 'total_revenue', expression: 'SUM(revenue)' },
    { name: 'order_count', expression: 'COUNT(*)' },
    { name: 'avg_order_value', expression: 'AVG(order_value)' },
  ]
);

Use Cases

  • SQL Runner Visualizations: Convert SQL query results into charts
  • Custom Reports: Create ad-hoc explores from custom SQL
  • Testing: Preview visualizations without warehouse connection
  • Dashboard Tiles: Add SQL-based charts to dashboards
  • Data Exploration: Quick visualization of custom queries
  • Temporary Views: Create ephemeral explores for one-time use

Key Differences

FeaturecreateVirtualViewcreateTemporaryVirtualView
Warehouse ClientRequiredNot required (uses fake client)
SQL ExecutionCan execute queriesCannot execute queries
Use CaseProduction visualizationsTesting/preview
PerformanceFull warehouse featuresLimited to mock data

Related Utilities

  • Subtotal Utilities: For generating subtotal keys in virtual views
  • Chart Data Models: Use virtual explores with CartesianChartDataModel
  • SQL Runner: Integration with SQL Runner features