【问题标题】:How to import csv data with parent/child (category-subcategory) hierarchy to MySQL using Python如何使用 Python 将具有父/子(类别-子类别)层次结构的 csv 数据导入 MySQL
【发布时间】:2015-01-15 23:29:11
【问题描述】:

我正在使用 Python 的 MySQLdb 模块将包含父/子(类别-子类别)层次结构的 csv 文件导入 MySQL。这是一个示例 csv 文件:

vendor,category,subcategory,product_name,product_model,product_price
First vendor,category1,subcategory1,product1,model1,100
First vendor,category1,subcategory2,product2,model2,110
First vendor,category2,subcategory3,product3,model3,130
First vendor,category2,subcategory4,product5,model7,190

在 MySQL 中,我想使用具有层次结构的 category 表,如下所示:

CREATE TABLE IF NOT EXISTS `category` (
`category_id` int(11) NOT NULL AUTO_INCREMENT,
`parent_id` int(11) NOT NULL DEFAULT '0',
`status` tinyint(1) NOT NULL,
PRIMARY KEY (`category_id`),
KEY `parent_id` (`parent_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;

我的问题是:如何确定此表中的parent_id

这是我目前拥有的 Python 脚本:

import MySQLdb
import csv
con = MySQLdb.connect('localhost', 'root', '', 'testdb', use_unicode=True, charset='utf8')
with con:
    cur = con.cursor()
    csv_data = csv.reader(file('test.csv'))
    csv_data.next()
    for row in csv_data:
        cur.execute("SELECT manufacturer_id FROM manufacturer WHERE name=%s", [row[0]],)
        res = cur.fetchall()
        if res:
            vendor_id = res[0][0]
        else:
            cur.execute("INSERT INTO manufacturer (name) VALUES (%s)", (row[0],))
            vendor_id = cur.lastrowid
        cur.execute("SELECT category_id FROM category_description WHERE name=%s", [row[2]])
        res = cur.fetchall()
        if res:
            category_id = res[0][0]
        else:

            # What parent_id should be inserted here?
            cur.execute("INSERT INTO category (`status`, `parent_id`) VALUES (%s,%s)", (1,))

            category_id = cur.lastrowid
            cur.execute("INSERT INTO category_description (category_id, name) VALUES (%s,%s)", (category_id,row[2],))
            cur.execute("INSERT INTO product (model, manufacturer_id, price,) VALUES (%s, %s, %s)", (row[4], `vendor_id`, row[8],))                           
            product_id = cur.lastrowid
            cur.execute("INSERT INTO product_to_category (product_id, category_id) VALUES (%s, %s)", (product_id, category_id,))
    cur.commit()

以下是我的示例中使用的其他表的定义:

CREATE TABLE IF NOT EXISTS `manufacturer` (
`manufacturer_id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL,
PRIMARY KEY (`manufacturer_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;

CREATE TABLE IF NOT EXISTS `category_description` (
`category_id` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`category_id`,`language_id`),
KEY `name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;

CREATE TABLE IF NOT EXISTS `product` (
`product_id` int(11) NOT NULL AUTO_INCREMENT,
`model` varchar(64) NOT NULL,
`manufacturer_id` int(11) NOT NULL,
`price` decimal(15,4) NOT NULL DEFAULT '0.0000',
PRIMARY KEY (`product_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;

CREATE TABLE IF NOT EXISTS `product_to_category` (
`product_id` int(11) NOT NULL,
`category_id` int(11) NOT NULL,
PRIMARY KEY (`product_id`,`category_id`),
KEY `category_id` (`category_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;

【问题讨论】:

  • 您的层次结构是否具有两个级别的固定深度,或者出现在 CSV 文件的一行中的类别是否也可以作为另一行中的子类别出现(反之亦然)?
  • 是的,层次固定深度有两个级别。
  • 我认为如果出现在 CSV 文件的一行中的类别也显示为另一行中的子类别,我需要重命名该类别或子类别,例如 row[1] = row[ 1].replace('category_with_name_like_subcategory_name','unique_category_name')
  • 我发现了一个问题:作为父类别插入的行索引不正确。

标签: python mysql mysql-python hierarchical-data


【解决方案1】:

在分层表结构中,位于其层次结构顶部的任何成员都没有父级。我可能会使用 NULL 父 ID 显示此内容,但根据您定义 category 表的方式,您似乎希望通过为父 ID 提供值 0 来显示此内容。

由于您只有两个级别(类别和子类别)的固定深度层次结构,因此任务相对简单。对于 CSV 数据的每一行,您需要:

  1. 检查父级(row[1])是否在表中;如果没有,则插入其父 ID 为 0
  2. 从步骤 1 中获取父级的 category_id
  3. 检查孩子(row[2])是否在表中;如果不是,则插入其父 ID 等于步骤 2 中的 category_id

在您的示例代码中,您永远不会访问父级 (row[1]);您需要将其插入表中以使其具有孩子可以参考的 ID。如果您在此之前已经插入了父母,您可能仍然应该检查以确保它在那里。

这里还有一些其他问题:

  1. category_description 表的 PK 是在您忘记在表中定义的列 (language_id) 上定义的。
  2. 您确实应该在此物理模型中使用 InnoDB,以便可以在 category_descriptionproductproduct_to_category 中强制执行外键约束。
  3. 在您的示例中,cur.commit() 将引发异常——这是 MySQLdb 中 Connection 对象的方法。当然,COMMIT 无论如何都没有针对 MyISAM 表实现,因此您也可以通过完全删除该行来避免异常。
  4. 根据您向我们展示的 CSV 数据,引用 row[8] 也会引发异常。 (这是一个很好的例子,说明了为什么您应该测试 MCVE 以确保其正常工作!)
  5. 如果你确实切换到 InnoDB——而且你可能应该——你可以使用with con as cur: 来获得一个游标,当你退出with 块时它会自行提交。这样可以节省几行代码,让您无需对连接对象进行微观管理即可管理事务。

【讨论】:

  • 如果csv中删除了某些类别或子类别,您能否建议下次如何导入csv而不截断表?从哪里删除?
  • 您的问题不清楚。如果您需要将另一个 CSV 文件导入到相同的表中,您应该能够以相同的方式进行操作。
  • 是的,将另一个 CSV 文件导入到同一个表中。但是另一个包含新类别和子类别的 CSV 文件。在 mysql 中插入期间 - 如何删除旧类别?
  • 不需要删除旧的分类。只要类别名称是唯一的,您就可以在表中存储任意数量的不同层次结构。如果不是,您需要单独的表或表中的层次结构标识符。
猜你喜欢
  • 1970-01-01
  • 2015-11-05
  • 2020-10-20
  • 2011-01-08
  • 1970-01-01
  • 2011-05-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多