Skip to content

データのインポート/エクスポート

DuckDB WASMアダプターは、様々な形式のデータインポート/エクスポートをサポートしています。

サポートされる形式

形式インポートエクスポート説明
CSVカンマ区切り値
JSONJavaScript Object Notation
Parquetカラムナ形式の効率的なファイル
ArrowApache Arrowフォーマット

CSVのインポート

基本的な使い方

javascript
import { createConnection } from '@northprint/duckdb-wasm-adapter-core';

const connection = await createConnection();

// ファイル入力から
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];

await connection.importCSV(file, 'users', {
  header: true,
  delimiter: ','
});

CSVオプション

javascript
interface ImportCSVOptions {
  // ヘッダー行があるか(デフォルト: true)
  header?: boolean;
  
  // 区切り文字(デフォルト: ',')
  delimiter?: string;
  
  // スキップする行数
  skipRows?: number;
  
  // カラム名の配列(headerがfalseの場合に使用)
  columns?: string[];
  
  // NULL値を表す文字列
  nullString?: string;
  
  // 日付形式
  dateFormat?: string;
  
  // タイムスタンプ形式
  timestampFormat?: string;
  
  // 自動型検出(デフォルト: true)
  autoDetect?: boolean;
}

実例

基本的なCSVインポート

javascript
// users.csv
// name,email,age
// 田中太郎,tanaka@example.com,30
// 佐藤花子,sato@example.com,25

await connection.importCSV(file, 'users', {
  header: true,
  delimiter: ','
});

// データを確認
const result = await connection.execute('SELECT * FROM users');
console.log(result.toArray());
// [
//   { name: '田中太郎', email: 'tanaka@example.com', age: 30 },
//   { name: '佐藤花子', email: 'sato@example.com', age: 25 }
// ]

TSVファイルのインポート

javascript
// タブ区切りファイル
await connection.importCSV(file, 'products', {
  header: true,
  delimiter: '\t'
});

ヘッダーなしCSV

javascript
// ヘッダーがないCSVファイルの場合
await connection.importCSV(file, 'data', {
  header: false,
  columns: ['id', 'name', 'value', 'created_at']
});

日本語を含むCSV

javascript
// Shift-JISエンコードのファイルを扱う場合
const text = await file.text();
const decoder = new TextDecoder('shift-jis');
const decodedText = decoder.decode(await file.arrayBuffer());
const utf8File = new File([decodedText], file.name, { type: 'text/csv' });

await connection.importCSV(utf8File, 'japanese_data', {
  header: true
});

JSONのインポート

基本的な使い方

javascript
// JavaScriptオブジェクトから
const data = [
  { id: 1, name: '商品A', price: 1000 },
  { id: 2, name: '商品B', price: 2000 },
  { id: 3, name: '商品C', price: 3000 }
];

await connection.importJSON(data, 'products');

ファイルからのインポート

javascript
// JSONファイルから
const file = fileInput.files[0];
const text = await file.text();
const data = JSON.parse(text);

await connection.importJSON(data, 'imported_data');

ネストされたJSON

javascript
// ネストされたJSONの処理
const nestedData = [
  {
    id: 1,
    name: '田中太郎',
    address: {
      city: '東京',
      postal: '100-0001'
    },
    tags: ['VIP', 'プレミアム']
  }
];

// フラット化してインポート
const flatData = nestedData.map(item => ({
  id: item.id,
  name: item.name,
  city: item.address.city,
  postal: item.address.postal,
  tags: JSON.stringify(item.tags)
}));

await connection.importJSON(flatData, 'users');

大きなJSONファイル

javascript
// ストリーミング処理(大きなファイル用)
async function importLargeJSON(file, tableName, batchSize = 1000) {
  const text = await file.text();
  const data = JSON.parse(text);
  
  // バッチ処理
  for (let i = 0; i < data.length; i += batchSize) {
    const batch = data.slice(i, i + batchSize);
    await connection.importJSON(batch, tableName);
  }
}

Parquetのインポート/エクスポート

Parquetインポート

javascript
// Parquetファイルのインポート
const parquetFile = fileInput.files[0];
await connection.importParquet(parquetFile, 'analytics_data');

// インポートしたデータを確認
const result = await connection.execute('SELECT COUNT(*) FROM analytics_data');
console.log('レコード数:', result.toArray()[0].count);

Parquetエクスポート

javascript
// クエリ結果をParquet形式でエクスポート
const parquetBlob = await connection.exportParquet('SELECT * FROM large_table');

// ダウンロード
const url = URL.createObjectURL(parquetBlob);
const a = document.createElement('a');
a.href = url;
a.download = 'data.parquet';
a.click();

