Open22

duckdbメモ

terrierscriptterrierscript
await db.registerFileURL("sample1.json", "http://localhost:3000/sample1.json", DuckDBDataProtocol.HTTP, false)

みたいにファイルでできる

terrierscriptterrierscript

insertJSONFromPathを二回かけようとすると、すでにテーブルがあると怒られる。

下記のようにcreate: falseすると通せる

await connection.insertJSONFromPath("sample1.json", {
        name: "fruit",
  })
await connection.insertJSONFromPath("sample2.json", {
    name: "fruit",
    create: false
})
terrierscriptterrierscript
SELECT A FROM foo AS A

とすると、Aがstructでとれる

SELECT A,B FROM foo AS A JOIN baz AS B 

みたいなときにめっちゃ便利

terrierscriptterrierscript

https://duckdb.org/docs/stable/data/csv/overview.html#parameters
csvが意図しないキャストするのはread_csvで制御できそう。
auto_type_candidatesとかはbigintないとかとかしておく方いいかも

terrierscriptterrierscript

auto_type_candidates、そんなに良さそうでもなかった。
結局all_varchar=trueにしてしまったほうが扱いやすいかもしれない

db.opencastBigIntToDoubleなどもあったが、これらで有益そうなのはなかった

terrierscriptterrierscript

all_varchar=trueで読み込むと下記でレコードを普通のJSONにできる(all_varcharでないとbigintが混ざったりするのでできないケースにぶち当たる)

const parseRecord = (record: arrow.Table<any>) => {
  const records = record.toArray().map(t => t.toJSON())
  return JSON.parse(JSON.stringify(records))
}
terrierscriptterrierscript
       const sample = await conn.query(`SELECT * FROM ${table} LIMIT 1;`)
       console.log(table, JSON.stringify(Object.keys(parseRecord(sample)[0])))

でとれたキーを

const companyKeys = ["name","cd", ...とれたキー] as const

export const CompanySchema = z.record(z.enum(companyKeys), z.string().nullable())

とすると、ざっくり型がとれる

terrierscriptterrierscript

JSON.parse(JSON.stringfify( だと対応できるのは一段のものだけ。複雑化したり、aggregate functionによっては対応できない

terrierscriptterrierscript

これなら概ねいけそう。

export const parseArrowTable = (record: arrow.Table) => {
  return record.toArray().map(t => {
    return JSON.parse(
      JSON.stringify(t, (_, value) => {
        if (typeof value === "bigint") {
          return Number(value)
        }
        return value
      })
    )
  })
}
terrierscriptterrierscript
      const result = await conn.query(`
        SELECT 
          1 AS v_int,
          1.2::FLOAT AS v_float,
          NULL AS v_null,
          {"a":2, "b":'c', "d":NULL} AS v_struct, 
          [1,2,3] AS v_list,
          ['abc','def'] AS v_str_list,
          map([1, 2], ['a', 'b']) AS v_map,
          DATE '1992-03-27' - INTERVAL 5 DAY AS v_date,
          TIME '12:34' AS v_time,
          'infinity'::DATE AS v_infinity,
          true AS v_boolean,
          { "a": { 
            "b":'c',
            "d": {
              "e": map([1,2],['a','b']) ,
              "f": DATE '1992-03-27' - INTERVAL 5 DAY,
              "g": {
                "h": false
              }
            }
          }} AS v_nested,
        `)

      const r = parseArrowTable(result)
terrierscriptterrierscript

SELECT * FROM read_csv( ... と直接読み出しもできるが、CREATE TABLE xxx FROM とかでテーブル作るのは利点ある

const createTableQuery = (table: string, url: string) => {
  return `CREATE OR REPLACE TABLE ${table} AS SELECT * FROM read_csv('${url}', all_varchar=true);`
}

メリット

  • console.logあたりにCREATE TABLE文出しておいて、$ duckdb -uiで開いたときに、コピペして環境再現しやすい
  • テーブル名を先に決めておけるのでクエリ使いまわしやすい
    デメリット
  • 使わないテーブルのセットアップが含まれる