Why is this ActiveRecord Query NOT ambiguous? - mysql

With Rails 3, I am using the following kind of code to query a MySQL database:
MyData.joins('JOIN (SELECT id, name FROM sellers) AS Q
ON seller_id = Q.id').
select('*').
joins('JOIN (SELECT id, name FROM users) AS T
ON user_id = T.id').
select("*").each do |record|
#..........
Then, a bit further down, I try to access a "name" with this code: (note that both sellers and users have a name column).
str = record.name
This line is giving me a "user name" instead of a "seller name", but shouldn't it give nothing? Since I joined multiple tables with a name column, shouldn't I be get an error like "column 'name' is ambiguous"? Why isn't this happening?
And by the way, the code behaves the same way whether I include that first "select('*')" line or not.
Thank you.

Firstly, there's no reason to call select twice - only the last call will actually be used. Secondly, you should not be using select("*"), because the SQL database (and Rails) will not rename the ambiguous columns for you. Instead, use explicit naming for the extra columns that you need:
MyData.joins('JOIN (SELECT..) AS Q ON ...', 'JOIN (SELECT...) AS T ON ...').
select('my_datas.*, T.name as t_name, Q.name as q_name').
each do |record|
# do something
end
Because of this, there's no reason to make a subquery in your JOIN statements:
MyData.joins('JOIN sellers AS Q ON ...', 'JOIN users AS T ON ...').
And finally, you should already have belongs_to associations set up for seller and user. That would mean that you can just do this:
MyData.joins(:seller, :user).
select("my_datas.*, sellers.name as seller_name, users.name as user_name").
each do |record|
# do something
end
Now you can call record.seller_name and record.user_name without any ambiguity.

Related

How to use the distinct method in Rails with Arel Table?

I am looking to run the following query in Rails (I have used the scuttle.io site to convert my SQL to rails-friendly syntax):
Here is the original query:
SELECT pools.name AS "Pool Name", COUNT(DISTINCT stakings.user_id) AS "Total Number of Users Per Pool" from stakings
INNER JOIN pools ON stakings.pool_id = pools.id
INNER JOIN users ON users.id = stakings.user_id
INNER JOIN countries ON countries.code = users.country
WHERE countries.kyc_flow = 1
GROUP BY (pools.name);
And here is the scuttle.io query:
<%Staking.select(
[
Pool.arel_table[:name].as('Pool_Name'), Staking.arel_table[:user_id].count.as('Total_Number_of_Users_Per_Pool')
]
).where(Country.arel_table[:kyc_flow].eq(1)).joins(
Staking.arel_table.join(Pool.arel_table).on(
Staking.arel_table[:pool_id].eq(Pool.arel_table[:id])
).join_sources
).joins(
Staking.arel_table.join(User.arel_table).on(
User.arel_table[:id].eq(Staking.arel_table[:user_id])
).join_sources
).joins(
Staking.arel_table.join(Country.arel_table).on(
Country.arel_table[:code].eq(User.arel_table[:country])
).join_sources
).group(Pool.arel_table[:name]).each do |x|%>
<p><%=x.Pool_Name%><p>
<p><%=x.Total_Number_of_Users_Per_Pool%>
<%end%>
Now, as you may notice, sctuttle.io does not include the distinct parameter which I need. How in the world can I use distinct here without getting errors such as "method distinct does not exist for Arel Node?" or just syntax errors?
Is there any way to write the above query using rails ActiveRecord? I am sure there is, but I am really not sure how.
Answer
The Arel::Nodes::Count class (an Arel::Nodes::Function) accepts a boolean value for distinctness.
def initialize expr, distinct = false, aliaz = nil
super(expr, aliaz)
#distinct = distinct
end
The #count expression is a shortcut for the same and also accepts a single argument
def count distinct = false
Nodes::Count.new [self], distinct
end
So in your case you could use either of the below options
Arel::Nodes::Count.new([Staking.arel_table[:user_id]],true,'Total_Number_of_Users_Per_Pool')
# OR
Staking.arel_table[:user_id].count(true).as('Total_Number_of_Users_Per_Pool')
Suggestion 1:
The Arel you have seems a bit overkill. Given the natural relationships you should be able to simplify this a bit e.g.
country_table = Country.arel_table
Staking
.joins(:pools,:users)
.joins( Arel::Nodes::InnerJoin(
country_table,
country_table.create_on(country_table[:code].eq(User.arel_table[:country])))
.select(
Pool.arel_table[:name],
Staking.arel_table[:user_id].count(true).as('Total_Number_of_Users_Per_Pool')
)
.where(countries: {kyc_flow: 1})
.group(Pool.arel_table[:name])
Suggestion 2: Move this query to your controller. The view has no business making database calls.

Don't want to fetch single column (name)

def fetchProposalByStudio(studioId: Int): List[ProposalDetails] = {
ConnectionPoolManager.getDB(config = appConfig).localTx { implicit session: DBSession =>
logger.info("Querying proposal table to fetch all the proposals")
SQL("""SELECT [except name] p.id, id, proposal_title, challenge, possible_solution, explanation,
| submission_date, status, submitted_by, remark
| FROM proposal p inner join knolder k on k.id = p.knolder_id
| where k.studio_id =? order by p.id desc""".stripMargin)
.bind(studioId)
.map(rs =>
ProposalDetails(
rs.int("id"),
rs.int("id"),
rs.string("proposal_title"),
rs.string("challenge"),
rs.string("possible_solution"),
rs.string("explanation"),
rs.string("submission_date"),
Some(ProposalStatus.withName(rs.string("status"))),
rs.string("submitted_by"),
rs.string("remark"),
**rs.string("name")**
)
)
.list().apply()
}
}
I don't want to fetch this column name in my query but without involving this in the query i am getting this error due to using case class.
13:28:24.446 [default-akka.actor.default-dispatcher-8] INFO c.k.l.b.ProposalImpl - Something went wrong while fetching the proposals. Exception message: ERROR: syntax error at or near "["
Position: 8
Smells of a syntax problem...
Perhaps:
SELECT [except name] -- should be
SELECT `except name` -- in mysql
If you don't want a particular column in an SQL resultset, you simply don't mention it in the SELECT.
There is no notion of SELECT * EXCEPT FirstName FROM person - if Person has FirstName, LastName, Age, Address and you don't want FirstName, you don't put it in the select list:
SELECT LastName, Age, Address FROM Person
^^^^^^^
no FirstName mentioned here
Mention every column you do want, do not mention any column you don't want.
If the complaint is "but there are 527 columns and I want all except one" - you can do something like:
SELECT CONCAT(column_name, ',') FROM information_schema.columns WHERE table_name = 'Person' and column_name <> 'FirstName'
which produces a resultset like:
LastName,
Age,
Address,
... 523 other columns
And you can then copy that resultset and paste it into your code, and it already has commas on the end..
If you want the columns all on one line, use GROUP_CONCAT or use a decent text editor to replace \r\n with nothing. If you want to surround the column name in backticks, put it into the CONCAT.. The ultimate point here is that you're a software developer: you can write code that writes code, then you can copy the output, which is valid code, and paste it into some other code somewhere else

Multiplet select statements, each one uses CASE and return two values

i have the follwoing query:
SELECT COALESCE(income_adsense, income_adsense_u) AS "REV",
CASE COALESCE(income_adsense, income_adsense_u)
WHEN income_adsense THEN "REAL"
WHEN income_adsense_u THEN "USER"
END AS source
FROM revenue_report LIMIT 1;
which will return answer like this:
REV | source
376 | REAL
now the query works fine but the problem is i want to execute this select couple of times for different entity (adsense in the example).
the best i could get is this:
SELECT rr.site_id, ws.website_name,
CASE COALESCE(income_adsense, income_adsense_u)
WHEN income_adsense THEN "REAL"
WHEN income_adsense_u THEN "USER"
END AS adsense_source,
CASE COALESCE(income_taboola, income_taboola_u)
WHEN income_taboola THEN "REAL"
WHEN income_taboola_u THEN "USER"
END AS taboola_source
FROM revenue_report rr
INNER JOIN websites ws ON ws.website_id = rr.site_id
WHERE (data_date BETWEEN '2017-03-18' AND '2017-03-18')
GROUP BY site_id
LIMIT 1
but the problem here is i'm missing the "REV" value from the upper example. I know it doesn't exists in the second query, but this is the last working attempt. any idea how can i add the "REV" value logic to the second query?
in the second query i will get this structure of result:
site_id|website_name|adsense_source|taboola_source
but here im missing the COALESCE result from the first query which in the example was 376
Well why can't you just include it in your SELECT list like
SELECT rr.site_id, ws.website_name,
COALESCE(income_adsense, income_adsense_u) AS "REV", //Here
CASE COALESCE(income_adsense, income_adsense_u)
WHEN income_adsense THEN "REAL"
WHEN income_adsense_u THEN "USER"
END AS adsense_source,
CASE COALESCE(income_gol, income_gol_u)
WHEN income_taboola THEN "REAL"
WHEN income_taboola_u THEN "USER"
END AS taboola_source
FROM revenue_report rr
INNER JOIN websites ws ON ws.website_id = rr.site_id
WHERE (data_date BETWEEN '2017-03-18' AND '2017-03-18')
GROUP BY site_id
LIMIT 1

Can I query a record with multiple associated records that fit certain criteria?

I have two tables, Show, Character. Each Show has_many Characters.
class Show < ActiveRecord::Base
has_many :characters
class Character < ActiveRecord::Base
belongs_to :show
What I want to do is return the results of a Show that is associated with multiple Characters that fit certain criteria.
For example, I want to be able to return a list of Shows that have as characters both Batman and Robin. Not Batman OR Robin, Batman AND Robin.
So the query should be something like
Show.includes(:characters).where(characters: {'name = ? AND name = ?', "Batman", "Robin"})
But this returns an error. Is there a syntax for this that would work?
UPDATE
The query
Show.includes(:characters).where('characters.name = ? AND characters.name = ?', "Batman", "Robin")
returns a value of 0, even though there are definitely Shows associated with both Batman and Robin.
Using plain SQL, one solution is :
select s. *
from shows as s
join characters as c1 on (s.id=c1.show_id)
join characters as c2 on (s.id=c2.show_id)
where c1.name='Batman'
and c2.name='Robin';
Using Arel, I would translate as :
Show.joins('join characters as c1 on shows.id=c1.show_id').joins('join
characters as c2 on shows.id=c2.show_id').where('c1.name = "Batman"').where(
'c2.name="Robin"')
So you'll have to get a little fancy with SQL here; especially if you want it to be performant and handle different types of matchers.
select count(distinct(characters.name)) as matching_characters_count, shows.* from shows
inner join characters on characters.show_id = shows.id and (characters.name = 'Batman' or characters.name = 'Robin')
group by shows.id
having matching_characters_count = 2
To translate into ActiveRecord
Show.select('count(distinct(characters.name)) as matching_characters_count, shows.*').
joins('inner join characters on characters.show_id = shows.id and (characters.name = "Batman" or characters.name = "Robin")').
group('shows.id').
having('matching_characters_count = 2')
Then you'd probably do a pass with interpolation and then AREL it up; so you wouldn't be building string queries.
A plain SQL solution using aggregate functions. The SQL statements returns the ID values of the ´shows´ records you are looking for.
SELECT c.show_id
FROM characters c
WHERE c.name IN ('Batman','Robin')
GROUP BY c.show_id
HAVING COUNT(DISTINCT c.name) = 2
You can put this statement into a select_values() call and then grab the shows with the values of the returned array.
I think this must work:
super_heroes = ['Batman', 'Robin']
Show.where(
id: Character.select(:show_id).where(name: super_heroes).group(:show_id)
.having("count(distinct characters.name) = #{super_heroes.size}")
)
I just write the #sschmeck query in a Rails way, as a subquery, and add a super_heroes var to show how it can be scaled.
Reduce the number of entries in the query as soon as possible is basic idea to get a better query in performance.
Show.where("id IN (( SELECT show_id FROM characters WHERE name = 'Batman') INTERSECT (SELECT show_id FROM characters WHERE name = 'Robin'))")
In above query, the 1st sub query gets the 'show_id' for 'Batman', the next one gets the 'show_id' for 'Robin'. In my opinion, just a few characters match the condition after doing 'INTERSECT'. Btw, you can use explain command in 'rails db' for choosing what is better solution.
I think you're looking for this:
Show.includes(:characters).where(characters: {name: ["Batman", "Robin"]})
or
Show.includes(:characters).where('characters.name IN ?', ["Batman", "Robin"]).references(:characters)
The references is required because we're using a string in the where method.
So first of all, Why is your approach is wrong?
So in the "characters_shows" table you can find records that looks like this
show_id name character_id character_name
1 first show 1 batman
2 second 1 batman
1 first show 2 robin
1 first show 3 superman
As you can see there will never be a case where the character name is batman and robin at the same row
Another approach will be something like this
names = ["Batman", "Robin"]
Show.joins(:characters).where(characters: {name: names}).group("shows.id").having("COUNT(*) = #{names.length}")

Wordpress Mysql wpdb query duplicates despite using DISTINCT

I've already looked at all similar solved questions but I've not found the solution yet.
So In this wordpress there are houses and circuits (custom post types). A house can have several circuits attached (repeater custom field ). The query gets dinamically built to retrieve all houses associated to any circuit from a list of IDS.
This works, but it repeats a house when it has more than one circuit that matches. For example:
SELECT DISTINCT *
FROM reoask5_posts p
INNER JOIN reoask5_postmeta pm_ci ON p.ID = pm_ci.post_id
WHERE p.post_type = 'casa'
AND p.post_status = 'publish'
AND pm_ci.meta_key LIKE 'circuito|__|_ci' ESCAPE '|'
AND (pm_ci.meta_value = 194 OR pm_ci.meta_value = 189)
...retrieves houseA which has circuito_0_ci = 194 and retrieves two times houseB which has circuito_0_ci = 194 and circuito_1_ci = 189 associated.
So what should I modify to filter out duplicates? What I'm doing wrong?
UPDATE1: More info
The table schema for posts (p)
The table schema for postmeta (pm_ci)
Still doesn't work well. Selectin specific columns from post database does filter out duplicates. But it fails in looping through the results like a normal wordpress loop. Even if I select ALL the columns:
SELECT DISTINCT p.id, p.post_author, p.post_date, p.post_date_gmt, p.post_content, p.post_title, p.post_excerpt, p.post_status, p.comment_status, p.ping_status, p.post_password, p.post_name, p.to_ping, p.pinged, p.post_modified, p.post_modified_gmt, p.post_content_filtered, p.post_parent, p.guid, p.menu_order, p.post_type, p.post_mime_type, p.comment_count
It gives me errors:
Notice: Undefined property: stdClass::$ID in .... line 250
Notice: Undefined property: stdClass::$ID in .... line 251
// etc...
This the simplified php code that comes after defining the query. All of these calls fail when I select specific columns:
$total_posts= $wpdb->get_results($query);
foreach ($total_posts as $post){
$post->post_name;
get_permalink($post->ID);
$coordenadas = get_field( "coords", $post->ID);
// ...
}
If I add all the 4 columns from metaposts to the SELECT line:
... p.comment_count, pm_ci.meta_id, pm_ci.post_id, pm_ci.meta_key, pm_ci.meta_value
It still gives me errors when trying to use those functions, that work when I use SELECT *
And I don't know how would I be supposed to use group by in this case. Still learning.
UPDATE2: Found the solution
The reason for the errors was because SELECT DISTINCT p.id should read SELECT DISTINCT p.ID . The SQL query returned the ID in a case insensitive manner, storing it in key 'id' , but the wordpress functions need that key in capitals ('ID') in order to work.
Thank you!
Your using DISTINCT * . This means that if one value in any one column is different (from a different row) it will be shown.
What you should instead do, is write distinct followed by the specific columns your interested in.
So, if your only interested in the specific houses -
Example:
select distinct houses
FROM ...
Using your Format:
select distinct
p.post_type
FROM ...
You could also use GROUP BY to count the number of different circuits a house has.