编程新体验:描述需求,让 ChatGPT 写代码

Published: 2023-02-23

Tags: ChatGPT

本文总阅读量

前段时间用 ChatGPT 问了不少问题,菜谱、脚本、起变量/模块名称,一直没有尝试用它来生成代码,刚好有个小需求,需要写 Python 脚本在两个数据库间同步数据,决定让 ChatGPT 试试

保存了对话的 HTML

我使用了 Chrone 扩展 SingleFile,将与 ChatGPT 网页版的对话保存为 html 格式,便于查阅,你可以点击链接查看本次聊天,排版的效果比本文粘贴的要好。

链接地址:ChatGPT_2023_2_23_18_05_13.html

生成建表语句与更新 SQL

我:请帮我创建一个表,表名 tmp_withdraw_pass,需要包含 withdraw_id, app_name 字段,字符串类型,day_num 字段为 int 类型,同时也需要有个 updated_at 字段,请提供建表语句

ChatGPT:以下是创建名为 tmp_withdraw_pass 的表的 SQL 语句:

CREATE TABLE tmp_withdraw_pass (
    withdraw_id VARCHAR(255),
    app_name VARCHAR(255),
    day_num INT,
    updated_at TIMESTAMP
);

因为没有限定 PostgreSQL 数据,于是追加补充

我:建表语句需要支持 postgresql

ChatGPT:如果您需要在 PostgreSQL 数据库中创建该表,请使用以下 SQL 语句:

CREATE TABLE tmp_withdraw_pass (
    withdraw_id VARCHAR(255),
    app_name VARCHAR(255),
    day_num INTEGER,
    updated_at TIMESTAMP
);

我本来计划,在同步数据到表中后,通过 SQL 来更新 day_num 序号,使其达到效果如下(如 20 条数据,分成 10 组,那么每组有两条数据)

id001, appname01, 1, 2023-02-23
id002, appname01, 1, 2023-02-23
id003, appname01, 2, 2023-02-23
id004, appname01, 2, 2023-02-23
...
id019, appname01, 10, 2023-02-23
id020, appname01, 10, 2023-02-23

于是问 AI

我:基于以上 tmp_withdraw_pass 表,请帮我写个 sql,它负责 set 更新表中的 day_num 字段,数据均分成十份,更新后,day_num 字段的内容第一份为 1,第二份为 2,第三份为 3,以此类推,你需要根据 withdraw_id 排序后再分段

它回复到

ChatGPT:以下是一个 SQL 查询语句,用于将 tmp_withdraw_pass 表中的 day_num 字段平均分成十份并进行更新。更新后,第一组记录 day_num 字段的值为 1,第二组为 2,以此类推。在更新之前,该查询会首先根据 withdraw_id 字段进行排序,然后再按顺序将记录分成十个相等的部分。

WITH sorted_withdrawals AS (
  SELECT withdraw_id, app_name, day_num, updated_at,
         ROW_NUMBER() OVER (ORDER BY withdraw_id) as row_num,
         COUNT(*) OVER() as total_count
  FROM tmp_withdraw_pass
), 
updated_withdrawals AS (
  SELECT withdraw_id, app_name, 
         CEIL(row_num * 10.0 / total_count) AS new_day_num, 
         updated_at
  FROM sorted_withdrawals
)
UPDATE tmp_withdraw_pass
SET day_num = u.new_day_num
FROM updated_withdrawals u
WHERE tmp_withdraw_pass.withdraw_id = u.withdraw_id;

核对代码发现我没有表述数据应该根据 app_name 分组更新,于是补充如下

我:在你提供的 sql 基础上,请改写 sql,使其按 app_name 分组,不同 app_name 互不影响

ChatGPT: 以下是一个改写后的 SQL 查询语句,它在按 app_name 字段进行分组的基础上,将 day_num 字段平均分成十份并进行更新。更新后,第一组记录 day_num 字段的值为 1,第二组为 2,以此类推。在更新之前,该查询会首先根据 withdraw_id 字段进行排序,然后再按顺序将记录分成十个相等的部分。

WITH sorted_withdrawals AS (
  SELECT withdraw_id, app_name, day_num, updated_at,
         ROW_NUMBER() OVER (PARTITION BY app_name ORDER BY withdraw_id) as row_num,
         COUNT(*) OVER(PARTITION BY app_name) as total_count
  FROM tmp_withdraw_pass
), 
updated_withdrawals AS (
  SELECT withdraw_id, app_name, 
         CEIL(row_num * 10.0 / total_count) AS new_day_num, 
         updated_at
  FROM sorted_withdrawals
)
UPDATE tmp_withdraw_pass
SET day_num = u.new_day_num
FROM updated_withdrawals u
WHERE tmp_withdraw_pass.withdraw_id = u.withdraw_id
AND tmp_withdraw_pass.app_name = u.app_name;

