mysql - Applying an outer join to a complex statement

杀马特。学长 韩版系。学妹 提交于 2019-12-24 08:28:08

问题


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:

  1. GROUP_CONCAT over the name & score with addition of ORDER BY condition - note that the ORDER BY must be identical in both of the GROUP_CONCAT.
  2. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!