Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Guide for adding SQL function translations to dbplyr backends. Use when implementing new database-specific R-to-SQL translations for functions like string manipulation, date/time, aggregates, or window functions.
.claude/skills/microck-sql-translation/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 71% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 26% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 2% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 23% | 0% |
| case-18 | ✗→✓ | ▲ Improved | -18% | 0% |
Use this skill when adding new SQL function translations for a specific database backend.
This skill guides you through adding SQL translations to dbplyr. SQL translations convert R functions to their SQL equivalents for different database backends.
Before implementing any SQL translation, you MUST research the SQL syntax and behavior using the sql-research skill. See that skill for the complete research workflow.
Quick summary:
research/{dialect}-{function}.mdSQL translations are defined in backend-specific files:
R/backend-sqlite.R - SQLiteR/backend-postgres.R - PostgreSQLR/backend-mysql.R - MySQLR/backend-mssql.R - MS SQL ServerTranslations are added to the sql_translation() method for the connection class. This method returns a sql_variant() with three components:
Scalar translations (for mutate/filter):
rsql_translator(.parent = base_scalar, # Simple function name mapping log10 = function(x) sql_expr(log(!!x)), # Function with different arguments round = function(x, digits = 0L) { digits <- as.integer(digits) sql_expr(round(((!!x)) %::% numeric, !!digits)) }, # Infix operators paste0 = sql_paste(""), # Complex logic grepl = function(pattern, x, ignore.case = FALSE) { if (ignore.case) { sql_expr(((!!x)) %~*% ((!!pattern))) } else { sql_expr(((!!x)) %~% ((!!pattern))) } } )
Aggregate translations (for summarise):
rsql_translator(.parent = base_agg, sd = sql_aggregate("STDEV", "sd"), median = sql_aggregate("MEDIAN"), quantile = sql_not_supported("quantile") )
Window translations (for mutate with groups):
rsql_translator(.parent = base_win, sd = win_aggregate("STDEV"), median = win_absent("median"), quantile = sql_not_supported("quantile") )
Common translation patterns:
sql_expr() - Build SQL expressions with !! for interpolationsql_cast(type) - Type casting (e.g., sql_cast("REAL"))sql_aggregate(sql_name, r_name) - Simple aggregatessql_paste(sep) - String concatenationsql_not_supported(name) - Mark unsupported functionswin_aggregate(sql_name) - Window aggregateswin_absent(name) - Window functions not supportedInteractive testing:
rRscript -e "devtools::load_all(); library(dplyr, warn.conflicts = FALSE); translate_sql(your_function(x), con = simulate_yourdb())"
Write tests:
R/{name}.R go in tests/testthat/test-{name}.RExample test:
rtest_that("backend_name translates function_name correctly", { lf <- lazy_frame(x = 1, con = simulate_backend()) expect_snapshot( lf |> mutate(y = your_function(x)) ) })
Update backend documentation:
@description section in the backend file (e.g., R/backend-postgres.R)@examples if helpfulExample:
r#' Backend: PostgreSQL #' #' @description #' See `vignette("translation-function")` and `vignette("translation-verb")` for #' details of overall translation technology. Key differences for this backend #' are: #' #' * Many stringr functions #' * lubridate date-time extraction functions #' * Your new translation
bash# Format code air format . # Run relevant tests Rscript -e "devtools::test(filter = 'backend-name', reporter = 'llm')" # Check documentation Rscript -e "devtools::document()"
Parent translators:
base_scalar - Common scalar functions (math, string, logical)base_agg - Common aggregates (sum, mean, min, max)base_win - Common window functionsSQL expression building:
sql_expr() to build SQL!! to interpolate R variables%as% for AS, %::% for ::, etc.Argument handling:
check_bool(), check_unsupported_arg()as.integer())See also:
vignette("translation-function") - Function translation overviewvignette("new-backend") - Creating new backendsBefore completing a SQL translation:
research/{dialect}-{function}.mdsql_translator() sectionair format .| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | fail→fail | 14,355 | 8,426 | -41% | 1 | 1 | 0% | 2,607 | 2,538 | -3% | 0 | 0 | — |
case-01 | fail→fail | 14,381 | 4,846 | -66% | 1 | 1 | 0% | 2,698 | 1,964 | -27% | 0 | 0 | — |
case-02 | fail→pass | 18,588 | 24,141 | +30% | 1 | 1 | 0% | 3,087 | 5,284 | +71% | 0 | 0 | — |
case-04 | pass→pass | 9,099 | 2,308 | -75% | 1 | 1 | 0% | 1,533 | 1,741 | +14% | 0 | 0 | — |
case-05 | pass→pass | 9,612 | 2,314 | -76% | 1 | 1 | 0% | 1,583 | 1,730 | +9% | 0 | 0 | — |
case-06 | pass→pass | 9,567 | 2,241 | -77% | 1 | 1 | 0% | 1,712 | 1,638 | -4% | 0 | 0 | — |
case-07 | pass→pass | 5,435 | 1,897 | -65% | 1 | 1 | 0% | 692 | 1,608 | +132% | 0 | 0 | — |
case-08 | pass→pass | 6,597 | 3,156 | -52% | 1 | 1 | 0% | 1,128 | 1,908 | +69% | 0 | 0 | — |
case-09 | fail→pass | 10,242 | 4,477 | -56% | 1 | 1 | 0% | 1,794 | 2,258 | +26% | 0 | 0 | — |
case-10 | fail→pass | 11,810 | 2,871 | -76% | 1 | 1 | 0% | 1,812 | 1,855 | +2% | 0 | 0 | — |
case-11 | fail→pass | 9,717 | 3,756 | -61% | 1 | 1 | 0% | 1,609 | 1,975 | +23% | 0 | 0 | — |
case-12 | pass→pass | 6,867 | 3,734 | -46% | 1 | 1 | 0% | 1,177 | 1,785 | +52% | 0 | 0 | — |
case-18 | fail→pass | 15,759 | 3,285 | -79% | 1 | 1 | 0% | 2,345 | 1,929 | -18% | 0 | 0 | — |
case-13 | pass→pass | 7,846 | 1,760 | -78% | 1 | 1 | 0% | 1,349 | 1,657 | +23% | 0 | 0 | — |
case-14 | fail→pass | 9,905 | 2,077 | -79% | 1 | 1 | 0% | 1,804 | 1,715 | -5% | 0 | 0 | — |
case-15 | pass→pass | 5,835 | 2,655 | -54% | 1 | 1 | 0% | 1,012 | 1,795 | +77% | 0 | 0 | — |
case-16 | pass→pass | 10,440 | 5,079 | -51% | 1 | 1 | 0% | 1,796 | 2,363 | +32% | 0 | 0 | — |
case-17 | pass→pass | 5,271 | 1,897 | -64% | 1 | 1 | 0% | 889 | 1,613 | +81% | 0 | 0 | — |
case-19 | fail→pass | 5,182 | 1,744 | -66% | 1 | 1 | 0% | 872 | 1,611 | +85% | 0 | 0 | — |
case-20 | fail→pass | 9,645 | 1,767 | -82% | 1 | 1 | 0% | 1,691 | 1,674 | -1% | 0 | 0 | — |
case-21 | fail→pass | 3,942 | 1,838 | -53% | 1 | 1 | 0% | 649 | 1,635 | +152% | 0 | 0 | — |
case-22 | pass→pass | 12,061 | 10,733 | -11% | 1 | 1 | 0% | 2,272 | 3,264 | +44% | 0 | 0 | — |
case-23 | pass→pass | 7,037 | 4,844 | -31% | 1 | 1 | 0% | 1,347 | 2,213 | +64% | 0 | 0 | — |
case-24 | pass→pass | 10,684 | 5,372 | -50% | 1 | 1 | 0% | 1,911 | 2,411 | +26% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted. The headline lift of +38 percentage points is the difference between those two pass rates over the 24 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.