Parquetの利点

  • 高圧縮率 - CSVと比較して10分の1以下のサイズ
  • 型情報の保持 - データ型が保存される
  • 高速な読み込み - カラムナ形式で効率的
  • 大規模データ - 大きなデータセットに最適

CSVのエクスポート

基本的なエクスポート

javascript
// クエリ結果をCSVとしてエクスポート
const csv = await connection.exportCSV('SELECT * FROM users', {
  header: true,
  delimiter: ','
});

// ファイルとしてダウンロード
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'users.csv';
link.click();

エクスポートオプション

javascript
interface ExportCSVOptions {
  // ヘッダー行を含むか(デフォルト: true)
  header?: boolean;
  
  // 区切り文字(デフォルト: ',')
  delimiter?: string;
  
  // 引用符文字(デフォルト: '"')
  quote?: string;
  
  // NULL値の表現(デフォルト: '')
  nullString?: string;
  
  // 日付フォーマット
  dateFormat?: string;
  
  // タイムスタンプフォーマット
  timestampFormat?: string;
}

実例

javascript
// カスタム設定でエクスポート
const csv = await connection.exportCSV(
  'SELECT * FROM sales WHERE date >= ?',
  ['2024-01-01'],
  {
    header: true,
    delimiter: '\t',  // タブ区切り
    nullString: 'N/A',
    dateFormat: 'YYYY-MM-DD'
  }
);

JSONのエクスポート

基本的なエクスポート

javascript
// クエリ結果をJSONとしてエクスポート
const json = await connection.exportJSON('SELECT * FROM products');

// ファイルとしてダウンロード
const blob = new Blob([JSON.stringify(json, null, 2)], { 
  type: 'application/json' 
});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'products.json';
link.click();

カスタム変換

javascript
// データを変換してエクスポート
const data = await connection.exportJSON('SELECT * FROM users');

const transformedData = data.map(user => ({
  ...user,
  fullName: `${user.firstName} ${user.lastName}`,
  age: new Date().getFullYear() - new Date(user.birthDate).getFullYear()
}));

// 変換後のデータをエクスポート
const json = JSON.stringify(transformedData, null, 2);

バルクインポート

大量データの効率的なインポート

javascript
async function bulkImport(files, tableName) {
  const connection = await createConnection();
  
  // トランザクション開始
  await connection.execute('BEGIN TRANSACTION');
  
  try {
    // テーブル作成(最初のファイルから推測)
    await connection.importCSV(files[0], tableName, {
      header: true,
      createTable: true
    });
    
    // 残りのファイルをインポート
    for (let i = 1; i < files.length; i++) {
      await connection.importCSV(files[i], tableName, {
        header: true,
        createTable: false  // テーブルは既に存在
      });
    }
    
    // コミット
    await connection.execute('COMMIT');
    console.log(`${files.length}ファイルのインポート完了`);
    
  } catch (error) {
    // ロールバック
    await connection.execute('ROLLBACK');
    console.error('インポートエラー:', error);
    throw error;
  }
}

フレームワーク統合

React

jsx
import { useImport, useExport } from '@northprint/duckdb-wasm-adapter-react';

function DataManager() {
  const { importCSV, importJSON, importing, error: importError } = useImport();
  const { exportCSV, exportJSON, exporting, error: exportError } = useExport();
  
  const handleCSVImport = async (event) => {
    const file = event.target.files[0];
    if (!file) return;
    
    await importCSV(file, 'imported_data', {
      header: true
    });
  };
  
  const handleExportCSV = async () => {
    const csv = await exportCSV('SELECT * FROM users');
    downloadFile(csv, 'users.csv', 'text/csv');
  };
  
  return (
    <div>
      <h3>インポート</h3>
      <input type="file" accept=".csv" onChange={handleCSVImport} />
      {importing && <p>インポート中...</p>}
      {importError && <p>エラー: {importError.message}</p>}
      
      <h3>エクスポート</h3>
      <button onClick={handleExportCSV} disabled={exporting}>
        CSVエクスポート
      </button>
      {exporting && <p>エクスポート中...</p>}
      {exportError && <p>エラー: {exportError.message}</p>}
    </div>
  );
}

Vue

vue
<template>
  <div>
    <h3>データインポート</h3>
    <input 
      type="file" 
      accept=".csv,.json"
      @change="handleFileImport"
      :disabled="importing"
    >
    
    <h3>データエクスポート</h3>
    <button @click="exportToCSV" :disabled="exporting">
      CSVエクスポート
    </button>
    <button @click="exportToJSON" :disabled="exporting">
      JSONエクスポート
    </button>
    
    <p v-if="importing || exporting">処理中...</p>
    <p v-if="error" class="error">{{ error.message }}</p>
  </div>
</template>

<script setup>
import { useImport, useExport } from '@northprint/duckdb-wasm-adapter-vue';

