PHP Codeigniter - Join a table if column does not equal NULL - php

Setup:
Codeigniter 3 running on a variant of PHP 5 server. Using the Query Builder to deal with the db.
Background:
Creating a software that has a client profile. Users can add multiple notes to the profile, and link a contact to that note. The profile then displays all the notes and the contact on the corresponding profile, using the join() method from Codeigniter 3's Query Builder.
Issue:
A note can be added without a client contact, in the case of a generalised note. This sets the default value of NULL in the DB which in turn prevents the client_model from returning the notes, because it cant join the tables.
Current code:
function get_client_notes($client_id)
{
$this->db->join('nh_note_types', 'nh_note_types.type_id = nh_client_notes.client_notes_type');
$this->db->join('nh_user_profiles', 'nh_user_profiles.user_profile_user_id = nh_client_notes.client_notes_added_by');
$this->db->join('nh_client_contacts', 'nh_client_contacts.client_contact_id = nh_client_notes.client_notes_client_contact_id');
$this->db->order_by("client_notes_added_date", "desc");
$query = $this->db->get_where('nh_client_notes', array('client_notes_client_id' => $client_id));
return $query;
}
Currently if the value for client_notes_client_contact_id is NULL it will not return any data for that row.
What I am trying to find out is if there is a way to do the following:
IF the client_notes_client_contact_id is not null then join the tables, else carry on past.
Or any other way that would join the tables if there is a value and where it is NULL, then don't join.
Any help is appreciated!

Your current MySQL query with the above Query builder would 'build' like this:
SELECT
*
FROM
nh_client_notes
JOIN nh_note_types ON
( nh_note_types.type_id = nh_client_notes.client_notes_type )
JOIN nh_user_profiles ON
( nh_user_profiles.user_profile_user_id = nh_client_notes.client_notes_added_by )
JOIN nh_client_contacts ON
( nh_client_contacts.client_contact_id
=
nh_client_notes.client_notes_client_contact_id
)
WHERE
client_notes_client_id = 479
ORDER BY
client_notes_added_date DESC
However, this will require all the JOINs to have an matching ID Available. Thus the correct MySQL would be LEFT JOIN on client_notes_client_contact_id key, which is as you've requested to be conditional.
In this instance, adjust your Query builder to have a third parameter on the join() to make this 'left'.
<?php
$this->db->join(
'nh_client_contacts',
'nh_client_contacts.client_contact_id = nh_client_notes.client_notes_client_contact_id',
'left'
);
?>
In doing so, this'll correct your query to bring back client_notes regardless of client_notes_client_contact_id.

Related

How to group by aggregation of column in foreign table with many-to-many relation in Eloquent?

