简体   繁体   English

Chartkick 同时分组和排序

[英]Chartkick group and order by at the same time

I have a working chart in chartkick but months are not showing in order我在 chartkick 中有一个工作图表,但月份没有按顺序显示

(October -> March -> February) (十月 -> 三月 -> 二月)

I need them in a correct order我需要它们以正确的顺序

(February -> March -> October ) (二月 -> 三月 -> 十月)

My schema我的架构

  create_table "business_data", force: :cascade do |t|
    t.integer "net_sales"
    t.text "next_steps"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "month_name"
    t.date "start_date"
    t.date "end_date"
  end

Controller Controller

@business_data = BusinessDatum.all.order(start_date: :asc)

View看法

<%= line_chart @business_data.group(:month_name).sum(:net_sales) %>

By adding the .order I get the following error:通过添加.order我得到以下错误:

PG::GroupingError: ERROR:  column "business_data.start_date" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: ...a" GROUP BY "business_data"."month_name" ORDER BY "business_...

¿How can I order the months in the correct order? ¿我怎样才能以正确的顺序排列月份?

tl;dr tl;博士

You can try finding the aggregates sales per month_name , and then sort them您可以尝试查找每月的总month_name ,然后对其进行排序

Here is what that looks like这就是它的样子

<%= line_chart BusinessDatum.all.group(:month_name).sum(:net_sales).sort_by {|s| Date::MONTHNAMES.index(s[0])} %>

Note: I put the whole query on a single line, but that should work even if you break it between controller and action (or preferably move completely into the controller action)注意:我将整个查询放在一行上,但即使您在 controller 和操作之间中断它也应该有效(或者最好完全进入 controller 操作)

Longer Discussion:更长的讨论:

Based on the question, I am going to guess that there is a conceptual misunderstanding about how ActiveRecord/Postgres is executing your query.基于这个问题,我猜测对于 ActiveRecord/Postgres 如何执行您的查询存在概念上的误解。 If you look at the original query如果您查看原始查询

BusinessDatum.all.order(start_date: :asc).group(:month_name).sum(:net_sales)

the SQL that this expression would generate is该表达式将生成的 SQL 是

; SQL generated by AR
SELECT 
  SUM("net_sales"), "month_name"
FROM 
  "business_data" 
GROUP BY 
  "month_name" 
ORDER BY 
  "start_date" ASC

I am gonna guess that you were expecting Postgres to first order the data by start_date and then do a group_by (assuming that if grouping is applied to the ordered data, the aggregations will be ordered as well).我猜你希望 Postgres 首先按start_date对数据进行排序,然后执行group_by (假设如果对有序数据应用分组,聚合也将被排序)。 However, that is not how Postgres works.然而,这不是 Postgres 的工作方式。

In fact, if we change the query to have order by before group by , it would be an invalid query.事实上,如果我们将查询更改为在group by之前有order by ,这将是一个无效的查询。 The following is an invalid query以下是无效查询

; this query is invalid because the order of order by and group by is not correct
SELECT 
  SUM("net_sales"), "month_name"
FROM 
  "business_data" 
ORDER BY 
  "start_date" ASC
GROUP BY 
  "month_name"

Let's dig a little more.让我们再挖一点。 If you look at the SQL generated by AR, the error that you are seeing gets clear.如果您查看 AR 生成的 SQL,您看到的错误就会很清楚。 Postgres is going to take the data in the table, group the data by month, and then sum the net_sales . Postgres 将取表中的数据,按月份对数据进行分组,然后对net_sales When it does that the outcome will contain only the sum(net_sales) and month_name .当它这样做时,结果将仅包含sum(net_sales)month_name The data will look something like this数据看起来像这样

sum(net_sales)总和(净销售额) month_name月名
100 100 October十月
200 200 February二月
300 300 March行进

