【问题标题】:SQLite/Postgres/Heroku: Problem translating querySQLite/Postgres/Heroku:翻译查询的问题
【发布时间】:2011-08-17 18:28:42
【问题描述】:

Heroku 在我的 Postgres-Query 上抛出一个错误声明:

ActiveRecord::StatementInvalid (PGError: ERROR: 语法错误或 在“日期”附近 2011-05-03T13:58:22+00:00 应用程序[web.1]:第 1 行:...2011-05-31') 按提取分组(从时间戳开始的年份 日期)||EXT...

开发中的 SQLite 查询按预期工作。代码如下:

  def self.calculate(year, month, user_id, partner_id)
    case ActiveRecord::Base.connection.adapter_name  
      when 'SQLite'
        where(':user_id = entries.user_id OR :partner_id = entries.user_id', {    
            :user_id => user_id,
            :partner_id => partner_id
        }).
        where('entries.date <= :last_day', { 
            :last_day => Date.new(year, month, 1).at_end_of_month
        }).
        select('entries.date, ' + 
               'sum(case when joint = "f" then amount_calc else 0 end) as sum_private, ' + 
               'sum(case when joint = "t" and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_user_joint, ' + 
               'sum(case when joint = "t" and user_id = ' + partner_id.to_s + ' then amount_calc else 0 end) as sum_partner_joint, ' +              
               'sum(case when compensation = "t" and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_user_compensation, ' +
               'sum(case when compensation = "t" and user_id = ' + partner_id.to_s + ' then amount_calc else 0 end) as sum_partner_compensation '
        ).
        group("strftime('%Y-%m', date)")
      when 'PostgreSQL'
        where(':user_id = entries.user_id OR :partner_id = entries.user_id', {    
            :user_id => user_id,
            :partner_id => partner_id
        }).
        where('entries.date <= :last_day', { 
            :last_day => Date.new(year, month, 1).at_end_of_month
        }).
        select('entries.date, ' + 
               'sum(case when joint = "f" then amount_calc else 0 end) as sum_private, ' + 
               'sum(case when joint = "t" and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_user_joint, ' + 
               'sum(case when joint = "t" and user_id = ' + partner_id.to_s + ' then amount_calc else 0 end) as sum_partner_joint, ' +              
               'sum(case when compensation = "t" and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_user_compensation, ' +
               'sum(case when compensation = "t" and user_id = ' + partner_id.to_s + ' then amount_calc else 0 end) as sum_partner_compensation '
        ).
        group("EXTRACT(YEAR FROM TIMESTAMP date)||EXTRACT(MONTH FROM TIMESTAMP date)")
    else
      raise 'Query not implemented for this DB adapter'
    end
  end

我真的很感激任何提示。由于我已经在这里提出了一个问题,我也不确定两个查询的总和中的case when joint = "t",有没有更好的方法来做到这一点?

更新

感谢 peufeu 和一匹没有名字的马,代码现在看起来像:

when 'PostgreSQL'
where(':user_id = entries.user_id OR :partner_id = entries.user_id', {    
    :user_id => user_id,
    :partner_id => partner_id
}).
where('entries.date <= :last_day', { 
    :last_day => Date.new(year, month, 1).at_end_of_month
}).
select('min(entries.date) as date, ' + 
       'sum(case when joint = false then amount_calc else 0 end) as sum_private, ' + 
       'sum(case when joint = true and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_user_joint, ' + 
       'sum(case when joint = true and user_id = ' + partner_id.to_s + ' then amount_calc else 0 end) as sum_partner_joint, ' +              
       'sum(case when compensation = true and user_id = ' + user_id.to_s + ' then amount_calc else 0 end) as sum_user_compensation, ' +
       'sum(case when compensation = true and user_id = ' + partner_id.to_s + ' then amount_calc else 0 end) as sum_partner_compensation '
).
group('EXTRACT(YEAR FROM "date"), EXTRACT(MONTH FROM "date")')

...按预期工作。

我的另一个陈述现在遇到了麻烦,我在这里编辑它,因为它似乎与 peufeu 的答案有关。模型/控制器:

   def self.all_entries_month(year, month, user_id, partner_id)
    mydate = Date.new(year, month, 1)

    where(':user_id = entries.user_id OR (:partner_id = entries.user_id AND entries.joint = :true)', {
        :user_id => user_id,
        :partner_id => partner_id,
        :true => true
    }).
    where(':first_day <= entries.date AND entries.date <= :last_day', { 
        :first_day => mydate,
        :last_day => mydate.at_end_of_month
    })
  end

  # group by tag and build sum of groups named group_sum
  def self.group_by_tag
    group('tag').
    select('entries.*, sum(amount_calc) as group_sum')
  end

控制器:

@income = Entry.all_entries_month(@year, @month, current_user.id, current_partner.id).income
@cost = Entry.all_entries_month(@year, @month, current_user.id, current_partner.id).cost

# group cost by categories
@group_income = @income.group_by_tag.order('group_sum desc')
@group_cost = @cost.group_by_tag.order('group_sum')

错误是:

ActionView::Template::Error (PGError: ERROR:  column "entries.id" must appear in the GROUP BY clause or be used in an aggregate function
2011-05-03T18:35:20+00:00 app[web.1]: : SELECT entries.*, sum(amount_calc) as group_sum FROM "entries" WHERE (1 = entries.user_id OR (2 = entries.user_id AND entries.joint = 't')) AND ('2011-04-01' <= entries.date AND entries.date <= '2011-04-30') AND (amount_calc <= 0 AND compensation = 'f') GROUP BY tag ORDER BY group_sum):
2011-05-03T18:35:20+00:00 app[web.1]:     6:    </thead>
2011-05-03T18:35:20+00:00 app[web.1]:     7:    <tbody>
2011-05-03T18:35:20+00:00 app[web.1]:     8:        <% if categories %>
2011-05-03T18:35:20+00:00 app[web.1]:     9:            <% categories.each do |category| %>     
2011-05-03T18:35:20+00:00 app[web.1]:     10:               <tr>
2011-05-03T18:35:20+00:00 app[web.1]:     11:                   <td class="align-left"><%= category.tag %></td>
2011-05-03T18:35:20+00:00 app[web.1]:     12:                   <td class="align-right"><%= my_number_to_percentage (category.group_sum.to_f / total_cost) * 100 %></td>

更新 2:我找到了解决方案

  # group by tag and build sum of groups named group_sum
  def self.group_by_tag
    group('tag').
    select('tag, sum(amount_calc) as group_sum')
  end

【问题讨论】:

  • 有人不得不说:如果你要部署到 Heroku,你应该在 PostgreSQL 之上进行开发。或者,更一般地说,您应该始终在您正在部署的同一堆栈上进行开发。
  • 嗯...我不能反对。另一方面,我会在本地遇到同样的问题;)
  • 但它会在开发过程中得到修复,并且您会在发生时知道您的数据库接口问题,而不是在最后一次尝试全部找到它们。
  • 嗯,实际上我使用早期原型进行了早期部署,其中仅包含种子文件、查询和两个基本视图。尚未实现交互。我之前就知道我会遇到 Postgres 的麻烦 :)

