You just need to add a DISTINCT
to your SQL query, something like:
$cities = $wpdb->get_col("SELECT DISTINCT(meta_value) FROM $wpdb->usermeta WHERE meta_key = 'my_cities_meta_key'" );
Alternatively, if you want to do it with php for some reason (if you want to know that a city is listed twice before displaying only unique entries)
$cities = $wpdb->get_col("SELECT meta_value FROM $wpdb->usermeta WHERE meta_key = 'my_cities_meta_key'" );
$cities = array_keys(array_flip($cities));
If you want to filter based on user role using only SQL, something like this should work, though it’s a little hacky because WP stores user roles in a serialized array:
$cities = $wpdb->get_col("SELECT Distinct(c.meta_value)
FROM $wpdb->usermeta AS r
INNER JOIN $wpdb->usermeta AS c
ON r.user_id = c.user_id
WHERE r.meta_value LIKE '%subscriber%'
AND c.meta_key = 'my_cities_meta_key'");
To add filters based on multiple meta keys you do more INNER JOIN
, e.g. for your region metakey:
$cities = $wpdb->get_col("SELECT Distinct(c.meta_value)
FROM $wpdb->usermeta AS r
INNER JOIN $wpdb->usermeta AS c
ON r.user_id = c.user_id
INNER JOIN $wpdb->usermeta AS re
ON r.user_id = re.user_id
WHERE r.meta_value LIKE '%subscriber%'
AND c.meta_key = 'my_cities_meta_key'
AND re.meta_key = 'region'");