Advance query. Rank most related fields in mysql - php

Let's assume we have a database like this:
Project_tbl:
-----------------
id | Project_name
-----------------
1 | A
2 | B
3 | C
-----------------
personel_project_tbl:
--------------------
user_id | Project_id
--------------------
1 | 1
2 | 2
3 | 1
3 | 2
2 | 3
--------------------
instrument_project_tbl:
--------------------------
instrument_id | Project_id
--------------------------
1 | 1
1 | 2
2 | 2
2 | 1
1 | 3
--------------------------
Now, I need to sort the list of projects and rank them with regard to their similarity to the project A.
For example:
A and B have 1 users in common over the 3 users and 2 instruments over the 2 instrument so their similarity ranking is (1/2 + 2/2) / 2 = 75%
A and C have no user in common but have 1 over 2 instruments so it will be (1/2)/2 = 25%
So B is more similar than be and output should be
--------------
Project | Rank
--------------
2 | 75
3 | 25
That's the first solution came to my mind...
If I did it in PHP and MySQL, it would be something like:
for all tables as table_x
for all projects (except A) as prj_y
unique = (Select distinct count(items) from table_x where project is A)
count += (Select distinct count(items) from table_x
where project is prj_x and items are in
(select distinct items from table_x where project is a)
)/unique
So the complexity would be O(n2) and with indexing the select also would cost O(log n) which wouldn't be affordable.
Do you have any idea to do it totally in MySQL or do it in a better and faster way?
******** More information and notes:**
I'm limited to PHP and MySQL.
This is just an example, in my real project the tables are more than 20 tables so the solution should have high performance.
this question is the supplementary question for this one : Get the most repeated similar fields in MySQL database if yr solution can be used or applied in a way for both of them (somehow) It would be more than great.
I want to multiply the value of related projects with the similarity of items to get the best option...
In conclusion, these two questions will : get the most related projects, get the similar items of all projects and find the most similar item for current project where the project is also similar to the current one! yo
Thanks for your intellectual answers, its really appreciated if you could shed some light on the situations

You could do it this way:
SET #Aid = (SELECT id
FROM Project_tbl
WHERE Project_name = 'A');
SELECT P.id
, (IFNULL(personel.prop, 0) +
IFNULL(instrument.prop, 0)
)/2*100 Rank
, personel.prop AS personell
, instrument.prop AS instrument
FROM Project_tbl P
LEFT JOIN
( SELECT B.Project_id pid, COUNT(*)/C.ref prop
FROM personel_project_tbl A,
personel_project_tbl B,
(SELECT COUNT(*) AS ref
FROM personel_project_tbl
WHERE Project_id = #Aid
) AS C
WHERE A.user_id = B.user_id
AND A.Project_id = #Aid
GROUP BY B.Project_id
) personel ON P.id = personel.pid
LEFT JOIN
( SELECT B.Project_id pid, COUNT(*)/C.ref prop
FROM instrument_project_tbl A,
instrument_project_tbl B,
(SELECT COUNT(*) AS ref
FROM instrument_project_tbl
WHERE Project_id = #Aid
) AS C
WHERE A.instrument_id = B.instrument_id
AND A.Project_id = #Aid
GROUP BY B.Project_id
) instrument ON P.id = instrument.pid
WHERE P.id <> #Aid
ORDER BY Rank DESC
The idea is to have one subquery for each table, and each of these subqueries maps project id to correspondence ratio for a given table.
I'm saying nothing at all about performance. You'll have to try and see whether it is fast enough for your needs, but as I see it there is no way to beat the O(n2) complexity you mention, as you have to inspect all the data.

Related

I need to calculate percentage of counted items