于是我得到了一个 SQL 脚本,可以将表中的数据分组标号,经过测试运行没有问题,AI 理解自然语言的能力和生成代码的能力可见一斑。

生成 Python 脚本

我:请提供给我一个 python 脚本,它的作用是连接数据库实例 A 的 withdraw_record 表,从中查询出 status = '' 的数据,插入到数据库 B 的 tmp_withdraw_pass 表中,withdraw_record 表包含 wirdraw_id, status, updated 等字段,tmp_withdraw_pass 表包含 wirdraw_id, app_name, day_num, updated_at 字段,脚本需要使用 psycopg2 库,同步数据需要支持循环,因为会从多张 withdraw_record 表同步数据到 tmp_withdraw_pass 表,app_name 是不同的

ChatGPT:下面是一个使用 psycopg2 库编写的 Python 脚本,它的作用是从数据库 A 的多张 withdraw_record 表中查询出 status 字段为 '' 的记录,并将这些记录插入到数据库 B 的 tmp_withdraw_pass 表中。在这个脚本中,我们将 withdraw_record 表分别映射到 app_name 值,通过循环遍历不同的 app_name 值,从相应的 withdraw_record 表中读取数据,然后将它们插入到 tmp_withdraw_pass 表中。

import psycopg2

# 数据库 A 的连接信息
A_DB_HOST = 'localhost'
A_DB_NAME = 'database_a'
A_DB_USER = 'user_a'
A_DB_PASSWORD = 'password_a'

# 数据库 B 的连接信息
B_DB_HOST = 'localhost'
B_DB_NAME = 'database_b'
B_DB_USER = 'user_b'
B_DB_PASSWORD = 'password_b'

# 各个 app_name 对应的 withdraw_record 表名
WITHDRAW_RECORD_TABLES = {
    'app_name_1': 'withdraw_record_1',
    'app_name_2': 'withdraw_record_2',
    'app_name_3': 'withdraw_record_3',
    # ...
}

# 连接数据库 A 和 B
a_conn = psycopg2.connect(host=A_DB_HOST, dbname=A_DB_NAME, user=A_DB_USER, password=A_DB_PASSWORD)
b_conn = psycopg2.connect(host=B_DB_HOST, dbname=B_DB_NAME, user=B_DB_USER, password=B_DB_PASSWORD)

# 循环遍历不同的 app_name 值
for app_name, table_name in WITHDRAW_RECORD_TABLES.items():
    # 从数据库 A 中查询符合条件的记录
    with a_conn.cursor() as a_cur:
        a_cur.execute(f"SELECT withdraw_id, updated FROM {table_name} WHERE status = ''")
        records = a_cur.fetchall()

    # 将记录插入到数据库 B 的 tmp_withdraw_pass 表中
    with b_conn.cursor() as b_cur:
        for withdraw_id, updated in records:
            b_cur.execute(
                "INSERT INTO tmp_withdraw_pass (withdraw_id, app_name, day_num, updated_at) "
                "VALUES (%s, %s, %s, %s)",
                (withdraw_id, app_name, 0, updated)
            )

    # 提交事务
    b_conn.commit()

    # 关闭数据库连接
a_conn.close()
b_conn.close()

接下来是不断的优化代码环节

我:请重新生成,因为数据库实例 A 上的 dbname 是不同的,表名则是相同的,表名都是 withdraw_record

ChatGPT: 好的,如果数据库实例 A 上的 dbname 不同,但是表名相同,那么可以将 WITHDRAW_RECORD_TABLES 字典改为只包含表名即可,如下所示:

import psycopg2

# 数据库 A 的连接信息
A_DB_HOST = 'localhost'
A_DB_NAME = 'database_a'
A_DB_USER = 'user_a'
A_DB_PASSWORD = 'password_a'

# 数据库 B 的连接信息
B_DB_HOST = 'localhost'
B_DB_NAME = 'database_b'
B_DB_USER = 'user_b'
B_DB_PASSWORD = 'password_b'

# 需要同步的表名
WITHDRAW_RECORD_TABLES = [
    'withdraw_record',
    # ...
]

