2010-03-15 11 views
5

Éste parece ser un problema simple, pero no puedo hacer que funcione en un solo seleccione o anidado seleccione. Recupere los autores y (si los hay) asesores de un documento (artículo) en una fila.Subselección de columna múltiple en mysql 5 (5.1.42)

I para explicar el problema, aquí están las dos tablas de datos (pseudo)

papers (id, title, c_year) 
persons (id, firstname, lastname) 

además de una mesa de enlace w/un atributo adicional (pseudo):

paper_person_roles(
    paper_id 
    person_id 
    act_role ENUM ('AUTHOR', 'ADVISER') 
) 

Esto es, básicamente, una lista de documentos escritos (tabla: documentos) y una lista del personal y/o estudiantes (tabla: personas)

un artículo tiene mis (1, N) autores.
Un artículo puede tener asesores (0, N).
Una persona puede estar en el rol 'AUTOR' o 'ASESOR' (pero no al mismo tiempo).

La aplicación pone finalmente a cabo filas de la tabla que contiene los entradas siguientes:

 
TH: || Paper_ID | Author(s)   | Title     | Adviser(s) | 
TD: || 21334 |John Doe, Jeff Tucker|Why the moon looks yellow|Brown, Rayleigh| 
... 

Mi primer acercamiento fue como:
seleccionar/extraer una lista completa de los artículos en la aplicación, por ejemplo.

SELECT 
    q.id, q.title 
FROM 
    papers AS q 
ORDER BY 
    q.c_year 
y guarde los resultados de la consulta en una matriz (en la aplicación). Después de este paso , recorra la matriz de la información devuelta y recupere los autores y los asesores (si corresponde), mediante una declaración preparada (? Es la identificación del documento) de la tabla de enlace como
APPLICATION_LOOP(paper_ids in array) 
    SELECT 
     p.lastname, p.firstname, r.act_role 
    FROM 
     persons AS p, paper_person_roles AS r 
    WHERE 
     p.id=r.person_id AND r.paper_id = ? 
    # The application does further processing from here (pseudo): 
    foreach record from resulting records 
    if record.act_role eq 'AUTHOR' then join to author_column 
    if record.act_role eq 'ADVISER' then join to avdiser_column 
    end 
    print id, author_column, title, adviser_column 
APPLICATION_LOOP 
Esto funciona hasta ahora y le da al salida deseada. ¿Sería necesario devolver el cálculo al DB?

No soy muy competente en SQL no trivial y no puedo encontrar una solución con una sola llamada de selección (combinada o anidada). I intenté algo. como

SELECT 
    q.title 
    (CONCAT_WS(' ', 
    (SELECT p.firstname, p.lastname AS aunames 
     FROM persons AS p, paper_person_roles AS r 
     WHERE q.id=r.paper_id AND r.act_role='AUTHOR') 
    ) 
    ) AS aulist 
FROM 
    papers AS q, persons AS p, paper_person_roles AS r 
en varias variaciones, pero no hubo suerte ...

¿Tal vez hay alguna posibilidad?

Gracias de antemano

de fondo redondo

Respuesta

2

La siguiente consulta funcionó con mis datos de prueba, pruébelo.

Las dos subconsultas son necesarias para obtener la lista de autores/asesores por artículo.

Select 
    p.id, 
    p.title, 
    p_aut.aut_name, 
    p_adv.adv_name 
From papers p 
Left Join (
    Select pp_aut.paper_id, 
     Group_Concat(Concat(p_aut.firstname, ' ', p_aut.lastname)) aut_name 
    From paper_person_roles pp_aut 
    Join persons p_aut On (p_aut.id = pp_aut.person_id) 
    Where pp_aut.act_role='AUTHOR' 
    Group By pp_aut.paper_id 
) p_aut On (p_aut.paper_id = p.id) 
Left Join (
    Select pp_adv.paper_id, 
     Group_Concat(Concat(p_adv.firstname, ' ', p_adv.lastname)) adv_name 
    From paper_person_roles pp_adv 
    Join persons p_adv On (p_adv.id = pp_adv.person_id) 
    Where pp_adv.act_role='ADVISER' 
    Group By pp_adv.paper_id 
) p_adv On (p_adv.paper_id = p.id) 
Group By p.id, p.title 
+0

Peter, su respuesta es increíble. Este realmente funciona, solo tuve que eliminar el CONCAT interno (..) del asesor unido para obtener exactamente el resultado deseado (apellido de los asesores solamente). La base de datos no es muy grande (hasta ahora), por lo que el tiempo de respuesta en estas uniones múltiples es inferior a 1/10'th de segundo. Intentaré imaginar cómo insertar un espacio en blanco si no se devuelve ningún asesor (se devuelve el campo NULO). ¡Muchas gracias! –

+1

De nada :) Para insertar un espacio en blanco en lugar de 'NULL', puede usar' COALESCE (p_aut.aut_name, '') ' –

+0

OK, aprendí mucho de su respuesta. Ahora tengo algo de confianza en poner atributos NULL-able en las tablas (para los que no existen primero- y nombres intermedios), porque solo entonces las funciones Concat/Group_Concat arrojarán resultados útiles sin más posprocesamiento. BTW. Probé la consulta temporalmente en el entorno de producción y el tiempo de respuesta se redujo a la mitad en comparación con la anterior solución "procedimental" . Gracias y buenas noches –

2

En mi experiencia, las bases de datos SQL no son muy buenas para agregar datos tabulares como esos en una sola fila de datos condensados. Básicamente, creo que el enfoque que estás utilizando está bien, sin embargo, la otra alternativa que me surge es simplemente unirte a la mesa de personas de modo que consigas una fila por cada persona que tenga un rol para un trabajo determinado.

Algo así como:

SELECT q.id, q.title, p.firstName, p.lastName, r.act_role FROM papers q, persons p, 
     paper_person_roles r where r.paper_id = q.id and r.person_id = p.id 

Lo que para el ejemplo dado que mostró anteriormente obtendrían sus datos como el siguiente:

21334 |Why the moon looks yellow|John Doe |AUTHOR 
21334 |Why the moon looks yellow|Jeff Tucker|AUTHOR 
21334 |Why the moon looks yellow|Brown  |ADVISER 
21334 |Why the moon looks yellow|Rayleigh |ADVISER 

y que es lo suficientemente fácil de analizar al final resultar' que estas buscando.

Con este tipo de cosas, todo se trata de concesiones:
- ¿Está gastando demasiado tiempo de volver a la base de datos una y otra?
- ¿Hay demasiados datos que no se pueden unir a todos a la vez?
- ¿Su "optimización" hace que su código sea demasiado difícil de leer?

Francamente, si su código funciona de la manera que usted desea y aún no ha logrado los problemas de rendimiento, manténgalo como está y vuelva a tomar esta decisión el día en que empiece a ver la degradación del rendimiento a medida que su conjunto de datos aumenta.

+0

Bryan, sus preguntas en negrita son válidas y tendré que pensar en eso. El simple 'all tables' select w/row postprocessing (como sugirió) también debería ser aceptable en este caso (no demasiadas filas en el db). Solo incluiría una secuencia inteligente de varias uniones (como propuso Peter) en el código de producción si puedo entenderla bien en algún momento en el futuro ;-) Gracias –

Cuestiones relacionadas