2009-01-08 31 views
5

que estoy tratando de hacer una gráfica de los carriles, por ejemplo, la cantidad de ventas promedio por día por cada día en un determinado intervalo de fechasmejor manera en MySQL o rieles para conseguir AVG por día dentro de un intervalo de fechas específico

Supongamos que tengo un modelo products_sold que tiene un atributo float "sales_price". Pero si un día específico no tiene ventas (por ejemplo, ninguno en el modelo/db), quiero devolver simplemente 0.

¿Cuál es la mejor manera en MySQL/Rails para hacer esto? Sé que puedo hacer algo como esto:

(Esta consulta SQL puede ser el camino completamente equivocado para conseguir lo que estoy queriendo demasiado)

SELECT avg(sales_price) AS avg, DATE_FORMAT(created_at, '%m-%d-%Y') AS date 
    FROM products_sold WHERE merchant_id = 1 GROUP BY date; 

y obtener resultados como este:

 
| avg | date | 
    23 01-03-2009 
    50 01-05-2009 
    34 01-07-2009 
    ...  ... 

Lo que me gustaría conseguir es la siguiente:

 
| avg | date | 
    23 01-03-2009 
    0 01-04-2009 
    50 01-05-2009 
    0 01-06-2009 
    34 01-07-2009 
    0 01-08-2009 
    ...  ... 

¿Puedo hacer esto con SQL o tendré que post-procesar los resultados para encontrar qué fechas en el rango de fechas no están en el conjunto de resultados de SQL? Tal vez necesito algunos sub-selects o declaraciones IF?

Gracias por ayudar a todos.

Respuesta

7

¿Existe alguna razón (aparte de la fecha que ya se mencionó) por la que no usaría las funciones integradas de función de grupo en ActiveRecord?Parece preocupado por el "postprocesamiento", que no creo que sea realmente algo de lo que preocuparse.

Estás en Rails, por lo que probablemente deberías estar buscando una solución de Rails primero [1]. Mi primer pensamiento sería hacer algo como

Product.average(:sales_price, :group => "DATE(created_at)", :conditions => ["merchant_id=?", 1]) 

que ActiveRecord se convirtió prácticamente en el SQL que describió. Suponiendo que hay una asociación entre declarada has_many comerciante y del producto, entonces probablemente sería mejor usar esa, así que algo como:

ave_prices = Merchant.find(1).products.average(:sales_price, :group => "DATE(created_at)") 

(espero que su descripción del modelo como "products_sold" es cierta tipo de error de transcripción, por cierto, si no está recibiendo un mensaje con el nombre de su clase!)

Después de todo eso, ha vuelto al punto de partida, pero ha llegado de una manera más convencional Rails (¡y Rails realmente valora las convenciones!). Ahora tenemos que llenar los vacíos.

Supongo que conoce su intervalo de fechas, digamos que se define como todas las fechas desde from_date hasta to_date.

date_aves = (from_date..to_date).map{|dt| [dt, 0]} 

Eso construye la lista completa de fechas como una matriz. No necesitamos las fechas donde nos dieron un promedio:

ave_price_dates = ave_prices.collect{|ave_price| ave_price[0]} # build an array of dates 
date_aves.delete_if { |dt| ave_price.dates.index(dt[0]) } # remove zero entries for dates retrieved from DB 
date_aves.concat(ave_prices)  # add the query results 
date_aves.sort_by{|ave| ave[0] } # sort by date 

Esa gran cantidad parece un poco desordenado para mí: Creo que podría ser más concisa y más limpio. Investigaría construyendo un Hash o Struct en lugar de quedarme en arreglos.


[1] No estoy diciendo que no utilizan SQL - se producen situaciones donde ActiveRecord no puede generar la consulta más eficiente y que recurrir a find_by_sql. Está bien, se supone que es así, pero creo que deberías tratar de usarlo solo como último recurso.

0

¿MySQL tiene funciones de retorno de conjunto? Es decir. funciones que devuelven valores diferentes en cada fila de una consulta? Como un ejemplo de PostgreSQL, que puede hacer:

select 'foo', generate_series(3, 5); 

Esto producirá un conjunto de resultados que consta de 2 columnas y 3 filas, donde la columna de la izquierda contiene 'foo' en cada fila y columna de la derecha contiene 3, 4 y 5.

Entonces, suponiendo que tiene un equivalente de generate_series() en MySQL, y subconsultas: Lo que necesita es un LEFT OUTER JOIN de esta función a la consulta que ya tiene. Esto asegurará que se ve cada fecha aparece en la salida:

SELECT 
    avg(sales_price) as avg, 
    DATE_FORMAT(the_date, '%m-%d-%Y') as date 
FROM (select cast('2008-JAN-01' as date) + generate_series(0, 364) as the_date) date_range 
LEFT OUTER JOIN products_sold on (the_date = created_at) 
WHERE merchant_id = 1 
GROUP BY date; 

Es posible que tenga que jugar con esto un poco para obtener el derecho de sintaxis para MySQL.

2

Para cualquier consulta de este tipo, deberá encontrar un mecanismo para generar una tabla con una fila para cada fecha que desee informar. A continuación, realizará una combinación externa de esa tabla con la tabla de datos que está analizando. También es posible que deba jugar con NVL o COALESCE para convertir nulos en ceros.

La parte difícil es encontrar la forma de generar la tabla (temporal) que contiene la lista de fechas para el rango que necesita analizar. Eso es específico de DBMS.

Sin embargo, su idea de mapear valores de fecha/hora a una fecha única es inmediata. Tendría que recurrir a un truco similar, asignando todas las fechas a un formato de fecha ISO 8601 como 2009-W01 para la semana 01, si desea analizar las ventas semanales.

Además, sería mejor que asignara su formato de FECHA a la notación 2009-01-08 porque entonces puede ordenar el orden de las fechas con una ordenación de caracteres simples.

2

Para secar un poco:

ave_prices = Merchant.find(1).products.average(:sales_price, :group => "DATE(created_at)") 
date_aves = (from_date..to_date).map{|dt| [dt, ave_prices[dt.strftime "%Y-%m-%d"] || 0]} 
Cuestiones relacionadas