As you can see, the grouped data does not contain the start_date .如您所见,分组数据不包含start_date In fact, it probably would not make sense to have a single start_date here as the start_date column can contain lots of values.事实上,这里有一个start_date可能没有意义,因为start_date列可以包含很多值。 This is why the error you saw gave you two options to fix:这就是为什么您看到的错误为您提供了两个修复选项:

  1. Either use start_date in the group by clause (so that the start_date is part of the grouped data (which will then allow you to order by start_date . If you add start_date in the group clause though, the data will look something like this (the month_name and start_date are no longer unique in the grouped datagroup by子句中使用start_date (以便start_date是分组数据的一部分(然后允许您按start_date排序。如果您在 group 子句中添加start_date ,数据将看起来像这样( month_namestart_date在分组数据中不再唯一
sum(net_sales)总和(净销售额) month_name月名 start_date开始日期
50 50 October十月 Oct 1, 2022 2022 年 10 月 1 日
50 50 October十月 Oct 2, 2022 2022 年 10 月 2 日
200 200 February二月 Feb 1, 2022 2022 年 2 月 1 日
300 300 March行进 March 5, 2022 2022 年 3 月 5 日
  1. use the start_date in the aggregate function (such as max(start_date) )使用聚合 function 中的 start_date (例如max(start_date)

I don't think either of the two suggestions makes sense in this case.在这种情况下,我认为这两个建议中的任何一个都没有意义。

Note, that the aggregated data does not guarantee that the data is ordered by month_name by default.请注意,聚合数据不保证数据默认按month_name名称排序。 You can try order by month_name .您可以尝试order by month_name However, I do not think that will give you data in the order you want because order by month_name will use the alphabetic ordering of months.但是,我认为这不会按照您想要的顺序为您提供数据,因为order by month_name排序将使用月份的字母顺序。

So, one solution would be to generate the aggregate on the database as follows:因此,一种解决方案是在数据库上生成聚合,如下所示:

# you do not need the "all" here; it should work without it
@business_data = BusinessDatum.all.group(:month_name).sum(:net_sales)`

The generated data will contain the month_name (as strings).生成的数据将包含month_name (作为字符串)。 The output of the above expression will be a hash上述表达式的 output 将是 hash

{"March" => 300, "February" => 200, "October" => 300}

You can then sort this data on the application server (ie within rails)然后,您可以在应用程序服务器上对这些数据进行排序(即在 rails 内)

@business_data.sort_by {|s| Date::MONTHNAMES.index(s[0])}

This will generate the data in the calendar month order这将按日历月顺序生成数据

[ ["February", 200], ["March", 300], ["October", 100] ]

Here the sort method converted the hash to an array while sorting, but that is okay (probably preferable over hash as arrays communicate ordering) since chartkick can take array or hash as arguments for line chart. Here the sort method converted the hash to an array while sorting, but that is okay (probably preferable over hash as arrays communicate ordering) since chartkick can take array or hash as arguments for line chart. If, for some reason, you do want a hash, you can convert it to hash by using the to_h function on the array.如果出于某种原因,您确实需要 hash,您可以使用阵列上的to_h function 将其转换为 hash。

Caveat: One assumption that I made here is that Business Data can have multiple entries for a given month, where the start_date for entries in a given month, may or may not be unique.警告:我在这里所做的一个假设是,业务数据在给定月份可以有多个条目,其中给定月份中条目的start_date可能是唯一的,也可能不是唯一的。

Like the error msg points out, in a SELECT query with aggregate functions, ORDER BY only allows references to grouped or aggregated columns.就像错误消息指出的那样,在带有聚合函数的SELECT查询中, ORDER BY只允许引用分组或聚合列。 Other references would be ambiguous.其他参考将是模棱两可的。 But - as a quick fix - you can work with an expression based on the grouped column month_name :但是- 作为一个快速修复 - 您可以使用基于分组列month_name表达式

SELECT month_name, sum(net_sales)
FROM   business_data
GROUP  BY month_name
ORDER  BY to_date(month_name, 'Month');  -- date derived from month name

If start_date is guaranteed to lie within month_name , you can also:如果start_date保证在month_name内,您还可以:

SELECT month_name, sum(net_sales)
FROM   business_data
GROUP  BY date_trunc('month', start_date), month_name
ORDER  BY date_trunc('month', start_date);

This also enforces the same year (like you probably want).这也强制执行同一年(就像您可能想要的那样)。

Storing the name of the month is not very useful to begin with.存储月份的名称一开始并不是很有用。 (What about the year anyway?) (那一年呢?)

If possible, fix your relational design to store a date instead.如果可能,请修复您的关系设计以存储date Or, if start_date always lies within month_name , simply drop the column month_name .或者,如果start_date始终位于month_name内,只需删除列month_name It's just redundant ballast.这只是多余的镇流器。 Don't store redundant data.不要存储冗余数据。 You can derive the name of the month from start_date cheaply with to_char() :您可以使用to_char()廉价地从start_date派生月份名称:

SELECT to_char(start_date, 'Month') AS month_name

However, to group and order by month, throw in date_trunc() like above:但是,要按月分组和排序,请像上面一样输入date_trunc()

SELECT to_char(date_trunc('month', start_date), 'Month') AS month_name, sum(net_sales)
FROM   business_data
GROUP  BY date_trunc('month', start_date)
ORDER  BY date_trunc('month', start_date);

db<>fiddle here db<> 在这里摆弄

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM