如何将 mysqldump 导入 Pandas

Posted

技术标签:

【中文标题】如何将 mysqldump 导入 Pandas【英文标题】:How to import a mysqldump into Pandas 【发布时间】:2015-02-19 11:21:13 【问题描述】:

如果有一种简单的方法可以将mysqldump 导入 Pandas,我很感兴趣。

我有几个小 (~110MB) 表,我希望将它们作为 DataFrames。

我希望避免将数据放回数据库,因为这需要安装/连接到这样的数据库。我有 .sql 文件并想将包含的表导入 Pandas。是否有任何模块可以做到这一点?

如果版本控制很重要,则 .sql 文件都将“MySQL dump 10.13 Distrib 5.6.13, for Win32 (x86)”列为生成转储的系统。

事后的背景

我在没有数据库连接的计算机上本地工作。我的工作的正常流程是从第三方获得 .tsv、.csv 或 json 文件,并进行一些分析,这些分析会返回。一个新的第三方以 .sql 格式提供了他们的所有数据,这破坏了我的工作流程,因为我需要大量开销才能将其转换为我的程序可以作为输入的格式。我们最终要求他们以不同的格式发送数据,但出于业务/声誉原因,我们想先寻找解决方法。

编辑:下面是带有两个表的示例 MYSQLDump 文件。

/*
MySQL - 5.6.28 : Database - ztest
*********************************************************************
*/


/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`ztest` /*!40100 DEFAULT CHARACTER SET latin1 */;

USE `ztest`;

/*Table structure for table `food_in` */

DROP TABLE IF EXISTS `food_in`;

CREATE TABLE `food_in` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `Cat` varchar(255) DEFAULT NULL,
  `Item` varchar(255) DEFAULT NULL,
  `price` decimal(10,4) DEFAULT NULL,
  `quantity` decimal(10,0) DEFAULT NULL,
  KEY `ID` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=latin1;

/*Data for the table `food_in` */

insert  into `food_in`(`ID`,`Cat`,`Item`,`price`,`quantity`) values 

(2,'Liq','Beer','2.5000','300'),

(7,'Liq','Water','3.5000','230'),

(9,'Liq','Soda','3.5000','399');

/*Table structure for table `food_min` */

DROP TABLE IF EXISTS `food_min`;

CREATE TABLE `food_min` (
  `Item` varchar(255) DEFAULT NULL,
  `quantity` decimal(10,0) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

/*Data for the table `food_min` */

insert  into `food_min`(`Item`,`quantity`) values 

('Pizza','300'),

('Hotdogs','200'),

('Beer','300'),

('Water','230'),

('Soda','399'),

('Soup','100');

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

【问题讨论】:

经过一番研究,似乎没有库/模块可以做到这一点。我会留下这个问题,希望最终有。 @Merlin:如果有帮助的话,显然有人创建了a Python script 来将 mysqldump 转换为 CSV。 【参考方案1】:

我在没有数据库连接的计算机上本地工作。我工作的正常流程是获得一个 .tsv

试试mysqltotsv pypi 模块:

pip3 install --user mysqltotsv
python3 mysql-to-tsv.py --file dump.sql --outdir out1

这将在out1 目录中生成多个.tsv 文件(在MySQL 转储中找到的每个表对应一个.tsv 文件)。从那里开始,您可以通过加载 TSV 文件来继续使用 Pandas 的正常工作流程。

【讨论】:

【参考方案2】:

我想分享我对此问题的解决方案并寻求反馈:

import pandas as pd
import re
import os.path
import csv
import logging
import sys


def convert_dump_to_intermediate_csv(dump_filename, csv_header, csv_out_put_file, delete_csv_file_after_read=True):
    """
    :param dump_filename: five an mysql export dump (mysqldump...syntax)
    :param csv_header: the very first line in the csv file which should appear, give a string separated by coma
    :param csv_out_put_file: the name of the csv file
    :param delete_csv_file_after_read: if you set this to False, no new records will be written as the file exists.
    :return: returns a pandas dataframe for further analysis.
    """
    with open(dump_filename, 'r') as f:
        for line in f:
            pre_compiled_all_values_per_line = re.compile('(?:INSERT\sINTO\s\S[a-z\S]+\sVALUES\s+)(?P<values>.*)(?=\;)')
            result = pre_compiled_all_values_per_line.finditer(line)
            for element in result:
                values_only = element.groups('values')[0]
                value_compile = re.compile('\(.*?\)')
                all_identified = value_compile.finditer(values_only)
                for single_values in all_identified:
                    string_to_split = single_values.group(0)[1:-1]
                    string_array = string_to_split.split(",")

                    if not os.path.exists(csv_out_put_file):
                        with open(csv_out_put_file, 'w', newline='') as file:
                            writer = csv.writer(file)
                            writer.writerow(csv_header.split(","))
                            writer.writerow(string_array)
                    else:
                        with open(csv_out_put_file, 'a', newline='') as file:
                            writer = csv.writer(file)
                            writer.writerow(string_array)
    df = pd.read_csv(csv_out_put_file)
    if delete_csv_file_after_read:
        os.remove(csv_out_put_file)
    return df


if __name__ == "__main__":
    log_name = 'test.log'
    LOGGER = logging.getLogger(log_name)
    LOGGER.setLevel(logging.DEBUG)
    LOGGER.addHandler(logging.NullHandler())
    FORMATTER = logging.Formatter(
        fmt='%(asctime)s %(levelname)-8s %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S')
    SCREEN_HANDLER = logging.StreamHandler(stream=sys.stdout)
    SCREEN_HANDLER.setFormatter(FORMATTER)
    LOGGER.addHandler(SCREEN_HANDLER)

    dump_filename = 'test_sql.sql'
    header_of_csv_file = "A,B,C,D,E,F,G,H,I" # i did not identify the columns in the table definition...
    csv_output_file = 'test.csv'
    pandas_df = convert_dump_to_intermediate_csv(dump_filename, header_of_csv_file, csv_output_file, delete_csv_file_after_read=False)
    LOGGER.debug(pandas_df)

当然,记录器部分可以删除.... 编辑:没有看到这个话题那么老。我很抱歉。

【讨论】:

【参考方案3】:

我发现自己和你的情况相似,@firelynx 的回答真的很有帮助!

但由于我对文件中包含的表的了解有限,我通过添加标题生成(pandas 自动拾取它)以及搜索转储文件中的所有表来扩展脚本。结果,我最终得到了以下脚本,它确实运行得非常快。我切换到io.StringIO,并将生成的表格保存为table_name.csv 文件。

附:我也支持不要依赖这种方法的建议,并提供代码仅用于说明目的:)