I have two tables:
1st: reasons
id | title
---------------------------------
1 | Customer didn't like it
2 | Needs improving
3 | Wrong format
2nd: projects
id | title | rejected
------------------------------------
1 | Priject 1 | Null
2 | Priject 2 | 1
3 | Priject 3 | 1
4 | Priject 4 | Null
5 | Priject 5 | 2
I need to display Reasons.Title and number of project rejected for that reason. I've managed to join those tables together, with this code
SELECT reasons.title as title, count(*) as num
FROM reasons
LEFT JOIN reasons on projects.rejected = reasons.id
WHERE projects.rejectedIS NOT NULL
GROUP BY projects.rejected
Now I need to add percentage, so my final table looks like this
title | num | percentage
--------------------------------------------------
Customer didn't like it | 2 | 66,6
Needs improving | 1 | 33,3
The format of percentage is of course not important.
I would like to get this done with MySql, so I do not need to use two queries and extra PHP, but if there is another solution, other from MySql, I'm open to suggestions
You can do this by getting the total in the FROM clause:
SELECT r.title as title, count(*) as num,
COUNT(*) / pp.cnt as ratio
FROM reasons r JOIN
projects p
ON p.rejected = r.id CROSS JOIN
(SELECT COUNT(*) as cnt FROM projects p WHERE rejects IS NOT NULL) pp
GROUP BY r.title, pp.cnt;
Notes:
This fixes the table names, so the query has a projects table.
This removes the WHERE because it is not needed.
This changes the LEFT JOIN to an inner join.

MySQL custom Join row by row

So I have the following tables
**accounts**
id | iban
1 | ES80 2310 0001 1800 0001 2345
2 | ES91 2100 0418 4502 0005 1332
**acc_rel**
account_id | target_table | target_id
1 | users | 2
2 | clients | 5
**users**
id | username | password
2 | abc | cba
**clients**
id | company_name
5 | some_name
So the thing is that with acc_rel I have an account related with an user, a client, or whatever other table.
Because of reasons, not my reasons, I can't change this tables or the way they "works".
What I need to do is, retrieve the account information with the username or the company_name from the other tables, I need to do it in a single query so I can use a WHERE to filter, or ORDER BY and LIMIT. At least I think that I need it in a single query to do so.
This would be the perfect output:
account_id | account_owner
1 | abc
2 | some_name
So how can I do it? I don't know which tables I am going to do the JOIN or what column name I need, thought I can know it with PHP before doing the query.
A solution changing all the tables scheme would be appreciated too if the correct way to do this is another.
Based on #knowledge... answer I made this query and it works exactly like I need it.
SELECT AR.account_id,COALESCE(U.username, C.company_name) AS account_owner
FROM accounts AS A
INNER JOIN acc_rel AS AR ON A.id = AR.account_id
LEFT JOIN users AS U ON U.id = AR.target_id AND AR.target_table = 'users'
LEFT JOIN clients AS C ON C.id = AR.target_id AND AR.target_table = 'clients'
It is going to be a pain to maintain if I need to add new tables but well, it works the way I need.
select AR.account_id,COALESCE(U.username,C.company_name) as account_owner
from accounts A
join acc_rel AR on A.id=AR.account_id
left join users U on U.id=AR.traget_id and AR.target_table='user'
left join clients C on C.id=AR.traget_id and AR.target_table='clients'

PHP MySql get data from a table and left join to the same table

I have this table names MyLevels.
id | level | ParentLevelId
---------------------
1 | basic | 1
2 | silver| 1
3 | gold | 2
4 | stone | 3
5 | wood | 2
on this table every level has a parent level. For example the "gold" level parent id is 2. It's mean the parent level is "silver".
So I need a query to get the below in a html table:
Parent level comes from ParentLevelId.
level | parent level | distance From Basic
-------------------------------------------
basic | basic | 0
silver | basic | 1
gold | silver | 2
The distance from basic reference to the number that each level is far from basic level.
For example the wood level id is 5 and 5 - 1 = 4. which means the it's 4 level far from basic level.
This is the only code I have:
$conn->prepare('SELECT id, level, ParentLevelId FROM MyLevels');
Any help appreciated.
Thanks
Please try this.
select c.level as level, p.level as parent_level, c.id-p.id as distance_from_parent
from MyLevels AS p
JOIN MyLevels AS c on p.id = c.ParentLevelId
can you try this ?
SELECT a.level, b.level ParentLeve , IFNULL(b.id,0) FROM
MyLevels a left join MyLevels b on a.id=b.ParentLevelId
Just another version to add to the fun.
I thought the op was wanting to get the Distance from basic, which in this case is id = 1.
Anyway, another way to write this is...
SELECT
b.level AS level,
a.level AS parent_level,
b.id-1 AS distance_from_basic
FROM MyLevels a
JOIN MyLevels b ON a.id = b.ParentLevelId
ORDER BY b.id
This was tested against the desired results provided by the OP.
The results:
Level Parent Level Distance From Basic
basic basic 0
silver basic 1
gold silver 2
stone gold 3
wood silver 4

