--- title: "Bring Fabric data into R" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Bring Fabric data into R} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>", eval = FALSE) ``` Microsoft Fabric stores and serves data through Lakehouses, Warehouses, Eventhouses, semantic models, files, and APIs. 'fabricQueryR' helps you bring that data into your local R session for analysis, visualization, and reporting. For most everyday tasks, the result is a tibble (a modern R data frame). The best method depends on where the data lives and whether you need a whole table or a filtered result. Start with a small read and let Fabric filter the data when possible. Move to Arrow streaming or Spark only when the data is too large for local memory or the transformation needs distributed computing. This guide compares the common methods in that order. ## Choose a reading method | Your source or goal | Start with | Why | |---|---|---| | Warehouse, SQL Database, or a SQL-shaped Lakehouse query | `item$sql_query()` (`fabric_sql_query()`) | Filter and summarize on the server with familiar SQL | | One Lakehouse Delta table | `lakehouse$read_table()` (`fabric_lakehouse_read_table()`) | Read a table without writing SQL | | One Warehouse table | `warehouse$read_table()` (`fabric_warehouse_read_table()`) | Read a table by name without writing SQL | | Eventhouse table or KQL result | `kql_database$read_table()` (`fabric_kql_read_table()`) or `$query()` (`fabric_kql_query()`) | Use the Eventhouse query engine | | Power BI/Fabric semantic model | `model$dax_query()` (`fabric_pbi_dax_query()`) | Use model relationships and measures | | CSV, Parquet, or Arrow file in OneLake | `lakehouse$onelake_read_file()` (`fabric_onelake_read_file()`) | Read the file itself | | API for GraphQL | `api$query()` (`fabric_graphql_query()`) | Request the fields exposed by the API | | A transformation that genuinely needs Spark | `lakehouse$livy_query()` (`fabric_livy_query()`) | Run distributed code in Fabric | SQL is a good default for relational data because Fabric does the filtering and R receives only the rows you need. A direct table reader is simpler when you want one table and do not need joins or grouping. ## Discover the data source once Discovery returns read-only R6 objects. They carry the IDs, connection details, and credential needed by their read methods. Read service fields such as `$displayName` and `$id` directly: ```{r, eval = FALSE} library(fabricQueryR) workspaces <- fabric_workspaces() matches <- Filter( \(x) identical(x$displayName, "Analytics workspace"), workspaces ) stopifnot(length(matches) == 1L) workspace <- matches[[1L]] lakehouse <- workspace$lakehouses()[[1L]] warehouse <- workspace$warehouses()[[1L]] kql_database <- workspace$kql_databases()[[1L]] model <- workspace$semantic_models()[[1L]] ``` The workspace methods above correspond to `fabric_lakehouses()`, `fabric_warehouses()`, `fabric_kql_databases()`, and `fabric_semantic_models()`. Your workspace does not need to contain every item type. Discover only the source used by your workflow. ## Read with SQL Use an item's `$sql_query()` method (`fabric_sql_query()`) for one read-only `SELECT` query. It opens and closes the connection for you and returns a tibble: ```{r, eval = FALSE} recent_orders <- warehouse$sql_query( sql = paste( "SELECT TOP 100 order_id, order_date, amount", "FROM dbo.orders", "WHERE order_date >= ?", "ORDER BY order_date DESC" ), params = list(as.Date("2026-01-01")) ) head(recent_orders) ``` The default `numeric_policy = "auto"` uses ODBC's numeric conversion and warns once per R session that precision may be lost. Use `backend = "adbc"` for exact conversion, or `numeric_policy = "exact"` to reject ODBC results containing INT, BIGINT, DECIMAL, or NUMERIC columns before fetching. Casting these columns to `varchar` in SQL also preserves their values. Explicit `numeric_policy = "driver"` accepts driver conversion without the warning. The ADBC backend needs the optional 'adbi' and 'adbcdrivermanager' packages, plus the Microsoft SQL Server ADBC driver. 'adbi' is available from the R-DBI repository while it is archived on CRAN: ```r install.packages( c("adbi", "adbcdrivermanager"), repos = c("https://r-dbi.r-universe.dev", "https://cloud.r-project.org") ) ``` Install the SQL Server driver separately with `dbc install mssql`. See `?fabric_sql_connect` for driver selection and connection options. Put changing values in `params` rather than pasting them into the SQL text. This handles quoting safely. Use `$sql_connect()` (`fabric_sql_connect()`) and normal 'DBI' functions when several queries should share one connection: ```{r, eval = FALSE} orders <- local({ con <- warehouse$sql_connect() on.exit(DBI::dbDisconnect(con), add = TRUE) DBI::dbListTables(con) DBI::dbGetQuery(con, "SELECT TOP 100 * FROM dbo.orders") }) ``` Direct 'DBI' reads use the driver's numeric conversion. The default SQL backend uses Microsoft ODBC Driver 18 for SQL Server. See `?fabric_sql_connect` for the ODBC and ADBC setup choices. ## Read one table without writing a query Lakehouse and Warehouse table readers accept a table name and can limit the data before it enters R. The `$read_table()` methods call `fabric_lakehouse_read_table()` and `fabric_warehouse_read_table()`, respectively: ```{r, eval = FALSE} lakehouse_rows <- lakehouse$read_table( table = "orders", columns = c("order_id", "order_date", "amount"), limit = 100L ) warehouse_rows <- warehouse$read_table( table = "orders", schema = "dbo", limit = 100L ) ``` Warehouse reads use the same `numeric_policy = "auto"` default as SQL queries: driver conversion for ODBC, or exact conversion for ADBC. The ODBC precision warning is shared by queries and table reads and appears only once per session. Use `numeric_policy = "exact"` to reject unsafe ODBC results, or `numeric_policy = "driver"` to explicitly accept conversion without a warning. Use `lakehouse$tables()` (`fabric_lakehouse_tables()`) when you are unsure which Lakehouse tables or schemas are available. Direct Lakehouse reads use Python's 'deltalake' and 'nanoarrow' through 'reticulate'. **To enable automatic installation, restart R first**, then explicitly select a managed environment before loading the package: ```{r, eval = FALSE} Sys.setenv(RETICULATE_PYTHON = "managed") library(fabricQueryR) fabric_delta_config(initialize = TRUE) ``` `"managed"` tells 'reticulate' to create or reuse an environment that satisfies the package's requirements. Loading 'fabricQueryR' declares them with `reticulate::py_require()`, and initialization uses 'uv' to download Python and the dependencies if needed. 'reticulate' also downloads 'uv' if needed. No separate `py_install()` call is necessary. Both entries in `available` should then be `TRUE`, with installed package versions in `versions`. Without `RETICULATE_PYTHON = "managed"`, an existing Python selected through RStudio, environment variables, `use_python()`, or a project virtualenv can take precedence. Declaring requirements does not install them into that interpreter or upgrade it; Python 3.9, for example, cannot satisfy this backend's minimum of Python 3.10. Once Python starts, restart R before changing its selection. `reticulate::py_config()` reports which interpreter was chosen and why. To enable managed setup for future sessions in this project, add `RETICULATE_PYTHON=managed` to the project's `.Renviron` file and restart R. Use `fabric_delta_config()` to inspect requirements without starting Python. For a reusable environment that you manage yourself, create it with `reticulate::virtualenv_create()` from a compatible installed Python, or with `uv venv --python 3.11 --seed` at a chosen path. Then install the dependencies with `reticulate::py_install(..., envname = ..., method = "virtualenv")` and select that environment before Python starts. `py_install()` uses 'pip' in a virtualenv; it does not upgrade an environment's Python version. See `?fabric_delta_config` for complete examples of both setup routes, and `?fabric_onelake_read_delta_table` for supported table features, OneLake permissions, and exact numeric conversions. ## Read Eventhouse data An Eventhouse is optimized for event, log, and time-series data. Its query language is KQL. Read a whole table by name with `$read_table()` (`fabric_kql_read_table()`): ```{r, eval = FALSE} events <- kql_database$read_table( table = "Events", limit = 100L ) ``` Use `$query()` (`fabric_kql_query()`) when Fabric should filter or summarize the events first: ```{r, eval = FALSE} daily_events <- kql_database$query( query = paste( "Events", "| where observed_at >= ago(7d)", "| summarize event_count = count() by bin(observed_at, 1d)", "| order by observed_at asc" ) ) ``` ## Query a semantic model with DAX A semantic model is a dataset ready for reporting, commonly used in Power BI. Query it with `$dax_query()` (`fabric_pbi_dax_query()`) and DAX (Data Analysis Expressions): ```{r, eval = FALSE} sales_by_region <- model$dax_query( dax = paste( "EVALUATE", "SUMMARIZECOLUMNS(", " 'Region'[Region],", " \"Total Sales\", [Total Sales]", ")" ) ) ``` The [Semantic Models vignette](semantic-model-refresh.html) shows more things you can do with semantic models, like refreshing their data. ## Read a OneLake file Use the file reader when the file itself is the data contract. Paths in a Lakehouse usually begin with `Files/`. The `$onelake_read_file()` method calls `fabric_onelake_read_file()`: ```{r, eval = FALSE} orders_file <- lakehouse$onelake_read_file( path = "Files/exports/orders.parquet" ) ``` CSV, Parquet, and Arrow IPC files can become R or Arrow objects. Other file types can be downloaded as raw bytes or to disk. Use a Lakehouse table reader, not a file reader, for managed data below `Tables/`. ## Read through GraphQL or Spark GraphQL is useful when a Fabric API item exposes a purpose-built selection of fields: ```{r, eval = FALSE} api <- workspace$graphql_apis()[[1L]] response <- api$query( query = "{ products { items { id name category } } }" ) products <- response$data$products$items ``` Use [Working with GraphQL](graphql-schema-and-rows.html) for schema inspection and pagination. `$graphql_apis()` is the workspace method for `fabric_graphql_apis()`. Spark is the later choice for distributed transformations, Spark-only data formats, or logic already written for Spark: ```{r, eval = FALSE} result <- lakehouse$livy_query( kind = "sql", code = "SELECT category, count(*) AS n FROM orders GROUP BY category" ) counts <- result$output$parsed ``` Spark has startup cost. Prefer SQL or a direct reader for a small, ordinary table read. ## Scale up with Arrow streams If a selected result is larger than your computer's working memory, process it as a stream instead of collecting it all at once. Several readers accept `result = "arrow_stream"`. This can be used with the 'arrow' R package to read the data in batches. Here `$read_table()` calls `fabric_lakehouse_read_table()`: ```{r, eval = FALSE} row_count <- local({ stream <- lakehouse$read_table( table = "large_orders", result = "arrow_stream" ) on.exit(nanoarrow::nanoarrow_pointer_release(stream), add = TRUE) reader <- arrow::as_record_batch_reader(stream) on.exit(reader$Close(), add = TRUE, after = FALSE) count <- 0 repeat { batch <- reader$read_next_batch() if (is.null(batch)) break # Process or write this batch before reading the next one. count <- count + batch$num_rows } count }) ``` `reader$read_table()` collects every remaining batch into one Arrow Table; use it only when the full selected result fits in memory. Direct Delta reads stage the selected data to a temporary file before returning the stream, so allow enough local disk space even when processing batches. Close an Arrow reader after use. For disk-backed OneLake Delta streams, this also deletes the staged temporary file; when consuming one directly through 'nanoarrow', call `stream[["release"]]()` instead. See the ['arrow' R package](https://arrow.apache.org/docs/r/) for more information on working with Arrow streams and record batches.