ruby-on-railsrubypostgresqlfind-by-sql

Why is find_by_sql not working with PostgreSQL?


I have a PostgreSQL query that sums values by month, extracting the month value from a date:

SELECT vendors.name AS vendor, 
SUM(purchases.invoice_total) AS invoice_total, 
EXTRACT(MONTH FROM purchases.date) AS date 
FROM vendors, purchases 
WHERE purchases.vendor_id = vendors.id 
AND purchases.store_id = ? 
AND purchases.purchase_department_id = ? 
AND (purchases.date BETWEEN ? AND ?) 
GROUP BY vendor, EXTRACT(MONTH FROM purchases.date) 
ORDER BY vendor, date;

After executing the query directly in the DB console, I get something like this:

+------------------------------------+---------------+------+
| vendor                             | invoice_total | date |
+------------------------------------+---------------+------+
| Store 1                            |        448.56 |    1 |
| Store 1                            |        263.44 |    2 |
| Store 1                            |        939.47 |    3 |

The problem comes when using Rails' find_by_sql function. The vendor and invoice_total values get returned correctly, but the date values get returned as nil.

#<Purchase date: nil, invoice_total: #<BigDecimal:85c75c8,'0.26344E3',18(18)>>

This is my call. I've even tried with hardcoded values and nothing, but it's like the Extract function won't execute in Rails:

<% @purchasesSQL = Purchase.find_by_sql(["SELECT vendors.name AS vendor, SUM(purchases.invoice_total) AS invoice_total, 
    EXTRACT(MONTH FROM purchases.date) AS date 
    FROM vendors, purchases 
    WHERE purchases.vendor_id = vendors.id 
    AND purchases.store_id = ? 
    AND purchases.purchase_department_id = ? 
    AND (purchases.date BETWEEN ? AND ?) 
    GROUP BY vendor, EXTRACT(MONTH FROM purchases.date) 
    ORDER BY vendor, date;", @store.id, purchase_department.id, @start, @end])
%>

I've already done some debugging and the values I'm passing are not nil nor wrong.


Solution

  • You say that you have a column named date in your Purchase model's table (that's a terrible name for column BTW, using type names for column names just leads to pain and suffering). Presumably your date column is a date or timestamp. In your Purchase.find_by_sql, you have this:

    EXTRACT(MONTH FROM purchases.date) AS date 
    

    and Purchase.find_by_sql will be trying to create Purchase instances. So you're giving Purchase a simple number and calling it date, Purchase probably notices that it has a date column and tries to parse your simple number as a full date or timestamp. A simple number cannot be parsed as a date or timestamp in any meaningful fashion so they're probably all get converted to nil.

    You have two things to do here:

    1. Renamed that date column to something else, something that doesn't conflict with any keywords.
    2. And to hopefully fix your immediate problem, use a different alias for your month number:

      EXTRACT(MONTH FROM purchases.date) AS month_number
      

      That should get you a month_number method on the returned Purchase objects.