Hi everybody I have 3 table:
a table called content with the below attributes:
id
name
table_type_id
release_date
popularity
another table called content_genres with the below attributes:
content_id
genres_id
another table called genres with the below attributes:
id
name
each content can have multiple genres, and a genre can have multiple content.(many to many)
okay, until here is the definition of the different tables, now i am trying to make a query to search the content that has for example the genre_id=1 and at the same time the genre_id=2
in postgresql this would be easy:
SELECT content.id
FROM content INNER JOIN content_genres ON content.id =content_genres.content_id
WHERE content_genres.`genres_id`= 1
INTERSECT
SELECT content.id
FROM content INNER JOIN content_genres ON content.id =content_genres.content_id
WHERE content_genres.`genres_id`= 2
;
I make one query, I make another query and then I make an intersection getting that content that has the genre_id 1 and 2
but when I try to write this same query in eloquent I have some problems:
query 1:
$content1=$this->content::join('content_genres','content_genres.content_id','=','content.id')
->with('genres')
->where('content_genres.genres_id',1)
->where('content.table_type_id',1)
//->whereYear('release_date',2017)
->select('content.id','content.name','content.popularity')
->orderBy('popularity','desc')->get();
query 2:
$content2=$this->content::join('content_genres','content_genres.content_id','=','content.id')
->with('genres')
->where('content_genres.genres_id',2)
->where('content.table_type_id',1)
//->whereYear('release_date',2017)
->select('content.id','content.name','content.popularity')
->orderBy('popularity','desc')->get();
intersection:
$final_result=$content1->intersect($content2);
okay how we have seen at this way we are able to make a intersection but I have some problems:
when I want to do a manual pagination I don't know how can I count the elements that is going to have the intersection, and after that limit the results of the intersection.
example:
number of results from query1:
18950
number of results from query2:
22650
number of results from intersection
3457
this is very slow, because I can not say limit the query 1 to 100 results, limit the query 2 to 100 results and then make the intersection, I can not do this because the number of results from the intersection is not going to be always the same so for that reason how can I make a manual pagination over the intersection without load all the results from query1 and query2, saying I want to paginate the intersections in pages from 20 results?
The last thing is the big problem which I have had all the week.
real example
you go to this page, then in year put none, and in genres select two random genres. how you can see the pagination of that intersection is always 20, doesn't depends if there is more results in the intersection or there isn't, always is 20. And I am pretty sure that they haven't load from the db all the results.
Good result:
Thanks to the answer the correct way to do this is the below:
$this->content::join('content_genres as g1','g1.content_id','=','content.id')
->join('content_genres as g2','g2.content_id','=','content.id')
->where('g1.genres_id', 1)
->where('g2.genres_id', 2)
it works for me, I could have chosen the other option but I have a many to many relation , because my content_genres is a pivot table, but I think that I would be also valid.
You should merge both queries. I see two ways of doing this.
1) Join content_genres twice:
$this->content::join('content_genres as g1','g1.content_id','=','content.id')
->join('content_genres as g2','g2.content_id','=','content.id')
->where('g1.genres_id', 1)
->where('g2.genres_id', 2)
2) Use whereHas():
$this->content::whereHas('content_genres', function($query) {
$query->where('genres_id', 1)
})->whereHas('content_genres', function($query) {
$query->where('genres_id', 2)
})
This requires a relationship: content → HasMany → content_genres
Related
My goals is returning multiple types from a search query. I have two tables of users and movies. If search query is 'Mat', i should return both results for users with name like '%mat%' and movies with title '%mat%'.
I can use UNION or JOIN two tables in one request, but i guess there will be ordering problem.
For example i have this structure:
Users table:
id
name
1
Mat
Movies table:
id
title
1
Matrix
2
Matilda
Search query - "mat". Logically, i guess i should get user first, then two others movies. Also, there can be inverse situation where i should get movies first, and users. What is the best solution? I can make two separate requests for users and movies and then combine them into one array with 50/50 limit of each other, but i guess this is not the optimal solution.
As I do not know anything about speed and complexity of php and mysql(i) scripts, I had this question:
I have a database with 3 tables:
'Products' with about 9 fields. Containing data of products, like 'long' content text.
'Categories' with 2 fields. Containing name of categories
'Productcategories' with 2 fields. Containing which product has which categories. Each product is part of 1-3 categories.
In order to set up pagination (I need row_count because I wish to know what the last page is), I was wondering what the most sufficient way to do it is, and or it depends on the amount of products (50, 100, 500?). The results returned depends on a chosen category:
"SELECT * FROM `productcategories`
JOIN products ON products.proID = productcategories.proID
WHERE productcategories.catID =$category";
Idea 1:
1 query which only selects 1 field, instead of all. And then counts the total rows for my pagination with mysqli_num_rows().
A second query which directly selects 5 or 10 (with LIMIT I expect) products to be actually shown.
Idea 2:
Only 1 query (above), on which you use mysqli_nuw_rows() for row count and later on, filter out the rows you want to show.
I do not know which is the best. Idea 1 seems faster as you have to select a lot less data, but I do not know or the 2 queries needed influence the speed a lot? Which is the fastest: collecting 'big' amounts of data or doing queries?
Feel free to correct me if I am completely on the wrong path with my ideas.
It is generally considered best practice to return as little data as possible so the short answer is to use the two queries.
However, MySQL does provide one interesting function that will allow you to return the row count that would have been returned without the limit clause:
FOUND_ROWS()
Just keep in mind not all dbms' implement this, so use with care.
Example:
mysql> SELECT SQL_CALC_FOUND_ROWS * FROM tbl_name
-> WHERE id > 100 LIMIT 10;
mysql> SELECT FOUND_ROWS();
Use select count(1) as count... for the total number of rows. Then select data as needed for pagination with limit 0,10 or something like that.
Also for total count you don't need to join to the products or categories tables as that would only be used for displaying extra info.
"SELECT count(1) as count FROM `productcategories` WHERE catID=$category";
Then for data:
"SELECT * FROM `productcategories`
JOIN categories ON categories.catID = productcategories.catID
JOIN products ON products.proID = productcategories.proID
WHERE productcategories.catID=$category limit 0,10";
Replacing * with actual fields needed would be better though.
I am developing a car rental site. I have two tables test_tbl_cars and test_reservations.
I am using the search query (cribbed from Jon Kloske in "How do I approach this PHP/MYSQL query?"):
$sql = mysql_query("SELECT
test_tbl_cars.*,
SUM(rental_start_date <= '$ddate' AND rental_end_date >= '$adate') AS ExistingReservations
FROM test_tbl_cars
LEFT JOIN test_reservations USING (car_id)
GROUP BY car_id
HAVING ExistingReservations = 0");
This gives me excellent search results but the test_tbl_cars table contains many cars which in any given search returns several of the same car model as being available.
How can I filter the query return such that I get one of each model available?
Use Distict clause
$sql = mysql_query("SELECT
DISTINCT test_tbl_cars.model, test_tbl_cars.*,
SUM(rental_start_date <= '$ddate' AND rental_end_date >= '$adate') AS ExistingReservations
FROM test_tbl_cars
LEFT JOIN test_reservations USING (car_id)
GROUP BY car_id
HAVING ExistingReservations = 0");
Awww, should have tagged me, I only saw this now over a year later! You've probably already figured out how to work this by now, but I'll take a crack at it anyway for completeness sake and because most of the answers here I don't think are doing what you want.
So the problem you are having is that in the other question each room had a unique ID and it was unique rooms people were interested in booking. Here, you're extending the concept of a bookable item to a pool of items of a particular class (in this case, model of car).
There's may be a way to do this without subqueries but by far the easiest way to do it is to simply take the original idea from my other answer and extend it by wrapping it up in another query that does the grouping into models (and as you'll see shortly, we get a bunch of other useful stuff for free out of doing this).
So, firstly lets start by getting the list of cars with counts of conflicting reservations (as per the update to my other answer):
(I'll use your query for these examples as a starting point, but note you really should use prepared statements or at the very least escaping functions supplied by your DB driver for the two parameters you're passing)
SELECT car_id, model_id, SUM(IF(rental_id IS NULL, 0, rental_start_date <= '$ddate' AND rental_end_date >= '$adate')) AS ConflictingReservations
FROM test_tbl_cars
LEFT JOIN test_reservations USING (car_id)
GROUP BY car_id
This will return one row per car_id giving you the model number, and the number of reservations that conflict with the date range you've specified (0 or more).
Now at this stage if we were asking about individual cars (rather than just models of cars available) we could restrict and order the results with "HAVING ConflictingReservations = 0 ORDER BY model_id" or something.
But, if we want to get a list of the availability of ~models~, we need to perform a further grouping of these results to get the final answer:
SELECT model_id, COUNT(*) AS TotalCars, SUM(ConflictingReservations = 0) AS FreeCars, CAST(IFNULL(GROUP_CONCAT(IF(ConflictingReservations = 0, car_id, NULL) ORDER BY car_id ASC), '') AS CHAR) AS FreeCarsList
FROM (
SELECT car_id, model_id, SUM(IF(rental_id IS NULL, 0, rental_start_date <= '$ddate' AND rental_end_date >= '$adate')) AS ConflictingReservations
FROM test_tbl_cars
LEFT JOIN test_reservations USING (car_id)
GROUP BY car_id
) AS CarReservations
GROUP BY model_id
You'll notice all we're doing is grouping the original query by model_id, and then using aggregate functions to get us the model_id, a count of total cars we have of this model, a count of free cars of this model we have which we achieve by counting all the times a car has zero ConflictingReservations, and finally a cute little bit of SQL that returns a comma separated list of the car_ids of the free cars (in case that was also needed!)
A quick word on performance: all the left joins, group bys, and subqueries could make this query very slow indeed. The good news is the outer group by should only have to process as many rows as you have cars for, so it shouldn't be slow until you end up with a very large number of cars. The inner query however joins two tables (which can be done quite quickly with indexes) and then groups by the entire set, performing functions on each row. This could get quite slow, particularly as the number of reservations and cars increases. To alleviate this you could use where clauses on the inner query and combine that with appropriate indexes to reduce the number of items you are inspecting. There's also other tricks you can use to move the comparison of the start and end dates into the join condition, but that's a topic for another day :)
And finally, as always, if there's incorrect edge cases, mistakes, wrong syntax, whatever - let me know and I'll edit to correct!
I have page that display information from two different tables , and for that I have two queries.
There is no related info between these two tables.
Since both queries may contain a lot of information, I need create pagination.
BUT I don't want two separate paginations, I want only one that will contain results from query 1 and query 2 together.
How can I do that?
The only idea I have is to fetch all info of both queries into arrays, then combine the arrays into one, then create pagination that based on that array.
That of course would not help save resources.
You could use a union - the columns you're displaying must line up, so something like this should work:
select
col1 col1_alias,
col2 col2_alias,
...
from
table1
where
...
union
select
col1,
col2,
...
from
table2
where
...
order by col1_alias, col2_alias
limit 10
Basically the union will pull all the data together, and the order by and limit will apply to the whole result set.
The names of the columns don't need to match in the second select, but use column names from the first select for your order by (or create aliases, which is probably more readable depending on your dataset).
I'm trying to query 2 tables where the first table will return 1 row and the second table will return multiple rows. So basically the first table with return text on a page and the second table will return a list that will go within the page. Both tables have a reference row which is what both tables are queried on. (See Below)
SELECT shop_rigs.*, shop_rigs_images.*, shop_rigs_parts.*
FROM shop_rigs
LEFT JOIN shop_rigs_images
ON shop_rigs.shoprigs_ref = shop_rigs_images.shoprigsimg_ref
LEFT JOIN shop_rigs_parts
ON shop_rigs.shoprigs_ref = shop_rigs_parts.shoprigsparts_ref
WHERE shoprigs_enabled='1' AND shoprigs_ref='$rig_select'
ORDER BY shoprigs_order ASC
Is it better to just do 2 queries?
Thanks,
dane
I would do this in two queries. The problem isn't efficiency or the size of the respective tables, the problem is that you're create a Cartesian product between shop_rigs_images and shop_rigs_parts.
Meaning that if a given row of shop_rigs has three images and four parts, you'll get back 3x4 = 12 rows for that single shop_rig.
So here's how I'd write it:
SELECT ...
FROM shop_rigs
INNER JOIN shop_rigs_images
ON shop_rigs.shoprigs_ref = shop_rigs_images.shoprigsimg_ref
WHERE shoprigs_enabled='1' AND shoprigs_ref='$rig_select'
ORDER BY shoprigs_order ASC
SELECT ...
FROM shop_rigs
INNER JOIN shop_rigs_parts
ON shop_rigs.shoprigs_ref = shop_rigs_parts.shoprigsparts_ref
WHERE shoprigs_enabled='1' AND shoprigs_ref='$rig_select'
ORDER BY shoprigs_order ASC
I left the select-list of columns out, because I agree with #Doug Kress that you should select only the columns you need from a given query, not all columns with *.
If you're pulling a large amount of data from the first table, then it would be better to do two queries.
Also, for efficiency, it would be better to specify each column that you actually need, instead of all columns - that way, less data will be fetched and retrieved.
Joins are usually more efficient than running 2 queries, as long as you are joining on indexes, but then it depends on your data and indexes.
You may want to run a "explain SELECT ....." for both options and compare "possible keys" and "rows" from your results.