> hypequery

Where

Filter queries using conditions, operators, and predicates

Where

The where() method filters rows based on conditions. It's the most flexible clause in the query builder, supporting simple comparisons, complex predicates, and ClickHouse-specific operators.

Overview

Use where() to:

  • Filter rows by column values
  • Chain multiple conditions (AND logic)
  • Build complex expressions with OR logic
  • Use ClickHouse functions and operators
  • Work with arrays, tuples, and subqueries

Basic Where Clauses

Simple Conditions

const users = await db.table('users')
  .where('status', 'eq', 'active')
  .select(['id', 'name', 'email'])
  .execute();

Multiple Conditions (AND)

const users = await db.table('users')
  .where('status', 'eq', 'active')
  .where('age', 'gte', 18)
  .where('country', 'eq', 'US')
  .select(['id', 'name'])
  .execute();

Comparison Operators

OperatorDescriptionExample
eqEqualwhere('status', 'eq', 'active')
neqNot equalwhere('status', 'neq', 'deleted')
gtGreater thanwhere('age', 'gt', 18)
gteGreater than or equalwhere('age', 'gte', 18)
ltLess thanwhere('price', 'lt', 100)
lteLess than or equalwhere('price', 'lte', 100)
likePattern matchingwhere('email', 'like', '%@company.com')
notLikeNot like patternwhere('email', 'notLike', '%@spam.com')
inIn arraywhere('status', 'in', ['active', 'pending'])
notInNot in arraywhere('status', 'notIn', ['deleted', 'banned'])
betweenInclusive rangewhere('age', 'between', [18, 65])

Comparison Examples

// Equal
await db.table('users')
  .where('status', 'eq', 'active')
  .execute();

// Not equal
await db.table('users')
  .where('status', 'neq', 'deleted')
  .execute();

// Greater than
await db.table('products')
  .where('price', 'gt', 100)
  .execute();

// Range
await db.table('users')
  .where('age', 'between', [18, 65])
  .execute();

// Pattern matching
await db.table('users')
  .where('email', 'like', '%@gmail.com')
  .execute();

// Array membership
await db.table('orders')
  .where('status', 'in', ['pending', 'processing', 'shipped'])
  .execute();

Null Checks

// Is null
await db.table('users')
  .where('deleted_at', 'isNull')
  .execute();

// Is not null
await db.table('users')
  .where('email', 'isNotNull')
  .execute();

OR Conditions

orWhere()

const users = await db.table('users')
  .where('status', 'eq', 'active')
  .orWhere('status', 'eq', 'pending')
  .select(['id', 'name', 'status'])
  .execute();

orWhere with Callback

const users = await db.table('users')
  .where('country', 'eq', 'US')
  .orWhere((expr) =>
    expr.and([
      expr.fn('hasAny', 'tags', ['vip', 'premium']),
      expr.fn('endsWith', 'email', expr.literal('@company.com')),
    ])
  )
  .execute();

Function Predicates

For complex conditions, use predicate builder callbacks:

const events = await db.table('events')
  .where((expr) =>
    expr.and([
      expr.fn('hasAny', 'tags', ['launch', 'beta']),
      expr.fn('endsWith', 'status', expr.literal('active')),
    ])
  )
  .execute();

Predicate Helpers

HelperPurposeExample
expr.fn(name, ...args)Call ClickHouse functionexpr.fn('hasAny', 'tags', ['a', 'b'])
expr.col(column)Explicit column referenceexpr.col('created_at')
expr.array(values)ClickHouse array literalexpr.array([1, 2, 3])
expr.literal(value) / expr.value(value)Force literal valueexpr.literal('active')
expr.raw(sql)Inline raw SQL fragmentexpr.raw('date > now() - INTERVAL 1 DAY')
expr.and([...])Combine with ANDexpr.and([cond1, cond2])
expr.or([...])Combine with ORexpr.or([cond1, cond2])

Predicate Examples

// Using ClickHouse functions
await db.table('products')
  .where((expr) =>
    expr.fn('hasAny', 'categories', ['electronics', 'gadgets'])
  )
  .execute();

// Complex AND/OR
await db.table('events')
  .where((expr) =>
    expr.or([
      expr.fn('startsWith', 'event_type', expr.literal('user_')),
      expr.fn('startsWith', 'event_type', expr.literal('admin_')),
    ])
  )
  .execute();

// Combining multiple conditions
await db.table('orders')
  .where((expr) =>
    expr.and([
      expr.fn('greater', expr.col('total'), expr.literal(1000)),
      expr.fn('notEquals', expr.col('status'), expr.literal('cancelled')),
    ])
  )
  .execute();

