Rails PG::UndefinedTable: ERROR: missing FROM-clause entry for table

activerecord, orm, postgresql, ruby-on-rails, sql

Solution

The error

ERROR: missing FROM-clause entry for table "agency"

…should hint that somewhere in your query you have mistakenly used `agency` as a table name, without pluralizing it. But where exactly did you do that?

The only difference between working and non-working snippets of yours are these bits:

joins(:agency).
where(agency: {state: 'active'}).

…a-and both lines refer to `agency`. Welp , no easy path, need to examine both.

The `joins` part is responsible for generating an `INNER JOIN` part of the query, and if you look at the resulting SQL, it only uses `agencies`, which is the actual name of the table. This is because `#joins` accepts a symbol that denotes an association being joined — Rails traced it to a `belongs_to` association which, due to your adherence to naming conventions of Rails, points at the right model which knows its table name, allowing this to work.

The `where` part, however, is broken. This particular "sub-hash" condition form you used expects the hash key to be a table name and uses it in the query directly. Replace it with the actual name of the table and you should be good:

where(agencies: {state: 'active'})

The way of the `#merge`

There is another way to accomplish the same feat, and it involves, instead of `#where`, the `#merge` method, which merges the conditions of one relation into conditions of another:

merge( Agency.where(state: 'active') )

This does work even if the two are relations on different models, it's handy for filtering joined records, which is exactly what you're doing.

Additionally, it allows you to use some capabilities of the model class.

In particular, scopes:

# Inside Agency
scope :active, -> { where(state: 'active') }

# Somewhere else
merge(Agency.active)

Also, learning what the table name of the other model is. In `#where` you have to specify the table name in the query, breaking into the scope of the model's database persistence, potentially from outside. With `#merge` you defer to the model, hopefully a single source of truth on that.

Problem

I have models ``` class Offer < ActiveRecord::Base belongs_to :agency end class Agency < ActiveRecord::Base has_many :offers end ``` When I do such request - everything is OK ``` @offers = Offer.with_state(:confirmed). includes(:destination, :cruise_line, :ship). paginate(per_page: 10, page: params[:page]).decorate ``` But I want to select only offers that belong to `active` agencies (column `state` from `agencies` table), so I try to do like this: ``` @offers = Offer.with_state(:confirmed). includes(:destination, :cruise_line, :ship). joins(:agency). where(agency: {state: 'active'}). paginate(per_page: 10, page: params[:page]).decorate ``` Having done this I get error `PG::UndefinedTable: ERROR: missing FROM-clause entry for table "agency"`. What is wrong with my code? The query gives me this error and sql: ``` PG::UndefinedTable: ERROR: missing FROM-clause entry for table "agency" LINE 1: ...id" WHERE ("offers"."state" IN ('confirmed')) AND "agency"."... ^ : SELECT "offers"."id" AS t0_r0, "offers"."name" AS t0_r1, "offers"."destination_id" AS t0_r2, "offers"."cruise_line_id" AS t0_r3, "offers"."ship_id" AS t0_r4, "offers"."departure_date" AS t0_r5, "offers"."departure_port_id" AS t0_r6, "offers"."arrival_date" AS t0_r7, "offers"."arrival_port_id" AS t0_r8, "offers"."flight_price" AS t0_r9, "offers"."bonus" AS t0_r10, "offers"."itinerary" AS t0_r11, "offers"."board_language_id" AS t0_r12, "offers"."agency_landing_page" AS t0_r13, "offers"."benefits" AS t0_r14, "offers"."inner_price" AS t0_r15, "offers"."inner_price_normal" AS t0_r16, "offers"."outer_price" AS t0_r17, "offers"."outer_price_normal" AS t0_r18, "offers"."balcony_price" AS t0_r19, "offers"."balcony_price_normal" AS t0_r20, "offers"."suite_price" AS t0_r21, "offers"."suite_price_normal" AS t0_r22, "offers"."lucky_price" AS t0_r23, "offers"."lucky_price_normal" AS t0_r24, "offers"."valid_from" AS t0_r25, "offers"."valid_till" AS t0_r26, "offers"."created_at" AS t0_r27, "offers"."updated_at" AS t0_r28, "offers"."description" AS t0_r29, "offers"."agency_id" AS t0_r30, "offers"."state" AS t0_r31, "destinations"."id" AS t1_r0, "destinations"."name" AS t1_r1, "destinations"."created_at" AS t1_r2, "destinations"."updated_at" AS t1_r3, "cruise_lines"."id" AS t2_r0, "cruise_lines"."name" AS t2_r1, "cruise_lines"."created_at" AS t2_r2, "cruise_lines"."updated_at" AS t2_r3, "ships"."id" AS t3_r0, "ships"."name" AS t3_r1, "ships"."picture" AS t3_r2, "ships"."cruise_line_id" AS t3_r3, "ships"."created_at" AS t3_r4, "ships"."updated_at" AS t3_r5 FROM "offers" INNER JOIN "agencies" ON "agencies"."id" = "offers"."agency_id" LEFT OUTER JOIN "destinations" ON "destinations"."id" = "offers"."destination_id" LEFT OUTER JOIN "cruise_lines" ON "cruise_lines"."id" = "offers"."cruise_line_id" LEFT OUTER JOIN "ships" ON "ships"."id" = "offers"."ship_id" WHERE ("offers"."state" IN ('confirmed')) AND "agency"."state" = 'active' LIMIT 10 OFFSET 0 ```

Original source