The ideia is: select all professions from a table. After this count how many professionals have the profession id in his category column. The category column store the professions id's separeted by commas (1, 2, 3, 420). The professions table has 604 rows.
I have the following piece of code:
<?php
$select_professions = mysql_query("SELECT * FROM professions");
if(mysql_num_rows($select_professions) == "0"){
echo"No registers in DB";
}else{while($row_professions = mysql_fetch_assoc($select_professions)){
$id = $row_professions['id'];
$count_profiles = mysql_query("SELECT
COUNT(FIND_IN_SET(professions.id, professional.category) > 0) AS profile_numbers
FROM
professions
INNER JOIN
professional
WHERE
FIND_IN_SET(professions.id,professional.category) > 0
AND
professions.id = $id
GROUP BY
professions.id");
$reg_profiles = mysql_fetch_assoc($count_profiles);
$numProfiles = $reg_profiles['profile_numbers'];
if($numProfiles > 4){
$style = 'display:none';
}else{
$style = '';
}
?>
My basic question is WHY this is so slow in Google Chrome?
Its taking like 15 seconds to load entire page with these results in a html table. In Edge or Firefox is taking about 5 seconds. I heard about Chrome using so much memory lately but I don't believe its soo slowly. In time this is the first time I use the FIND_IN_SET function on mysql. Is that may are slowing down the request? Anyone knows what I'm doing wrong or how can be optimized? This is actualy working but we know that 15 seconds of waiting makes the user give up or think that page is not working. I have to say too that if I do the same consultation on my HeidiSQL it takes 1 second.
I recommend to normalize this:
The category column store the professions id's separeted by commas (1,
2, 3, 420)
This is an n:n relationship. Your layout:
professionals:
id | catgeory
12 | 1,2,4,50
professions
id | desc
1 | prof A
2 | prof B
...
The string operations (split the list, normalize internal, query result in to temp, ...) is very cost intensive. Better:
professionals:
id | ...
12 | ..
profrelations
pid | cid
12 | 1
12 | 2
12 | 4
12 | 50
professions
id | desc
1 | prof A
2 | prof B
...
This would skip the COUNT(FIND_IN_SET(professions.id, professional.category) > 0) as a string operation (even twice):
SELECT COUNT(cid) AS profile_numbers from professionals, profrelations where
professionals.id = profrelations.pid AND profrelations.pid = $id;
etc. You might restructure the above query like this, as long as you won't actually need any column from professions.
You can add a unique index on the cols (pid, cid) in table profrelations as one professional actually can have one profession only one times.
Remark
The different behaviour in two browser might result from the server caching the query: You're doing the query with Chrome, it's slow, but the result gets cached. Next with FF, server will respond with the cached result as its the same query again - fast. Try it three times or the other way round, should then be the same in all browsers.
At first,
this operation COUNT(FIND_IN_SET(professions.id, professional.category) > 0) will not return result that you expected. Count in above expression will return 1 even if find_in_set returns 0.
Secondly, I wouldn't use join in this case at all. This tables have no direct relation by identifiers.
I would optimize the query as following:
SELECT COUNT(professions.id) AS profile_numbers FROM professions, professional
WHERE FIND_IN_SET(professions.id,professional.category) > 0 AND professions.id = $id
GROUP BY professions.id
Related
I have been tasked with creating a search function that when searched, certain fields will have more weight than others.
Here is an simplified example.
cars (table)
year, make, model, color, type (columns)
Let's say someone searches for the following:
Year: 1968
Make: Ford
Model: Mustang
Color: Red
Type: Sports Car
If the cars in the table have none of the correct fields they should not show up, but if record has some of the correct fields but not all they should still show up. But certain fields should be weighted higher than others.
For instance maybe they are weighted like this:
Column - Weight
Year - 30
Make - 100
Model - 85
Color - 10
Type - 50
So if a record matches the search in the "make" field and the "model" field, that record would be above a record that matched in the "year", "color" and "type" field, because of the weights we placed on each column.
So lets say that the query matches at least one field for two records in the database, they should be ordered by the most relevant based on the weight:
1971, Ford, Fairlane, Blue, Sports Car (weight = 185)
1968, Dodge, Charger, Red, Sports Car (weight = 90)
I have been racking my brain trying to figure out how to make this work. If anyone has done something like this please give me an idea of how to make it work.
I would like to do as much of the work in MySQL as possible via joins, I think this will be bring up the results faster than doing most of the work in PHP. But any solution to this problem would be much appreciated.
Thanks in advance
Bear with me, this is going to be a strange query, but it seems to work on my end.
SELECT SUM(
IF(year = "1968", 30, 0) +
IF(make = "Ford", 100, 0) +
IF(model = "Mustang", 85, 0) +
IF(color = "Red", 10, 0) +
IF(type = "Sports Car", 50, 0)
) AS `weight`, cars.* FROM cars
WHERE year = "1968"
OR make = "Ford"
OR model = "Mustang"
OR color = "Red"
OR type = "Sports Car"
GROUP BY cars.id
ORDER BY `weight` DESC;
Basically, this groups all results by their id (which is necessary for the SUM() function, does some calculations on the different fields and returns the weight as a total value, which is then sorted highest-lowest. Also, this will only return results where one of the columns matches a supplied value.
Since I don't have an exact copy of your database, run some tests with this on your end and let me know if there's anything that needs to be adjusted.
Expected Results:
+============================================================+
| weight | year | make | model | color | type |
|============================================================|
| 130 | 1968 | Ford | Fairlane | Blue | Roadster |
| 100 | 2014 | Ford | Taurus | Silver | Sedan |
| 60 | 2015 | Chevrolet | Corvette | Red | Sports Car |
+============================================================+
So, as you can see, the results would list the closest matches, which in this case are two Ford (+100) vehicles, one from 1968 (+30), and a Red Sports Car (10 + 50) as the closest matches (using your criteria)
One more thing, if you also want to display the rest of the results (ie results with a 0 weight match score) simply remove the WHERE ... OR ..., so it will check against all records. Cheers!
Further to the comments below, checking the weight after a LEFT JOIN on a pivot table:
SELECT SUM(
IF(cars.year = "1968", 30, 0) +
IF(cars.make = "Ford", 100, 0) +
IF(cars.model = "Mustang", 85, 0) +
IF(cars.color = "Red", 10, 0) +
IF(types.name = "Sports Car", 50, 0)
) AS `weight`, cars.*, types.* FROM cars
LEFT JOIN cars_types ON cars_types.car_id = cars.id
LEFT JOIN types ON cars_types.type_id = types.id
WHERE year = "1968"
OR cars.make = "Ford"
OR cars.model = "Mustang"
OR cars.color = "Red"
OR types.name = "Sports Car"
GROUP BY cars.id
ORDER BY `weight` DESC;
Here is a picture of the LEFT JOIN in practice:
As you can see, the Cobalt matches on color (silver) and model (Cobalt) (85 + 10) while the Caliber matches on type (Sports Car) (50). And yes, I know a Dodge Caliber isn't a Sports Car, this was for example's sake. Hope that helped!
If I understand your logic you can just do something like direct comparison in PHP between the value requested and the value returned.
The query will sound like:
SELECT Year,Make,Model,Color,Type
FROM table
WHERE year='$postedyear' OR make='$postedmake'
OR model='$postedmodel' OR color='$postedcolor'
Then in php looping between the results:
foreach($results as $result){
$score = 0;
if($result['year']==$postedyear{$score=$score+30;}
//continue with the other with the same logic.
}
After each foreach iteration $score will be the score of that selected row. If you push the score to the $result array you can also sort it by score before displaying the results.
Variation on #lelio-faieta
In php you can have a result array containing arrays of values for each item matching at least one of the search terms, the associative array of values to match and the associate array of weights, both with the same indexes. You would just get an array of matches for each index. (maybe use array_intersect_assoc()) Then you multiply by the weights and sum, add to the original data. Then you do have to sort the result array at that point.
There is a solution doing this via the mysql query directly, but that would end up with an overgrown resource thirsty query for every single search you perform.
Doing it in PHP is not much difference in resource usage, bounding to several loops in results and processing it.
I've had a very similar project and my best suggestion would be: "use SphinxSearch"
Very easy to install, needs a bit of a learning curve to setup afterwards, but very similar to mysql queries etc. With this you can apply weights to every column match and rank your results afterwards.
Also, it is a multitude of time faster that typical mysql queries.
My ch_skills table looks like
uid | skill1 | skill2 | skill3 | skill4 | skill5
1 1 2 2 0 1
2 1 1 2 1 1
3 1 2 3 0 1
My first question: is this correct? I mean would it be better if I made it like this:
uid | skillid | skill_lvl
1 1 1
1 2 2
1 3 2
1 4 0
1 5 1
Everything worked fine until now with the example #1, but now I'm in a trouble with the sql queries. Currently, I'm using 5 different queries to get the level of each skill. I use the following code:
For skill1:
$query = $this->db->prepare("SELECT `skills`.`skill_ID` as `Skill1_id`,
`skill_name`.`skill_name` as `Skill1_name`, `skill_level` as `Skill1_level`,
`skill_price` as `Skill1_price`
FROM `skills`, `skill_name`, `ch_skills`
WHERE `skill_name`.`skill_ID` = `skills`.`skill_ID`
AND `skills`.`skill_ID`= 1
AND `skills`.`skill_level` = `ch_skills`.`skill1`
AND `ch_skills`.`uid` = :uid");
For skill2:
$query = $this->db->prepare("SELECT `skills`.`skill_ID` as `Skill1_id`,
`skill_name`.`skill_name` as `Skill1_name`, `skill_level` as `Skill1_level`,
`skill_price` as `Skill1_price`
FROM `skills`, `skill_name`, `ch_skills`
WHERE `skill_name`.`skill_ID` = `skills`.`skill_ID`
AND `skills`.`skill_ID`= 2
AND `skills`.`skill_level` = `ch_skills`.`skill2`
AND `ch_skills`.`uid` = :uid");
And so on... As you can see, there's only two differences: skill_id = 2, and skill2 as the coulmn's name. Is there any way for querying all the 5 skills in only 1 query? Or would you recommend me anyway to change the table structure?
Note: skills stands for the skill prices, and skill_name for the skill's names.
As the other commenters have suggested, your best choice is to change the table exactly as you proposed.
The biggest reason not to have a wide table like you show in your first example, is that adding a skill means changing the structure of the database, which could break existing queries.
Secondly, as you see when you're trying to query the results, having a single table doesn't even make it easier to work with.
The only possible benefit to a non-normalized table like your example is that it takes up slightly less disk space. But in todays world, disk space should never be your primary concern.
To answer your question about querying the original non-normalized example, however, there are two ways to do it:
Use a union statement which would combine 5 distinct queries together. This is pretty inefficient
Create a table with (in this case) 5 rows (or if you have a Skills table use that). Then join the ch_skills table to that, which should take each row and split it 5 times. See below: (note: I'm assuming for the purposes of this example that skills and skill_name are in a 1:1 relationship and only have 5 records each)
SELECT skills.skill_ID,
skill_name.skill_name,
skill_level as Skill_level,
skill_price as Skill_price
FROM skills
JOIN skill_name on skill_name.skill_ID = skills.skill_ID
JOIN ch_skills
WHERE ch_skills.uid = :uid
AND ((skills.skill_ID = 1 AND skills.skill_level = ch_skills.skill1)
OR (skills.skill_ID = 2 AND skills.skill_level = ch_skills.skill2)
OR (skills.skill_ID = 3 AND skills.skill_level = ch_skills.skill3)
OR (skills.skill_ID = 4 AND skills.skill_level = ch_skills.skill4)
OR (skills.skill_ID = 5 AND skills.skill_level = ch_skills.skill5))
I have a query as follow: (shows are a table with tv shows and IMDB ID and recommended_titles is a table with two columns with IMDB_ID)
Select t2.* from shows t, shows t2, recommended_titles WHERE
t.imdb_id = recommended_titles. title_id_1
AND recommended_titles.title_id_2=t2.imdb_id
AND t.imdb_id = 0367279 LIMIT 7
The query is fine but I realized that it was only checking in the first column for my imdb id when it can also appear in my second one.
So i try to add the following:
OR
recommended_titles.title_id_2=t.imdb_id
AND t.imdb_id = recommended_titles. title_id_1
AND t.imdb_id = 0367279 LIMIT 7
But apparently OR can't be used with AND,
any suggestions as how I should do this ?
Edit:
To explain what I'm trying to do, here's a quick example in case my explanations above are too confusing.
table shows has rows like this:
name of a tv show | 00001
name of another | 00002
name of another | 00003
table recommended titles has (notice that an ID can be in either column)
00001 | 00002
00002 | 00003
You may look at operator precedence in mysql (and see that AND has an higher precedence than OR), or use parenthesis (much easier to use and maintain)
(t.imdb_id = recommended_titles.title_id_1 OR
recommended_titles.title_id_2=t.imdb_id)
AND recommended_titles.title_id_2=t2.imdb_id
AND t.imdb_id = 0367279 LIMIT 7
Do it like this,
(recommended_titles.title_id_2=t.imdb_id
OR t.imdb_id = recommended_titles. title_id_1)
AND t.imdb_id = 0367279 LIMIT 7
(t.imdb_id = recommended_titles. title_id_1
OR recommended_titles.title_id_2=t.imdb_id )
AND t.imdb_id = 0367279 LIMIT 7
Just use parenthesis to group your conditions for priorities.
This is what my customers_basket table looks like:
customers_id | products_id | basket_quantity
3 | 56:3121fefbe6043d6fc12e3b3de2c8fc38 | 3
3 | 56:fb4c9278fcfe6225b58c06711a7e62ef | 1
3 | 56:8e334fce09556108f5416e27154b6c27 | 1
3 | 52:f3b9f38e4ddd18035bc04cd264b0f052 | 1
This is the query I'm using:
$products_in_cart_query = "SELECT products_id FROM customers_basket WHERE customers_id = " . $_SESSION['customer_id'] ."";
$products_in_cart = $db->Execute($products_in_cart_query);
$products_in_cart_model = $products_in_cart->fields['products_id'];
$products_in_cart_model = substr($products_in_cart_model, 0, strpos($products_in_cart_model, ":"));
The end result I get is 56,56,56,52
First of all, how do I use the first line's quantity field? I'd need to list that products_id 3 times since quantity is 3. Therefore, the end result needs to be: 56,56,56,56,56,52
or, for easier understanding (56,56,56),56,56,52
And second, how do I count how many same values I have? In this case, I have 5x56 and 1x52. I need to use those counts in my further calculation.
EDIT: further calculations explained
I need to know how many of each product_id I have and then run something like this:
foreach(product_id) {
$shipping_cost += FIXED_VALUE * basket_qty;
}
To get the basket quantity, you have to select it. It would be best if the first portion of the product ID was stored in a separate column, rather than having to do messy operations like substringing.
Query 1: 2-character codes and corresponding quantities
SELECT SUBSTR(products_id, 1, 2) AS product_code, basket_quantity
FROM Customers_Basket
WHERE customers_id = 3;
Query 2: 2-character codes and summed quantities
SELECT product_code, SUM(basket_quantity) AS total_quantity
FROM (SELECT SUBSTR(products_id, 1, 2) AS product_code, basket_quantity
FROM Customers_Basket
WHERE customers_id = 3
)
GROUP BY product_code;
If you really, really, really desperately want 3 rows of data for the product ID 56:3121fefbe6043d6fc12e3b3de2c8fc38, then you have to know ways to generate rows. They're truly painful in the absence of convenient SQL support (so much so, that you'd do better to select a row in PHP with the quantity and then generate the appropriate number of rows in your array in the client-side (PHP) code). I'm going to assume that some variation on these queries will get you the information you want.
This is the books table on db;
book_ID writer_ID
-------- -----------
1 10
2 10
3 10
4 10
5 10
This is the rates table on the db,
book_ID rate
------- --------
1 4
2 3
2 5
2 1
2 4
3 5
4 2
4 5
4 2
4 4
5 3
now, i have the writer_ID at first, and i have to find all book_ID (connected to that writer_ID) and the average rates of each book_ID from the rates table. finally, i have to find the greatest rate average and its book_ID
this is my code
$query="SELECT * FROM books WHERE seller_id ='$id'";
$result = mysql_query($query);
while ($info = mysql_fetch_array($result)) {
//getaveragerate is the function that returns average of the rates from rates table
$arr = array(ID => $info['book_ID'], average => getaveragerate($info['book_ID']));
}
$greatest_average_and_books_id_number = max($arr); // dont know how to get highest average and its ID together from array
that is my question, sorry but english is not my native language, i am trying my best to explain my problem. sometimes i cant and i just stuck.
thanks for understanding.
Or just let the database do it for you:
SELECT max(fieldname) FROM rates WHERE id='34'
If you are limited as to which functions you can perform (ie using some CRUD class):
SELECT * FROM rates WHERE id='34' ORDER BY id DESC LIMIT 1
You haven't told us what fields from the database will be returned by your query. It also looks like you're filtering (WHERE clause) on key column, which should only return one record. Therefore you can strip out everything you have there and only put:
$greatest_record = 34;
No need for a query at all!
With a little more information on what you're doing and what fields you're expecting:
$query = "SELECT id, rate FROM rates";
$result = mysql_query($query);
$myarray = array();
$greatest_number = 0;
while ($row = mysql_fetch_array($result)) {
myarray[] = $row; // Append the row returned into myarray
if ($row['id'] > $greatest_number) $greatest_number= $row['id'];
}
// Print out all the id's and rates
foreach ($myarray as $row_num => $row) {
print "Row: $row_num - ID: {$row['id']}, Rate: {$row['rate']} <br>";
}
print "Highest ID: $greatest_number";
Note that we maintained what was the greatest number at each row returned from the database, so we didn't have to loop through the $myarray again. Minor optimization that could be a huge optimization if you have tens of thousands of rows or more.
This solution is on the basis that you actually need to use the ID and RATE fields from the database later on, but want to know what the largest ID is now. Anyone, feel free to edit my answer if you think there's a better way of getting the greatest_number from the $myarray after it's generated.
Update:
You're going to need several queries to accomplish your task then.
The first will give you the average rate per book:
SELECT
book_id,
avg(rate) as average_rate
FROM Rates
GROUP BY book_id
The second will give you the max average rate:
SELECT
max(averages.average_rate),
averages.book_id
FROM (
SELECT
book_id,
avg(rate) as average_rate
FROM Rates
GROUP BY book_id
)
as averages
WHERE averages.average_rate = max(averages.average_rate)
This will give you a list of books for a given writer:
SELECT book_id
FROM Books
WHERE writer_id = $some_id
Don't try to do everything in one query. Mixing all those requirements into one query will not work how you want it to, unless you don't mind many very near duplicate rows.
I hope you can use this update to answer the question you have. These SQL queries will give you the information you need, but you'll still need to build your data structures in PHP if you need to use this data some how. I'm sure you can figure out how to do that.