I have different results from query for COUNT('e.id') or COUNT(e.id)

不打扰是莪最后的温柔 提交于 2019-12-13 01:19:44

问题


I have following code:

def self.department_members(department)
  where(organization_id: department.organization_id)
    .joins("LEFT JOIN core_employments As e ON
      e.organization_id = #{department.organization_id} AND
      core_members.user_id = e.user_id")
    .group('core_members.id')
end

def self.can_automerged(department)
  department_members(department).having("COUNT('e.id') = 1")
  # department_members(department).having("COUNT(e.id) = 1")
end

def self.can_not_automerged(department)
  department_members(department).having("Count('e.id') > 1")
end

When I use

department_members(department).having("COUNT('e.id') = 1")

my test completes without errors. When I use

department_members(department).having("COUNT(e.id) = 1")

my test fails. I can't understand why. Can u explain why? I use Rails-4 and PostgreSQL.

schema:

  create_table "core_members", force: :cascade do |t|
    t.integer  "user_id",                                    null: false
    t.integer  "project_id",                                 null: false
    t.boolean  "owner",                      default: false
    t.string   "login"
    t.string   "project_access_state"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.integer  "organization_id"
    t.integer  "organization_department_id"
  end

  create_table "core_employments", force: :cascade do |t|
    t.integer  "user_id"
    t.integer  "organization_id"
    t.boolean  "primary"
    t.string   "state"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.integer  "organization_department_id"
  end

test:

module Core
  require "initial_create_helper"
  describe Member do
    describe "automerge" do
      before(:each) do
        @organization = create(:organization)
        @department1 = create(:organization_department,organization: @organization)
        @department2 = create(:organization_department,organization: @organization)

        @user = create(:user)
        @user_with_many_employments = create(:user)

        @department1.employments.create!(user: @user)
        @department1.employments.create!(organization: @organization, user: @user_with_many_employments)
        @department2.employments.create!(organization: @organization, user: @user_with_many_employments)
        @project = create_project
        @project.members.create!(user: @user,
                                organization: @organization)
        @project.members.create!(user: @user_with_many_employments,
                                organization: @organization)

      end

      it "::can_not_automerged" do
        expect(Member.can_not_automerged(@department1).to_a.map(&:user)).to match_array [@user_with_many_employments]
      end
      it "::can_automerged" do
        expect(Member.can_automerged(@department1).to_a.map(&:user)).to match_array [@user]
      end
    end
  end
end

回答1:


I have different results from query for COUNT('e.id') or COUNT(e.id)

'e.id' is a string constant, so COUNT('e.id') is just an awkward, misleading way of saying COUNT(*).

COUNT(e.id), on the other hand, counts all rows in the result where e.id IS NOT NULL - since count() does not count NULL values.

The manual about count():

count(*) ... number of input rows

count(expression) ... number of input rows for which the value of expression is not null

As you can see, there are even two separate functions internally. And it should be noted that count(*) is slightly faster. So use that unless you need the second variant. Related:

  • PostgreSQL: running count of rows for a query 'by minute'

You might counter with:
"But e.id is the PRIMARY KEY of core_employments, so it is defined NOT NULL!"

But that would overlook the conditional LEFT JOIN in your query that still introduces NULL values in your NOT NULL column, where the join conditions are not met. Related:

  • Query with LEFT JOIN not returning rows for count of 0

That said, LEFT [OUTER] JOIN is misleading, too. The later condition

having("COUNT(e.id) = 1")

forces it to act like a plain [INNER] JOIN. Once you have fixed that, you might as well simplify to:

having("COUNT(*) = 1")

And if all you care is that at least one related row exists in core_employments, translating to having("COUNT(*) >= 1"), the superior (clearer, faster) technique in simple cases would be an EXISTS semi-join:

WHERE EXISTS (SELECT FROM core_employments WHERE <conditions>)


来源:https://stackoverflow.com/questions/50086813/i-have-different-results-from-query-for-counte-id-or-counte-id

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