I am starting to get headaches over this so I thought I just post it here.
I have two tables that are related through a pivot table (as it is a many-to-many relationship). I use Laravel and Eloquent (but general help on how to achieve this with normal SQL queries is also highly appreciated).
I want to order the first table based a column of the second one but the column needs to be "aggregated" for this.
Example with Cars that are shared by many drivers and can have different colors:
Car-Table: [id, color]
Driver-Table: [id, name]
Car.Driver-Table: [car_id, driver_id]
I need a query that gets all drivers that only drive red cars and then all that don't drive red cars.
I have to use a query because I'll maybe do other things (like filtering) on this query afterwards and want to paginate in the end.
I already use queries that get either one of the two groups. They look like this:
In the Driver model:
public function redCars() {
return $this->cars()->where('color', 'red');
}
public function otherColoredCars() {
return $this->cars()->where('color', '<>', 'red');
}
And then in somewhere in a controller:
$driversWithOnlyRedCars = Driver::whereDoesntHave('otherColoredCars')->get();
$driversWithoutRedCars = Driver::whereDoesntHave('redCars')->get();
Is there a way to combine these two?
Maybe I am just thinking completely wrong here.
Update for clarification:
Basically I would need something like this (ot any other way that would lead to the same outcome)
$driversWithOnlyRedCars->addTemporaryColumn('order_column', 0); // Create temporary column with value 0
$driversWithoutRedCars->addTemporaryColumn('order_column', 1);
$combinedQuery = $driversWithOnlyRedCars->combineWith($driversWithoutRedCars); // Somehow combine both queries
$orderedQuery = $combinedQuery->orderBy('order_colum');
$results = $combinedQuery->get();
Update 2
I think, I found out how to get near my goal with raw queries.
Would be something like this:
$a = DB::table(DB::raw("(
SELECT id, 0 as ordering
FROM drivers
WHERE EXISTS (
SELECT * FROM cars
LEFT JOIN driver_car ON car.id = driver_car.car_id
WHERE driver.id = driver_car.driver_id
AND cars.color = 'red'
)
) as only_red_cars"));
$b = DB::table(DB::raw("(
SELECT id, 1 as ordering
FROM drivers
WHERE EXISTS (
SELECT * FROM cars
LEFT JOIN driver_car ON car.id = driver_car.car_id
WHERE driver.id = driver_car.driver_id
AND cars.color <> 'red'
)
) as no_red_cars"));
$orderedQuery = $a->union($b)->orderBy('ordering');
Now the problem is that I need the models ordered like this and paginated in the end so this is not really an answer to my question. I tried to convert this back to models but I didn't succeed yet. What I tried:
$queriedIds = array_column($orderedQuery->get()->toArray(), 'id');
$orderedModels = Driver::orderByRaw('(FIND_IN_SET(drivers.id, "' . implode(',', $queriedIds) . '"))');
But looks like FIND_IN_SET only allows for a column of the table as second parameter. Is there another way to get the Models in the right order out of the ordered union query?
You can use a UNION query:
$driversWithOnlyRedCars = Driver::select('*', DB::raw('0 as ordering'))
->whereDoesntHave('otherColoredCars');
$driversWithoutRedCars = Driver::select('*', DB::raw('1 as ordering'))
->whereDoesntHave('redCars');
$drivers = $driversWithOnlyRedCars->union($driversWithoutRedCars)
->orderBy('ordering')
->orderBy('') // TODO
->paginate();
How do you want drivers with the same ordering to be ordered? You should add a second ORDER BY clause to get a consistent order every time you execute the query.
This is the best I got:
$driversWithOnlyRedCars = Driver::whereHas('cars',function($q){
$q->where('color', 'red');
})->withCount('cars')->get()->where('cars_count',1);

Join on get_where - is this possible?

I have a table called Listings and a table called wines and one more called wineinfo
I originally was using the following to get the info from the listings table only, how ever since I restructured my DB, it requires the use of two other tables.
$listing = $this->db->get_where( "listings", [ "listingID" => $id ]
)->row();
if( !$listing )
throw new Exception("Error: Listing you're trying to bid on does not exist.", 1);
// check not to bid on own listings
if( $listing->list_uID == $userid )
throw new Exception("Error: Dont't bid on your own listings.", 1);
I then tried changing the code so the JOIN statements could work
$this->db->select("FROM listings.*, Vintage, Vineyard, Wine_Name, Region, Advice, Grape,Producer,Type id,wine_id,Wine_Name,");
$this->db->from("wineinfo");
$this->db->where(["listingsID" => $id]);
$this->db->where(["wineinfo.wine_id" => "listings.wine_id"]);
$this->db->where(["wineinfo.Vintage" => "listings.wine_id"]);
$this->db->join("wines", "wineinfo.wine_id = wines.wine_id");
$listing = $this->db->get()->row();
I am being given this error.
Unknown table 'listings'
But there is 100% a table called listings.
I know I've missed something, or definitely messed up the code, I am only just learning about this and the code above worked for something else, but now I've amended it to this, it hasn't.
I then tried changing the code so the JOIN statements could work
you are trying to combine 3 tables with 2 FROM and one JOIN clauses, which is incorrect the way you do it.
you need to keep SELECT clean, just select the columns you need, like:
$this->db->select("listings.*, wineinfo.*, wine.*");
then the FROM clause:
$this->db->from("wineinfo");
then make the joins:
$this->db->join("listings", "wineinfo.wine_id = listings.listingsID");
$this->db->join("wines", "wineinfo.wine_id = wines.wine_id");
followed by your where clauses.
please note, I don't know your table structure, so I can only guess your JOIN relationships. Also this is a simplified example, where I suppose that the 3 tables don't have matching column names.
response to "ambiguous" comment: you can limit your select clause to just necessary columns, e.g.
$this->db->select("listings.*, wineinfo.vintage, wine.*");
or use an alias:
$this->db->select("listings.*, wineinfo.wine_id as my_wineID, wine.*");
The FROM in $this->db->select("FROM listings.*, Vintage, Vineyard, Wine_Name, Region, Advice, Grape,Producer,Type id,wine_id,Wine_Name,"); is not supposed to be there.
The generated out query now looks like:
SELECT FROM *... FROM sometable

Grocery Crud - Can't differentiate attribute in returning query table

I've been using Grocery Crud to develop a simple local application that allows users to register themselves and like bands and rate them and select people they know that are also registered in the application.
Entities:
Person(person_id,person_URL, fullname, hometown)
Band(band_id,band_URL,band_name,country,genre)
Relationships:
Likes(person_id,band_id,rate)
Knows(person_id,known_person_id)
My questions are:
1) I want to return a table of person and known person like below:
KNOWS
person_id | fullname | known_person_id | known_fullname
but I can't use *set_relation_n_n* function 'cause the relationship is (Person -> Likes -> Person), so it's giving me error. The other solution I came up with is making a custom table making a query to return the values I want and show it in the table (code below). The custom table returned is correct but when I render it to my Grocery Crud table, I need to specify $crud->columns('person_id', 'fullname', 'known_person_id', 'fullname'), and it cannot differentiate the fullname of the person and the fullname of the known person. How would I make it in order to be able to show the table that way?
2) I have the same issue in another table but could manage that using the function *set_relation_n_n* 'cause it's a relationship (Person -> Likes -> Band), so since it's 2 different entities it didn't return me a error. The problem is that the query (code below) returns me the whole table and I want only 25 records per page. When I try to use "LIMIT 25" in the query, it returns me ONLY 25 records and the "next page" button doesn't work. Any solutions?
Below, all the information:
CODE for question 1:
function knows_management()
{
$crud = new grocery_CRUD();
$crud->set_model('model_socialnetwork');
$crud->set_table('knows');
$crud->set_subject('Known');
$crud->basic_model->set_query_str('SELECT tb1.person_id, tb1.fullname, tb1.known_person_id, person.fullname FROM (SELECT person.person_id, person.fullname, knows.known_person_id FROM person INNER JOIN knows ON person.person_id = knows.person_id) tb1 INNER JOIN person ON tb1.known_person_id = person.person_id');<br>
$crud->columns('person_id','fullname','known_person_id','fullname');
$output = $crud->render();
$this->_socialnetwork_output($output);
}
CODE for question 2:
function likes_management()
{
$crud = new grocery_CRUD();
$crud->set_model('model_socialnetwork');
$crud->set_table('likes');
$crud->set_subject('Like');
$crud->columns('person_id','fullname','band_id','band_name', 'rate');
$crud->basic_model->set_query_str('SELECT tb2.person_id, tb2.fullname, tb2.band_id, band.band_name, tb2.rate FROM(SELECT tb1.person_id, person.fullname, tb1.band_id, tb1.rate FROM(SELECT person.person_id, likes.band_id, likes.rate FROM person INNER JOIN likes ON person.person_id = likes.person_id) tb1 INNER JOIN person ON tb1.person_id = person.person_id) tb2 INNER JOIN band ON tb2.band_id = band.band_id');
$output = $crud->render();
$this->_socialnetwork_output($output);
}
Question 1) What if you use an alias name in your query, for example
SELECT tb1.person_id, tb1.fullname as Tb1fullName, tb1.known_person_id, person.fullname as PersonFullName
Question 2) I would not recommend you to add LIMIT directly / manually in your query. In the file application/config/grocery_crud.php, you have two options directly related to pagination
You should use and configure them properly
// The default per page when a user firstly see a list page
$config['grocery_crud_default_per_page'] = 25;
....
//Having some options at the list paging. This is the default one that all the websites are using.
//Make sure that the number of grocery_crud_default_per_page variable is included to this array.
$config['grocery_crud_paging_options'] = array('10','25','50','100');

