You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在R的reactable中实现数据库数据的迭代分页获取?

问题描述

我在R的Shiny应用中对接数据库,目标表数据量极大,为节省传输时间,希望结合分页控件及其他Shiny输入迭代拉取数据。最初尝试用reactable::getReactableState("table", "page")获取页码构造SQL查询,但遇到两个核心问题:

  • 仅传递当前页10条数据时,分页UI无法显示,只能通过注入NA行实现,这种方式并不理想;
  • 监听reactable::getReactableState("table", "page")的observeEvent()触发异常:用户点击第3页时虽能正确获取对应数据,但表格重渲染会触发页码重置为1,覆盖正确数据;若改用reactable::updateReactable()更新数据则会引发无限循环。

目前通过uiOutput()和renderUI()模拟reactable分页控件,但方法繁琐,希望找到更简洁的实现方式。

最初的示例代码:

library(bslib)
library(shiny)
library(reactable)
library(data.table)
library(RSQLite)

shiny::shinyApp(
    ui = bslib::page_navbar(
        bslib::nav_panel(
            title = "Reactable",
            bslib::card(
                reactable::reactableOutput("table")
            )
        )
    ),
    server = function(input, output, session) {
        # Connect to database
        con <- dbConnect(RSQLite::SQLite(), ":memory:")

        # Create table
        DBI::dbExecute(
            con,
            "CREATE TABLE TestTable(TestID INTEGER PRIMARY KEY AUTOINCREMENT, TestValue INTEGER)"
        )

        # Write toy data
        dt <- data.table(TestValue = runif(n = 50000, min = 1, max = 10) |> round())
        dbWriteTable(con, "TestTable", dt, append = TRUE, row.names = FALSE)

        # Function to fetch data for the current page
        fetch_data <- function(page, page_size, total_rows) {
            offset <- (page - 1) * page_size
            query <- paste0(
                "SELECT * FROM TestTable ORDER BY TestID LIMIT ", page_size, " OFFSET ", offset
            )
            y <- dbGetQuery(con, query) |> data.table::as.data.table()

            # toy example of injecting NA rows
            x <- 1:total_rows
            x <- data.table::data.table(
                TestID = x[!x %in% y$TestID]
            )
            y <- rbind(x, y, fill = TRUE)
            y <- y[order(TestID)]

            return(y)
        }

        # reactive values to hold data + pagination informatino
        vals <- shiny::reactiveValues(
            data = NULL,
            page_size = 10,
            page = 1,
            total_rows = NULL
        )

        # Fetch the total number of rows
        vals$total_rows <- dbGetQuery(con, "SELECT COUNT(*) AS Total FROM TestTable")$Total

        # initial data pull
        vals$data <- fetch_data(1, 10, dbGetQuery(con, "SELECT COUNT(*) AS Total FROM TestTable")$Total)

        # when the page changes, fetch new data
        shiny::observeEvent(
            reactable::getReactableState("table", "page"),
            {
                vals$page <- reactable::getReactableState("table", "page")
                vals$data <- fetch_data(vals$page, vals$page_size, vals$total_rows)
                reactable::updateReactable("table", data = vals$data)
                reactable::updateReactable("table", page = vals$page)
            }
        )

        # render
        output$table <- reactable::renderReactable({
            reactable::reactable(
                fetch_data(1, 10, dbGetQuery(con, "SELECT COUNT(*) AS Total FROM TestTable")$Total),
                sortable = FALSE,
                pagination = TRUE
            )
        })

        # Drop table on stop
        shiny::onStop(function() {
            dbExecute(con, "DROP TABLE TestTable")
            dbDisconnect(con)
        })
    }
)
解决方案

针对reactable结合数据库分页的问题,核心是让reactable知晓总数据量,同时避免页码重置和循环更新,以下是优化后的实现:

关键思路

  • 利用reactable的pagination参数指定totalRows,无需注入NA行即可显示完整分页UI;
  • 改用input$table_page获取页码状态(reactable会自动将表格页码绑定为input$<tableId>_page),替代getReactableState避免监听冲突;
  • 使用响应式数据源驱动表格渲染,更新数据时仅调用updateReactable刷新内容,不手动设置页码。

优化后代码

library(bslib)
library(shiny)
library(reactable)
library(data.table)
library(RSQLite)

shiny::shinyApp(
    ui = bslib::page_navbar(
        bslib::nav_panel(
            title = "Reactable",
            bslib::card(
                reactable::reactableOutput("table")
            )
        )
    ),
    server = function(input, output, session) {
        # 数据库连接
        con <- dbConnect(RSQLite::SQLite(), ":memory:")
        
        # 创建测试表并写入数据
        DBI::dbExecute(con, "CREATE TABLE TestTable(TestID INTEGER PRIMARY KEY AUTOINCREMENT, TestValue INTEGER)")
        dt <- data.table(TestValue = runif(n = 50000, min = 1, max = 10) |> round())
        dbWriteTable(con, "TestTable", dt, append = TRUE, row.names = FALSE)
        
        # 预查询总记录数(仅执行一次)
        total_rows <- dbGetQuery(con, "SELECT COUNT(*) AS Total FROM TestTable")$Total
        page_size <- 10
        
        # 响应式变量存储当前页数据
        current_data <- reactiveVal()
        
        # 初始加载第一页数据
        current_data(fetch_data(1, page_size))
        
        # 分页数据获取函数
        fetch_data <- function(page, page_size) {
            offset <- (page - 1) * page_size
            query <- paste0(
                "SELECT * FROM TestTable ORDER BY TestID LIMIT ", page_size, " OFFSET ", offset
            )
            dbGetQuery(con, query) |> data.table::as.data.table()
        }
        
        # 监听页码变化,更新当前页数据
        observeEvent(input$table_page, {
            current_page <- input$table_page
            current_data(fetch_data(current_page, page_size))
            # 仅更新表格数据,无需手动设置页码
            updateReactable("table", data = current_data())
        })
        
        # 渲染表格,指定totalRows让分页UI正常显示
        output$table <- reactable::renderReactable({
            reactable(
                current_data(),
                sortable = FALSE,
                pagination = list(
                    pageSize = page_size,
                    totalRows = total_rows
                )
            )
        })
        
        # 停止应用时清理数据库资源
        onStop(function() {
            dbExecute(con, "DROP TABLE TestTable")
            dbDisconnect(con)
        })
    }
)

代码说明

  • input$table_page自动获取页码:reactable会将表格的页码状态暴露为input$<tableId>_page,无需调用getReactableState,避免状态监听冲突;
  • pagination参数指定totalRows:直接告诉reactable总数据量,无需伪造全量数据结构,分页控件即可正确显示总页数和导航;
  • 响应式数据驱动渲染:初始加载第一页数据,页码变化时更新数据并刷新表格,不手动设置页码,避免页码重置问题;
  • 移除NA行注入:直接传递当前页真实数据,不再需要伪造空行填充结构。

内容的提问来源于stack exchange,提问作者sonshine

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 07:48:13