Agent Wranglers

DAT-78Recommended

Import files through a staging table

Load each uploaded CSV or spreadsheet into a staging table first and check every row. Show the person each failing row with its line number, then apply the good rows in one transaction.

How to do it

Parse with a CSV library, never by splitting on commas. RFC 4180 lets a quoted field hold commas, line breaks and doubled quotes. Read the file as UTF-8 and drop a leading byte order mark, which some spreadsheet programs write. Cap the rows and the bytes per file. Match each row on a natural key, such as an SKU. Then a second import updates rows instead of adding copies, as Make writes safe to repeat says. Store the import's ID on every row it wrote, so one bad import can be undone whole. Keep cells that start with = as plain text, and escape them on the way out as Neutralise formulas in spreadsheet exports says.

Examples

Python example

import csv
import io

MAX_ROWS = 10_000

def read_import(raw: bytes):
    reader = csv.DictReader(io.StringIO(raw.decode("utf-8-sig"), newline=""))
    good, bad = [], []
    for row in reader:
        if len(good) + len(bad) == MAX_ROWS:
            raise ImportTooLarge(MAX_ROWS)
        problem = check_row(row)
        if problem:
            bad.append({"line": reader.line_num, "problem": problem})
        else:
            good.append(row)
    return good, bad

Go example

const maxRows = 10_000

func readImport(raw []byte) (good [][]string, bad []RowError, err error) {
	reader := csv.NewReader(bytes.NewReader(bytes.TrimPrefix(raw, []byte("\xef\xbb\xbf"))))
	header, err := reader.Read()
	if err != nil {
		return nil, nil, err
	}
	for {
		record, err := reader.Read()
		if err == io.EOF {
			return good, bad, nil
		}
		if err != nil {
			return nil, nil, err
		}
		if len(good)+len(bad) == maxRows {
			return nil, nil, ErrTooManyRows
		}
		line, _ := reader.FieldPos(0)
		if problem := checkRow(header, record); problem != "" {
			bad = append(bad, RowError{Line: line, Problem: problem})
		} else {
			good = append(good, record)
		}
	}
}

How to check

Import a test file with a byte order mark, a quoted comma, a line break in a quoted field and a bad line 7. The preview names line 7 alone, and the other rows land once. Import the same file again, and the row count stays the same.

Sources

For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/data/transactions-and-concurrency/staged-imports">Agent Wranglers data and databases playbook</a>. In code you write, add a comment next to it with the same link.