Codeigniter active record left join issue

I have two tables 'accounts_transactions' and 'accounts_bills_transactions'.
I have to left join these two using active record of codeigniter.But the names of key columns used to join are different.So I am not getting the key column from the left table in the output .What query should I write to get the key column from the left table included in the result.
My code is
$this->db->select('*');
$this->db->from('accounts_transactions');
$this->db->join('accounts_bills_transactions', 'accounts_transactions.id = accounts_bills_transactions.transaction_id','left');
$query = $this->db->get();
So, as you see the key columns used to join here are , id from left table and transaction_id from second table.The problem is that I am not getting the id from left table in the result.But I am getting all other columns.I assume the problem is because of difference in column names used to join.ie both the column names are not named 'id' .So how can I get the id from left table included in the result.
You could alias them:
$this->db->select('accounts_transatctions.*, account_transactions.id AS a_id,
accounts_bills_transactions.*,
account_bills_transactions.id AS ab_id');
$this->db->from('accounts_transactions');
$this->db->join('accounts_bills_transactions', 'accounts_transactions.id = accounts_transactions.transaction_id','left');
$query = $this->db->get();
The two IDs will now be available as a_id and ab_id (or whatever alias you choose)
Note: I'm not sure if you can alias in AR without avoiding escaping (haven't been using CI for a while). Should you get any error for that reason, just pass false as second parameter of $this->db->select():
$this->db->select('...', false);
you can try this if you confuse of using $this->where or $this->join
$query = $this->db->query("select ......");
return $query;
You problem is so simple. You can use this query
$query = $this->db
->select('at.*')
->select('abt.id as abt_id');
->from('accounts_transactions at');
->join('accounts_bills_transactions abt', 'at.id = abt.transaction_id','left');
->get()
->result();
When same column are used in join it selects only one. You need to give alise to the other column in second table. The best practice is to use a structure like this
accounts_transatctions
--------------------------
accounts_transatctions_id
other_columns
accounts_bills_transactions
---------------------------
accounts_bills_transactions_id
accounts_transatctions_id
other_columns

