【问题标题】:How to extract nested hashes into database table?如何将嵌套哈希提取到数据库表中?
【发布时间】:2014-01-20 12:14:40
【问题描述】:

我正在尝试从 Mailchimp 中提取一些以嵌套哈希返回的数据。这是我得到的结果的精简版。对于每封电子邮件,有多个 GROUPINGS,对于每个 GROUPING,有多个 GROUPS。

我的目标是将其放入一个 mysql 表中,其布局如下:email_addr、list、grouping1_id、grouping1_name、group1_name、group1_interest、group2_name、group2_interest、grouping2_id、grouping2_name 等。所以每个订阅者只有一行,所有分组和分组信息。

{"email"=>"dummy@gmail.com", "merges"=>{"EMAIL"=>"dummy@gmail.com", 
"GROUPINGS"=>[{"id"=>1, "name"=>"Grouping One", "groups"=>[{"name"=>"Group One", 
"interested"=>false}, {"name"=>"Group", "interested"=>true}, 
{"name"=>"Group Three", "interested"=>true}]}, {"id"=>2, "name"=>"Grouping Two", 
"groups"=>[{"name"=>"Group Four", "interested"=>false}, 
{"name"=>"Group Five", "interested"=>false}]}]}}

现在,我在下面的代码运行并将嵌套块的结果插入到表中,但每次通过 groups.each_with_index 语句都有一行。到目前为止,我的方法似乎过于复杂,但我不确定如何正确处理数据。

感谢任何帮助。

更新: 我稍微清理了逻辑并将数据库写入分离到哈希处理的每个级别。现在数据已正确插入和更新到数据库中。虽然这样还是觉得很不雅。

