-- Backfill owner_id by address matching (pick first active owner, fallback to any) UPDATE public.violations v SET owner_id = matched.owner_id FROM ( SELECT DISTINCT ON (v2.id) v2.id as violation_id, o.id as owner_id FROM public.violations v2 JOIN public.owners o ON v2.association_id = o.association_id AND LOWER(TRIM(REPLACE(REPLACE(REPLACE(REPLACE(v2.address, '.', ''), ',', ''), '#', ''), '-', ''))) = LOWER(TRIM(REPLACE(REPLACE(REPLACE(REPLACE(o.property_address, '.', ''), ',', ''), '#', ''), '-', ''))) WHERE v2.owner_id IS NULL AND v2.address IS NOT NULL AND v2.address != '' AND o.property_address IS NOT NULL AND o.property_address != '' ORDER BY v2.id, (CASE WHEN o.status = 'active' THEN 0 ELSE 1 END), o.created_at ) matched WHERE v.id = matched.violation_id; -- Also backfill unit_id where missing but owner has a unit UPDATE public.violations v SET unit_id = matched.unit_id FROM ( SELECT DISTINCT ON (v2.id) v2.id as violation_id, u.id as unit_id FROM public.violations v2 JOIN public.owners o ON v2.owner_id = o.id JOIN public.units u ON u.association_id = v2.association_id AND LOWER(TRIM(REPLACE(REPLACE(REPLACE(REPLACE(u.address, '.', ''), ',', ''), '#', ''), '-', ''))) = LOWER(TRIM(REPLACE(REPLACE(REPLACE(REPLACE(v2.address, '.', ''), ',', ''), '#', ''), '-', ''))) WHERE v2.unit_id IS NULL AND v2.address IS NOT NULL AND v2.address != '' ORDER BY v2.id, u.created_at ) matched WHERE v.id = matched.violation_id; -- Create a trigger function to auto-link owner on insert/update CREATE OR REPLACE FUNCTION public.auto_link_violation_owner() RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ DECLARE v_owner_id UUID; BEGIN -- Only run if owner_id is null and address is provided IF NEW.owner_id IS NULL AND NEW.address IS NOT NULL AND NEW.address != '' THEN SELECT o.id INTO v_owner_id FROM public.owners o WHERE o.association_id = NEW.association_id AND LOWER(TRIM(REPLACE(REPLACE(REPLACE(REPLACE(o.property_address, '.', ''), ',', ''), '#', ''), '-', ''))) = LOWER(TRIM(REPLACE(REPLACE(REPLACE(REPLACE(NEW.address, '.', ''), ',', ''), '#', ''), '-', ''))) ORDER BY (CASE WHEN o.status = 'active' THEN 0 ELSE 1 END), o.created_at LIMIT 1; IF v_owner_id IS NOT NULL THEN NEW.owner_id := v_owner_id; END IF; END IF; RETURN NEW; END; $$; CREATE TRIGGER auto_link_violation_owner_trigger BEFORE INSERT OR UPDATE ON public.violations FOR EACH ROW EXECUTE FUNCTION public.auto_link_violation_owner();