So far, we've focused on exploring data, building dashboards, and making them interactive using filters, drill-downs, and scheduled reports. But behind every great dashboard is something less visible but just as important: a solid data model.

We covered what LookML is and how to write your first simple view. We built something that worked. But here's the thing: working and working well are two very different things. In this article, we're diving into views, one of the most important pieces of your LookML model. We'll learn how to design them so they're intuitive for users, performant for your database, and maintainable for future you.

Think of it like cooking. Anyone can follow a recipe and make spaghetti. But a great chef knows why you salt the pasta water, when to add the garlic so it doesn't burn, and how to make the sauce cling to the noodles just right.

In this series, we're going beyond "it works" and into "it works beautifully". We'll start by mastering the fundamentals of views, which is the foundation of your LookML model. If you get these essentials right, everything else becomes easier.

Let's dive in..

What is a Looker View, Really?

Before we get into details, let's ground ourselves in the basics.

A view in LookML is essentially a blueprint for a table (or logical dataset) in your database. It tells Looker: "Here's what this data looks like. Here are the columns. Here's how to calculate things from it."

Think of a view like a menu at a restaurant. The menu doesn't contain the actual food because it describes what's available, how it's prepared, and how much it costs. Similarly, view doesn't hold your data, it describes how Looker should interpret and present it to users.

None
Photo by Karen Z on Unsplash

Here's a basic view you might have seen before:

view: orders {
  sql_table_name: ecommerce.orders ;;

  dimension: order_id {
    type: string
    sql: ${TABLE}.order_id ;;
  }

  dimension: order_date {
    type: date
    sql: ${TABLE}.order_date ;;
  }

  measure: total_revenue {
    type: sum
    sql: ${TABLE}.order_total ;;
  }
}

This works. It gets the job done. Your users can query the data. But we can make it so much better. In this article, we'll cover the three essential components that every view should have:

  1. Primary keys (for accurate counting)
  2. Labels and descriptions (for user-friendliness)
  3. Dimension groups (for clean date handling)

Master these three, and you're already ahead of most LookML developers.

The Power of Primary Keys

Every view should have a primary key, which is a field that uniquely identifies each row.

Think of a primary key like a social security number or student ID. This is something that uniquely identifies a record and that cannot be duplicated.

Looker uses primary keys to:

  • Count records accurately when you use count measures
  • Join tables correctly when connecting multiple views together
  • Avoid double counting when multiple tables are involved in a query

Without a primary key Looker might count the same order twice, inflate your revenue numbers, or produce inaccurate results when you join tables.

Here's how you mark a dimension as a primary key:

dimension: order_id {
  primary_key: yes
  type: string
  sql: ${TABLE}.order_id ;;
}

The one line (primary_key: yes) tells Looker: "This is the unique identifier for this table.". It's a small detail that prevents big problems down the road.

When You Don't Have a Natural Primary Key

Sometimes you're working with log tables or event data where there isn't a single column that's unique. In that case, you can create a composite key by containing multiple fields:

dimension: primary_key {
  primary_key: yes
  hidden: yes
  sql: CONCAT(${TABLE}.user_id, '-', ${TABLE}.event_timestamp) ;;
}

This creates a unique identifier by coming user_id and event_timestamp. We mark it hidden: yes because users don't need to see this technical field.

How to Verify Uniqueness

Not sure if a field is actually unique? Run this quick SQL check in your database:

SELECT order_id, COUNT(*)
FROM ecommerce.orders
GROUP BY order_id
HAVING COUNT(*) > 1

If this query returns any rows you've got duplicates, which means order_id isn't a true primary key. The bottom line is that you always should define a primary key in every view. Always. Your future self (and your data analysts) will thank you.

Making Views User-Friendly

Here's something I see all the time: developers build views with technical field names like cust_id_fk or order_stat_cd and expect business users to figure it out. Don't do this to your users.

Remember that your views aren't just for you. They're for analysts, marketers, sales reps who don't think in SQL or database schemas. Your job is to translate technical database columns into language they understand.

Labels: Renaming Fields for Clarity

Labels are the "customer-facing" names for your fields. The field might be called order_id in your code, but your users see something more intuitive.