def organize_members_subs
  @members_data = @members_subs["data"]
  @members_data.each do |member|
    @email_addr = member["email"]
    @db.query("INSERT INTO db.details
        (email_addr, list)
        VALUES  ('#{@email_addr}', '#{@list}' ) ")

    groupings = member["merges"]["GROUPINGS"]
    groupings.each_with_index do |grouping, index|
      @groupings_name = grouping["name"]
      @groupings_id = grouping["id"]
      @groupings_label = "grp#{index}_"
      @db.query("UPDATE db.details
        SET grouping#{index}_id = '#{@groupings_id}'
          , grouping#{index}_name = '#{@groupings_name}'
        WHERE email_addr = '#{@email_addr}' ")

      groups = member["merges"]["GROUPINGS"][index]["groups"] 
      groups.each_with_index do |group, index|
        @group_name = group["name"]
        @group_interested = group["interested"]
        @db.query("UPDATE db.details
        SET #{@groupings_label}group#{index}_name = '#{@group_name}'
          , #{@groupings_label}group#{index}_int = '#{@group_interested}'
        WHERE email_addr = '#{@email_addr}' ")

        break if index == groups.length
      end
      break if index == groupings.length
    end
  end
end

【问题讨论】:

  • 首先,让@groupingsXname@groupingsX_id 成为一个哈希数组怎么样,这样您就可以用@groupings[index] = {"name" => grouping["name"], "id" => grouping["id"]} 替换这六个分配,或者只是让grouping 成为一个哈希数组?另外,考虑使用:name:id,而不是"name""id" 作为键。最后,将{"email"=>"email",... 放在多行中会很有帮助,并带有行继续字符\,因此读者不必水平滚动即可阅读该行。
  • @CarySwoveland 感谢您的建议。我做了一些更改,现在确实将数据正确写入数据库。不过,它似乎仍然可以改进。你有什么建议?如果您添加答案,我会接受。
  • 你能澄清一下,第一个{"email" => "email"...实际上是指一个电子邮件地址吗?所以在实际使用中会是{"email" => "andy@example.com"....?我知道你已经缩减了这个例子,但是如果这是实际发生的事情,你可以使用虚拟电子邮件地址吗?我不熟悉 Mailchimp。
  • @Beartech 是的,你是对的。我的占位符选择不佳。我在示例中添加了一个虚拟电子邮件地址。
  • 所以{"email"=>"dummy@gmail.com", 可以被剥离以移除外部哈希?列是恒定的吗?即是否会有可变数量的分组?以及分组内的可变数量的组?另外,有没有机会为此使用 Rails?

标签: mysql ruby mailchimp


【解决方案1】:

首先,我想仔细看看你的哈希值。我没有自己重新格式化,而是这样做了:

require "awesome_print"
h = `{"email"=>..., "interested"=>false}]}]}}`
ap h

向下滚动到我的答案底部以查看 ap 的哈希格式。

假设 db 结构是给定的,我将回答您的问题,但想说明几点:

  • 如果"id" 对于每条grouping 记录都是唯一的,您能否将其设为密钥,而无需使用index
  • 如果"name" 对于每条grouping 记录都是唯一的,您能否同时省略"id"index
  • 如果"name" 对于每条group 记录都是唯一的(对于给定的grouping),您能否只为每个组设置group["name"] => group["interested"]

继续您的代码,我还将假设您的哈希结构已给出。稍后,我将重新审视这个假设。

我对您的代码提出的更改相当小,有些纯粹是风格上的:

  • 使所有实例变量成为局部变量,这意味着必须将两个附加参数传递给def organize_members_subs
  • 除了两个可能的例外,消除定义后只使用一次的局部变量。例如,而不是 groupings_id = grouping["id"],然后是SET grouping#{index}_id = '#{@groupings_id}',只有SET grouping#{index}_id = '#{grouping["id"]}'。 两个可能的例外是groupingsgroups。例如,您可以通过编写来摆脱前者 member["merges"]["GROUPINGS"].each_with_index do |grouping, index_1|。我会将它们保留为变量(这样我就可以轻松检查它们的值), 但这是一个风格决定。
  • groupings.each_with_index do |grouping, index| 中的变量index 在内部块的范围内,它使用同名的迭代器变量。 我认为后者优先,但它们应该以不同的方式命名。我已将它们分别更改为 index_outindex_in
  • index_out 的范围从 0groupings.length-1,因此 break if index_out == groupings.length 永远不会被执行,因此可能会被删除。 break if index_in == groups.length 同上。
  • 我将groupings_label = "grp#{index}_" 向下移动以提醒人们注意这一事实,即仅在以后需要它,而不是在前面的SET 表达式中。

这些更改导致以下结果:

    def organize_members_subs(db, list, @members_subs["data"])
      members_data.each do |member|
        email_addr = member["email"]
        db.query("INSERT INTO db.details
            (email_addr, list)
            VALUES  ('#{email_addr}', '#{list}' ) ")
        groupings = member["merges"]["GROUPINGS"]
        groupings.each_with_index do |grouping, index_out|
          db.query("UPDATE db.details
          SET grouping#{index_out}_id   = '#{grouping["id"]}'
            , grouping#{index_out}_name = '#{grouping["name"]}'
          WHERE email_addr = '#{email_addr}' ")
          groupings_label = "grp#{index_out}_"

          groups = member["merges"]["GROUPINGS"][index_out]["groups"] 
          groups.each_with_index do |group, index_in|
            db.query("UPDATE db.details
            SET #{groupings_label}group#{index_in}_name = '#{group["name"]}'
              , #{groupings_label}group#{index_in}_int = '#{group["interested"]}'
            WHERE email_addr = '#{email_addr}' ")   
          end
        end
      end
    end

查看您的哈希,我想知道您是否可以将其简化为以下内容(格式由出色的打印提供):

  {
      "email" => "dummy@gmail.com",
     "merges" => {
             "EMAIL" => "dummy@gmail.com",
         "GROUPINGS" => {
             1 => {
                   "name" => "Grouping One",
                 "groups" => {
                       "Group One" => false,
                       "Group Two" => true,
                     "Group Three" => true
                 }
             },
             2 => {
                   "name" => "Grouping Two",
                 "groups" => {
                     "Group Four" => false,
                     "Group Five" => false
                 }
             }
         }
     }
  }

甚至

{
     "email" => "dummy@gmail.com",
    "merges" => {
            "EMAIL" => "dummy@gmail.com",
        "GROUPINGS" => {
            "Grouping One" => {
                  "Group One" => false,
                  "Group Two" => true,
                "Group Three" => true
            },
            "Grouping Two" => {
                "Group Four" => false,
                "Group Five" => false
            }
        }
    }
}

这些不是建议,而是值得深思的食物。

对您的哈希应用了很棒的打印:

ap h # =>
    {
         "email" => "dummy@gmail.com",
        "merges" => {
                "EMAIL" => "dummy@gmail.com",
            "GROUPINGS" => [
                [0] {
                        "id" => 1,
                      "name" => "Grouping One",
                    "groups" => [
                        [0] {
                                  "name" => "Group One",
                            "interested" => false
                        },
                        [1] {
                                  "name" => "Group",
                            "interested" => true
                        },
                        [2] {
                                  "name" => "Group Three",
                            "interested" => true
                        }
                    ]
                },
                [1] {
                        "id" => 2,
                      "name" => "Grouping Two",
                    "groups" => [
                        [0] {
                                  "name" => "Group Four",
                            "interested" => false
                        },
                        [1] {
                                  "name" => "Group Five",
                            "interested" => false
                        }
                    ]
                }
            ]
        }
    }

【讨论】:

    【解决方案2】:

    首先,也许是额外的,但我喜欢使用符号,因为我在 Rails 中做了很多工作。所以让我们从这里偷一个方法:How do I convert a Ruby hash so that all of its keys are symbols?

    def recursive_symbolize_keys(h)
      case h
        when Hash
          Hash[
              h.map do |k, v|
                [ k.respond_to?(:to_sym) ? k.to_sym : k, recursive_symbolize_keys(v) ]
              end
          ]
        when Enumerable
          h.map { |v| recursive_symbolize_keys(v) }
        else
          h
      end
    end
    

    好的,让我们构建一个类,以便随着我们的需求变化而更容易操作和扩展:

    class MemberSub
      attr_accessor :email, :groupings, :data_hash, :list, :data_row, :db_sql
    
      def initialize(data_hash)
    
        #convert all keys to symbols
        @data_hash = recursive_symbolize_keys(data_hash)
        @email = @data_hash[:email]
        @list = 'Members'
        @groupings = @data_hash[:merges][:GROUPINGS]
        @data_row  = data_row
        @db_sql = db_insert
      end
    
      def data_row
        #returns a data row for DB
        row_hash = {}
        row_hash['email'] = @email
        row_hash['list'] = @list
        gc = 1
        #iterate through groupings
        @groupings.each_with_index do |grouping, index|
          row_hash["grouping#{index + 1}_id"] = grouping[:id]
          row_hash["grouping#{index + 1}_name"] = grouping[:name]
          #iterate through the groups
          grouping[:groups].each do |group|
            row_hash["group#{gc}_name"] = group[:name]
            row_hash["group#{gc}_interest"] = group[:interested]
            gc += 1
          end
        end
        row_hash
      end
    
      def db_insert
        "INSERT INTO db.details (#{@data_row.keys}) VALUES (#{@data_row.values})".tr('[]','')
      end  
    
    end
    

    现在您可以使用任何迭代方法连续输入它并创建一个新对象:

    row = MemberSub.new({"email"=>"dummy@gmail.com", "list"=>"Members", "merges"=>  
    {"EMAIL"=>"dummy@gmail.com", "GROUPINGS"=>[{"id"=>1, "name"=>"Grouping One", "groups"=>  
    [{"name"=>"Group One", "interested"=>false}, {"name"=>"Group Two", "interested"=>true},  
    {"name"=>"Group Three", "interested"=>true}]}, {"id"=>2, "name"=>"Grouping Two", "groups"=>  
    [{"name"=>"Group Four", "interested"=>false}, {"name"=>"Group Five", "interested"=>false}]}]}})
    

    并进行查询:

    db.query(row.db_sql)
    
    db.query(INSERT INTO db.details ("email", "list", "grouping1_id", "grouping1_name", 
    "group1_name", "group1_interest", "group2_name", "group2_interest", "group3_name", 
    "group3_interest", "grouping2_id", "grouping2_name", "group4_name", "group4_interest",
     "group5_name", "group5_interest") VALUES ("dummy@gmail.com", "Members", 1, "Grouping One",
     "Group One", false, "Group Two", true, "Group Three", true, 2, "Grouping Two", "Group Four",
     false, "Group Five", false))
    

    其他方法应该是不言自明的。您不必将它们都作为attar_accessor 提供,但我只是这样做了。

    【讨论】:

    • 如果你想省略 recursive_symbolize_keys 方法,只需将其余代码中的符号替换为带引号的字符串即可。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-04-21
    • 2020-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-04
    相关资源
    最近更新 更多