library(ducklake)
library(dplyr)
attach_ducklake("views_lake", lake_path = vignette_temp_dir)
with_transaction(
create_table(mtcars, "cars"),
author = "Data Engineer",
commit_message = "Add the Motor Trend car data"
)Tables hold data. A lake also holds three kinds of things about the data: views, which store a query; comments, which document a table or a column; and variable labels, which the lake stores as comments. All three live in the catalog, so they travel with the lake and every client of it can read them.
Views
A view is a stored query. Reading it runs the query against the
tables as they are now, so a view is never stale.
create_view() takes a dplyr pipeline built on lake tables
and stores its SQL. Like any change to the lake, it can be committed
with an author and a message:
with_transaction(
get_ducklake_table("cars") |>
filter(mpg > 25) |>
select(mpg, cyl, wt) |>
create_view("v_efficient_cars"),
author = "Data Engineer",
commit_message = "Define the efficient cars everyone reports on"
)
#> Created view "v_efficient_cars".
#> Committed snapshot 2 (Data Engineer): Define the efficient cars everyone
#> reports on
get_ducklake_table("v_efficient_cars") |> collect()
#> # A tibble: 6 × 3
#> mpg cyl wt
#> <dbl> <dbl> <dbl>
#> 1 32.4 4 2.2
#> 2 30.4 4 1.62
#> 3 33.9 4 1.84
#> 4 27.3 4 1.94
#> 5 26 4 2.14
#> 6 30.4 4 1.51get_ducklake_table() reads views and tables alike, and
further verbs pipe onto a view as they would onto a table:
get_ducklake_table("v_efficient_cars") |>
count(cyl)
#> # A query: ?? x 2
#> # Database: DuckDB 1.5.5 [unknown@Linux 6.17.0-1022-azure:R 4.6.1//tmp/Rtmpgs6jUU/ducklake/ducklake2e8e60917960.duckdb]
#> cyl n
#> <dbl> <dbl>
#> 1 4 6Views are versioned. Creating, replacing, or dropping one is a snapshot in the lake’s history, like any change to a table:
list_table_snapshots("v_efficient_cars")
#> snapshot_id snapshot_time schema_version
#> 1 2 2026-09-17 21:44:21 2
#> changes author
#> 1 views_created, main.v_efficient_cars Data Engineer
#> commit_message commit_extra_info
#> 1 Define the efficient cars everyone reports on <NA>When a view is the right tool
Reach for a view when the logic belongs to the lake rather than to
you: a team’s shared definition of an analysis population, a filter
every report must apply, or a derived layer that is cheap enough to
compute on read and so need not be stored. Every client sees the same
definition, whether it connects from R, Python, or plain SQL, and a
change to the definition is recorded in the snapshot history. A
validation rule is logic of this kind:
vignette("data-checks") stores each rule as a view of the
rows that break it.
Two cases call for something else. An exploratory pipeline that only
you use is better kept in your R script under version control, where it
can change freely without adding snapshots to a shared lake. And a
result that should be materialized, because it is expensive to compute
or because downstream users need a fixed dataset, belongs in a table:
create_table() or replace_table() on the same
pipeline.
Drop a view when its logic is no longer needed:
drop_view("v_efficient_cars")
#> Dropped view "v_efficient_cars".For logic a view cannot hold, such as a parameterized SQL macro,
DuckDB SQL is the escape hatch:
DBI::dbExecute(get_ducklake_connection(), "CREATE MACRO ...").
Table and column comments
Comments live in the catalog, so the documentation travels with the data instead of in a sidecar file:
set_table_comment("cars", "Motor Trend road tests of 1973-74 models")
#> Commented table "cars".
set_column_comments(
"cars",
mpg = "Miles per US gallon",
wt = "Weight (1000 lbs)"
)
#> Commented 2 columns on "cars".
get_table_comments("cars")
#> object_type schema_name table_name column_name
#> 1 column main cars mpg
#> 2 column main cars wt
#> 3 table main cars <NA>
#> comment
#> 1 Miles per US gallon
#> 2 Weight (1000 lbs)
#> 3 Motor Trend road tests of 1973-74 modelsVariable labels
If your data carries variable labels, they survive the lake.
create_table() stores each column’s label
attribute as a column comment, and collect() puts the
attribute back, so label-aware tools such as gtsummary and gt behave as
if the data never left R. Data imported with haven from SAS, SPSS, or
Stata arrives with these labels already. For a data frame built in R,
the labelled package sets them:
library(labelled)
df_visits <- data.frame(subject = c("S1", "S2"), sbp = c(128, 141)) |>
set_variable_labels(
subject = "Subject identifier",
sbp = "Systolic blood pressure (mmHg)"
)
with_transaction(
create_table(df_visits, "visits"),
author = "Data Engineer",
commit_message = "Add the visit measurements"
)
#> Stored 2 column labels as column comments.
#> Committed snapshot 6 (Data Engineer): Add the visit measurements
df_collected <- get_ducklake_table("visits") |> collect()
var_label(df_collected)
#> $subject
#> [1] "Subject identifier"
#>
#> $sbp
#> [1] "Systolic blood pressure (mmHg)"The same round trip works without labelled: a label is the
label attribute of a column,
attr(df_visits$sbp, "label") <- "..." sets it, and
get_table_comments("visits") shows what was stored. Labels
also follow columns through pipelines. A table derived with
create_table() from a lake table keeps the comments of the
columns it selects, so a silver or gold layer stays labelled without any
extra step.
detach_ducklake("views_lake")