所以,首先,我们可以像这样扩充read_dump 函数

from io import StringIO
import re, shutil

def read_dump(dump_filename, target_table):
    sio = StringIO()

    read_mode = 0 # 0 - skip, 1 - header, 2 - data
    with open(dump_filename, 'r') as f:
        for line in f:
            line = line.strip()
            if line.lower().startswith('insert') and target_table in line:
                read_mode = 2
            if line.lower().startswith('create table') and target_table in line:
                read_mode = 1
                continue

            if read_mode==0:
                continue

            # Filling up the headers
            elif read_mode==1:
                if line.lower().startswith('primary'):
                    # add more conditions here for different cases 
                    #(e.g. when simply a key is defined, or no key is defined)
                    read_mode=0
                    sio.seek(sio.tell()-1) # delete last comma
                    sio.write('\n')
                    continue
                colheader = re.findall('`([\w_]+)`',line)
                for col in colheader:
                    sio.write(col.strip())
                    sio.write(',')

            # Filling up the data -same as @firelynx's code
            elif read_mode ==2:
                data = re.findall('\([^\)]*\)', line)
                try:
                    # ...
                except IndexError:
                    pass
                if line.endswith(';'):
                    break
    sio.seek(0)
    with open (target_table+'.csv', 'w') as fd:
        shutil.copyfileobj(sio, fd,-1)
    return # or simply return sio itself

要查找表格列表,我们可以使用以下函数:

def find_tables(dump_filename):
    table_list=[]

    with open(dump_filename, 'r') as f:
        for line in f:
            line = line.strip()
            if line.lower().startswith('create table'):
                table_name = re.findall('create table `([\w_]+)`', line.lower())
                table_list.extend(table_name)

    return table_list

然后将两者结合起来,例如在您将运行的 .py 脚本中

python this_script.py mysqldump_name.sql [table_name]

import os.path
def main():
    try:
        if len(sys.argv)>=2 and os.path.isfile(sys.argv[1]):
            if len(sys.argv)==2:
                print('Table name not provided, looking for all tables...')
                table_list = find_tables(sys.argv[1])
                if len(table_list)>0:
                    print('Found tables: ',str(table_list))
                    for table in table_list:
                        read_dump(sys.argv[1], table)
            elif len(sys.argv)==3:
                read_dump(sys.argv[1], sys.argv[2])
    except KeyboardInterrupt:
        sys.exit(0)

【讨论】:

也许将你的代码墙分解成段并解释每个段的作用会帮助你的答案更容易被使用 我猜你是对的,这里展示完整的脚本更像是一个完整的解决方案,它只会促进代码的盲目复制粘贴......【参考方案4】:

没有

Pandas 没有 native 读取 mysqldump 而不通过数据库的方式。

有一个可能的解决方法,但在我看来这是一个非常糟糕的主意。

解决方法(不推荐用于生产)

当然,您可以使用预处理器解析 mysqldump 文件中的数据。

MySQLdump 文件通常包含很多我们在加载 pandas 数据帧时不感兴趣的额外数据,因此我们需要对其进行预处理并去除噪音,甚至重新格式化行以使其符合要求。

使用StringIO,我们可以读取文件,在将数据输入the pandas.read_csv funcion之前对其进行处理

