sábado, 29 de diciembre de 2012

Limitar los resultados solamente a un lado de la relación

Sucede que en ocasiones queremos obtener información de dos o más tablas, pero limitando la cantidad de resultados solamente a un lado de la relación, normalmente en la tabla maestra. 

Supongamos que adicionamos al modelo con el que hemos trabajado, la posibilidad de  almacenar varios teléfonos asociados a un mismo cliente, indicando además que tipo de teléfono es (fax, trabajo, móvil, casa), y detalles sobre días de la semana y horas en que se aconseja llamar a ese teléfono.
Luego nos solicitan obtener los datos de los 3 clientes que más han aportado a la compañía en el año 2012 conjuntamente con el valor total del importe, mostrando además todos sus teléfonos con las características adicionales.

Muestro a continuación las mejoras al modelo que hemos estado utilizando, donde se incluyen  dos nuevas tablas, una donde se especifican los tipos de teléfonos y otra donde se almacenarán propiamente los teléfonos asociados a cada cliente. Pierde sentido mantener el campo telefono de la tabla clientes.

Evidentemente para dar solución a este problema no podemos limitar la consulta a solo 3 registros, pues no obtendríamos el resultado correcto, ya que un cliente puede tener más de un teléfono.
Obtengamos primeramente los 3 clientes solicitados (problema similar abordado en otro post), y luego veamos cómo obtener sus teléfonos en la misma consulta. Está claro que para la presentación de esta información, sea cual sea la plataforma o aplicación se necesita luego  de un procesamiento previo, muchas veces incluidas implícitamente en los motores de plantillas de reportes, o de generación de páginas web dinámicas, a partir de un agrupamiento del conjunto de datos.

En postgres esta sería una solución a esa primera consulta:

SELECT c.id_cliente, c.nombre, c.direccion, c.ciudad, 
       SUM(a.precio_unitario*a.cantidad) AS importe_total
FROM clientes c INNER JOIN ordenes o ON (c.id_cliente=o.cliente)
     INNER JOIN articulos a ON (a.orden=o.id_orden)
WHERE EXTRACT(YEAR FROM o.fecha) = 2012
GROUP BY  c.id_cliente, c.nombre, c.direccion, c.ciudad
ORDER BY importe_total DESC
LIMIT 3

Si utilizáramos un enlace de la tabla clientes con la tabla telefonos_clientes para obtener los teléfonos, no tendríamos forma de conocer a cuántos registros debemos limitar el resultado, por lo que una posible solución es utilizar la consulta anterior como subconsulta a la que haremos un enlace con la tabla telefonos_clientes para obtener todos los telefonos de los clientes obtenidos en la subconsulta.

SELECT c.id_cliente, c.nombre, c.direccion, c.ciudad, c.importe_total, 
       t.numero as telefono, tt.nombre as tipo_telefono, t.detalles
FROM (
  SELECT c.id_cliente, c.nombre, c.direccion, c.ciudad, 
         SUM(a.precio_unitario*a.cantidad) AS importe_total
  FROM clientes c INNER JOIN ordenes o ON (c.id_cliente=o.cliente)
       INNER JOIN articulos a ON (a.orden=o.id_orden)
  WHERE EXTRACT(YEAR FROM o.fecha) = 2012
  GROUP BY  c.id_cliente, c.nombre, c.direccion, c.ciudad
  ORDER BY importe_total DESC
  LIMIT 3
) c 
LEFT JOIN telefonos_clientes t ON (c.id_cliente=t.cliente)
LEFT JOIN tipos_telefonos tt ON (t.tipo=tt.id_tipo)
ORDER BY c.importe_total DESC

Note que el enlace con telefonos_clientes se hace utilizando un LEFT JOIN, pues puede suceder que algunos clientes no tengan teléfonos asociados. Note además que para garantizar que el resultado final nos muestre los clientes ordenados por importe de mayor a menor, es obligatorio volver a ordenar por ese valor en la consulta exterior.

Esto es todo por ahora, en la próxima entrada estaremos hablando sobre cómo definir y obtener un índice de popularidad basado en resultados históricos.

1 comentario:

  1. Este comentario ha sido eliminado por un administrador del blog.

    ResponderEliminar