What does it mean the colon in queries yii2 framework? - yii2

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.

Related

Pass array in raw MySQL query in Ruby on Rails

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.

Zend Framework - mysql query

$xyz = mysql_fetch_array(mysql_query('select sum(value) points where userId = $userIdDB'))['suma'];
How that query will looks in zend framework? I need to select sum of records from DB as int.
And another question: can i make mysql queries. I dont have in real any knowledge from zend, so I please for full explanation.
What about mysql connections in zend?
In ZEND
$select = $db->select()
->from('points',array(new Zend_Db_Expr('sum(value)')))
->where('userId = ?', $userIdDB);
When Adding Expression Columns
Columns in SQL queries are sometimes expressions, not simply column
names from a table. Expressions should not have correlation names or
quoting applied. If your column string contains parentheses,
Zend_Db_Select recognizes it as an expression.
You also can create an object of type Zend_Db_Expr explicitly, to
prevent a string from being treated as a column name. Zend_Db_Expr is
a minimal class that contains a single string. Zend_Db_Select
recognizes objects of type Zend_Db_Expr and converts them back to
string, but does not apply any alterations, such as quoting or
correlation names.
EXAMPLE IN ZEND
// Build this query using Zend_Db_Expr explicitly:
// SELECT p."product_id", p.cost * 1.08 AS cost_plus_tax
// FROM "products" AS p
$select = $db->select()
->from(array('p' => 'products'),
array('product_id',
'cost_plus_tax' =>
new Zend_Db_Expr('p.cost * 1.08'))
);

DBIx::Class test agains mysql datetime function

I am using DBIx::Class and I would like to select rows based on what day of the year they were inserted on. Below is my query:
$rows = $c->model("DB::Test")->search(
{
"DAYOFYEAR(entry_time)"=>$day_of_year,
});
However this doesn't work because DBIx::Class treats DAYOFYEAR(entry_time) as a column. Is there anyway I could have it use that value litteraly? I know sometimes making it a scalar such as \'DAYOFYEAR(entry_time)' will work for some situations, but I've tried that and it doesn't work. Does anyone know of a way that I could do this? Thanks!
Using \ and 'DAYOFYEAR(entry_time)' is the right approach, and part of the FAQ.
[How do I] .. search with an SQL function on the left hand side?
To use an SQL function on the left hand side of a comparison you currently need to resort to literal SQL:
->search( \[ 'YEAR(date_of_birth) = ?', [ plain_value => 1979 ] ] );
Note: the plain_value string in the [ plain_value => 1979 ] part should be either the same as the name of the column (do this if the type of the return value of the function is the same as the type of the column) or in the case of a function it's currently treated as a dummy string (it is a good idea to use plain_value or something similar to convey intent). The value is currently only significant when handling special column types (BLOBs, arrays, etc.), but this may change in the future.

MySQL - using a foreach of sorts in an SQL Procedure

I've been doing a lot of searching for some way to create a "Foreach" of sorts with a MySQL query.
I know how I'd go about this if it were PHP or Java, but I want this to be all MySQL.
So, I'm going to create a sort of 'theoretical' MySQL situation, since the original query has a lot of unnecessary information that I feel confuses the scope of the question.
What I'm envisioning is something that works in this sort of way (This will be an ugly mess of php and mysql pseudo code.)
#rooms = ('master_bedroom'=>'MBS_MASTER_BEDROOM_SIZE',
'bed_room2' = 'B2S_BR_2_SIZE')
FOREACH(#rooms as #room=>#column_name)
{
UPDATE `relative_room_sizes` SET area = CALCULATE_AREA(`buildings`.#column_name)
WHERE `idx_computed_values`.`room_type` = #room
AND `idx_computed_values`.`computed_size` = 'large'
}
Am I just taking things too far, or is there some very basic feature of MySQL that I'm just having a lot of trouble discovering? I did a lot of searching here but any sort of foreach tends to relate back to using an actual programming language to take care of this sort of dirty work.
Edit:
Here's kind of the idea of what I want to achieve in the procedure, but I did it in PHP because... it's what I know :)...
Essentially I want to be able have a procedure that does what this PHP code will output, and hopefully in just as compact a manner.
<?php
$rooms = array(
'master_bedroom' => 'MBS_MASTER_BEDROOM_SIZE',
'bed_room2' => 'B2S_BR_2_SIZE',
'bed_room3' => 'B3S_BR_3_SIZE',
'bed_room4' => 'B4S_BR_4_SIZE',
'living_room' => 'LRS_LIVING_ROOM_SIZE',
'dining_room' => 'DRS_DINING_ROOM_SIZE ',
'kitchen' => 'KTS_KITCHEN_SIZE',
'family_room' => 'FRS_FAMILY_ROOM_SIZE');
$room_sizes = array('large','small');
$table_with_properties = 'idx_search';
?>
<?foreach($rooms as $room_type=>$column_name):?>
<?foreach ($room_sizes as $room_size):?>
#Initialize field in case it isn't already declared
INSERT INTO `idx_computed_values` (`room_type`,`computed_size`,`area`)
values ('<?=$room_type?>','<?=$room_size?>',0)
ON DUPLICATE KEY UPDATE `computed_size` = `computed_size`;
UPDATE `idx_computed_values` SET area =
(SELECT AVG(CALCULATE_AREA(`<?=$table_with_properties?>`.<?=$column_name?>))<? if($room_size == 'large'):?>+<?php endif;?><? if($room_size == 'small'):?>-<?php endif;?>
(0.6 * STDDEV(CALCULATE_AREA(`idx_search`.<?=$column_name?>)))
FROM `<?=$table_with_properties?>`
WHERE `<?=$column_name?>` IS NOT NULL )
WHERE `idx_computed_values`.`room_type` = '<?=$room_type?>'
AND `idx_computed_values`.`computed_size` = '<?=$room_size?>';
<? endforeach;?>
<? endforeach;?>
It sounds like you want to create a stored procedure. Assuming that the "array" comes from the result of a query, you'll have to use cursors. MySQL's documentation has an example you can refer to at http://dev.mysql.com/doc/refman/5.0/en/cursors.html or http://dev.mysql.com/doc/refman/5.0/en/create-procedure.html.
MySQL does have looping constructs if you define them in a stored procedure, as defined here: For loop example in MySQL
However SQL looping constructs are not MySQL's strong point because they are not easy to read and are relatively inefficient compared to when loops are done elsewhere like in Java/Python/PHP.
SQL is best used for when the data structure used to represent the data is appropriately vectorized, and you can perform operations simply, such as: update stuff from mytable where column = 'foobar' joining tables on keys if needed.
If you need looping constructs in SQL, then either the data is not laid out correctly in the database or the task you're trying to do is so complex that it can't be represented intuitively in SQL.

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);