{
  "slug": "csv-anti-join",
  "name": "gizza-ai/csv-anti-join",
  "version": "0.1.0",
  "title": "CSV Anti-Join — Rows in A Not in B — gizza.ai",
  "description": "Return rows in CSV A whose key has no match in CSV B, or the reverse, with composite keys, alternate delimiters, and browser-local processing.",
  "tags": [
    "csv anti join",
    "csv rows not in other csv",
    "csv not in",
    "csv left anti join",
    "csv compare by key",
    "csv symmetric difference",
    "csv unmatched rows",
    "csv key diff",
    "csv set difference"
  ],
  "category": "data",
  "urls": {
    "page": "https://gizza.ai/tools/csv-anti-join/",
    "markdown": "https://gizza.ai/tools/csv-anti-join/index.md",
    "descriptor": "https://gizza.ai/tools/csv-anti-join/tool.json",
    "deep_link_example": "https://gizza.ai/tools/csv-anti-join/?a=id%2Cname%0A1%2CAlice%0A2%2CBob%0A3%2CCarol&b=id%2Ccity%0A2%2CBerlin%0A3%2CCairo%0A4%2CDelhi&key=id&key_b=id&direction=a-only&delimiter=%2C&case_sensitive=true&trim_keys=true"
  },
  "cli": "gizza tool csv-anti-join \"id,name\n1,Alice\n2,Bob\n3,Carol\" 'b=id,city\n2,Berlin\n3,Cairo\n4,Delhi' 'key=id'",
  "tool": {
    "description": "Return the rows of CSV A whose key has no match in CSV B — the SQL LEFT ANTI JOIN / NOT IN primitive — and optionally the reverse. `key`/`key_b` are header names or 1-based indices (comma-separated for a composite key) matched on VALUES, so the key columns may be named differently in A and B (blank key_b reuses key). `direction` is a-only (rows in A not in B), b-only (the reverse), or both (symmetric difference with a leading '_source' A/B column). B is treated as a key SET, so duplicate keys never multiply output rows and every unmatched A row is kept. delimiter is a single char or comma/tab/semicolon/pipe; case_sensitive and trim_keys control key matching. Runs locally.",
    "parameters": {
      "additionalProperties": false,
      "properties": {
        "a": {
          "description": "CSV A (the first/left table) text; the first row is its header.",
          "type": "string"
        },
        "b": {
          "description": "CSV B (the second/right table) text; the first row is its header.",
          "type": "string"
        },
        "case_sensitive": {
          "default": true,
          "description": "Match key VALUES case-sensitively. false makes 'A1' and 'a1' match. Default true.",
          "type": "boolean"
        },
        "delimiter": {
          "default": ",",
          "description": "Field separator: a single char or 'comma'/'tab'/'semicolon'/'pipe'. Default ','.",
          "type": "string"
        },
        "direction": {
          "default": "a-only",
          "description": "Which unmatched rows to return: 'a-only' rows in A not in B; 'b-only' the reverse (rows in B not in A); 'both' the symmetric difference with a leading '_source' A/B column. Default 'a-only'.",
          "enum": [
            "a-only",
            "b-only",
            "both"
          ],
          "type": "string"
        },
        "key": {
          "description": "Key column(s) in A — a header name or 1-based column index. Use a comma-separated list for a composite key (e.g. 'first,last').",
          "type": "string"
        },
        "key_b": {
          "default": "",
          "description": "Key column(s) in B — header names or 1-based indices. Matched on VALUES, so they may be named differently than in A. Blank reuses `key`. Must have the same number of columns as `key`.",
          "type": "string"
        },
        "trim_keys": {
          "default": false,
          "description": "Trim surrounding whitespace on key values before matching, so ' 1 ' matches '1'. Default false.",
          "type": "boolean"
        }
      },
      "required": [
        "a",
        "b",
        "key"
      ],
      "type": "object"
    }
  }
}