I'm not sure any of this answers your question, because I don't see any question being asked.
If you are asking the question "Why am I observing this behavior?", see my answer below.
If you are asking, "What changes do I need to make to my statement so MySQL returns rows in a particular order?", also see my answer below.
If you are asking, "What is the exact statement do I need to run?", we're really just guessing, because it's not clear what order you want the rows returned in, there's no clear specification or description, there's no example data or example output, or rationale for which rows should be returned first.
MySQL is free to return the rows in any order it chooses, because there is no ORDER BY clause on the outermost query. (MySQL could actually ignore the ORDER BY clauses in the inline view queries, and be within spec of the ANSI SQL standard.)
To guarantee that the rows returned by the query will be returned in a particular order, add an ORDER BY clause on the outer query, before the LIMIT clause.
Note that the UNION set operator requires MySQL to check for "duplicate" rows within the combined set, and remove any duplicate rows.
If you don't have a requirement to perform that operation, you can use a UNION ALL set operator in place of the UNION.
In MySQL, it's also possible to omit the SELECT * FROM the beginning of the statement.
I could provide "try this" example SQL, but without knowing what order you actually want the rows returned in, we're just guessing.)
Here's my guess at a query that returns rows in the "order" you are expecting.
I'll show this using the UNION ALL set operator:
(
SELECT *,DATE_FORMAT(spa_date,"%e %M %Y") as spa_date_out
FROM spa
JOIN city USING (city_id)
WHERE spa_vip=1
)
UNION ALL
(
SELECT *,DATE_FORMAT(spa_date,"%e %M %Y") as spa_date_out
FROM spa
JOIN city USING (city_id)
WHERE spa_vip=0
)
ORDER
BY spa_vip DESC
, IF(spa_vip=1,RAND(),0)
, IF(spa_vip=0,spa_date,NULL) DESC
LIMIT 10,10
Let's unpack that a little bit. There's two queries that return rows, the UNION ALL set operator combines those two sets into a single set. (If your purpose for using the UNION set operator to remove duplicates, you can get that behavior back by replacing UNION ALL in this query with UNION.)
The first expression in the ORDER BY clause spa_vip gets us the rows from the first set first... we're guaranteed that all rows returned from that first query have a value of 1 for spa_vip, and all rows from the second query have a value of 0.
The second expression returns RAND() for rows from the first query, and a constant for rows from the second query.
The third expression returns a constant for all rows from the first query, and a value from the row for rows from the second query.
If that's the result you are expecting returned, that same result can also be returned by a statement like this:
SELECT *,DATE_FORMAT(spa_date,"%e %M %Y") as spa_date_out
FROM spa
JOIN city USING (city_id)
WHERE spa_vip IN (0,1)
ORDER
BY spa_vip DESC
, IF(spa_vip=1,RAND(),0)
, IF(spa_vip=0,spa_date,NULL) DESC
LIMIT 10,10