Change of category structure will cause two kinds of URLs for one post

when visiting the old structure URL, it will NOT be redirected to
the new one

That’s because:

  1. The default category taxonomy is hierarchical and so does the permalink structure, hence its rewrite rules use a RegEx pattern like /category/(.+?)/? which will match any category path even if it contains invalid/non-existent term slugs. ( That (.+?) part is the category path )

  2. And as long as the last term slug in the category path (in the above rule) is valid, i.e. the term exists, then WordPress would load the category page normally, i.e. without throwing a 404 error or redirecting to the correct URL (the one with the right category path).

So as for the first URL in question, where the category path is data-recovery/file-recovery/zip-file-recovery, it contains valid term slugs, and thus WordPress didn’t redirect to the new URL.

And as you may have guessed it now, invalid category paths like foo/bar/zip-file-recovery, where “foo” and “bar” are non-existent categories, would also work because the last slug is valid.

How to fix the issue

You can use this to ensure the category path is valid before loading the category archive page:

add_action( 'wp', 'wpse_399267_wp', 1 );
function wpse_399267_wp( $wp ) {
    if ( is_category() &&
        ( $the_cat = get_queried_object() ) &&
        ! empty( $wp->query_vars['category_name'] )
    ) {
        // Check if the category path is valid.
        $cat = get_category_by_path( $wp->query_vars['category_name'] );

        // If the category path is invalid, redirect to the correct one.
        if ( empty( $cat ) && ! is_wp_error( $cat ) ) {
            wp_redirect( get_category_link( $the_cat ) );
            exit;
        }
    }
}

Or if you’d rather want to show a 404 error page, then..

// Replace these:
wp_redirect( get_category_link( $the_cat ) );
exit;

// with these:
$GLOBALS['wp_query']->set_404();
status_header( 404 );
nocache_headers();