{"record":{"id":"aee6994c1b40ce51","repo":"pathwaycom/pathway","slug":"cannot-join-table-with-itself-use-table-copy","errorCode":null,"errorMessage":"Cannot join table with itself. Use <table>.copy() as one of the arguments of the join.","messagePattern":"Cannot join table with itself\\. Use <table>\\.copy\\(\\) as one of the arguments of the join\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/pathway/internals/joins.py","lineNumber":1009,"sourceCode":"        )\n\n        return (inner_table, final_mapping)\n\n    @staticmethod\n    def _table_join(\n        left: Joinable,\n        right: Joinable,\n        *on: expr.ColumnExpression,\n        mode: JoinMode,\n        id: expr.ColumnReference | None = None,\n        left_instance: expr.ColumnReference | None = None,\n        right_instance: expr.ColumnReference | None = None,\n        exact_match: bool = False,  # if True do not optionalize output columns even if other than inner join is used\n        left_exactly_once: bool = False,\n        right_exactly_once: bool = False,\n    ) -> JoinResult:\n        if left == right:\n            raise ValueError(\n                \"Cannot join table with itself. Use <table>.copy() as one of the arguments of the join.\"\n            )\n\n        left_table, left_substitutions = left._substitutions()\n        right_table, right_substitutions = right._substitutions()\n\n        chained_join_desugaring = SubstitutionDesugaring(\n            {**left_substitutions, **right_substitutions}\n        )\n\n        if id is not None:\n            id = chained_join_desugaring.eval_expression(id)\n            id_column = id._column\n        else:\n            id_column = None\n\n        common_column_names: StableSet[str] = StableSet()\n        if left_instance is not None and right_instance is not None:","sourceCodeStart":991,"sourceCodeEnd":1027,"githubUrl":"https://github.com/pathwaycom/pathway/blob/fa2f74a4649b7c5908690cf60137263d8d80de5f/python/pathway/internals/joins.py#L991-L1027","documentation":"join() compares left == right and raises ValueError if both joinables are the same object, because a self-join would make column references on both sides ambiguous. The message tells you to pass <table>.copy() as one side so each side gets its own ColumnReference namespace.","triggerScenarios":"t.join(t, t.k == t.k) — literally passing the same Table object as both left and right (also via join_inner/join_left/join_right sugar).","commonSituations":"Self-joins for pair/key matching (e.g. finding rows sharing a key within one table); refactor renaming two variables that end up aliasing the same table object.","solutions":["Copy one side: right = t.copy() then t.join(right, t.k == right.k)","Use two independently-built (even identical) connector/table objects if a copy is not desired"],"exampleFix":"# before\nres = t.join(t, t.k == t.k)\n\n# after\nt_right = t.copy()\nres = t.join(t_right, t.k == t_right.k)","handlingStrategy":"validation","validationCode":"def safe_self_join(t, cond_builder):\n    t2 = t.copy()\n    return t.join(t2, cond_builder(t, t2))\n\n# or pre-check: assert left is not right and left != right before calling join","typeGuard":null,"tryCatchPattern":null,"preventionTips":["For self-joins, always materialize right = t.copy() first","After refactors, verify the two join arguments are distinct objects, not aliases"],"tags":["pathway","join","self-join","api-misuse"],"backgroundTag":null,"analyzedSha":"fa2f74a4649b7c5908690cf60137263d8d80de5f","analyzedAt":"2026-08-15T01:48:17.006Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}