Mysql JoinInner if not null

Hi i have following query where it's use joininner statement to get all possible businesses. But when a business is created for first time only 1 category will be updated the rest 2 will remain null
public function searchBusinessByCategoryString($str = null, $city=null,$start,$perpage)
{
$select = $this->getDbTable()->getAdapter()->select();
$select->from('business as b', array('b.business_name','b.business_url','b.reviews_num','b.cat_id','b.business_id','b.rating','b.business_phone','b.business_add1','b.business_add2','b.x','b.y','b.photo_url'))
->joinInner('business_category as bc','b.cat_id = bc.cat_id',array('bc.cat_name'))
->joinInner('business_sub_category as bsc','b.sub_cat_id = bsc.b_sub_cat_id',array('bsc.b_subcat_name','bsc.b_sub_cat_id'))
->joinInner('business_sub_category as bsc2','b.sub_cat2_id = bsc2.b_sub_cat_id',array('bsc2.b_subcat_name','bsc2.b_sub_cat_id'))
->joinInner('business_sub_category as bsc3','b.sub_cat3_id = bsc3.b_sub_cat_id',array('bsc3.b_subcat_name','bsc3.b_sub_cat_id'))
->where("bsc.b_subcat_name like '".$str."%'")
->orWhere("bsc.b_subcat_name like '%".$str."'")
->orWhere("bsc.b_subcat_name= '".$str."'")
->orWhere("bsc2.b_subcat_name like '%".$str."'")
->orWhere("bsc2.b_subcat_name = '".$str."'")
->orWhere("bsc2.b_subcat_name like '".$str."%'")
->orWhere("bsc3.b_subcat_name like '%".$str."'")
->orWhere("bsc3.b_subcat_name = '".$str."'")
->orWhere("bsc3.b_subcat_name like '".$str."%'");
$result = $this->getDbTable()->getAdapter()->fetchAll($select);
return $result;
}
Now the issues is how can i not doing joininner query if the rest 2 categories are null? My above statement return empty result event though there is businesses with one category.
use leftJoin instead of innerJoin where the joined table can contain NULL value. INNER JOIN will join table, using the condition and will not keep lines when a null value is found on the joined table. LEFT JOIN will allow you to keep this line

Categories