MYSQL: Select entries in relation to multiple rows of another table

I have two tables in a database:
entries (ID, distance, status[,...])
entriesmeta (ID, entry_ID, metakey, metavalue)
Until now, I select entries like this:
SELECT ID,
distance
FROM entries
WHERE status = '2' HAVING distance < 30
ORDER BY distance
LIMIT 20
Now, the second table contains data of this sort:
ID | entry_ID | metakey | metavalue
1 | 137 | service | 13
2 | 137 | service | 7
3 | 137 | service | 76
4 | 84 | service | 23
etc.
The entries in the first table are restaurants, the services in the second table are services they offer. What I have been trying for a while now is this: Create a HTML search form that lets users choose certain services via checkboxes. The MYSQL query then should select all entries that offer ALL those services. So far, the search form puts the selected services in an array (7,13,9,27).
My problem is finding the correct database query. It should match the metavalue-field with the checkbox-array and then select all entries from the first table, which offer all those services. I have been experimenting with "LEFT JOIN ON entries.ID = entriesmeta.entry_ID" and several WHERE clauses, but no success. I wonder if someone can help.
Thank you in advance - and sorry for my bad English.
SELECT e.ID
FROM entries e
INNER JOIN entriesmeta m
ON e.ID = m.entry_id
WHERE
m.metakey = 'service' AND
m.metavalue IN (7,13,9,27)
GROUP BY e.ID
HAVING COUNT(DISTINCT m.metavalue) = 4
SQLFiddle Demo
This query will display all ID that has metakey of service and a value(s) of 7, 13, 9, 27.
This would get you all entries which have a given entriesmeta service value - which is what I think you are asking.
SELECT entries.* from entriesmeta
INNER JOIN entries on entries.ID = entriesmeta.entry_ID
WHERE entriesmeta.metavalue = <myValue>

MySQL Merging my selects together

and thanks for taking the time to try and help me.
Informations
I'm currently using CodeIgniter if it might have anything to do with your answer ;).
Problem
I'm in a hotel site, trying to figure out how to do my reservation rooms.
I want users to select a list of available services and return to them, a list of rooms that contains these services ( all of them ) AND after that, a list that contains at least one. This way I'll show to them a list of rooms that comply with all their need, and one that might do the trick, but doesnt have everything.
Here's how I store my services for my rooms ( Here might lie my problem in fact ... )
Table "services_rooms"
id_services_rooms | id_room | id_service
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
5 | 1 | 5
11 | 2 | 2
12 | 2 | 3
... | ... | ...
How can I manage to do my SQL to ask my server give me all of the rooms that contains the services 1, 2 AND 3, therefore, only my "id_room" 1 should come back ?
I've tried doing some joins / group_bys but the most I got was for exemple, 3 row coming back saying :
Return rows :
ID_ROOM 1 | ID_SERVICE 1
ID_ROOM 1 | ID_SERVICE 2
ID_ROOM 1 | ID_SERVICE 3
Another way to see it, would be like that : I want to ask my server which rooms contains ALL of these services : 1,2,3
It would answer : ID_ROOM 1.
I've seen a couple of other questions talking about merges and such but couldn't quite apply their answers to my problem.
Thanks again.
This is called Relational Division.
SELECT id_room
FROM services_rooms
WHERE id_service IN (1,2,3)
GROUP BY id_room
HAVING COUNT(*) = 3
SQLFiddle Demo
if unique constraint was not enforced on id_service for ech id_room, DISTINCT is required.
SELECT id_room
FROM services_rooms
WHERE id_service IN (1,2,3)
GROUP BY id_room
HAVING COUNT(DISTINCT id_service) = 3
The answer to your question returns all rooms that have at least one of the services. The query is:
SELECT id_room, count(*) as NumServices
FROM services_rooms
WHERE id_service IN (1,2,3)
GROUP BY id_room
HAVING COUNT(*) > 0
order by count(*) desc

Categories