{"record":{"id":"f7b6e5e3577f8050","repo":"ent/ent","slug":"more-than-1-table-for-batch-insert-q-q","errorCode":null,"errorMessage":"more than 1 table for batch insert: %q != %q","messagePattern":"more than 1 table for batch insert: %q != %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"dialect/sql/sqlgraph/graph.go","lineNumber":1516,"sourceCode":"\t\t}\n\t\ts.Set(c.ID.Column, sql.Expr(fmt.Sprintf(\"LAST_INSERT_ID(%s)\", s.Table().C(c.ID.Column))))\n\t}))\n}\n\ntype batchCreator struct {\n\tgraph\n\t*BatchCreateSpec\n}\n\nfunc (c *batchCreator) nodes(ctx context.Context, drv dialect.Driver) error {\n\tif len(c.Nodes) == 0 {\n\t\treturn nil\n\t}\n\tcolumns := make(map[string]struct{})\n\tvalues := make([]map[string]driver.Value, len(c.Nodes))\n\tfor i, node := range c.Nodes {\n\t\tif i > 0 && node.Table != c.Nodes[i-1].Table {\n\t\t\treturn fmt.Errorf(\"more than 1 table for batch insert: %q != %q\", node.Table, c.Nodes[i-1].Table)\n\t\t}\n\t\tvalues[i] = make(map[string]driver.Value)\n\t\tif node.ID != nil && node.ID.Value != nil {\n\t\t\tcolumns[node.ID.Column] = struct{}{}\n\t\t\tvalues[i][node.ID.Column] = node.ID.Value\n\t\t}\n\t\tedges := EdgeSpecs(node.Edges).GroupRel()\n\t\terr := setTableColumns(node.Fields, edges, func(column string, value driver.Value) {\n\t\t\tcolumns[column] = struct{}{}\n\t\t\tvalues[i][column] = value\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tfor column := range columns {\n\t\tfor i := range values {\n\t\t\tif _, exists := values[i][column]; !exists {","sourceCodeStart":1498,"sourceCodeEnd":1534,"githubUrl":"https://github.com/ent/ent/blob/69d5d4deb19599f129166634e09d33addcf3f2cc/dialect/sql/sqlgraph/graph.go#L1498-L1534","documentation":"ent's batch creator requires all nodes in one sqlgraph.BatchCreateSpec to target the same table. While iterating c.Nodes it compares each node's table with the previous one; any mismatch means a single multi-row INSERT statement would span different tables, which is impossible, so it reports the two conflicting table names.","triggerScenarios":"Calling client.Foo.CreateBulk(...) where the builder list mixes entity types/tables — typically only possible via custom code that appends heterogeneous NodeSpecs to one BatchCreateSpec, or a codegen/spec-assembly bug.","commonSituations":"Hand-built batch specs in generic helpers that loop over mixed entities; merging specs from different mutations into one batch; misuse of sqlgraph internals outside generated code.","solutions":["Group nodes by table and issue one sqlgraph batch insert per table.","If using generated code, ensure each entity type's CreateBulk is used on its own client (client.Foo.CreateBulk vs client.Bar.CreateBulk).","Fix custom spec-assembly code so a BatchCreateSpec never mixes NodeSpec.Table values."],"exampleFix":"// before: one spec with mixed tables\nspec.Nodes = append(spec.Nodes, fooNode, barNode)\n// after\nfooSpec.Nodes = []sqlgraph.NodeSpec{fooNode}\nbarSpec.Nodes = []sqlgraph.NodeSpec{barNode}","handlingStrategy":"validation","validationCode":"tables := map[string]struct{}{}\nfor _, n := range spec.Nodes { tables[n.Table] = struct{}{} }\nif len(tables) > 1 { return fmt.Errorf(\"batch must target a single table\") }","typeGuard":null,"tryCatchPattern":"if err := batchCreate(ctx, nodes); err != nil {\n    if strings.Contains(err.Error(), \"more than 1 table\") { /* split nodes per table */ }\n}","preventionTips":["Only batch entities of the same type via one client's CreateBulk","Never merge NodeSpecs of different tables into one BatchCreateSpec","Type-generic helpers should key batches by entity type"],"tags":["sql","ent","batch-insert","validation"],"backgroundTag":"mixed-table-batch-insert","analyzedSha":"69d5d4deb19599f129166634e09d33addcf3f2cc","analyzedAt":"2026-09-03T16:51:39.799Z","contentChangedAt":"2026-09-03T16:51:39.799Z","schemaVersion":2},"datasetVersion":"2026-09-11T00:17:11.886Z"}