【问题标题】:Reshape data long to wide in MySQL在 MySQL 中从长到宽重塑数据
【发布时间】:2021-04-04 05:48:06
【问题描述】:

我有一张桌子,我想从长到宽旋转/重塑。

The long format has 3 columns: 
  zipcode, category, zipnum
  '12345', 'A', '1'
  '40348', 'A', '2'
  '16132', 'B', '1'
  '09428', 'B', '2'
  '14818', 'B', '3'
  '93182', 'C', '1'

zipnum 列是每个邮政编码的行号,按类别分组。因此 zipnum 对于一个类别中的每个邮政编码都是唯一的。我试图实现的宽格式具有列类别、zipcode1、...、zipcodeN,其中 N 是整个表中 zipnum 的最大值。在这个例子中 N=3,但在我的实际数据集中 N 大约是 2000。

   category, zipcode1, zipcode2, zipcode3
   'A', '12345', '40348', NULL
   'B', '16132', '09428', '14818'
   'C', '93182', NULL, NULL

我对 SQL 比较陌生 - 我知道如何在 Python、R 等语言中非常轻松地做到这一点,但找不到使用 MySQL 的方法。老实说,我什至不知道从哪里开始。

【问题讨论】:

  • 您知道每个类别的最大邮政编码数量吗?
  • 我将不得不这样做几次,但在某些情况下它可能高达 ~2000。通常最大值将不低于 1000。
  • 认真考虑处理应用代码中数据显示的问题
  • 当你得到一个解决方案时,删除问题就像在嘲笑回答它的人一样是非常糟糕的

标签: mysql sql pivot reshape


【解决方案1】:

我建议您将它们组合成一个逗号分隔的字符串:

select category,
       group_concat(zip_code order by zipnum) as zip_codes
from t
group by category;

因为您的字符串会很长,您需要将group_concat_max_len 设置为大于其默认值的值。

为什么不希望这些列?嗯,MySQL 有一个表中 4,096 列的硬性限制,我认为这个限制也适用于结果集。 The limit, though, is further complicated by the storage engine and other settings.。有足够的复杂性,我只是不想动态创建可能导致数千列的查询。

您可能会发现 JSON 结构比“单纯”的字符串更方便。 MySQL 也支持。

【讨论】:

  • 谢谢,这真的很有帮助。我同意这可能是一个更好的方法。一个后续问题 - 我将如何倒退? IE。获取单个逗号分隔的字符串并重新整形为长格式。我也需要这样做
  • @linkspan 。 . .这在 MySQL 中很尴尬,但您可以使用 substring_index() 来提取特定元素。
【解决方案2】:

我认为这是使用 SQL 不容易做到的事情。例如,在 PostgreSQL 中有一个单独的库来执行此操作(请参阅here)。

但是dbplyr 在这里可能会有所帮助,无论是直接使用还是间接使用来生成可以在该包之外使用的 SQL。我没有 MySQL,所以我在 PostgreSQL 和 SQLite 上测试了以下内容。我认为它也应该与RMySQL 一起使用:

library(tibble)
library(tidyr)
library(DBI)
library(dplyr, warn.conflicts = FALSE)

df <- tibble::tribble(
    ~zipcode, ~category, ~zipnum,
'12345', 'A', '1',
'40348', 'A', '2',
'16132', 'B', '1',
'09428', 'B', '2',
'14818', 'B', '3',
'93182', 'C', '1')

pg <- dbConnect(RPostgres::Postgres())

df_pg <- copy_to(pg, df, overwrite = TRUE)

df_pg %>%
    pivot_wider(names_from = "zipnum", values_from = "zipcode",
                names_prefix = "zipcode")
#> # Source:   lazy query [?? x 4]
#> # Database: postgres [iangow@/tmp:5432/crsp]
#>   category zipcode3 zipcode2 zipcode1
#>   <chr>    <chr>    <chr>    <chr>   
#> 1 A        <NA>     40348    12345   
#> 2 C        <NA>     <NA>     93182   
#> 3 B        14818    09428    16132

df_pg %>%
    pivot_wider(names_from = "zipnum", values_from = "zipcode",
                names_prefix = "zipcode") %>%
    show_query()
#> <SQL>
#> SELECT "category", MAX(CASE WHEN ("zipnum" = '3') THEN ("zipcode") END) AS "zipcode3", MAX(CASE WHEN ("zipnum" = '2') THEN ("zipcode") END) AS "zipcode2", MAX(CASE WHEN ("zipnum" = '1') THEN ("zipcode") END) AS "zipcode1"
#> FROM "df"
#> GROUP BY "category"

df_sqlite <- dbplyr::memdb_frame(df)

df_sqlite %>%
    pivot_wider(names_from = "zipnum", values_from = "zipcode",
                names_prefix = "zipcode") 
#> # Source:   lazy query [?? x 4]
#> # Database: sqlite 3.35.2 [:memory:]
#>   category zipcode1 zipcode2 zipcode3
#>   <chr>    <chr>    <chr>    <chr>   
#> 1 A        12345    40348    <NA>    
#> 2 B        16132    09428    14818   
#> 3 C        93182    <NA>     <NA>

df_sqlite %>%
    pivot_wider(names_from = "zipnum", values_from = "zipcode",
                names_prefix = "zipcode") %>%
    show_query()
#> <SQL>
#> SELECT `category`, MAX(CASE WHEN (`zipnum` = '1') THEN (`zipcode`) END) AS `zipcode1`, MAX(CASE WHEN (`zipnum` = '2') THEN (`zipcode`) END) AS `zipcode2`, MAX(CASE WHEN (`zipnum` = '3') THEN (`zipcode`) END) AS `zipcode3`
#> FROM `dbplyr_001`
#> GROUP BY `category`

reprex package (v1.0.0) 于 2021-04-04 创建

【讨论】:

    猜你喜欢
    • 2011-01-16
    • 1970-01-01
    • 2017-11-04
    • 2012-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多