WP_Query with rewind_posts creates duplicate titles

You have a couple of issues here

  • You are checking whether the main query returned any posts and then loop through the posts of your custom query

  • rewind_posts() is wrongly used. By default, rewind_posts() rewinds the loop of the main query. You should rewind the posts of your custom query.

  • Your last loop just basically loops through all the posts again and then displays them all

  • If a post belongs to more than one term, you can have duplicates. Best to do a check to avoid those

  • Your tax_query does not really make sense. In your loop you are checking terms from the default taxonomy category while you are querying posts from another custom taxonomy. Anyways, that is for you to think about

Lets look at a skeletal solution

$args = [
    // Your query parameters
];
$query = new WP_Query( $args );

// Test to see if you have posts from the custom query, not main query
if ( $query->have_posts() ) :
    // Create an array to avoid duplicates
    $duplicates = [];

    // Run the loop for the first time
    while ( $query->have_posts() ) :
        $query->the_post();

        if ( in_category( 'First' ) ) :
            // Display what you need
            the_title();

            // Save the post ID to avoid duplicates
            $duplicates[] = get_the_ID();

        endif;
    endwhile; // End your first loop

    $query->rewind_posts(); // Rewind the custom loop

    // Start the second loop
    while ( $query->have_posts() ) :
        $query->the_post();

            if ( in_category( 'Second' ) ) :
                // Make sure the post is not duplicated
                if ( in_array( get_the_ID(), $duplicates ) )
                    continue;

            // Post is not duplicate, display it
            the_title();

            // Save the post ID to $duplicates
            $duplicates[] = get_the_ID();

        endif;
    endwhile; // End second loop

    $query->rewind_posts(); // Rewind second custom loop          

    // Run your loop one more time
    while ( $query->have_posts() ) :
        $query->the_post();

            // Check if the post is already displayed, if so, skip it
            if ( in_array( get_the_ID(), $duplicates ) )
                continue;

            // Display the post
            the_title();

    endwhile; // Close the last loop
    wp_reset_postdata(); // Reset $post global
endif;