我有一个非常简单的请求:我想使用共享某些列名而不是所有列名的SQL“堆叠”或垂直组合表。
如果要在R中解决此问题,Hadley Wickham的dplyr软件包具有一个名为bind_rows()
的不错的函数,该函数按行绑定不同的表,并在其中一个表中不存在列时强制使用NA值。
作为一个例子,假设我们有表'A':
a <- head(iris) %>%
dplyr::mutate(., species_num = 1:nrow(.)) %>%
dplyr::select(., -Petal.Width)
表'B':
b <- tail(iris) %>%
dplyr::mutate(., species_num = 7:12)
请注意,表B包含额外的列Petal.Width
。
如上所述,dplyr软件包中的R函数bind_rows()
将执行以下操作:
nice <- dplyr::bind_rows(a, b)
对吧?
好吧,我想在SQL中执行相同的操作,但是当列号和/或名称不同时UNION
失败...
(SELECT *, FROM a)
UNION
(SELECT *, FROM b);
现在,我意识到我可以在使用Petal.Width
之前将a
列简单地添加到表UNION
中,但是我要解决的现实问题涉及30多个表,每个表都包含一些专栏,但不同程度的其他专栏...我的最终目标是使这一过程自动化。简而言之,我需要一个不需要我解决问题或手动将列添加到各个表的解决方案。
有什么想法吗?
答案 0 :(得分:2)
尝试一下:
带有一些虚假数据的预备:
# con <- dbConnect(...)
DBI::dbWriteTable(con, "iris1", iris[1:3,-1])
DBI::dbWriteTable(con, "iris2", iris[4:6,-2])
DBI::dbWriteTable(con, "iris23", iris[7:9,-(2:3)])
设置字段名称列表:
list_of_tables <- c("iris1", "iris2", "iris23")
eachnames <- sapply(list_of_tables, function(a) DBI::dbQuoteIdentifier(con, DBI::dbListFields(con, a)), simplify = FALSE)
str(eachnames)
# List of 3
# $ iris1 :Formal class 'SQL' [package "DBI"] with 1 slot
# .. ..@ .Data: chr [1:4] "\"Sepal.Width\"" "\"Petal.Length\"" "\"Petal.Width\"" "\"Species\""
# $ iris2 :Formal class 'SQL' [package "DBI"] with 1 slot
# .. ..@ .Data: chr [1:4] "\"Sepal.Length\"" "\"Petal.Length\"" "\"Petal.Width\"" "\"Species\""
# $ iris23:Formal class 'SQL' [package "DBI"] with 1 slot
# .. ..@ .Data: chr [1:3] "\"Sepal.Length\"" "\"Petal.Width\"" "\"Species\""
allnames <- unique(unlist(eachnames, use.names=FALSE))
allnames
# [1] "\"Sepal.Width\"" "\"Petal.Length\"" "\"Petal.Width\"" "\"Species\""
# [5] "\"Sepal.Length\""
我通常使用DBI::dbQuoteIdentifier
有点防御性,尽管由于列名而特别要求(我使用的是postgres,但它不喜欢字段名中的转义符)。
可以使用以下方法创建字段名称列表,并用null as
进行扩展:
list_of_fields <- lapply(eachnames, function(a) {
paste(ifelse(allnames %in% a, allnames, paste("null as", allnames)), collapse = ", ")
})
str(list_of_fields)
# List of 3
# $ iris1 : chr "\"Sepal.Width\", \"Petal.Length\", \"Petal.Width\", \"Species\", null as \"Sepal.Length\""
# $ iris2 : chr "null as \"Sepal.Width\", \"Petal.Length\", \"Petal.Width\", \"Species\", \"Sepal.Length\""
# $ iris23: chr "null as \"Sepal.Width\", null as \"Petal.Length\", \"Petal.Width\", \"Species\", \"Sepal.Length\""
如果您有更复杂的查询需求,那么这是一个好的开始。这是一个不进行其他过滤的查询:
qry <- paste(
mapply(function(nm, flds) {
paste("( select",
paste(ifelse(allnames %in% flds, allnames, paste("null as", allnames)),
collapse = ", "),
"from", nm, ")")
}, names(eachnames), eachnames),
collapse = " union\n")
cat(qry)
# ( select "Sepal.Width", "Petal.Length", "Petal.Width", "Species", null as "Sepal.Length" from iris1 ) union
# ( select null as "Sepal.Width", "Petal.Length", "Petal.Width", "Species", "Sepal.Length" from iris2 ) union
# ( select null as "Sepal.Width", null as "Petal.Length", "Petal.Width", "Species", "Sepal.Length" from iris23 )
DBI::dbGetQuery(con, qry)
# Sepal.Width Petal.Length Petal.Width Species Sepal.Length
# 1 NA 1.7 0.4 setosa 5.4
# 2 NA NA 0.3 setosa 4.6
# 3 NA 1.5 0.2 setosa 4.6
# 4 NA 1.4 0.2 setosa 5.0
# 5 3.0 1.4 0.2 setosa NA
# 6 3.2 1.3 0.2 setosa NA
# 7 NA NA 0.2 setosa 5.0
# 8 NA NA 0.2 setosa 4.4
# 9 3.5 1.4 0.2 setosa NA
许多DBA通常建议不要使用SELECT *
,因此这是次要的好处。
答案 1 :(得分:0)
我今天早些时候使用@ r2evans提供的答案,提出了以下内容,这些内容适用于我的实际情况(有关可重复性,请参阅@ r2evans的非常详细的答案)。我确定apply方法比循环更有效,但这是我的开始。
for(i in 1:nrow(myTables)) {
# Load drivers
drv <- RPostgreSQL::postgresqlInitDriver()
# Establish connection to the database
con <- DBI::dbConnect(drv, host=creds[1], port=creds[2], dbname=creds[3],
user=creds[4], password=creds[5])
# Return column names and variable class
tableColumns <- DBI::dbGetQuery(con, paste0("SELECT column_name, data_type ",
"FROM information_schema.columns ",
"WHERE table_name = '",
myTables[i,'table_name'], "' ",
"ORDER BY ordinal_position"))
# Close out the database connection
DBI::dbDisconnect(con)
# Determine missing columnms
colDiffs <- setdiff(desiredCols, tableColumns$column_name)
if(length(colDiffs) > 0) {
# Create SQL subqueries, complete with inclusion for missing columns
myTables[i,'subquery'] <- paste0('(SELECT *, ',
paste(paste0('NULL AS ', colDiffs, ', '),
collapse = ''), "'",
myTables[i,'table_name'], "' AS table ",
"FROM schema.", myTables[i,'table_name'],
")")
} else {
# Create SQL subqueries
myTables[i,'subquery'] <- paste0("(SELECT *, '",
myTables[i,'table_name'], "' AS table ",
"FROM schema.", myTables[i,'table_name'], ")")
}
}
# Create final query
finalQuery <- paste(myTables$subquery, collapse=" UNION ")