如何将JSON文件转换为SQLite数据库

yvt65v4c  于 2022-11-14  发布在  SQLite
关注(0)|答案(6)|浏览(414)

如果我有一些样本数据,如何将其放入SQLite(最好是全自动的)?

{"uri":"/","user_agent":"example1"}
{"uri":"/foobar","user_agent":"example1"}
{"uri":"/","user_agent":"example2"}
{"uri":"/foobar","user_agent":"example3"}
yiytaume

yiytaume1#

我发现最简单的方法是使用jq和CSV作为中间格式。

获取CSV

首先将您的数据写入文件。我将在这里假定data.json
然后使用jq构造头部:

% head -1 data.json | jq -r 'keys | @csv'
"uri","user_agent"

head -1是因为我们只需要一行。jq-r使输出成为普通字符串,而不是 Package CSV的JSON字符串。然后,我们调用内部函数keys以数组形式获取输入的键。我们将其发送到@csv化程序,该格式化程序以引用的CSV格式输出带有标题的单个字符串。
然后,我们需要构建数据。

% jq -r 'map(tostring) | @csv' < data.json
"/","example1"
"/foobar","example1"
"/","example2"
"/foobar","example3"

现在我们获取全部输入,并使用.[]解构关联数组(Map),然后将其放回一个简单的数组[…]中。这基本上将我们的词典转换为键的数组。发送到@csv化程序,我们再次获得一些CSV。
把所有这些放在一起,我们得到一个简单的一行,形式是:

% (head -1 data.json | jq -r 'keys | @csv' && jq -r 'map(tostring) | @csv' < data.json) > data.csv

如果您需要在没有文件的情况下动态转换数据,请尝试以下操作:

% cat data.json | (read -r first && jq -r '(keys | @csv),(map(tostring) | @csv)' <<<"${first}" && jq -r 'map(tostring) | @csv')

加载到SQLite中

打开SQLite数据库:

sqlite3 somedb.sqlite

现在,在交互式外壳中执行以下操作(假设您将CSV写入data.csv并希望它位于名为my_table的表中):

.mode csv
.import data.csv my_table

现在关闭外壳,然后再次打开,以获得干净的环境。现在,您可以轻松地从数据库中使用SELECT并做任何您想做的事情。

把这一切放在一起

就在那里进行asciinema录制:

编辑

  • 编辑*:正如所指出的(谢谢@Leo),原始问题确实显示了换行符分隔的JSON对象,每个对象本身都符合rfc4627,但不是全部以该格式组合在一起。jq可以通过使用jq '.[]' <input.json >preprocessed.json对文件进行预处理,以大致相同的方式处理单个JSON对象数组。如果您碰巧正在处理JSON文本序列(rfc7464),那么幸运的是,jq也通过--seq参数为您提供了支持。
  • 编辑2*:换行符分隔的JSON和JSON文本序列都有一个重要的优点;它们将内存需求降低到O(1),这意味着您的总内存需求只取决于最长的输入行,而将整个输入放在一个数组中需要解析器能够处理后期错误(即在前100k个元素之后有语法错误),据我所知通常不是这样,或者它必须解析整个文件两次(首先验证语法,然后解析,在这个过程中丢弃以前的元素,就像jq --stream的情况一样),这也很少发生,或者,它会尝试一次解析整个输入并在一个步骤中返回结果(想象一下,接收到一个包含整个50G输入数据加上开销的Python字典),这通常是由内存支持的,因此您的内存使用量增加了大约您的总数据大小。
  • 编辑3*:如果遇到障碍物,请尝试使用KEYS_UNSORT,而不是KEYS。我自己还没有测试过(我假设我的专栏已经排序了),但@Kyle Barron报告说这是必要的。
  • 编辑4*:正如youngminz在下面的注解中指出的,原始命令在处理嵌套列表等非{数字,字符串}值时失败。该命令已更新(与map_values()不同,map()根据注解稍作修改,将对象转换为与[.[]]相同的键,从而使Map更具可读性)。键不受影响,如果您“真的”使用复杂类型作为键(这可能甚至不符合JSON,但我现在懒得去查找它),您可以对与键相关的Map执行相同的操作。
