【问题标题】:Merge the non-NULL fields of two SQL tables合并两个 SQL 表的非 NULL 字段
【发布时间】:2023-03-12 15:06:01
【问题描述】:

我有一个 SQLite 表,其中包含有关某些文件的一些元数据,该表在文件的散列上有一个 UNIQUE 列。我正在编写的客户端可以在有限的范围内仅通过解析相关文件来自行填充此数据库。但是,由于数据库主要用于有关客户端可能想要下载的文件的元数据,更重要的是,由于许多这些文件的作者没有在其中包含元数据,在这种情况下,相关的数据库行必须由人工填充,它还查询几个用户定义的服务器,每个服务器都有自己的可能不完整的数据库副本。每台服务器的每一行中可能有一些空白 (NULL) 字段,其他服务器可能已填充这些字段。对于任何给定的行,数据库的每一列都有一个规范的“正确”值,因此如果同一数据库的两个不同副本使用不同的值填充同一字段,则其中一个已损坏。

我下载了远程数据库的副本,它以 CSV 文件的形式提供给我,并将其加载到我的数据库中(我已经知道了——这是 Python,所以它真的很简单

self.cursor.execute('create temporary table newinfo(a,b,c,d,e,f)')
with open('remote.csv') as f:
    self.cursor.executemany('insert into newinfo values (?,?,?,?,?,?)', csv.reader(f))

它的性能可能不是很好,但如果我关心性能,我就不会使用 Python。任何更快地做到这一点的建议都将受到欢迎,但不是预期的。另外我应该提到,我实际上并没有下载到临时文件并解析它——我真的在HTTPResponse对象周围使用io.TextIOWrapper——为了简单起见,我只是把它写成打开一个本地文件为了写这个问题。)

不管怎样,在我下载了远程数据库表之后,我需要将它合并到我的本地表中。我认为本地数据库可能会填充远程数据库不会填充的字段,反之亦然。我需要做的是查看远程数据库中的字段(我刚刚下载到新的本地表中)是否不为 NULL,如果是,请将它们复制到本地表中的相同字段中。作为可选的奖励,我希望能够检测远程和本地数据库是否有任何已填充且不匹配的字段,如果是,则中止事务并向用户发出错误信号。

我的问题是:最有效的方法是什么?

【问题讨论】:

    标签: python sql sqlite


    【解决方案1】:

    您可以通过在文件哈希上连接旧临时表和新临时表并使用coalesce 来合并值。

    coalesce() 函数返回其第一个非 NULL 参数的副本,如果所有参数均为 NULL,则返回 NULL。 Coalesce() 必须至少有 2 个参数。

    因为您需要不在新行中的旧行,反之亦然,因此您需要FULL OUTER JOIN,即SQLite doesn't support。但是您可以使用LEFT OUTER JOINs 中的UNION 来模拟它(在this article 中进行了解释)。

    然后您可以从结果集中创建一个新表删除旧表。

    const sql = `
      CREATE TABLE oldinfo(hash UNIQUE, a, b, c);
      INSERT INTO oldinfo VALUES
        ('x', 10, NULL, 20),
        ('z', NULL, NULL, 30);
    
      CREATE TEMP TABLE newinfo(hash UNIQUE, a, b, c);
      INSERT INTO newinfo VALUES
        ('x', NULL, 1, NULL),
        ('y', 2, NULL, NULL),
        ('z', NULL, NULL, 3);
    
      SELECT 
        coalesce(n.hash, o.hash) hash,
        coalesce(n.a, o.a) a,
        coalesce(n.b, o.b) b,
        coalesce(n.c, o.c) c
      FROM oldinfo o
      LEFT JOIN newinfo n USING(hash)
      UNION  
      SELECT 
        coalesce(n.hash, o.hash) hash,
        coalesce(n.a, o.a) a,
        coalesce(n.b, o.b) b,
        coalesce(n.c, o.c) c
      FROM newinfo n
      LEFT JOIN oldinfo o USING(hash);
    `
    
    async function run() {
      const wasmUrl = 'https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.5.0/sql-wasm.wasm'
      const sqljs = await window.initSqlJs({locateFile: file => wasmUrl})
      const db = new sqljs.Database()
      const results = db.exec(sql) 
      ko.applyBindings(results[0])
    }
    run()
    table {border-collapse: collapse}
    th, td {border: 1px solid black}
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.5.0/sql-wasm.min.js"></script>
    <table>
      <thead>
        <tr data-bind="foreach: columns"><th data-bind="text: $data"></th></tr>
      </thead>
      <tbody data-bind="foreach: values">
        <tr data-bind="foreach: $data"><td data-bind="text: $data"></td></tr>
      </tbody>
    </table>

    Python 绩效奖金

    它的性能可能不是很好,但如果我关心性能,我就不会使用 Python。我们欢迎任何更快地做到这一点的建议,但不是预期的。

    CPython 的性能可能不应该归咎于此。如果您使用其他数据库的 Python DBAPI,您可能希望 executemany 为您执行某种多值 INSERT 优化,但事实并非如此(如果您不在事务中,您正在执行每个循环迭代的事务应该非常慢)。我在this answer 中解释了替代方案。

    【讨论】:

    • 这正是我想要的。谢谢你。还要感谢最后关于我关于性能的奖金问题。像你这样的人让我没有完全失去对 StackOverflow 的信心。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-14
    • 1970-01-01
    • 2021-04-12
    相关资源
    最近更新 更多