标签: ruby-on-rails ruby-on-rails-3 sqlite postgresql heroku


【解决方案1】:

有问题:

EXTRACT(YEAR FROM TIMESTAMP date)||EXTRACT(MONTH FROM TIMESTAMP date)

解决方案:

EXTRACT(YEAR FROM "date"), EXTRACT(MONTH FROM "date")

“TIMESTAMP”这个词在那里有点不合适;)......还有:

由于 DATE 是保留的 SQL 关键字,因此将其用作列名是一个非常糟糕的主意。在这里,我使用 " 引用它,所以 postgres 不会混淆。

而且您不需要浪费 CPU 时间来构建连接两个 EXTRACT 的字符串,只需记住 GROUP BY 可以使用多个参数。

那么查询当然会失败,因为您选择“日期”但您没有按日期分组。 postgres 无法从具有相同年月的所有行中知道您想要哪个日期。 MySQL 将从行中随机返回一个值,postgres 喜欢正确性,因此它会抛出错误。例如,您可以选择 min(date),这是正确的。

我不确定joint = "t"时的情况

这取决于你想如何得到你的结果,没有错。

也许使用多个查询会扫描表的一小部分(我不知道您的数据集),也可能不会。

【讨论】:

  • 关于时间戳的好消息。我没看到这个
  • 嗯,这看起来像一个 Ruby 异常,也许你应该执行“SELECT min(date) AS date”(即给它一个名字)?...
【解决方案2】:

我不知道 ruby​​/heroku,但是表达式

joint = "t"

指的是 PostgreSQL 中的 t,因为对象名称用双引号引起来。因此,除非 Ruby/Heroku 将那些双引号替换为单引号,否则这将是无效条件。

如果 t 应该是字符串文字(字符 t),那么您需要使用单引号:joint = 't'

如果jointboolean 类型,那么你应该在PostgreSQL 中使用joint = true

【讨论】:

  • 只要解决了其他问题(我的主要问题),我就无法尝试此操作,但是,我已经在 postgres 分支中将“t”更改为 true,将“f”更改为 false。感谢您指出这一点!
猜你喜欢
  • 1970-01-01
  • 2016-08-26
  • 1970-01-01
  • 2021-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多