from StringIO import StringIO
import re

def read_dump(dump_filename, target_table):
    sio = StringIO()
        
    fast_forward = True
    with open(dump_filename, 'rb') as f:
        for line in f:
            line = line.strip()
            if line.lower().startswith('insert') and target_table in line:
                fast_forward = False
            if fast_forward:
                continue
            data = re.findall('\([^\)]*\)', line)
            try:
                newline = data[0]
                newline = newline.strip(' ()')
                newline = newline.replace('`', '')
                sio.write(newline)
                sio.write("\n")
            except IndexError:
                pass
            if line.endswith(';'):
                break
    sio.pos = 0
    return sio

现在我们有一个函数可以读取数据并将其格式化为 CSV 文件,我们可以使用 pandas.read_csv() 读取它

import pandas as pd

food_min_filedata = read_dump('mysqldumpexample', 'food_min')
food_in_filedata = read_dump('mysqldumpexample', 'food_in')

df_food_min = pd.read_csv(food_min_filedata)
df_food_in = pd.read_csv(food_in_filedata)

结果:

        Item quantity
0    'Pizza'    '300'
1  'Hotdogs'    '200'
2     'Beer'    '300'
3    'Water'    '230'
4     'Soda'    '399'
5     'Soup'    '100'

   ID    Cat     Item     price quantity
0   2  'Liq'   'Beer'  '2.5000'    '300'
1   7  'Liq'  'Water'  '3.5000'    '230'
2   9  'Liq'   'Soda'  '3.5000'    '399'

流处理注意事项

这种方法称为流处理,非常精简,几乎不占用内存。一般来说,使用这种方法更有效地将 csv 文件读入 pandas 是一个好主意。

这是我建议反对的 mysqldump 文件的解析

【讨论】:

我不理解解析 SQL 转储文件的负面影响,特别是考虑到他也控制它们的生成,并且可以相当肯定,如果他使用相同的软件,任何未来的转储都将采用相同的格式版本和相同的命令行参数。直接从磁盘读取这些数据而不是将其加载到事务数据库然后再次读取它应该快一个数量级。 @NikoNyrh 更改 mysql 版本可能会更改转储的布局,这意味着您可能必须重写代码。这是根据定义的紧密耦合,是一种反模式。 确实,解析不受约束的文件并不理想,但在这种情况下,它似乎仍然是最好的解决方案。如果它不起作用,您会收到一些错误消息,您可以调整代码以适应小的偏差。但这越来越自以为是了,很高兴您仍然为他提供了示例代码。 @NikoNyrh 我只是不希望人们将此答案视为“好主意”或“不考虑后果的事情应该可以做”。我特别不希望有人说“我不应该复制粘贴 firelynx 的代码,他很烂”。因为很多人从 *** 复制粘贴代码,而这里很少有答案提供警告,说明为什么复制粘贴代码可能是个坏主意。 嘿伙计们,只是一点背景,所以你明白我原来的问题。我在没有数据库连接的计算机上本地工作。我工作的正常流程是从第三方获得 .tsv、.csv 或 json 文件,并进行一些分析,这些分析将被返回。一个新的第三方以 .sql 格式提供了他们的所有数据,这破坏了我的工作流程,因为我需要大量开销才能将其转换为我的程序可以作为输入的格式。我们最终要求他们以不同的格式发送数据,但出于商业/声誉的原因,我们想先寻找解决方法。谢谢!【参考方案5】:

一种方法是export mysqldump to sqlite(例如run this shell script)然后读取 sqlite 文件/数据库。

见the SQL section of the docs:

pd.read_sql_table(table_name, sqlite_file)

另一种选择是直接在mysql数据库上运行read_sql...

【讨论】:

我试图避免将数据放回数据库,并希望直接读取转储文件。我已经更新了问题。 @Keith pandas 不能有效地进行更新(它不是数据库!)所以通常你想一次性构建。 我不确定我明白你的意思。我想分析作为 mysqldump 给出的 Pandas 中的一些数据。通常我习惯于获取超级容易导入的 .tsv 文件。我希望格式的改变不会显着改变我的工作流程。 @Kevin 你能提供一个mysqldump输出的样本吗?我的印象是这将是一些疯狂的 SQL 查询(带有更新和值)。 有几个库允许对 pandas 对象进行 sql 查询(但就像我说的 pandas 对象不能有效地更新零碎)github.com/yhat/pandasql 和(我确定有 another 但不记得了)。 mysqldump 是一种用于传输表的标准化文件格式。

以上是关于如何将 mysqldump 导入 Pandas的主要内容,如果未能解决你的问题,请参考以下文章

每当我尝试在 PHP 中使用 mysqldump 时都会出现错误。如何正确导入并使用它?

如何将远程服务器MYSQL数据库导出导入

是否可以使用 mysqldump 转储和导入视图?

如何导出mysql数据

pl/sql Developer如何导入dump文件

mysql,mysqldump命令导入 导出表结构或数据