问题
I put together a query to pull out peoples' names, giving preference to the version of that name that is written in the language/writing script of the person viewing the page.
I was having some troubles with missing records earlier, and posted a question about that here. I received an answer that suggested using a CASE statement and applying a score to rank the names based on the preferred language/script. I've gotten as far as the ranking, which has allowed the correct number of records to be returned (no missing records now). See below:
SELECT
people.person_id,
names.name,
CASE
WHEN people.person_default_name_id=names.name_id AND language_scripts.script_id = :user_script_id THEN 3
WHEN names.language_id = :user_language_id THEN 2
WHEN language_scripts.script_id = :user_script_id THEN 1
ELSE 0
END as score
FROM
`people`
LEFT JOIN
`names` ON names.person_id=people.person_id
LEFT JOIN
`languages` ON names.language_id = languages.language_id
LEFT JOIN
`language_scripts` ON languages.language_id = language_scripts.language_id
GROUP BY
people.person_id
ORDER BY
names.name ASC
The trouble I'm now having is that, because I just need one result per person (hence the GROUP BY), it's simply pulling out the first record it encounters per person. I need to be able to put the "score" to work and only pull out the record with the highest available score (which could be 3, 2, 1 or 0) -- the one record per person with the highest score.
The suggestion in the original post was to use an outer query, but I'm not sure how to apply that here. Any ideas?
EDIT: This fiddle shows an example that pulls out all records with their scores: http://sqlfiddle.com/#!9/54ce8/13 -- what I want to be able to do is to just draw out the one record per person_id that has the highest score. In this example, I'd like a table with Jorge and Alejandro only as they are the highest scored names for each of the two.
回答1:
I can only think of one way to achieve this. With a combination of GROUP_CONCAT
and SUBSTRING_INDEX
on your current working query. Example query below:
SELECT person_id,
SUBSTRING_INDEX(GROUP_CONCAT(a.name ORDER BY a.score DESC),',',1) sname,
SUBSTRING_INDEX(GROUP_CONCAT(a.score ORDER BY a.score DESC),',',1) max_score FROM
(SELECT people.person_id,
names.name,
CASE
WHEN people.person_default_name_id=names.name_id AND language_scripts.script_id = '1' THEN 3
WHEN names.language_id = '1' THEN 2
WHEN language_scripts.script_id = '1' THEN 1
ELSE 0
END AS score
FROM `people`
LEFT JOIN `names` ON names.person_id=people.person_id
LEFT JOIN `languages` ON names.language_id = languages.language_id
LEFT JOIN `language_scripts` ON languages.language_id = language_scripts.language_id) a
GROUP BY person_id;
I did test the query in the fiddle as well http://sqlfiddle.com/#!9/54ce8/33
A bit of explanation:
GROUP_CONCAT
over the name & score with addition ofORDER BY
condition - note that theORDER BY
must be identical in both of theGROUP_CONCAT
.SUBSTRING_INDEX
to get the first value separated by comma (,) in the field.
回答2:
Is returning the score
important? Or is the goal to just return a name
that satisfies the condition for score=3, and if that doesn't exist, the return a name
that satisfies the condition for score=2, else ...
One way to do that would be to do conditional aggregation, to return value of name
when a condition is met, else return NULL, then pick out the non-NULL value with a MAX()
(or MIN()
aggregate function.
We could then use a COALESCE
function to give us the first non-NULL value, checking if we get a score=3 row, then a score=2 row, etc.
SELECT p.person_id
, COALESCE(
MAX(CASE
WHEN p.person_default_name_id = n.name_id AND s.script_id = '1'
THEN n.name
ELSE NULL
END)
, MAX(CASE
WHEN n.language_id = '1'
THEN n.name
ELSE NULL
END)
, MAX(CASE
WHEN s.script_id = '1'
THEN n.name
ELSE NULL
END)
, MAX(n.name)
) AS `name`
-- additional expressions so we can see what is happening
, MAX(CASE
WHEN p.person_default_name_id = n.name_id AND s.script_id = '1'
THEN n.name
ELSE NULL
END) AS s3_name
, MAX(CASE
WHEN n.language_id = '1'
THEN n.name
ELSE NULL
END
) AS s2_name
, MAX(CASE
WHEN s.script_id = '1'
THEN n.name
ELSE NULL
END) AS s1_name
, MAX(n.name) AS s0_name
FROM `people` p
LEFT
JOIN `names` n
ON n.person_id = p.person_id
LEFT
JOIN `languages` l
ON l.language_id = n.language_id
LEFT
JOIN `language_scripts` s
ON s.language_id = l.language_id
GROUP
BY p.person_id
ORDER
BY ...
Demonstration here: http://sqlfiddle.com/#!9/54ce8/37
来源:https://stackoverflow.com/questions/59185690/mysql-applying-an-outer-join-to-a-complex-statement