How to use SELECT DISTINCT ON with MySQL and Rails

旧城冷巷雨未停 提交于 2019-12-11 04:35:15

问题


I have quite a complex query to essentially select the cheapest delivery service price per delivery service.

In order to get unique records per delivery service, I utilise the DISTINCT function in SQL. This query provides correct results:

DeliveryServicePrice.active.select('DISTINCT ON (delivery_service_id) *').order('delivery_service_id, price ASC')

(only a part of the query)

However, this query only seems to work with PostgreSQL (which I think is strange considering PostgreSQL is a lot more strict with SQL standards); it does not work with MySQL and SQLite. I receive the following error:

Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'ON (delivery_service_id) * FROM `delivery_service_prices` WHERE `delivery_servi' at line 1: SELECT DISTINCT ON (delivery_service_id) * FROM `delivery_service_prices` WHERE `delivery_service_prices`.`active` = 1 AND (2808.0 >= min_weight AND 2808.0 <= max_weight AND 104.0 >= min_length AND 104.0 <= max_length AND 104.0 >= min_thickness AND 104.0 <= max_thickness) ORDER BY delivery_service_id, price ASC

The application I'm building is open source, so it's required to support all 3 database types. How do I create DISTINCT ON queries for MySQL and SQLite in the Rails framework syntax?

I'm using Rails 4.1.

Resources

My previous problem for reference:
How to select unique records based on foreign key column in Rails?

File and line number for where the query is being used.

Finished answer

DeliveryServicePrice.select('delivery_service_prices.id').active.joins('LEFT OUTER JOIN delivery_service_prices t2 ON (delivery_service_prices.delivery_service_id = t2.delivery_service_id AND delivery_service_prices.price > t2.price)').where('t2.delivery_service_id IS NULL')

回答1:


DISTINCT ON is a Postgres specific extension to the standard SQL DISTINCT. Neither of them is a "function", both are SQL key words - even though the parentheses required after DISTINCT ON make it look like a function.

There are a couple of techniques to rewrite this with standard-SQL, all of them more verbose, though. Since MySQL does not support window-functions row_number() is out.

Details and more possible query techniques:

  • Select first row in each GROUP BY group?

  • Fetch the row which has the Max value for a column

Rewritten with NOT EXISTS:

SELECT *
FROM   delivery_service_prices d1
WHERE  active = 1
AND    2808.0 BETWEEN min_weight AND max_weight
AND    104.0  BETWEEN min_length AND max_length
AND    104.0  BETWEEN min_thickness AND max_thickness
AND NOT EXISTS (
   SELECT 1
   FROM   delivery_service_prices d2
   WHERE  active = 1
   AND    2808.0 BETWEEN min_weight AND max_weight
   AND    104.0  BETWEEN min_length AND max_length
   AND    104.0  BETWEEN min_thickness AND max_thickness
   AND    d2.delivery_service_id = d1.delivery_service_id
   AND    d2.price < d1.price 
   AND    d2.<some_unique_id> < d1.<some_unique_id>  -- tiebreaker!
   )
ORDER  BY delivery_service_id
  • If there can be multiple rows with the same price for the same delivery_service_id, you need to add some unique tie-breaker to avoid multiple results per delivery_service_id. At least if you want a perfectly equivalent query. My example would select the row with the smallest <some_unique_id> from each set of dupes.

  • Unlike with DISTINCT ON, ORDER BY is optional here.




回答2:


DeliveryServicePrice.active.select(:delivery_service_id).distinct.order('delivery_service_id, price ASC')


来源:https://stackoverflow.com/questions/26894868/how-to-use-select-distinct-on-with-mysql-and-rails

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