Where Groups

Group conditions with parentheses:

const users = await db.table('users')
  .where('status', 'eq', 'active')
  .whereGroup((builder) => {
    builder
      .where('country', 'eq', 'US')
      .orWhere('country', 'eq', 'CA');
  })
  .execute();
// Generates: WHERE status = 'active' AND (country = 'US' OR country = 'CA')
const results = await db.table('orders')
  .whereGroup((builder) => {
    builder
      .where('status', 'eq', 'pending')
      .orWhere('status', 'eq', 'processing');
  })
  .whereGroup((builder) => {
    builder
      .where('total', 'gte', 100)
      .orWhere('priority', 'eq', 'high');
  })
  .execute();
// Generates: WHERE (status = 'pending' OR status = 'processing')
//          AND (total >= 100 OR priority = 'high')

Advanced IN Operators

ClickHouse supports advanced IN operators for distributed queries, tuples, and subqueries.

IN Operators Reference

OperatorDescriptionExample
in / notInStandard array membershipwhere('id', 'in', [1, 2, 3])
globalIn / globalNotInGLOBAL IN for distributed tableswhere('user_id', 'globalIn', [1, 2, 3])
inSubquery / globalInSubquerySubquery stringwhere('id', 'inSubquery', 'SELECT id FROM users')
inTable / globalInTableTable referencewhere('user_id', 'inTable', 'active_users')
inTuple / globalInTupleMulti-column tuple membershipwhere(['c1', 'c2'], 'inTuple', [[1, 2], [3, 4]])

Standard IN

await db.table('users')
  .where('id', 'in', [1, 2, 3, 4, 5])
  .execute();

await db.table('orders')
  .where('status', 'in', ['pending', 'processing', 'shipped'])
  .execute();

Tuple IN (Multi-column)

await db.table('events')
  .where(['counter_id', 'user_id'], 'inTuple', [
    [34, 123],
    [101500, 456],
  ])
  .execute();

Subquery IN

await db.table('orders')
  .where('user_id', 'inSubquery', 'SELECT id FROM users WHERE status = "active"')
  .execute();

Table Reference IN

await db.table('events')
  .where('user_id', 'inTable', 'active_users')
  .execute();

Global IN (Distributed Tables)

await db.table('distributed_events')
  .where('user_id', 'globalIn', [1, 2, 3])
  .execute();

Conditional Where

Skip where clauses when values are null or undefined:

function findUsers(filters: { status?: string; minAge?: number }) {
  return db.table('users')
    .where(filters.status ? ['status', 'eq', filters.status] : null)
    .where(filters.minAge ? ['age', 'gte', filters.minAge] : null)
    .select(['id', 'name', 'email'])
    .execute();
}

// Only applies status filter
findUsers({ status: 'active' });

// Only applies age filter
findUsers({ minAge: 18 });

// Applies both filters
findUsers({ status: 'active', minAge: 18 });

Type Safety

TypeScript ensures operators match column types:

// ✅ Valid - number column with number comparison
await db.table('users')
  .where('age', 'gte', 18)
  .execute();

// ✅ Valid - string column with string comparison
await db.table('users')
  .where('status', 'eq', 'active')
  .execute();

// ✅ Valid - array membership
await db.table('users')
  .where('status', 'in', ['active', 'pending'])
  .execute();

// ❌ Error - type mismatch (TypeScript may catch this)
await db.table('users')
  .where('age', 'eq', 'not_a_number')
  .execute();

Examples

Date Range Filter

const events = await db.table('events')
  .where('created_at', 'gte', '2024-01-01')
  .where('created_at', 'lt', '2024-02-01')
  .select(['id', 'type', 'created_at'])
  .execute();

Complex Filter with OR

const products = await db.table('products')
  .where('category', 'eq', 'electronics')
  .orWhere((expr) =>
    expr.and([
      expr.fn('hasAny', 'tags', ['featured', 'new']),
      expr.fn('greater', expr.col('stock'), expr.literal(10)),
    ])
  )
  .execute();

Conditional Filtering

function searchUsers(query: string, filters: UserFilters) {
  return db.table('users')
    .where('name', 'like', \`%\${query}%\`)
    .where(filters.status ? ['status', 'eq', filters.status] : null)
    .where(filters.country ? ['country', 'eq', filters.country] : null)
    .where(filters.minAge ? ['age', 'gte', filters.minAge] : null)
    .execute();
}

Advanced Tuple IN

const results = await db.table('events')
  .where(['event_type', 'user_id'], 'inTuple', [
    ['page_view', 123],
    ['page_view', 456],
    ['click', 123],
  ])
  .execute();

On this page