pbpqsu0x

pbpqsu0x2#

无需CSV或第三方工具的一种方法是结合使用SQLite的JSON1 extensionsqlite3 CLI工具中提供的readfile扩展。总的来说,这是一种“更直接”的解决方案,其优点是比CSV更一致地处理JSON null值,否则CSV会将它们作为空字符串导入。

如果输入文件是格式良好的JSON文件,例如以数组形式给出的示例:

[
{"uri":"/","user_agent":"example1"},
{"uri":"/foobar","user_agent":"example1"},
{"uri":"/","user_agent":"example2"},
{"uri":"/foobar","user_agent":"example3"}
]

则可以将其读入相应的my_table表中,如下所示。使用sqlite3 CLI打开SQLite数据库文件my_db.db

sqlite3 my_db.db

然后使用以下命令创建my_table

CREATE TABLE my_table(uri TEXT, user_agent TEXT);

最后,可以使用CLI命令将my_data.json中的JSON数据插入到表中:

INSERT INTO my_table SELECT 
  json_extract(value, '$.uri'), 
  json_extract(value, '$.user_agent')
FROM json_each(readfile('my_data.json'));

如果初始JSON文件是换行符分隔的JSON元素,则可以首先使用jq进行转换,方法是:

jq -s <my_data_raw.json >my_data.json
  • 很可能有一种方法可以使用JSON1在SQLite中直接实现这一点,但我没有追求这一点,因为在导入到SQLite之前,我已经使用jq来处理数据。*
a5g8bdjr

a5g8bdjr3#

Sqlitebiter似乎提供了一种Python解决方案:
一个CLI工具,用于将CSV/Excel/HTML/JSON/LTSV/Markdown/SQLite/TSV/Google-Sheets转换为SQLite数据库文件。http://sqlitebiter.rtfd.io/
文档:http://sqlitebiter.readthedocs.io/en/latest/
项目:https://github.com/thombashi/sqlitebiter

  • 上次更新时间约为3个月前
  • 上一期约1个月前关闭,没有一期开放
  • 今天注意到,2018-03-14
yr9zkbsy

yr9zkbsy4#

您可以使用spyql。Spyql读取json文件(每行1个json对象)并生成INSERT语句,您可以将这些语句通过管道传输到SQLite:

$ spyql -Otable=my_table "SELECT json->uri, json->user_agent FROM json TO sql" < sample3.json | sqlite3 my.db

这里假设您已经在SQLite数据库my.db中创建了一个空表。
免责声明:我是间谍网站的作者。

ojsjcaue

ojsjcaue5#

以下是编译成Deno脚本的第一个答案:

// just for convenience (pathExists)
import {} from "https://deno.land/x/simple_shell@0.9.0/src/stringUtils.ts";

/**
 * @description
 * convert a json db to csv and then to sqlite
 *
 * @note
 * `sqliteTableConstructor` is a string that is used to create the table, if it is specified the csv file *should not* contain a header row.
 * if it's not specified then the csv file *must* contain a header row so it can be used to infer the column names.
 */
const jsonToSqlite = async (
  {
    jsonDbPath,
    jsonToCsvFn,
    sqliteDbPath,
    sqliteTableConstructor,
    tableName,
  }: {
    jsonDbPath: string;
    sqliteDbPath: string;
    tableName: string;
    sqliteTableConstructor?: string;
    // deno-lint-ignore no-explicit-any
    jsonToCsvFn: (jsonDb: any) => string;
  },
) => {
  // convert it into csv
  const csvDbPath = `${jsonDbPath.replace(".json", "")}.csv`;
  if (csvDbPath.pathExists()) {
    console.log(`${csvDbPath} already exists`);
  } else {
    const db = JSON.parse(await Deno.readTextFile(jsonDbPath));
    const csv = jsonToCsvFn(db);
    await Deno.writeTextFile(csvDbPath, csv);
  }

  // convert it to sqlite
  if (sqliteDbPath.pathExists()) {
    console.log(`${sqliteDbPath} already exists`);
  } else {
    const sqlite3 = Deno.spawnChild("sqlite3", {
      args: [sqliteDbPath],
      stdin: "piped",
      stderr: "null", // required to make sqlite3 work
    });
    await sqlite3.stdin.getWriter().write(
      new TextEncoder().encode(
        ".mode csv\n" +
          (sqliteTableConstructor ? `${sqliteTableConstructor};\n` : "") +
          `.import ${csvDbPath} ${tableName}\n` +
          ".exit\n",
      ),
    );
    await sqlite3.status;
  }
};

