Skip to content

Query Builder

The Query Builder component provides an intuitive drag-and-drop interface for building analytics queries without writing code. It’s perfect for end-users who want to explore data interactively or for developers who need a quick way to prototype queries.

The Query Builder allows users to:

  • Drag dimensions and measures from a sidebar into query areas
  • Apply filters with various operators and conditions
  • Group by time periods with automatic time dimension handling
  • Preview results in real-time as they build queries
  • Export queries as JSON or copy generated SQL
  • Save and load query configurations
import { QueryBuilder } from '@drizzle-cube/react';
function AnalyticsPage() {
const [query, setQuery] = useState({
dimensions: [],
measures: [],
filters: [],
timeDimension: null
});
return (
<QueryBuilder
cubes={['users', 'orders', 'products']}
query={query}
onChange={setQuery}
onExecute={(results) => {
console.log('Query results:', results);
}}
/>
);
}
PropTypeDescription
cubesstring[]Array of cube names available for querying
queryQueryCurrent query state object
onChange(query: Query) => voidCallback fired when query changes
PropTypeDefaultDescription
onExecute(results: any) => void-Callback fired when user executes query
showSqlbooleanfalseWhether to show generated SQL preview
allowSavebooleantrueWhether to show save/load query buttons
heightstring'600px'Height of the query builder interface
theme'light' | 'dark''light'Visual theme

The query object follows the Drizzle Cube query format:

interface Query {
dimensions?: string[];
measures?: string[];
filters?: Filter[];
timeDimension?: TimeDimension;
order?: Record<string, 'asc' | 'desc'>;
limit?: number;
}
interface Filter {
member: string;
operator: 'equals' | 'notEquals' | 'contains' | 'gt' | 'gte' | 'lt' | 'lte' | 'inDateRange';
values: string[];
}
interface TimeDimension {
dimension: string;
granularity: 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
dateRange?: string[] | 'Last 7 days' | 'Last 30 days' | 'This month' | 'This year';
}

The Query Builder provides three main drop zones:

// Dimensions area - for grouping data
<DimensionsArea
dimensions={query.dimensions}
onAdd={(dimension) => {
setQuery(prev => ({
...prev,
dimensions: [...prev.dimensions, dimension]
}));
}}
onRemove={(dimension) => {
setQuery(prev => ({
...prev,
dimensions: prev.dimensions.filter(d => d !== dimension)
}));
}}
/>
// Measures area - for aggregated values
<MeasuresArea
measures={query.measures}
onAdd={(measure) => {
setQuery(prev => ({
...prev,
measures: [...prev.measures, measure]
}));
}}
/>
// Filters area - for data filtering
<FiltersArea
filters={query.filters}
onAdd={(filter) => {
setQuery(prev => ({
...prev,
filters: [...prev.filters, filter]
}));
}}
/>

Enable real-time query execution as users build their queries:

<QueryBuilder
cubes={cubes}
query={query}
onChange={setQuery}
onExecute={async (query) => {
const results = await semanticLayer.query(query);
setResults(results);
}}
realTimePreview={true}
previewLimit={100}
/>

The built-in filter builder supports various operators:

// String filters
{
member: 'users.name',
operator: 'contains',
values: ['john']
}
// Numeric filters
{
member: 'orders.total',
operator: 'gte',
values: ['100']
}
// Date range filters
{
member: 'orders.createdAt',
operator: 'inDateRange',
values: ['2024-01-01', '2024-12-31']
}

Special handling for time-based queries:

// Monthly sales over the last year
{
timeDimension: {
dimension: 'orders.createdAt',
granularity: 'month',
dateRange: 'Last 12 months'
}
}
// Custom date range with weekly grouping
{
timeDimension: {
dimension: 'users.signUpDate',
granularity: 'week',
dateRange: ['2024-01-01', '2024-03-31']
}
}

Customize how cubes, dimensions, and measures are displayed:

<QueryBuilder
cubes={cubes}
query={query}
onChange={setQuery}
cubeConfig={{
users: {
title: 'Users',
description: 'User registration and profile data',
dimensions: {
'users.name': { title: 'Full Name', type: 'string' },
'users.email': { title: 'Email Address', type: 'string' },
'users.signUpDate': { title: 'Sign Up Date', type: 'time' }
},
measures: {
'users.count': { title: 'Total Users', type: 'number' },
'users.avgAge': { title: 'Average Age', type: 'number' }
}
}
}}
/>