# 连接数据库 A 和 B
a_conn = psycopg2.connect(host=A_DB_HOST, dbname=A_DB_NAME, user=A_DB_USER, password=A_DB_PASSWORD)
b_conn = psycopg2.connect(host=B_DB_HOST, dbname=B_DB_NAME, user=B_DB_USER, password=B_DB_PASSWORD)

# 循环遍历不同的表名
for table_name in WITHDRAW_RECORD_TABLES:
    # 从数据库 A 中查询符合条件的记录
    with a_conn.cursor() as a_cur:
        a_cur.execute(f"SELECT withdraw_id, updated FROM {table_name} WHERE status = ''")
        records = a_cur.fetchall()

    # 将记录插入到数据库 B 的 tmp_withdraw_pass 表中
    with b_conn.cursor() as b_cur:
        for withdraw_id, updated in records:
            b_cur.execute(
                "INSERT INTO tmp_withdraw_pass (withdraw_id, app_name, day_num, updated_at) "
                "VALUES (%s, %s, %s, %s)",
                (withdraw_id, table_name, 0, updated)
            )

    # 提交事务
    b_conn.commit()

# 关闭数据库连接
a_conn.close()
b_conn.close()

生成跟预期不对,再引导一下

我:因为是从 A 同步多个不同 db 的 withdraw_record 表到 B 实例,所以 A_DB_NAME 需要从循环中获取,同样 a_conn 也应该在循环中

ChatGPT:好的,如果需要从 A 实例同步多个不同数据库的 withdraw_record 表到 B 实例,那么可以按照以下方式编写 Python 脚本:

import psycopg2

# 数据库 A 的连接信息
A_DB_HOST = 'localhost'
A_DB_USER = 'user_a'
A_DB_PASSWORD = 'password_a'

# 数据库 B 的连接信息
B_DB_HOST = 'localhost'
B_DB_USER = 'user_b'
B_DB_PASSWORD = 'password_b'

# 需要同步的数据库名和表名
WITHDRAW_RECORD_TABLES = {
    'database_a1': 'withdraw_record',
    'database_a2': 'withdraw_record',
    # ...
}

# 连接数据库 B
b_conn = psycopg2.connect(host=B_DB_HOST, user=B_DB_USER, password=B_DB_PASSWORD)

# 循环遍历不同的数据库和表名
for a_db_name, table_name in WITHDRAW_RECORD_TABLES.items():
    # 连接数据库 A
    a_conn = psycopg2.connect(host=A_DB_HOST, dbname=a_db_name, user=A_DB_USER, password=A_DB_PASSWORD)

    # 从数据库 A 中查询符合条件的记录
    with a_conn.cursor() as a_cur:
        a_cur.execute(f"SELECT withdraw_id, updated FROM {table_name} WHERE status = ''")
        records = a_cur.fetchall()

    # 将记录插入到数据库 B 的 tmp_withdraw_pass 表中
        with b_conn.cursor() as b_cur:
        for withdraw_id, updated in records:
            b_cur.execute(
                "INSERT INTO tmp_withdraw_pass (withdraw_id, app_name, day_num, updated_at) "
                "VALUES (%s, %s, %s, %s)",
                (withdraw_id, table_name, 0, updated)
            )

    # 提交事务
    b_conn.commit()

    # 关闭数据库连接
    a_conn.close()

# 关闭数据库连接
b_conn.close()

可以直接提供给它示例代码,有了更清晰的提示,AI 生成的代码会更符合预期。

我:因为表名都叫 withdraw_record,可以用变量存储表名,同时可以定义数组来存储 dbname,例如: WITHDRAW_RECORD_TABLES = ["database_a1", "database_a2"]

ChatGPT:好的,如果多个数据库中的表名都是 withdraw_record,那么可以按照以下方式修改 Python 脚本:

import psycopg2

# 数据库 A 的连接信息
A_DB_HOST = 'localhost'
A_DB_USER = 'user_a'
A_DB_PASSWORD = 'password_a'

# 数据库 B 的连接信息
B_DB_HOST = 'localhost'
B_DB_USER = 'user_b'
B_DB_PASSWORD = 'password_b'

# 需要同步的数据库名
WITHDRAW_RECORD_DB_NAMES = [
    'database_a1',
    'database_a2',
    # ...
]

# 表名
TABLE_NAME = 'withdraw_record'

# 连接数据库 B
b_conn = psycopg2.connect(host=B_DB_HOST, user=B_DB_USER, password=B_DB_PASSWORD)