用法示例:

await jsonToSqlite(
    {
      jsonDbPath: "./static/db/db.json",
      sqliteDbPath: "./static/db/db.sqlite",
      tableName: "radio_table",
      sqliteTableConstructor:
        "CREATE TABLE radio_table(name TEXT, country TEXT, language TEXT, votes INT, url TEXT, favicon TEXT)",
      jsonToCsvFn: (
        db: StationDBType[],
      ) => {
        const sanitize = (str: string) =>
          str.trim().replaceAll("\n", " ").replaceAll(",", " ");
        return db.filter((s) => s.name.trim() && s.url.trim())
          .map(
            (station) => {
              return (
                sanitize(station.name) + "," +
                sanitize(station.country) + "," +
                sanitize(station.language) + "," +
                station.votes + "," +
                sanitize(station.url) + "," +
                sanitize(station.favicon)
              );
            },
          ).join("\n");
      },
    },
  );

Edit1:

  • 默认情况下,将CSV导入到SQLite会将所有列类型设置为字符串。在这个编辑中,我允许用户在将CSV导入表之前首先(通过一个可选的构造函数)创建表,这样他就可以指定确切的列类型。
  • 改善榜样
    编辑2:
  • 事实证明,使用Deno和SQLite-Deno,您不需要使用CSV作为中间层或对SQLite进行外壳,以下是一个如何实现这一点的示例:

下面的代码将从json数据库创建一个新的SQL数据库。

import { DB } from "https://deno.land/x/sqlite@v3.2.1/mod.ts";

export interface StationDBType {
  name: string;
  country: string;
  language: string;
  votes: number;
  url: string;
  favicon: string;
}

export const db = new DB("new.sql");
db.query(
  "create TABLE radio_table (name TEXT, country TEXT, language TEXT, votes INT, url TEXT, favicon TEXT)",
);
const jsonDb: StationDBType[] = JSON.parse(
  await Deno.readTextFile("static/db/compressed_db.json"),
);

const sanitize = (s: string) => s.replaceAll('"', "").replaceAll("'", "");
db.query(
  `insert into radio_table values ${
    jsonDb.map((station) =>
      "('" +
      sanitize(station.name) +
      "','" +
      sanitize(station.country) +
      "','" +
      sanitize(station.language) +
      "'," +
      station.votes +
      ",'" +
      sanitize(station.url) +
      "','" +
      sanitize(station.favicon) +
      "')"
    ).join(",")
  }`,
);

db.close();
2admgd59

2admgd596#

使用换行符分隔的JSON对象文件,包括数据中的\n
添加标题列名并确保JSON是紧凑的(每条记录一行)。

cat <(echo '"line"') source.json | jq -c '.' > source.fauxcsv

使用列分隔符\t将JSON和标头作为“CSV”导入到临时表中,这在JSON中不会出现。然后通过SQLites JSON functions创建真实的表。

sqlite3 file.db \
 -cmd '.separator \t \n' \
 -cmd '.import --schema temp source.fauxcsv temp_json_lines' <<-'EOSQL'
   INSERT into records SELECT 
    json_extract(line, '$.rid'),
    coalesce(json_extract(line, '$.created_at'), strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
    json_extract(line, '$.name')
    FROM temp_json_lines;
EOSQL

相关问题