Add custom validation rules:

<QueryBuilder
cubes={cubes}
query={query}
onChange={setQuery}
validation={{
maxDimensions: 5,
maxMeasures: 10,
maxFilters: 20,
requiredMeasure: true, // Require at least one measure
validate: (query) => {
if (query.dimensions.length === 0 && query.measures.length === 0) {
return 'Please select at least one dimension or measure';
}
return null;
}
}}
/>

Add custom toolbar actions:

<QueryBuilder
cubes={cubes}
query={query}
onChange={setQuery}
actions={[
{
label: 'Export to CSV',
icon: <DownloadIcon />,
onClick: async (query) => {
const results = await semanticLayer.query(query);
exportToCsv(results);
}
},
{
label: 'Create Dashboard',
icon: <PlusIcon />,
onClick: (query) => {
createDashboardFromQuery(query);
}
}
]}
/>

Combine Query Builder with chart components:

function InteractiveChart() {
const [query, setQuery] = useState({ dimensions: [], measures: [] });
const [data, setData] = useState([]);
const handleQueryChange = async (newQuery) => {
setQuery(newQuery);
if (newQuery.measures.length > 0) {
const results = await semanticLayer.query(newQuery);
setData(results.data);
}
};
return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<QueryBuilder
cubes={['orders', 'products']}
query={query}
onChange={handleQueryChange}
/>
<ChartRenderer
data={data}
query={query}
type="bar"
/>
</div>
);
}

Create dynamic dashboard builders:

function DashboardBuilder() {
const [widgets, setWidgets] = useState([]);
const addWidget = (query) => {
const newWidget = {
id: Date.now(),
query,
type: 'chart',
title: generateTitleFromQuery(query)
};
setWidgets(prev => [...prev, newWidget]);
};
return (
<div>
<QueryBuilder
cubes={cubes}
query={{}}
onChange={() => {}}
onExecute={addWidget}
/>
<DashboardGrid widgets={widgets} />
</div>
);
}

Customize appearance with CSS variables:

.query-builder {
--qb-primary-color: #3b82f6;
--qb-background: #ffffff;
--qb-border: #e5e7eb;
--qb-text: #1f2937;
--qb-text-muted: #6b7280;
--qb-drop-zone: #f3f4f6;
--qb-drop-zone-active: #dbeafe;
}

Style individual components:

.query-builder-sidebar {
/* Sidebar containing cubes and fields */
}
.query-builder-canvas {
/* Main drop zone area */
}
.query-builder-dimension-tag {
/* Individual dimension pills */
}
.query-builder-measure-tag {
/* Individual measure pills */
}
.query-builder-filter-item {
/* Filter configuration items */
}

The Query Builder includes comprehensive accessibility features:

  • Keyboard navigation - Full keyboard support for all interactions
  • Screen reader support - ARIA labels and descriptions
  • Focus management - Proper focus flow and visual indicators
  • High contrast support - Works with system high contrast modes
<QueryBuilder
cubes={cubes}
query={query}
onChange={setQuery}
accessibility={{
announceChanges: true,
keyboardShortcuts: true,
highContrast: true
}}
/>

For applications with many cubes and fields:

<QueryBuilder
cubes={cubes}
query={query}
onChange={setQuery}
performance={{
virtualizeSchema: true, // Virtualize long field lists
debounceMs: 300, // Debounce query changes
lazyLoadCubes: true // Load cube schemas on demand
}}
/>

Enable query result caching:

<QueryBuilder
cubes={cubes}
query={query}
onChange={setQuery}
caching={{
enabled: true,
ttl: 5 * 60 * 1000, // 5 minutes
maxSize: 100 // Max cached queries
}}
/>

Handle various error scenarios:

<QueryBuilder
cubes={cubes}
query={query}
onChange={setQuery}
onError={(error, context) => {
console.error('Query Builder error:', error);
switch (context) {
case 'query-execution':
toast.error('Failed to execute query');
break;
case 'schema-loading':
toast.error('Failed to load cube schema');
break;
case 'validation':
toast.error(error.message);
break;
}
}}
errorBoundary={{
fallback: <div>Something went wrong with the Query Builder</div>
}}
/>
  • Explore Charts to visualize Query Builder results
  • Learn about Dashboards for multi-query interfaces
  • Check out React Hooks for programmatic query building
  • See Examples for complete implementations