Pass array in raw MySQL query in Ruby on Rails - mysql

So, I have a problem. I have a query which returns ids from one table (say table1) and I have to pass those ids to another query which uses table2. (Writing inner selects or joins is not an option due to some certain reasons).
Query:
client = Mysql2::Client.new(:host => "localhost", :username => "", :password => "", :database =>"test")
query1 = %Q{select id from table1 where code='ABC123'}
ids = client.query(query1)
query2 = %Q{select * from table2 where `table2`.`table1_id` IN (#{ids}) and status="rejected"}
table2_data = client.query(query2)
ids is Mysql2::Result type
Also, when I do ids.to_a, the resulting array has data something like this: [{"id"=>1}, {"id"=>2}]
I need some feasible way to pass ids to the second query. I tried ids.to_a, but it gives error due to the braces [ ]. I have also tried concatenating, say the MySQL result is:
array = ids.to_a # [1,2,3]
id_new = "("+#{array.join(',')}+")"
id_new becomes "(1,2,3)" which is a string and hence IN doesn't work.
Can anyone please suggest something how to pass ids array in the raw MySQL query?
I have banged my head finding the answer, but couldn't find an appropriate one.
Edit: I can use Active Record only for query1 and if that is the case and ids is an Active Record object, can anyone suggest how to pass it to query2 in the IN clause which is supposed to be a raw SQL query?
Edit2: I can't use Active Record (for query2) or join because it's making the query heavy and taking long time (>10s) to fetch the result (indices are present). So, I am using raw query to optimise it.

When I ran similar queries to try to mimic your problem I saw that I'm getting an array of array for ids, like [["1"], ["2"], ["3"]].
If this is also what you're getting then you should call ids.flatten before calling join:
query2 = %Q{select * from table2 where `table2`.`table1_id` IN (#{ids.flatten.join(',')}) and status="rejected"}
array.flatten removes extra braces, so:
[[1], [2], [3]].flatten
# => [1,2,3]
[[1], [2], [3]].flatten.join(',')
# => "1,2,3"
EDIT
Since you reported you are receiving a Mysql2::Result object, do this:
ids.to_a.map(&:values).flatten.join(',')
The to_a first converts the Mysql2::Result to an array of hashes that looks like this:
[{"id"=>"1"}, {"id"=>"2"}]
Then using map(&:values) we convert it to an array that looks like this:
[["1"], ["2"]]
This array is similar to the above (before the edit), so running flatten.join(',') converts it to the string you are looking for.
Note that instead of doing map(&:values).flatten you could use the common shortcut flat_map(&:values) which results in the same thing.

Are you sure it doesn't work because it is a string. I think it doesn't work because of duplicate brackets. Please try this:
array = ids.flat_map(&:values).join(',')
query2 = %Q{select * from table2 where `table2`.`table1_id` IN (#{array}) and status="rejected"}
I suggest to use a ORM (object-relational mapping) like the ActiveRecord or Sequel gems - especially because building database queries manually by string concatination is error prone and leads to vulnerabilities like sql injections.

If the main reason you posted was to learn how to extract data from an array of hashes, then you can ignore this answer.
However, if you wanted the best way to get the data from the database, I'd suggest you use ActiveRecord to do the donkey work for you:
class Table1 < ActiveRecord::Base
self.table_name = :table1
has_many :table2s
end
class Table2 < ActiveRecord::Base
self.table_name = :table2
belongs_to :table1
end
table2_data = Table2.joins(:table1).where(table1: {code: 'ABC123'}, status: 'rejected')
A key point is that a SQL join, will effectively do the processing of the IDs for you. You could code up the SQL join yourself, but ActiveRecord will do that for you, and allow you to add the additional queries, such that you can gather the data you want in one query.

You can join array with comma, like following code.
ids = ids.to_a.map{|h| h['id']}
query2 = %Q{select * from table2 where `table2`.`table1_id` IN (#{ids.join(',')}) and status="rejected"}
table2_data = client.query(query2)
It will work fine.

Related

Converting ActiveRecord::Result to ActiveRecord::Base

I have running an SQL query, which ends up returning * from table ABC.
I am running this in my ruby on rails code by below command:
query:
sql = select * from ABC WHERE <condition>
results = ActiveRecord::Base.connection.exec_query(sql)
I am getting the outputs as results which is of type ActiveRecord::Result
This, I am converting to an array, by using function to_hash provided by ActiveRecord::Result. However, this is an array of Hashes.
Is there a way in which I can convert it to an array of ActiveRecord's
(I need to do further processing with each active record)
For ex: single_result.outdated? (where outdated is a field belonging to another table DEF which is connected to table ABC via single_result.id)
Any help is appreciated. Thanks!

What does it mean the colon in queries yii2 framework?

I'm totally new in yii2 and I want to know what does it mean the colon in the query?
I have made a research about binding parameters, but in the yii2 documentation says:
// returns all inactive customers
$sql = 'SELECT * FROM customer WHERE status=:status';
which side is from the data base? the left side or the right side?
which is a simple text and which one is a column from the DB? Im so confused.
what would be another way to make the query without the colon? is it valid?
why it has 'anyo = **:**valor' in the next example? and some others dont?
$dbLibro = Libro::find()->where('tipo = "Nacimiento"')->andWhere('cerrado = 0')->andWhere('anyo = :valor',[':valor'=>date("Y")])->one();
I hope its clear cause the documentation is a bit confusing for me.
The colons are not directly related with Yii2, it's related with PHP PDO extension that used by Yii2.
Each colon is placeholder used later for binding value. Check for example this question.
If we write this query in ActiveQuery:
SELECT * FROM customer WHERE status = :status
we can get something like this:
$query = Customer::find()->where('status = :status', [':status' => Customer::STATUS_ACTIVE]);
Assuming STATUS_ACTIVE constant equals to 1, after execution it transforms to this:
SELECT * FROM "customer" WHERE status = 1
So the left side (before equals) represents column name, right part - value which will be safely binded after.
But you don't have to write params by yourself, Yii2 QueryBuilder generates it automatically for you.
There are other ways to write query without colons and they are used more often. This query can be written like this:
$query = Customer::find(['status' => Customer::STATUS_ACTIVE]);
$models = $query->all();
Or like this using shortcut:
$models = Customer::findAll(['status' => Customer::STATUS_ACTIVE]);
Or it can be even put inside of a scope:
$models = Customer::find()->active();
In this case Yii generates parameters automatically and it will be equivalent to this:
SELECT * FROM "customer" WHERE "status"=:qp1
Value 1 will be binded to :qp1 parameter, note that in this case column names are also double quoted.
If you try to use more conditions, params will be :qp2, :qp3 and so on (default PARAM_PREFIX is :qp).
As for your second query, it can be rewritten like this:
$model = Libro::find()
->where([
'tipo' => 'Nacimiento',
'cerrado' => 0,
'anyo' => date('Y'),
])->one();
Such queries look way better and readable in this state.
Yii2 allows generate even more complex conditions in queries, check this section of the docs for more details.
P.S. It's better to use english for naming in your code. Think about other international developers supporting your code. date('Y') can be calculated using database functions depending on used RDBMS.

Rails 3 connection.execute IN statement parameters array syntax

I have a syntax question regarding the Rails 3 ActiveRecord::Base.connection.execute method and parameter that i want to use with it. I have been fighting with this for some hours now and just do not seem to find any answers to this specific question on the internet.
The database is MySQL.
I need to create a temporary table through SELECT with IN condition statement, where the list of values against which IN should check, is a parameter - rails Array. The code looks like so:
arr = [1,2,3]
ActiveRecord::Base.connection.execute("CREATE TEMPORARY TABLE things SELECT * FROM objects WHERE objects.id IN #{arr}")
I get a MySQL syntax error!
ActiveRecord::Base.connection.execute("CREATE TEMPORARY TABLE things SELECT * FROM objects WHERE objects.id IN #{(arr)}")
Again MySQL syntax error!
ActiveRecord::Base.connection.execute("CREATE TEMPORARY TABLE things SELECT * FROM objects WHERE objects.id IN (#{arr})")
MySQL syntax error
The above attempts correspond to this question-answer:
How to execute arbitrary parameterized SQL in rails
I even tried to use in the manner like with find_by_sql, but still get an error:
ActiveRecord::Base.connection.execute(["CREATE TEMPORARY TABLE things SELECT * FROM objects WHERE objects.id IN (:ids)",{:ids => arr }]) - obviously, I get an MySQL error.
Am I missing something obvious? Please help! I need this exactly in this way (e.g. create temporary table with exactly such conditions), otherwise a more complicated query based on this one will not work. Thanks!
Here's another option that uses Rails' query sanitization
arr = [1,2,3]
query = "SELECT * FROM objects where id IN (?)"
query = ActiveRecord::Base.send :sanitize_sql_array, [query, arr]
ActiveRecord::Base.connection.execute(query)
If you convert arr.to_s you get "[1, 2, 3]".
I think arr.join(', ') should work.
ActiveRecord::Base.connection.execute("CREATE TEMPORARY TABLE things SELECT * FROM objects WHERE objects.id IN (#{arr.join(', ')})")

Rails scope overriding select

I have a model with a has_many relationship, and a scope to determine whether or not it has any children, such as:
scope :with_nomination, :include => [:nomination], :conditions => "nominations.service_id IS NOT NULL"
Using this, I can do something like Service.with_nomination and receive a list of all services with nomination children.
The problem is that when I do something like Service.select("id, firstName, lastName").with_nomination ActiveRecord in essense does a SELECT * FROM services which is very bad and does not utilize the indexes I so painstakingly set up.
How can I either rephrase my query or modify my scopes to work with the .select() command?
Turns out in the syntax I was using, a select is not possible, so it does a select * and any further selects are already overriden.
I re-wrote the scopes like so:
scope :no_nomination, joins("LEFT JOIN nominations ON nominations.service_id = services.id").where("nominations.service_id IS NULL")
# important distinction here, the left join allows you to find those records without children
scope :with_nomination, joins(:nomination).where("nominations.service_id IS NOT NULL")
Using this syntax allows me to do something like Service.select(:id,:user,:otherfield).with_nomination
8 years later...
This is ugly, but you could also convert the resulting ActiveRecord::Relation into to sql with to_sql and run the command manually with ActiveRecord::Base.connection.execute.
It might look like this:
query = Service.select("id, firstName, lastName").with_nomination.to_sql
records = ActiveRecord::Base.connection.execute(query)
records.first["firstName"] # => First Name
This doesn't eliminate the excess columns that the scope retrieves, and you have to access each field with string keys, but hey, at least you can still access them!

CakePHP database query - am I overcomplicating things?

So, I need to search a real estate database for all homes belonging to realtors who are part of the same real estate agency as the current realtor. I'm currently doing this something like this:
$agency_data = $this->Realtor->find('all',array(
'conditions'=>
array(business_name'=>$realtor_settings['Realtor']['business_name']),
'fields'=>array('num'),
'recursive'=> -1
));
foreach($agency_data as $k=>$v){
foreach($v as $k=>$v1){
$agency_nums[] = $v1['num'];
}
}
$conditions = array(
'realtor_num'=>$agency_nums
);
It seems a bit crazy to me that I'm having to work so hard to break down the results of my first query, just to get a simple, one-dimensional array of ids that I can use to build a condition for my subsequent query. Am I doing this in an insanely roundabout way? Is there an easy way to write a single CakePHP query to communicate "select * from homes where realtor_num in (select num from realtors where business_name = 'n')"? If so, would it be any more efficient?
For sure it's complicated (in your way) :)
Depending from the results you can do following:
$agency_data = $this->Realtor->find('list',array(
'conditions'=>array('business_name'=>$realtor_settings['Realtor']['business_name']),
'fields'=>array('num', 'num'),
'recursive'=> -1
));
$agency_data; //this already contain array of id's
Method 2 - building a sub query there are 2 ways strict and not so strict :) The first one you can see here (search for Sub-queries).
The other option is to have following conditions parameter:
$this->Realtor->find('all', array('conditions'=>array('field in (select num from realtors where business_name like "'.$some_variable.'"))));
Of course be careful with the $some_variable in the sub-query. You shold escape it - use Sanitize class for example.
$agency_data = $this->Realtor->find('all',array(
'conditions'=>
array('business_name'=>$realtor_settings['Realtor']['business_name']),
'fields'=>array('num'),
'recursive'=> -1
));
$conditions = Set::extract("{n}.Realtor.num", $agency_data);
I would use something like Set::extract to grab the list of data you are looking for. The advantage of doing it this way is that you can reuse the same dataset in other places and save queries. You could also write the set::extract statement in this format:
$conditions = Set::extract("/Realtor/num", $agency_data);