const { importCSV, importJSON, importing, error: importError } = useImport();
const { exportCSV, exportJSON, exporting, error: exportError } = useExport();

const error = computed(() => importError.value || exportError.value);

const handleFileImport = async (event) => {
  const file = event.target.files[0];
  if (!file) return;
  
  if (file.name.endsWith('.csv')) {
    await importCSV(file, 'imported_data', { header: true });
  } else if (file.name.endsWith('.json')) {
    const text = await file.text();
    const data = JSON.parse(text);
    await importJSON(data, 'imported_data');
  }
};

const exportToCSV = async () => {
  const csv = await exportCSV('SELECT * FROM users');
  downloadFile(csv, 'users.csv', 'text/csv');
};

const exportToJSON = async () => {
  const json = await exportJSON('SELECT * FROM users');
  downloadFile(JSON.stringify(json), 'users.json', 'application/json');
};
</script>

Svelte

svelte
<script>
  import { createDuckDB } from '@northprint/duckdb-wasm-adapter-svelte';
  
  const db = createDuckDB({ autoConnect: true });
  
  let importing = false;
  let exporting = false;
  let error = null;
  
  async function handleImport(event) {
    const file = event.target.files[0];
    if (!file) return;
    
    importing = true;
    error = null;
    
    try {
      if (file.name.endsWith('.csv')) {
        await db.importCSV(file, 'imported_data', { header: true });
      } else if (file.name.endsWith('.json')) {
        const text = await file.text();
        const data = JSON.parse(text);
        await db.importJSON(data, 'imported_data');
      } else if (file.name.endsWith('.parquet')) {
        await db.importParquet(file, 'imported_data');
      }
    } catch (e) {
      error = e;
    } finally {
      importing = false;
    }
  }
  
  async function exportData(format) {
    exporting = true;
    error = null;
    
    try {
      let data, filename, mimeType;
      
      if (format === 'csv') {
        data = await db.exportCSV('SELECT * FROM users');
        filename = 'users.csv';
        mimeType = 'text/csv';
      } else if (format === 'json') {
        const json = await db.exportJSON('SELECT * FROM users');
        data = JSON.stringify(json, null, 2);
        filename = 'users.json';
        mimeType = 'application/json';
      }
      
      downloadFile(data, filename, mimeType);
    } catch (e) {
      error = e;
    } finally {
      exporting = false;
    }
  }
</script>

<div>
  <h3>インポート</h3>
  <input 
    type="file" 
    accept=".csv,.json,.parquet"
    on:change={handleImport}
    disabled={importing}
  >
  
  <h3>エクスポート</h3>
  <button on:click={() => exportData('csv')} disabled={exporting}>
    CSV
  </button>
  <button on:click={() => exportData('json')} disabled={exporting}>
    JSON
  </button>
  
  {#if importing || exporting}
    <p>処理中...</p>
  {/if}
  
  {#if error}
    <p class="error">{error.message}</p>
  {/if}
</div>

パフォーマンスのヒント

大きなファイルの処理

javascript
// 10MB以上のファイルは自動的にファイル登録を使用
const largeFile = new File([...], 'large.csv', { type: 'text/csv' });

if (largeFile.size > 10 * 1024 * 1024) {
  // DuckDBのファイル登録システムを使用
  await connection.registerFile(largeFile);
  await connection.execute(`
    CREATE TABLE large_data AS 
    SELECT * FROM read_csv_auto('${largeFile.name}')
  `);
} else {
  // 通常のインポート
  await connection.importCSV(largeFile, 'large_data');
}

ストリーミング処理

javascript
// 大きなエクスポートをストリーミング
async function* streamExport(query, batchSize = 1000) {
  let offset = 0;
  
  while (true) {
    const batch = await connection.execute(
      `${query} LIMIT ${batchSize} OFFSET ${offset}`
    );
    
    const data = batch.toArray();
    if (data.length === 0) break;
    
    yield data;
    offset += batchSize;
  }
}

// 使用例
for await (const batch of streamExport('SELECT * FROM huge_table')) {
  // バッチごとに処理
  processBatch(batch);
}

エラーハンドリング

javascript
try {
  await connection.importCSV(file, 'data');
} catch (error) {
  if (error.code === 'INVALID_FORMAT') {
    console.error('ファイル形式が無効です');
  } else if (error.code === 'MEMORY_LIMIT') {
    console.error('メモリ不足です');
  } else if (error.code === 'DUPLICATE_COLUMN') {
    console.error('重複するカラム名があります');
  } else {
    console.error('インポートエラー:', error);
  }
}

まとめ

DuckDB WASMアダプターは、様々な形式のデータインポート/エクスポートを簡単に行えます。適切な形式を選択することで、パフォーマンスとストレージ効率を最適化できます。

Released under the MIT License.