Execute shortcode twice in the same page

I believe the real issue you had was with echoing your data instead of returning it.

A shortcode should return the output to be drawn, which is then drawn in place of the short code.

Here is the code using HEREDOC syntax for outputs and the query. I believe this makes things much more readable once you get used to it. See http://php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc

Changes to get_past_events:

  1. Moved it out of the other function (for clarity)
  2. Provided a default input variable (for clarity of input expectations)
  3. Skip processing if no events received
  4. Moved get_site_url() call out of loop (same for every item)
  5. Used HEREDOC notation (for clarity and clean output)
  6. Renamed the function to make it less likely to conflict

The get_past_events function:

function do_my_get_past_events( $past_events = array() ) {
   $output="";

   if ( is_array($past_events) && 0 < count($past_events) ) {
      $site_url = get_site_url();
      foreach ( $past_events as $past_event_slug ) {
         $output .= <<<HTML
<li><a href="https://wordpress.stackexchange.com/questions/175004/{$site_url}/eventi/{$past_event_slug}">{$past_event_slug}</a></li>

HTML;

      }
   }
   return $output;
}

Changes to archive_render:

  1. Pull shortcode attributes into an array (clarity)
  2. Declare event_items (so we can skip processing if needed)
  3. Added prepare statement (prevent sql injection) using digit
  4. Used HEREDOC for query string (clarity)
  5. Adjusted rientro variable (left hand declaration – clarity)
  6. Changed string variable to custom_short_code (clarity)
  7. Return the final output (the actual fix)
  8. Added optional block in case a default is wanted
  9. Renamed the function to make it less likely to conflict with anything

The archive_render function:

add_shortcode("archive", "do_my_custom_archive_render");
function do_my_custom_archive_render($atts) {

   $options = shortcode_atts(
      array(
         'year' => 0,
         'rientro' => 'no'
      ),
      $atts
   );

   $past_event_items="";

   if ( !empty( $options['year'] ) ) {
      global $wpdb;

      $query = <<<SQL
 SELECT event_name 
 FROM wp_em_events 
 WHERE 
   EXTRACT(YEAR FROM event_end_date) = %d 
   AND event_end_date < CURDATE()
SQL;

      $query = $wpdb->prepare( $query, (int) $options['year'] );

      $past_events = $wpdb->get_col($query);

      $past_event_items = do_my_get_past_events( $past_events );
   }

   $rientro = ( 'si' == "{$options['rientro']}" )? 'yes' : 'no';

   # If there are no event items for the year provided the user will get an empty box
   # If that is not what is wanted, uncomment the following 3 lines
   # if ( empty($past_event_items) ) {
   #    $past_event_items="<li>No Events Found</li>";
   # }

   $custom_short_code = <<<HTML
[one_third last="{$rientro}" class="" id=""]
   [accordian class="" id=""]
      [toggle title="{$options['year']}" open="no"]
         <ul>
            {$past_event_items}
         </ul>
      [/toggle]
   [/accordian]
[/one_third]
HTML;

   return do_shortcode( $custom_short_code );
}

Here is the same code without using HEREDOC for those unfamiliar with it.

function do_my_get_past_events( $past_events = array() ) {
   $output="";

   if ( is_array($past_events) && 0 < count($past_events) ) {
      $site_url = get_site_url();
      foreach ( $past_events as $past_event_slug ) {
         $output .= '<li><a href="' . $site_url . '/eventi/' . $past_event_slug . '">' . $past_event_slug . '</a></li>' . "\r\n";
      }
   }

   return $output;
}

add_shortcode("archive", "do_my_custom_archive_render");
function do_my_archive_render($atts) {

   $options = shortcode_atts(
      array(
         'year' => 0,
         'rientro' => 'no'
      ),
      $atts
   );

   $past_event_items="";

   if ( !empty( $options['year'] ) ) {
      global $wpdb;

      $query = "SELECT event_name FROM wp_em_events WHERE EXTRACT(YEAR FROM event_end_date) = %d AND event_end_date < CURDATE()";

      $query = $wpdb->prepare( $query, (int)$options['year'] );

      $past_events = $wpdb->get_col($query);

      $past_event_items = do_my_get_past_events( $past_events );
   }

   $rientro = ( 'si' == "{$options['rientro']}" )? 'yes' : 'no';

   # Uncomment the following 3 lines if you want.
   # if ( empty($past_event_items) ) {
   #    $past_event_items="<li>No Events Found</li>";
   # }

   $custom_short_code="[one_third last="" . $rientro . '" class="" id=""][accordian class="" id=""][toggle title="' . $options['year'] . '" open="no"]<ul>' . $past_event_items . '</ul>[/toggle][/accordian][/one_third]';

   return do_shortcode( $custom_short_code );
}