Yii2: query across two databases - oracle

I have two databases (one in Oracle and one mySQL) and I somehow need to join the data.
The following query works:
$qry = oracleTableName::find()
->with('mysqlTableName')
->asArray()
->all();
and returns the following layout:
[0]=> array(
[id] => 1
[name] => test
[mysqlID] => 7
[mysqlTableName] => array(
[id]=>7
[score]=>1
)
)
However, if I use the select statement, it fails, (saying that the column mysqlTableName.id is an invalid identifier):
$qry = oracleTableName::find()
->with('mysqlTableName')
->select([
'oracleTableName.id as OracleID',
'mysqlTableName.id as MysqlID',
'mysqlTableName.score as Score'
])
->asArray()
->all();
How can I select from both databases (or "access" the mysql result) so that I have one output i.e.:
[0]=>array(
[OracleID]=>1
[MysqlID]=>7
[Score]=>3
Thank you
UPDATE
Here is the actual query and outputs:
NOTE: in this example, the table "MapInvestorToOpportunity" is mysql and the table "INVESTOR" is Oracle
This works fine:
$performance= MapInvestorToOpportunity::find()
->with('investor')
->andWhere(['fk_opportunityID' => $this->fk_opportunityID])
->limit(5)
->asArray()
->all();
And yields the following output:
Array
(
[0] => Array
(
[id] => 43797
[uid] => 0451/0258_DD45834-99207
[fk_opportunityID] => 3
[status] => 1
[fk_investorID] => 99207
[investor] => Array
(
[INVESTOR_ID] => 99207
[COUNTRY_ID] => US
[PRIMARY_INSTITUTION] => DD71233
I can clearly see the country ID. However, as soon as I select the country ID it fails:
$performance= MapInvestorToOpportunity::find()
->with('investor') // or ('investor INVESTOR')
->andWhere(['fk_opportunityID' => $this->fk_opportunityID])
->select([
'fk_opportunityID',
'fk_investorID',
'map_investor_to_opportunity.INVESTOR_ID',
'COUNTRY_ID', // or 'INVESTOR.COUNTRY_ID'
])
->limit(5)
->asArray()
->all();
Column not found: 1054 Unknown column 'INVESTOR.COUNTRY_ID' in 'field list'
The SQL being executed was: SELECT `fk_opportunityID`, `fk_investorID`, `INVESTOR`.`COUNTRY_ID` FROM `map_investor_to_opportunity` WHERE `fk_opportunityID`='3' LIMIT 5
My understanding is that it is not possible to join the data in a query because it is two different databases. However, I just wanted to make absolutely sure... it seems a little crazy considering the array output clearly shows the data from the Oracle database
Many thanks

I don't think it is possible to create JOIN between two completely separate DBMS. with() will register eager loading rules, but it will not create actual join between two tables - it will perform two separate queries to obtain necessary models.
To create actual join you should use joinWith() instead of with().
$qry = oracleTableName::find()
->joinWith('mysqlTableName')
->select([
'oracleTableName.id as OracleID',
'mysqlTableName.id as MysqlID',
'mysqlTableName.score as Score'
])
->asArray()
->all();
But this will most likely fail, since there is no support for cross-DB joins.
Probably the best what you could get is to query results separately and combine them at PHP level.

You should just add a table identifier after the table name.
$qry = oracleTableName::find()
->with('mysqlTableName msql')
->select([
'oracleTableName.id as OracleID',
'msql.id as MysqlID',
'msql.score as Score'
])
->asArray()
->all();
The reason for this is, the with clause accepts relation name, not Table name, which might be slightly, but enough different. If this doesn't work, please comment with the result of this query and I will edit the answer accordingly
$qry = oracleTableName::find()
->with('mysqlTableName msql')
->select([
'oracleTableName.id as OracleID',
'msql.id as MysqlID',
'msql.score as Score'
])
->createCommand()->rawSql;

Related

Laravel JOIN with JSON string

I have two tables in Laravel, one is the comment table, and the second is the users table. In the comment table, I have this type of data.
For this comment table, I want to match the tags column's userid in JSON, so how can we join that userid with the user's table? here is what I tried, but that is not working as expected.
$messages = TopicComment::where('user_id', $currentUserId)
->join("users", "users.id", "=", "users.id")
->(function ($query) {
$query->whereJsonContains('tags.userid', users.id);
})
->
->get()->toArray();
with this package you can create a relation via a json field
https://github.com/staudenmeir/eloquent-json-relations
First, there seem to be a number of errors in your code.
Judging from the DB schema, there is no user_id column in your comments table and so, ::where('user_id', $currentUserId) will not work.
A similar issue occurs in your join statement. You're joining on "users.id","=","users.id" which is the same column in the same table.
There's no method called in the line with function($query). Ideally, should be a where clause.
Correct usage of whereJsonContains would be:
$query->whereJsonContains('tags', ['userid' => $currentUserId]);
Rouge arrow after that line.
And so, your final result after correcting the changes should look like:
use Illuminate\Support\Facades\DB;
...
$messages = TopicComment::join('users', 'users.id', DB::Raw("CAST(comments.tags->'$.userid' AS UNSIGNED)"))
->where(function ($query) use ($currentUserId) {
$query->whereJsonContains('tags', ['userid' => $currentUserId]);
})
->get()
->toArray();
I think the only way to extract is to use json tables.
DB::select(DB::raw("
SELECT document_types.*, jst.name
FROM document_types,
JSON_TABLE(settings, '$[*]' COLUMNS (
`userid` int(11) PATH '$.userid',
`name` varchar(255) PATH '$.name'
)) jst
inner join users on jst.userid = users.id WHERE users.id = :id"
,['id' => $currentUserId]))->get()
credit: https://dba.stackexchange.com/questions/306938/extract-json-object-inside-of-an-json-array-then-search-base-on-id
Unfortunately I don't have json functions on my mysql so the code will probably fail, maybe at least it'll help get you on the right track.
You can try this way...
eg:
<?php
$comments = DB::table('topic_comments')
->join('users', 'users.id', '=', 'topic_comments.tags->userid')
->select('topic_comments.*', 'users.name')
->get();

how to Run SQL insert Using Multiple Database Connections

this my code
DB::connection('mysql2')->insert('INSERT INTO pm_booking_service (id_booking, title, qty, amount) VALUES ('2','restaurant','1','27')' );
I don't know how to do insert Using Multiple Database Connections
but select is working fine
$bookings = DB::connection('mysql2')->select('select * from pm_booking');
When you have a string that starts with a single quote you need to escape any single quotes it contains otherwise they are interpreted as closing the string.
Use parameters to not need to quote the values manually:
DB::connection('mysql2')
->insert('INSERT INTO pm_booking_service (id_booking, title, qty, amount) VALUES (?,?,?,?)', [2,'restaurant',1,27]);
An example of this can be found in the docs as well
This can be rewritten in the query builder as :
DB::connection('mysql2')->table('pm_booking_service')
->insert([
'id_booking' => 2,
'title' => 'restaurant',
'qty' => 1,
'amount' => 27
]);
Update statements are also written similarly:
DB::update(
'update pm_booking_service set qty = ? where id = ?',
[100, 2]
);
This also can be written in the query builder as:
DB::connection('mysql2')->table('pm_booking_service')
->where('id', 2)
->update([ 'qty' => 100 ]);

How to display most recent record added to database at the bottom in laravel?

I want display the record in descending order and by using orderBy it is working, but it is displaying in top( i need to view records like in chat most recent at bottom, but now i am getting as mail most recent top) can any one help me.
$data = DB::table("chatbox")
->where('dept_name',Session::get('dept_name'))
->orderBy('created_at', 'desc')
->get();
$data = DB::table("chatbox")
->where('dept_name',Session::get('dept_name'))
->orderBy('created_at', 'ASC')
->get();
just change desc to ASC
If you want to do it like messaging app style (last added record at the bottom), you could do this:
Make sure you know how many records you want to show at one page, for example, 10 records
Do your query as usual, to pull the last 10 records you want to show (sort by created_at, desc)
Now you should have 10 latest records on hand. In your view, you do an ascending sort on the created_at, then render them. So this will cause oldest record on top, and latest record at bottom.
Additionally:
Whenever there is a new record comes in, you can just append it into the list.
If the user need more data (pull down or something), you fetch 10 more records, and at your view, sort all records ascending, and render it again.
You can sort the collection after getting the results:
https://laravel.com/docs/5.5/collections#method-sortby
$collection = collect([
['name' => 'Desk', 'price' => 200],
['name' => 'Chair', 'price' => 100],
['name' => 'Bookcase', 'price' => 150], ]);
$sorted = $collection->sortBy('price');
$sorted->values()->all();
$data = DB::table("chatbox")
->where('dept_name',Session::get('dept_name'))
->orderBy('created_at', 'desc')
->paginate(20);
$sorted = $data->sortBy('created_at');
$sorted->values()->all();
I did not test it but you might give it a try.

How to get distinct values for non-key column fields in Laravel?

This might be quite easy but have no idea how to.
I have a table that can have repeated values for a particular non-key column field. How do I write a SQL query using Query Builder or Eloquent that will fetch rows with distinct values for that column?
Note that I am not fetching that column only, it is in conjunction with other column values, so distinct() might not really work. So that question basically can be how to specify the column that I want to be distinct in a query now that distinct() accepts no parameters?
You should use groupby. In Query Builder you can do it this way:
$users = DB::table('users')
->select('id','name', 'email')
->groupBy('name')
->get();
In Eloquent you can also query like this:
$users = User::select('name')->distinct()->get();
in eloquent you can use this
$users = User::select('name')->groupBy('name')->get()->toArray() ;
groupBy is actually fetching the distinct values, in fact the groupBy will categorize the same values, so that we can use aggregate functions on them. but in this scenario we have no aggregate functions, we are just selecting the value which will cause the result to have distinct values
Though I am late to answer this, a better approach to get distinct records using Eloquent would be
$user_names = User::distinct()->get(['name']);
**
Tested for Laravel 5.8
**
Since you wanna get all columns from the table, you can collect all of the data and then filter it using Collections function called Unique
// Get all users with unique name
User::all()->unique('name')
or
// Get all & latest users with unique name
User::latest()->get()->unique('name')
For more information you can check Laravel Collection Documentations
EDIT: You might have issue with perfomance, by using Unique() you'll get all data first from User table, and then Laravel will filter it.
This way isn't good if you have lots of Users data. You can use query builder and call each fields that you wanna use, example:
User::select('username','email','name')->distinct('name')->get();
Grouping by will not work if the database rules don't allow any of the select fields to be outside of an aggregate function. Instead use the laravel collections.
$users = DB::table('users')
->select('id','name', 'email')
->get();
foreach($users->unique('name') as $user){
//....
}
Someone pointed out that this may not be great on performance for large collections. I would recommend adding a key to the collection. The method to use is called keyBy. This is the simple method.
$users = DB::table('users')
->select('id','name', 'email')
->get()
->keyBy('name');
The keyBy also allows you to add a call back function for more complex things...
$users = DB::table('users')
->select('id','name', 'email')
->get()
->keyBy(function($user){
return $user->name . '-' . $user->id;
});
If you have to iterate over large collections, adding a key to it solve the performance issue.
Note that groupBy as used above won't work for postgres.
Using distinct is probably a better option - e.g.
$users = User::query()->distinct()->get();
If you use query you can select all the columns as requested.
$users = User::select('column1', 'column2', 'column3')->distinct()->get(); retrieves all three coulmns for distinct rows in the table. You can add as many columns as you wish.
I found this method working quite well (for me) to produce a flat array of unique values:
$uniqueNames = User::select('name')->distinct()->pluck('name')->toArray();
If you ran ->toSql() on this query builder, you will see it generates a query like this:
select distinct `name` from `users`
The ->pluck() is handled by illuminate\collection lib (not via sql query).
// Get unique value for table 'add_new_videos' column name 'project_id'
$project_id = DB::table('add_new_videos')->distinct()->get(['project_id']);
I had the same issues when trying to populate a list of all the unique threads a user had with other users. This did the trick for me
Message::where('from_user', $user->id)
->select(['from_user', 'to_user'])
->selectRaw('MAX(created_at) AS last_date')
->groupBy(['from_user', 'to_user'])
->orderBy('last_date', 'DESC')
->get()
Here are 3 ways I have tested that will give same result:
User::distinct()->get(['name'])->pluck('name');
User::select('name')->distinct()->pluck('name')->all();
DB::table('users')->select('name')->groupBy('name')->get()->pluck('name')->all();
For those who like me doing same mistake. Here is the elaborated answer
Tested in Laravel 5.7
A. Records in DB
UserFile::orderBy('created_at','desc')->get()->toArray();
Array
(
[0] => Array
(
[id] => 2073
[type] => 'DL'
[url] => 'https://i.picsum.photos/12/884/200/300.jpg'
[created_at] => 2020-08-05 17:16:48
[updated_at] => 2020-08-06 18:08:38
)
[1] => Array
(
[id] => 2074
[type] => 'PROFILE'
[url] => 'https://i.picsum.photos/13/884/200/300.jpg'
[created_at] => 2020-08-05 17:20:06
[updated_at] => 2020-08-06 18:08:38
)
[2] => Array
(
[id] => 2076
[type] => 'PROFILE'
[url] => 'https://i.picsum.photos/13/884/200/300.jpg'
[created_at] => 2020-08-05 17:22:01
[updated_at] => 2020-08-06 18:08:38
)
[3] => Array
(
[id] => 2086
[type] => 'PROFILE'
[url] => 'https://i.picsum.photos/13/884/200/300.jpg'
[created_at] => 2020-08-05 19:22:41
[updated_at] => 2020-08-06 18:08:38
)
)
B. Desired Grouped result
UserFile::select('type','url','updated_at)->distinct('type')->get()->toArray();
Array
(
[0] => Array
(
[type] => 'DL'
[url] => 'https://i.picsum.photos/12/884/200/300.jpg'
[updated_at] => 2020-08-06 18:08:38
)
[1] => Array
(
[type] => 'PROFILE'
[url] => 'https://i.picsum.photos/13/884/200/300.jpg'
[updated_at] => 2020-08-06 18:08:38
)
)
So Pass only those columns in "select()", values of which are same.
For example: 'type','url'. You can add more columns provided they have same value like 'updated_at'.
If you try to pass "created_at" or "id" in "select()", then you will get the records same as A.
Because they are different for each row in DB.
In my case (with Laravel 9), I need:-
to use where clause.
to get complete row.
to get unique entries for specific column.
As more complex query, but still, I have an easy approach. May be that help you also.
User::where('id',auth()->user()->id)->distinct('name')->get();

codeigniter insert from query how to optimized

I want to optimized my query on codeigniter.
this query works but it seems, its take time to insert the result to my database table.
$this->db->select('client_id');
$this->db->from('event');
$query = $this->db->get();
foreach($query->result_array() as $row){
$client_id = $row['client_id'];
$data = array( 'event_id' => $event_id , 'client_id' => $client_id);
$this->db->insert('event_entry', $data);
}
I would like to know if theres a way it to optimized.
Instead of doing n number of inserts, doing just a single insert should improve execution time. You can achieve this in codeigniters active record by using insert_batch() .
$this->db->select('client_id');
$this->db->from('event');
$query = $this->db->get();
$data = array();
foreach($query->result_array() as $row){
$client_id = $row['client_id'];
array_push($data, array('event_id' => $event_id , 'client_id' => $client_id));
}
$this->db->insert_batch('event_entry', $data);
Produces:
INSERT INTO event_entry (event_id, client_id) VALUES ('event_id', 'client_id'), ('event_id', 'client_id'), ...
Replace all of that code with:
$this->db->query("
INSERT INTO event_entry (event_id, client_id)
SELECT ?, client_id
FROM event
", array($event_id));
And you will clearly notice the difference in execution time :) Plus in my opinion, having less code to worry about.
This query can't be run from Active Records, but it should be quite self explaining. Just like a normal SELECT, if fetches client_id and the already defined value $event_id by each event row. It then takes these values, and INSERT them into event_entry.
Note that ? and array($event_id) insert the value into the query escaped (and safe). Never insert into query as SELECT {$event_id}, client_id unless you know what you're doing.
Jeemusu's solution is indeed a nice way to do it through Active Records, but if it's performance you want all the way, one query is faster than two in this case.
you can use insert_batch command to insert data to database . Produce your data array with foreach loop and then use insert_batch database.http://ellislab.com/codeigniter/user-guide/database/active_record.html
please let me know if you need any help

Resources