Open22
duckdbメモ

await db.registerFileURL("sample1.json", "http://localhost:3000/sample1.json", DuckDBDataProtocol.HTTP, false)
みたいにファイルでできる

insertJSONFromPathを二回かけようとすると、すでにテーブルがあると怒られる。
下記のようにcreate: false
すると通せる
await connection.insertJSONFromPath("sample1.json", {
name: "fruit",
})
await connection.insertJSONFromPath("sample2.json", {
name: "fruit",
create: false
})

カラム数が違うものはUNION BY ALLは出来ないが、UNION BY NAMEならできる
const data = await connection.query(`
SELECT * FROM sample1.json
UNION BY NAME
SELECT * FROM sample2.json
`)

キャスト
::INTEGER
とかにするとキャスト楽にできる
const data = await connection.query(`
SELECT SUM(price)::INTEGER FROM sample1.json
`)

OPFS

pivot

- https://duckdb.org/docs/sql/statements/pivot.html#limitations
- USINGの変換はコツ必要。外側でやる

vercel: ^1.29.1-dev132.0

await conn.run(`SET home_directory = '/tmp';`)
await conn.run("INSTALL httpfs")
await conn.run("LOAD httpfs")

SELECT A FROM foo AS A
とすると、Aがstructでとれる
SELECT A,B FROM foo AS A JOIN baz AS B
みたいなときにめっちゃ便利

auto_type_candidatesとかはbigintないとかとかしておく方いいかも

auto_type_candidates、そんなに良さそうでもなかった。
結局all_varchar=trueにしてしまったほうが扱いやすいかもしれない
db.open
にcastBigIntToDouble
などもあったが、これらで有益そうなのはなかった

-
https://github.com/duckdb/duckdb-wasm/issues/1975#issuecomment-3061265665
registerOPFSFileNameするときに、Promiseで待ち発生させないと追いつかなくなるのがある

registerOPFSFileNamehはreadしてくれるわけではないかも

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))
}

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())
とすると、ざっくり型がとれる

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

これなら概ねいけそう。
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
})
)
})
}

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)

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
で開いたときに、コピペして環境再現しやすい - テーブル名を先に決めておけるのでクエリ使いまわしやすい
デメリット - 使わないテーブルのセットアップが含まれる