# 循环遍历不同的数据库名
for a_db_name in WITHDRAW_RECORD_DB_NAMES:
    # 连接数据库 A
    a_conn = psycopg2.connect(host=A_DB_HOST, dbname=a_db_name, user=A_DB_USER, password=A_DB_PASSWORD)

    # 从数据库 A 中查询符合条件的记录
    with a_conn.cursor() as a_cur:
        a_cur.execute(f"SELECT withdraw_id, updated FROM {TABLE_NAME} WHERE status = ''")
        records = a_cur.fetchall()

    # 将记录插入到数据库 B 的 tmp_withdraw_pass 表中
    with b_conn.cursor() as b_cur:
        for withdraw_id, updated in records:
            b_cur.execute(
                "INSERT INTO tmp_withdraw_pass (withdraw_id, app_name, day_num, updated_at) "
                "VALUES (%s, %s, %s, %s)",
                (withdraw_id, a_db_name, 0, updated)
            )

    # 提交事务
    b_conn.commit()

    # 关闭数据库连接
    a_conn.close()

# 关闭数据库连接
b_conn.close()

此时代码已较为符合预期,我又给 AI 提了一些建议

我:你可以修改 TABLE_NAME 为 A_TABLE_NAME,同时用 B_TABLE_NAME 存储 tmp_withdraw_pass 吗

我:b_conn 不用放在循环中初始化,因为它不会受到 A_DB_NAME 影响

我:b_cursor 也可以移动到循环外面

本次生成有个小惊喜,本来计划同步数据到临时表后再通过之前得来的 SQL 执行更新

没想到最后生成的代码 day_num = (i // (len(results) // 10)) + 1 达到了同样的效果

import psycopg2

A_DB_NAMES = ["database_a1", "database_a2"]
A_TABLE_NAME = "withdraw_record"
B_DB_NAME = "database_b"
B_TABLE_NAME = "tmp_withdraw_pass"

# 数据库 A 连接信息
A_HOST = "host"
A_PORT = "port"
A_USER = "user"
A_PASSWORD = "password"

# 数据库 B 连接信息
B_HOST = "host"
B_PORT = "port"
B_USER = "user"
B_PASSWORD = "password"

# 连接数据库 B
b_conn = psycopg2.connect(
    dbname=B_DB_NAME,
    user=B_USER,
    password=B_PASSWORD,
    host=B_HOST,
    port=B_PORT
)

# 初始化 b_cursor
b_cursor = b_conn.cursor()

def sync_withdraw_record():
    # 遍历 A_DB_NAMES
    for A_DB_NAME in A_DB_NAMES:
        # 连接数据库 A
        a_conn = psycopg2.connect(
            dbname=A_DB_NAME,
            user=A_USER,
            password=A_PASSWORD,
            host=A_HOST,
            port=A_PORT
        )
        a_cursor = a_conn.cursor()

        # 查询 withdraw_id 和 updated_at 字段
        select_sql = f"SELECT withdraw_id, updated_at FROM {A_TABLE_NAME} WHERE status = ''"
        a_cursor.execute(select_sql)
        results = a_cursor.fetchall()

        # 关闭数据库 A 连接
        a_cursor.close()
        a_conn.close()

        # 遍历结果集,插入到数据库 B 的 tmp_withdraw_pass 表中
        for i, result in enumerate(results):
            withdraw_id, updated_at = result
            day_num = (i // (len(results) // 10)) + 1
            app_name = A_DB_NAME
            insert_sql = f"INSERT INTO {B_TABLE_NAME} (withdraw_id, app_name, day_num, updated_at) VALUES (%s, %s, %s, %s)"
            b_cursor.execute(insert_sql, (withdraw_id, app_name, day_num, updated_at))

        # 提交事务
        b_conn.commit()

    # 关闭 b_cursor
    b_cursor.close()

最后我问了 psycopg2 如何安装,ChatGPT 再次给出完善的答案

一些感想

之前一直以为 AI 是人工智障,ChatGPT 也感觉就是替代 Google 手动检索,但深入体验后,感觉有些震撼,它在自然语言的理解上,已比普通人强太多,同时在写代码这件事儿上,也会比绝大部分熟练技术工更优秀。

未来的 Web,App 等开发可能只需动动嘴,描述你的需求,AI 就能自动生成代码、构建、产出一个可用产品,并且随着你的表述,持续调整,直到你满意为止。(突然想到老罗的 TNT...

现在基于 AI 的大量应用还在探索,相信过不了两年,将会有一场新的风暴席卷各行各业。

—— 新挑战,新机遇,面对疾风吧