I tried to configure Texticle and ran into this error on migration. Error msg is referring to the line I marked with <<<<
PGError: ERROR: schema "half_links" does not exist
: CREATE VIEW searches AS
SELECT items.id AS searchable_id, items.name AS term,
CAST('Item' AS varchar) AS searchable_type
FROM items
UNION
SELECT half_links.id AS searchable_id, half_links.item.name AS term, <<<<
CAST('HalfLink' AS varchar) AS searchable_type
FROM half_links
This is my migration
class CreateSearches < ActiveRecord::Migration
def self.up
ActiveRecord::Base.connection.execute <<-SQL
CREATE VIEW searches AS
SELECT items.id AS searchable_id, items.name AS term,
CAST('Item' AS varchar) AS searchable_type
FROM items
UNION
SELECT half_links.id AS searchable_id, half_links.item.name AS term,
CAST('HalfLink' AS varchar) AS searchable_type
FROM half_links
SQL
end
and models
:half_link belongs_to :item
:item has_many :half_links
Im running this on my localhost PSQL. How can I get around this?
The error is in the second select. half_links.item.name makes Postgres assume that half_links is a schema name (the correct syntax is ..)
Seeing how your associations are, I guess you mean to get the name of the items belonging to half_links.
Since Postgres have no idea on how the Rails associations works, you need to get the data yourself:
CREATE VIEW searches AS
SELECT items.id AS searchable_id, items.name AS term,
CAST('Item' AS varchar) AS searchable_type
FROM items
UNION
SELECT half_links.id AS searchable_id, items.name AS term,
CAST('HalfLink' AS varchar) AS searchable_type
FROM half_links join items on half_links.item_id = items.id
This is assuming that the foreign key in half_links to items are called item_id which it should be if you created the tables using Rails migration.
Additionally, this will only show the half_links that actually have an item associated to it, if there can be ones that don't have an item, then change the join to outer join. Term will in those cases be NULL.
Related
Let's say I have a User and User has_many :tags and I would like to remove all #users tags that have duplicated name. For example,
#user.tags #=> [<Tag name: 'A'>, <Tag name: 'A'>, <Tag name: 'B'>]
I would like to keep only the tags with unique names and delete the rest from the database.
I know I could pull out a list of unique tags names from user's tags and remove all users's tags and re-create user's tags with only unique names but it would be ineffficient?
On the other hand, select won't work as it returns only the selected column. uniq also won't work:
#user.tags.uniq #=> returns all tags
Is there a more efficient way?
UPDATE:
I would like to do this in a migration.
This method will give you an ActiveRecord::Relation with the duplicate tags:
class Tag < ApplicationRecord
belongs_to :user
def self.duplicate_tags
unique = self.select('DISTINCT ON(tags.name, tags.user_id) tags.id')
.order(:name, :user_id, :id)
self.where.not(id: unique)
end
end
Its actually run as a single query:
SELECT "tags".* FROM "tags"
WHERE "tags"."id" NOT IN
(SELECT DISTINCT ON(tags.name) tags.id
FROM "tags" GROUP BY "tags"."id", "tags"."user_id"
ORDER BY tags.name, tags.id)
You can remove the duplicates in a single query with #delete_all.
# Warning! This can't be undone!
Tag.duplicate_tags.destroy_all
If you need to destroy dependent associations or call your before_* or after_destroy callbacks, use the #destroy_all method instead. But you should use this together with #in_batches to avoid running out of memory.
# Warning! This can't be undone!
Tag.duplicate_tags.in_batches do |batch|
# destroys a batch of 1000 records
batch.destroy_all
end
You can write SQL model-independent query in the migration.
Here is PostgreSQL-specific migration code:
execute <<-SQL
DELETE FROM tags
WHERE id NOT IN (
SELECT DISTINCT ON(user_id, name) id FROM tags
ORDER BY user_id, name, id ASC
)
SQL
And here is more SQL common one:
execute <<-SQL
DELETE FROM tags
WHERE id IN (
SELECT DISTINCT t2.id FROM tags t1
INNER JOIN tags t2
ON (
t1.user_id = t2.user_id AND
t1.name = t2.name AND
t1.id < t2.id
)
)
SQL
This SQL fiddle shows
different queries you can use as sub-select in DELETE query depending on your goals: deleting first/last/all duplicates.
DB - PostgreSQL
Rails - 4
I have next SQL
sql = <<-SQL
LEFT OUTER JOIN (
SELECT SUM(id) AS sum_ids, some_key FROM second_models
WHERE id < 10000
GROUP BY some_key
) AS second_models ON first_models.id = second_models.first_model_id
SQL
record = FModel.joins(sql).last
record.sum_ids # DOESN'T WORK !
and I can see the record as ActiveRecord object, but can I get somehow field sum_ids which was built manually?
The additional field is inside the join section. It is not selected by default and thus can't be read. When executing your statement you get something like the following SQL query:
SELECT first_models.*
FROM first_models
INNER JOIN (
SELECT SUM(id) AS sum_ids, some_key
FROM second_models
WHERE id < 10000
GROUP BY some_key
) AS second_models
ON first_models.id = second_models.first_model_id
The first select statement prevents the sum_ids field from being accessible in your object since it's never returned to Rails. You want to change SELECT first_models.* to SELECT *. This is simply done by specifying the following select:
record = FModel.select(Arel.star).joins(sql).last
record.sum_ids
#=> should now give you your value
You can also add your field specifically using the following method:
f_models = FModel.arel_table
record = FModel.select(f_models[Arel.star]).select('sum_ids').joins(sql).last
This should result in SELECT first_models.*, sum_ids.
I've got the following query that works:
jobs = current_location.jobs.includes(:customer).all.where(complete: complete)
However, when I add a where clause to query the first name of the customer table, I get an error.
jobs = current_location.jobs.includes(:customer).all.where(complete: complete).where("customers.fist_name = ?", "Bob")
Here is the error:
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "customers"
LINE 1: ...bs"."complete" = $2 AND "jobs"."status" = $3 AND (customers....
^
: SELECT "jobs".* FROM "jobs" INNER JOIN "jobs_users" ON "jobs"."id" = "jobs_users"."job_id" WHERE "jobs_users"."user_id" = $1 AND "jobs"."complete" = $2 AND "jobs"."status" = $3 AND (customers.last_name = 'Bob') ORDER BY "jobs"."start" DESC LIMIT $4 OFFSET $5
The current_location method:
def current_location
return current_user.locations.find_by(id: cookies[:current_location])
end
Location Model
has_many :jobs
has_and_belongs_to_many :customers
Job Model
belongs_to :location
belongs_to :customer
Customer Model
has_many :jobs
has_and_belongs_to_many :locations
How can I fix this issue?
includes will only join the table if you set a reference to the association.
When using includes you ensure a reference to the association in 2 fashions:
You can use the references method this will join the table whether or not there are any query conditions (If you MUST use raw SQL as shown in your question then this is the method you would need to use) e.g.
current_location.jobs
.includes(:customer)
.references(:customer)
Or you can use the hash finder version of where: (Please note that when using an associative reference in the where clause you must reference the table name, in this case customers and not the association name customer)
current_location.jobs
.includes(:customer)
.where(customers: {first_name: "Bob" })
Both of these will eager load the customer for the jobs referenced.
The first option (references) will OUTER JOIN the customers table so that all the jobs are loaded even if they have no customers as long as no query conditions reference the customers table.
The second option (using where) will OUTER JOIN the customers table but given the query parameter against the customers table it will act very much like an INNER JOIN.
If you only need to search the jobs based on customer information then joins is a better choice as this will create an INNER JOIN with the customers table but will not try to load any of the customer data in the query e.g.
current_location.jobs.joins(:customer).where(customers: {first_name: "Bob" })
joins will always include the associated table regardless of a reference in the query.
Sidenote: the all in both your queries is completely unnecessary
includes(:customer) does not necessarily join the customers table into the SQL query. You need to use joins(:customer) to force Rails to join the customers table into the SQL query and make it available to query conditions.
jobs = current_location.jobs
.joins(:customer)
.includes(:customer)
.where(complete: complete)
.where(customers: { first_name: 'Bob' })
I have a query with a lot of joins and I'm eager_loading some of associations at the time. And I need to compute some value as attribute of one of models.
So, I'm trying this code:
ServiceObject
.joins([{service_days: :ou}, :address])
.eager_load(:address, :service_days)
.where(ous: {id: OU.where(sector_code: 5)})
.select('SDO_CONTAINS(ous.service_area_shape, SDO_GEOMETRY(2001, 8307, sdo_point_type(addresses.lat, addresses.lng, NULL), NULL, NULL) ) AS in_zone')
Where SQL function call in select operates data from associated addresses and ous tables.
I'm getting next SQL (so my in_zone column getting calculated and returned as first column before other columns for all eager_loaded models):
SELECT SDO_CONTAINS(ous.service_area_shape, SDO_GEOMETRY(2001, 8307, sdo_point_type(addresses.lat, addresses.lng, NULL), NULL, NULL) ) AS in_zone, "SERVICE_OBJECTS"."ID" AS t0_r0, "SERVICE_OBJECTS"."TYPE" AS t0_r1, <omitted for brevity> AS t2_r36 FROM "SERVICE_OBJECTS" INNER JOIN "SERVICE_DAYS" ON "SERVICE_DAYS"."SERVICE_OBJECT_ID" = "SERVICE_OBJECTS"."ID" INNER JOIN "OUS" ON "OUS"."ID" = "SERVICE_DAYS"."OU_ID" INNER JOIN "ADDRESSES" ON "ADDRESSES"."ID" = "SERVICE_OBJECTS"."ADDRESS_ID" WHERE "OUS"."ID" IN (SELECT "OUS"."ID" FROM "OUS" WHERE "OUS"."SECTOR_CODE" = :a1) [["sector_code", "5"]]
But it seems like that in_zone isn't accessible from either model used in query.
I need to have calculated in_zone as attribute of ServiceObject model object, how I can accomplish that?
Ruby on Rails 4.2.6, Ruby 2.3.0, oracle_enhanced adapter 1.6.7, Oracle 12.1
I have successfully replicated your issue and it turns out that this is a known issue in Rails. The problem is that when using eager_load, Rails maps the columns of all eager-loaded tables into table and column aliases in the form of t0_r0, t0_r1, etc... (you can see these in the SQL that you pasted in the question). And while doing that, it simply ignores the custom columns in the select, probably because it cannot determine which eager-loaded table it should attribute the custom column to. It is sad that this issue is open for more than 2 years now...
Nevertheless I think I found a workaround. It seems that if you don't eager load the tables but manually join them (with joins), you can as well include them (with includes) and the custom columns will be returned as there will be no column aliasing taking place. The point is that you must not use associations in the joins clauses but you have to specify the joins yourself. Also note that you must specify all columns from the main table in the select manually too (see the service_objects.* in the select).
Try the following approach:
ServiceObject
.joins('INNER JOIN "SERVICE_DAYS" ON "SERVICE_DAYS"."SERVICE_OBJECT_ID" = "SERVICE_OBJECTS"."ID"')
.joins('INNER JOIN "OUS" ON "OUS"."ID" = "SERVICE_DAYS"."OU_ID"')
.joins('INNER JOIN "ADDRESSES" ON "ADDRESSES"."ID" = "SERVICE_OBJECTS"."ADDRESS_ID"')
.includes(:service_days, :address)
.where(ous: {id: OU.where(sector_code: 5)})
.select('service_objects.*, SDO_CONTAINS(ous.service_area_shape, SDO_GEOMETRY(2001, 8307, sdo_point_type(addresses.lat, addresses.lng, NULL), NULL, NULL) ) AS in_zone')
The computation in the select should still work as the related tables are joined together but there should be no column aliasing present.
Of course this approach means that you'll get three queries instead of just one but unless you return a huge amount of records, the following two queries run by the includes clause should be very fast as they simply load the relevant records using foreign keys.
That monkey patch helped #Envek:
module ActiveRecord
Base.send :attr_accessor, :_row_
module Associations
class JoinDependency
JoinBase && class JoinPart
def instantiate_with_row(row, *args)
instantiate_without_row(row, *args).tap { |i| i._row_ = row }
end; alias_method_chain :instantiate, :row
end
end
end
end
then it is possible to do:
ServiceObject
.joins([{service_days: :ou}, :address])
.eager_load(:address, :service_days)
.where(ous: {id: OU.where(sector_code: 5)})
.select('SDO_CONTAINS(ous.service_area_shape, SDO_GEOMETRY(2001, 8307, sdo_point_type(addresses.lat, addresses.lng, NULL), NULL, NULL) ) AS in_zone')
.first
._row_['in_zone']
I made a Select using Active Record with a lot of Joins. This resulted in duplicate values. After the select function there's the distinct function with value :id. But that didn't work!
Here's the code:
def join_query
<<-SQL
LEFT JOIN orders on orders.purchase_id = purchases.id
LEFT JOIN products on products.id = orders.complete_product_id
SQL
end
def select_query
<<-SQL
purchases.*,
products.reference_code as products_reference_code
SQL
end
result = Purchase.joins(join_query)
.select(select_query)
.distinct(:id)
Of course, neither distinct! or uniq functions worked. The distinct! returned a error from "ActiveRecord::ImmutableRelation" that I don't know what means.
To fix this I did a hack, converting the ActiveRecord_Relation object to an Array and I used the uniq function of Ruby.
What's going on here?
try this out:
def select_query
<<-SQL
DISTINCT ON (purchases.id) purchases.id,
products.reference_code as products_reference_code
SQL
end
add more comma separated column names in select clause
Purchase.select(select_query).joins(join_query)