addSql(<<<'SQL' -- Step 1: Create temp table with canonical mapping for COMPOSANT orphans CREATE TEMP TABLE _cf_composant_canonical AS SELECT DISTINCT ON (c.typecomposantid, cf.name) cf.id AS canonical_id, c.typecomposantid AS mt_id, cf.name AS cf_name FROM custom_fields cf INNER JOIN custom_field_values cfv ON cfv.customfieldid = cf.id INNER JOIN composants c ON c.id = cfv.composantid WHERE cf.typecomposantid IS NULL AND cf.typepieceid IS NULL AND cf.typeproductid IS NULL AND cf.machineid IS NULL AND c.typecomposantid IS NOT NULL ORDER BY c.typecomposantid, cf.name, cf.id; SQL); $this->addSql(<<<'SQL' -- Step 2: Create temp table listing ALL orphaned composant field ids with their canonical CREATE TEMP TABLE _cf_composant_all AS SELECT cf.id AS orphan_id, canon.canonical_id FROM custom_fields cf INNER JOIN custom_field_values cfv ON cfv.customfieldid = cf.id INNER JOIN composants c ON c.id = cfv.composantid INNER JOIN _cf_composant_canonical canon ON canon.mt_id = c.typecomposantid AND canon.cf_name = cf.name WHERE cf.typecomposantid IS NULL AND cf.typepieceid IS NULL AND cf.typeproductid IS NULL AND cf.machineid IS NULL AND c.typecomposantid IS NOT NULL AND cf.id != canon.canonical_id; SQL); $this->addSql(<<<'SQL' -- Step 3: Re-point custom_field_values from duplicates to canonical UPDATE custom_field_values cfv SET customfieldid = dup.canonical_id FROM _cf_composant_all dup WHERE cfv.customfieldid = dup.orphan_id; SQL); $this->addSql(<<<'SQL' -- Step 4: Set typecomposantid on canonical fields UPDATE custom_fields cf SET typecomposantid = canon.mt_id FROM _cf_composant_canonical canon WHERE cf.id = canon.canonical_id; SQL); $this->addSql(<<<'SQL' -- Step 5: Delete duplicate (non-canonical) custom_fields for composants DELETE FROM custom_fields WHERE id IN (SELECT orphan_id FROM _cf_composant_all); SQL); $this->addSql('DROP TABLE IF EXISTS _cf_composant_all'); $this->addSql('DROP TABLE IF EXISTS _cf_composant_canonical'); // ── PIECES ───────────���────────────────────────────���───────────── $this->addSql(<<<'SQL' CREATE TEMP TABLE _cf_piece_canonical AS SELECT DISTINCT ON (p.typepieceid, cf.name) cf.id AS canonical_id, p.typepieceid AS mt_id, cf.name AS cf_name FROM custom_fields cf INNER JOIN custom_field_values cfv ON cfv.customfieldid = cf.id INNER JOIN pieces p ON p.id = cfv.pieceid WHERE cf.typecomposantid IS NULL AND cf.typepieceid IS NULL AND cf.typeproductid IS NULL AND cf.machineid IS NULL AND p.typepieceid IS NOT NULL ORDER BY p.typepieceid, cf.name, cf.id; SQL); $this->addSql(<<<'SQL' CREATE TEMP TABLE _cf_piece_all AS SELECT cf.id AS orphan_id, canon.canonical_id FROM custom_fields cf INNER JOIN custom_field_values cfv ON cfv.customfieldid = cf.id INNER JOIN pieces p ON p.id = cfv.pieceid INNER JOIN _cf_piece_canonical canon ON canon.mt_id = p.typepieceid AND canon.cf_name = cf.name WHERE cf.typecomposantid IS NULL AND cf.typepieceid IS NULL AND cf.typeproductid IS NULL AND cf.machineid IS NULL AND p.typepieceid IS NOT NULL AND cf.id != canon.canonical_id; SQL); $this->addSql(<<<'SQL' UPDATE custom_field_values cfv SET customfieldid = dup.canonical_id FROM _cf_piece_all dup WHERE cfv.customfieldid = dup.orphan_id; SQL); $this->addSql(<<<'SQL' UPDATE custom_fields cf SET typepieceid = canon.mt_id FROM _cf_piece_canonical canon WHERE cf.id = canon.canonical_id; SQL); $this->addSql(<<<'SQL' DELETE FROM custom_fields WHERE id IN (SELECT orphan_id FROM _cf_piece_all); SQL); $this->addSql('DROP TABLE IF EXISTS _cf_piece_all'); $this->addSql('DROP TABLE IF EXISTS _cf_piece_canonical'); } public function down(Schema $schema): void { // Data migration — not reversible. The old per-entity duplicates are gone. $this->addSql('SELECT 1'); } }