view: orders {
label: "Customer Orders"

  dimension: order_id {
    primary_key: yes
    label: "Order Number"
    type: string
    sql: ${TABLE}.order_id ;;
  }
  dimension: order_status {
    label: "Order Status"
    type: string
    sql: ${TABLE}.status ;;
  }
  dimension: order_total {
    label: "Order Total"
    type: number
    sql: ${TABLE}.order_total ;;
    value_format_name: usd
  }
}

Now when your users open the Explore, they don't see cryptic field names. They will see clear, business-friendly labels like "Order Number", " Order Status", and "Order Total". It's a small change that makes a massive difference in user experience.

Descriptions: Adding Context Where It Matters

Descriptions are like helpful tooltips that appear when users hover over a field. Use them to explain what the field contains, what values are possible, or any important context.

dimension: order_status {
  label: "Order Status"
  description: "Current status: Pending, Processing, Shipped, Delivered, or Cancelled"
  type: string
  sql: ${TABLE}.status ;;
}
dimension: customer_segment {
  label: "Customer Segment"
  description: "Customer categorization based on lifetime value: VIP ($1000+), Premium ($500+), Standard ($100+), or New"
  type: string
  sql: ${TABLE}.customer_segment ;;
}
measure: total_revenue {
  label: "Total Revenue"
  description: "Sum of all order totals including tax and shipping"
  type: sum
  sql: ${TABLE}.order_total ;;
  value_format_name: usd
}

Now when someone hovers over "Order Status" they see: "Current status: Pending, Processing, Shipped, Delivered, or Cancelled". No guessing. No Slack messages at 4 PM asking "What does 'P' mean in the status field?"

Documentation That Lives Where It's Needed

Most data dictionaries are PDFs that sit in SharePoint and get outdated within a month. But when you build descriptions directly in your LookML, the documentation lives right where users need it, updates automatically when you change the code, and is impossible to ignore or miss.

Be generous with labels and descriptions. If you're not sure whether to add one, add it. Your users will appreciate the clarity.

Dimension Groups: Your Secret Weapon for Dates

Here's a common rookie mistake: creating separate dimensions for every possible date format.

# ❌ Don't do this
dimension: order_date {
  type: date
  sql: ${TABLE}.order_date ;;
}
dimension: order_week {
  type: date
  sql: DATE_TRUNC('week', ${TABLE}.order_date) ;;
}
dimension: order_month {
  type: date
  sql: DATE_TRUNC('month', ${TABLE}.order_date) ;;
}
dimension: order_quarter {
  type: string
  sql: CONCAT('Q', QUARTER(${TABLE}.order_date)) ;;
}
dimension: order_year {
  type: number
  sql: YEAR(${TABLE}.order_date) ;;
}

That is five separate fields cluttering up your field picker, and users still can't analyze by day of the week or hour of day. There's a better way.

One Definition, Multiple Timeframes

A dimension group is like a Swiss Army knife for dates because one definition automatically generates multiple timeframe options.

dimension_group: order {
  type: time
  timeframes: [date, week, month, quarter, year]
  sql: ${TABLE}.order_date ;;
}

That's it. Five lines of code instead of 25. Now, in the Looker UI, users automatically see:

  • Order Date
  • Order Week
  • Order Month
  • Order Quarter
  • Order Year

All from one definition. Clean, organized, and users can slice data by whatever timeframe makes sense for their analysis.

Going Beyond Basic Dates

If your table has timestamp (not just dates), you can add even more granularity:

dimension_group: order {
  type: time
  timeframes: [time, hour, date, day_of_week, week, month, quarter, year]
  sql: ${TABLE}.order_timestamp ;;
}

Now users also get Order Time (exact timestamp), Order Hour (the hour), and Order Day of Week (Monday, Tuesday, etc).

This is perfect for answering questions like:

  • What time of day do we get the most orders?
  • Which day of the week has the highest conversion rate?
  • Do we see more activity in the morning or afternoon?

Dimension groups keep your field picker clean while giving users maximum flexibility in how they analyze time-based data. Instead of a cluttered mess of seven separate date fields you get one clean, organized structure that expands when users need it.

Use dimension groups for every date or timestamp field in your views. There's no downside, and your users will love the flexibility.

The Three Essentials

These three fundamentals form the foundation of every well-designed view:

  • Primary keys ensure accurate counting and proper joins
  • Labels and descriptions translate technical field names into business-friendly language that users actually understand.
  • Dimension groups generate multiple timeframes from one definition, keeping your field picker clean and organised.

Master these three, and you're already building better views than most LookML developers.

Happy modelling!