I am attempting to join two tables using the Laravel's query builder however I seem to be having an issue getting the desired result using the query builder, I can however get it quite simply using a raw SQL statement. I simply want to return all mod rows that have the corrosponding value in the tag column in the tags table.
Schema
--------------
mods
--------------
id - int - (primary key)
name - varchar
--------------
tags
--------------
id - int
modid - int - (primary key of its parent mod)
tag - varchar
Working SQL query
SELECT * FROM mod JOIN tags ON tags.tag LIKE '%FPS%'
Query Builder
DB::table('mods')
->join('tags', function ($join) {
$join->on('tags.tag', 'like', '%FPS%');
})
->get();
Currently this is telling me: Unknown column '%FPS%' in 'on clause' but I am unsure how else to structure this. I intend on adding more orOn clauses as well as I will want to get results on multiple tags but firstly I just want to get a single tag working.
Appreciate any help.
SELECT * FROM mod JOIN tags ON tags.tag LIKE '%FPS%'
Your query builder is refusing to generate this query because it's nonsense!
To work correctly, a JOIN clause needs to compare two columns for equality -- one column on each side of the join table. A JOIN clause that doesn't do this is functionally "downgraded" to a WHERE clause. In the case of this query, the two tables end up cross joined.
What you probably want is:
SELECT * FROM mod
JOIN tags ON tags.modid = mod.id
WHERE tags.tag LIKE '%FPS%';
$join->on('tags.tag', 'like', '%FPS%');
Try by replacing
$join->where('tags.tag', 'like', '%FPS%');
This because the on method waiting a name of a field not a query value if you want it to deal with it in this way, you should use DB::raw('%FPS%').
Maybe you are trying to do something like the following:
DB::table('mods')
->select(DB::raw('mods.id as modid, mods.name, tags.id as tagid, tags.tag'))
->join('tags', function ($join) {
$join->on('tags.modid', '=', 'mods.id');
})
->where('tags.tag', 'like', '%FPS%')
->get();
If you want to see what is run in the database use
dd(DB::getQueryLog())
to see what queries were run.
Try this
Thank you
I am making an online directory, this directory contains businesses, this is how the current table structure is set out:
1) "Business"
ID (PK)
Name
Phone_Number
Email
2) Tags
id (PK)
tag
3) Business_tags
id (PK)
business_id (FK)
tag_id (FK)
There are over 9k rows inside of the business table, and over 84,269 rows and there are over 29k rows inside the ("Business_tags") table (As a business can have multiple tags).
Inside the business model, is the following:
public function tags()
{
return $this->belongsToMany('App\Tags');
}
The issue is when I am trying to do a search, so for example, let's say that someone wants to search for a "Chinese" then it's takes more time than it probably should to return a value. For example, I am using:
$business = Business::where(function ($business) use ($request) {
$business->whereHas('tags', function ($tag) use ($request) {
});
})->paginate(20);
Searching takes on average: 35 seconds to display the results.
Here is the raw sql:
select * from `businesses` where (exists (select * from `tags` inner join `business_tags` on `tags`.`id` = `business_tags`.`tags_id` where `business_tags`.`business_id` = `businesses`.`id` and `name` in ('chinese')))
This takes on average: 52.4s to run inside Sequel pro (Using the raw SQL statement)
Any ideas how I can improve the performance of this query so that it's a lot faster? I want to have this functionality, but the user is not going to wait this long for a response!
EDIT:
1 PRIMARY businesses NULL ALL NULL NULL NULL NULL 8373 100.00 Using where
2 DEPENDENT SUBQUERY business_tags NULL ALL NULL NULL NULL NULL 30312 10.00 Using where
2 DEPENDENT SUBQUERY tags NULL eq_ref PRIMARY PRIMARY 4 halalhands.business_tags.tags_id 1 10.00 Using where
You're over-complicating this, and not using eloquent relationships correctly. You should be using JOINs instead:
$businesses = Business::join('business_tags', 'business_tags.business_id', '=', 'business.id')
->join('tags', function($join) {
$join->on('business_tags.tag_id', '=', 'tags.id')
->where('tags.name', '=', 'chinese');
})->get();
Or in raw SQL:
SELECT *
FROM `business`
INNER JOIN `business_tags` ON `business_tags`.`business_id` = `business`.`id`
INNER JOIN `tags` ON `business_tags`.`tag_id` = `tags`.`id` AND `tags`.`name` = 'chinese'
(Note that you could put that tags.name = 'chinese' part in the WHERE clause and yield the same effect)
Your current query does an exists subquery to get all the records from the pivot table that match the criteria, then passing that back to the main query. It's an extra step, and it's unnatural.
Eloquent relationships are NOT for complex queries like this, but are rather there to provide additional, related information about a record without having to write another query manually.
For instance, if you want to view a business, you might query with() phone numbers and addresses from other tables. You might want to list out their tags, or sync() them. But eloquent does not build and filter queries, that's what query builder is for.
Let me know if you need more explanation.
As a lot of others are also going to tell you.
Have you run EXPLAIN on your query?
Have you added indexes to your tables?
Because even with the amount of data you have mentioned the query should have been faster than what you have reported.
Also see if a JOIN can work here and if faster?(just a thought)
i have multiple table that join together and i need one query and get all references too ! is that possible in yii2??
get them in hierarchy array ??
How ???
Is it possible to do not use join???
thanks for your help!!!!
If you created the model classes for each table using Gii and selected to create the relations in the generated models, you can do something like the following.
1) In your Countries model just change the method that declares the relationship with Airports like this:
public function getAirports() {
return $this->hasMany(Airports::className(), ['country_id' => 'id'])->with('airlines');
}
2) When you do the query for the countries and you need to have the related airports, airlines and flightbooked do it like this:
$countries = Countries::find()
->where('something = something_else')
->with('airports')
->with('flightbooked')
->all();
This way you will get all the related models populated with way less queries to the database than using lazy-loading.
I just wanted to give a small suggestion:
As you are maintaining the relations in the tables and if you have generated your code using Gii, then that will generate the joins for you. You can then access any column of any table easily.
But I think UNION may not be an alternative to JOIN.
Maybe u can use union all for this. with this operator, you can concatenate the result sets from multiple queries together, preserving all of the rows from each. Note that a UNION operator (without the ALL keyword) will eliminate any "duplicate" rows which exist in the resultset. The UNION ALL operator preserves all of the rows from each query (and will likely perform better since it doesn't have the overhead of performing the duplicate check and removal operation).
The number of columns and data type of each column must match in each of the queries. If one of the queries has more columns than the other, we sometimes include dummy expressions in the other query to make the columns and datatypes "match". Often, it's helpful to include an expression (an extra column) in the SELECT list of each query that returns a literal, to reveal which of the queries was the "source" of the row.
SELECT 'col1' AS source, col23, col343, col33, d FROM table1 WHERE ...
UNION ALL
SELECT 'col2', t2.fee, table2.fi, table2.fo, 'fum' FROM table2 JOIN table3 ON ...
UNION ALL
SELECT 'col3', '1', '2', buckle, my_shoe FROM table4
You can wrap a query like this in a set of parenthesis, and use it as an inline view (or "derived table", in MySQL lingo), so that you can perform aggregate operations on all of the rows. e.g:
select one.a
, SUM(one.b)
FROM (
SELECT 'q1' AS source, a, b, c, d FROM t1
UNION ALL
SELECT 'q2', t2.fee, t2.fi, t2.fo, 'fum' FROM t2
) one
GROUP BY one.a
ORDER BY one.a
But i think joining tables more suitable. Hope help you
I have a column in one of my table where I store multiple ids seperated by comma's.
Is there a way in which I can use this column's value in the "IN" clause of a query.
The column(city) has values like 6,7,8,16,21,2
I need to use as
select * from table where e_ID in (Select city from locations where e_Id=?)
I am satisfied with Crozin's answer, but I am open to suggestions, views and options.
Feel free to share your views.
Building on the FIND_IN_SET() example from #Jeremy Smith, you can do it with a join so you don't have to run a subquery.
SELECT * FROM table t
JOIN locations l ON FIND_IN_SET(t.e_ID, l.city) > 0
WHERE l.e_ID = ?
This is known to perform very poorly, since it has to do table-scans, evaluating the FIND_IN_SET() function for every combination of rows in table and locations. It cannot make use of an index, and there's no way to improve it.
I know you said you are trying to make the best of a bad database design, but you must understand just how drastically bad this is.
Explanation: Suppose I were to ask you to look up everyone in a telephone book whose first, middle, or last initial is "J." There's no way the sorted order of the book helps in this case, since you have to scan every single page anyway.
The LIKE solution given by #fthiella has a similar problem with regards to performance. It cannot be indexed.
Also see my answer to Is storing a delimited list in a database column really that bad? for other pitfalls of this way of storing denormalized data.
If you can create a supplementary table to store an index, you can map the locations to each entry in the city list:
CREATE TABLE location2city (
location INT,
city INT,
PRIMARY KEY (location, city)
);
Assuming you have a lookup table for all possible cities (not just those mentioned in the table) you can bear the inefficiency one time to produce the mapping:
INSERT INTO location2city (location, city)
SELECT l.e_ID, c.e_ID FROM cities c JOIN locations l
ON FIND_IN_SET(c.e_ID, l.city) > 0;
Now you can run a much more efficient query to find entries in your table:
SELECT * FROM location2city l
JOIN table t ON t.e_ID = l.city
WHERE l.e_ID = ?;
This can make use of an index. Now you just need to take care that any INSERT/UPDATE/DELETE of rows in locations also inserts the corresponding mapping rows in location2city.
From MySQL's point of view you're not storing multiple ids separated by comma - you're storing a text value, which has the exact same meaing as "Hello World" or "I like cakes!" - i.e. it doesn't have any meaing.
What you have to do is to create a separated table that will link two objects from the database together. Read more about many-to-many or one-to-many (depending on your requirements) relationships in SQL-based databases.
Rather than use IN on your query, use FIND_IN_SET (docs):
SELECT * FROM table
WHERE 0 < FIND_IN_SET(e_ID, (
SELECT city FROM locations WHERE e_ID=?))
The usual caveats about first form normalization apply (the database shouldn't store multiple values in a single column), but if you're stuck with it, then the above statement should help.
This does not use IN clause, but it should do what you need:
Select *
from table
where
CONCAT(',', (Select city from locations where e_Id=?), ',')
LIKE
CONCAT('%,', e_ID, ',%')
but you have to make sure that e_ID does not contain any commas or any jolly character.
e.g.
CONCAT(',', '6,7,8,16,21,2', ',') returns ',6,7,8,16,21,2,'
e_ID=1 --> ',6,7,8,16,21,2,' LIKE '%,1,%' ? FALSE
e_ID=6 --> ',6,7,8,16,21,2,' LIKE '%,6,%' ? TRUE
e_ID=21 --> ',6,7,8,16,21,2,' LIKE '%,21,%' ? TRUE
e_ID=2 --> ',6,7,8,16,21,2,' LIKE '%,2,%' ? TRUE
e_ID=3 --> ',6,7,8,16,21,2,' LIKE '%,3,%' ? FALSE
etc.
Don't know if this is what you want to accomplish. With MySQL there is feature to concatenate values from a group GROUP_CONCAT
You can try something like this:
select * from table where e_ID in (Select GROUP_CONCAT(city SEPARATOR ',') from locations where e_Id=?)
this one in for oracle ..here string concatenation is done by wm_concat
select * from table where e_ID in (Select wm_concat(city) from locations where e_Id=?)
yes i agree with raheel shan .. in order put this "in" clause we need to make that column into row below code one do that job.
select * from table where to_char(e_ID)
in (
select substr(city,instr(city,',',1,rownum)+1,instr(city,',',1,rownum+1)-instr(city,',',1,rownum)-1) from
(
select ','||WM_CONCAT(city)||',' city,length(WM_CONCAT(city))-length(replace(WM_CONCAT(city),','))+1 CNT from locations where e_Id=? ) TST
,ALL_OBJECTS OBJ where TST.CNT>=rownum
) ;
you should use
FIND_IN_SET Returns position of value in string of comma-separated values
mysql> SELECT FIND_IN_SET('b','a,b,c,d');
-> 2
You need to "SPLIT" the city column values. It will be like:
SELECT *
FROM table
WHERE e_ID IN (SELECT TO_NUMBER(
SPLIT_STR(city /*string*/
, ',' /*delimiter*/
, 1 /*start_position*/
)
)
FROM locations);
You can read more about the MySQL split_str function here: http://blog.fedecarg.com/2009/02/22/mysql-split-string-function/
Also, I have used the TO_NUMBER function of Oracle here. Please replace it with a proper MySQL function.
IN takes rows so taking comma seperated column for search will not do what you want but if you provide data like this ('1','2','3') this will work but you can not save data like this in your field whatever you insert in the column it will take the whole thing as a string.
You can create a prepared statement dynamically like this
set #sql = concat('select * from city where city_id in (',
(select cities from location where location_id = 3),
')');
prepare in_stmt from #sql;
execute in_stmt;
deallocate prepare in_stmt;
Ref: Use a comma-separated string in an IN () in MySQL
Recently I faced the same problem and this is how I resolved it.
It worked for me, hope this is what you were looking for.
select * from table_name t where (select (CONCAT(',',(Select city from locations l where l.e_Id=?),',')) as city_string) LIKE CONCAT('%,',t.e_ID,',%');
Example: It will look like this
select * from table_name t where ',6,7,8,16,21,2,' LIKE '%,2,%';
I am query a database to get all the employers names out of my database, but I only want to get the ones where their ID is present in my jobs table, here is what I am trying to do.
$this->db->select('*')
->from('employers')
->join('jobwall', 'jobwall.employers_employer_id = employers.employer_id', 'left');
However this does not return the correct results, how can I select all my employers from the employers table but only if they have data in the jobwall table?
You need to add a WHERE clause:
$sql = '
SELECT *
FROM employers
LEFT JOIN jobwall ON jobwall.employers_employer_id = employers.employer_id
WHERE employers.employer_id
IN (SELECT employers_employer_id FROM jobwall)
';
$this->db->query($sql);
I'm not sure how complicated this would be to create using Codeigniter's activerecord class.
I think a better WHERE clause might be:
SELECT *
FROM employers
LEFT JOIN jobwall ON jobwall.employers_employer_id = employers.employer_id
WHERE jobwall.id IS NOT NULL
This will exclude any rows that don't have a corresponding job.
You should use whatever Primary Key your jobwall table has if jobwall.id doesn't exist.
This can also be written using ActiveRecord easily.
I don't understand you exactly, but I think you should use join without left:
this->db->select('*')->from('employers')->join('jobwall', 'jobwall.employers_employer_id = employers.employer_id');