MySQL multiple table search - mysql

I am creating an online shop where people can upload lessons.
My tables structure for this is
products - (contains basic lesson information)
p_tags - id|productid|tag - (contains all tags related to a product)
p_subjects - id|subjectid|productid - (contains all subjects related to a product)
p_years - id|yearid|productid - (contains all years related to a product)
p_types - id|typeid|productid - (contains all subjects related to a product)
subjects - id|name - (contains all subjects links to p_subjects)
resourcetypes - id|name - (contains all product types links to p_types)
years - id|name - (contains all years links to p_years)
What I am trying to do is write a query that can generate a relevance score based on user search criteria. This is what I have so far:
SELECT
IFNull(a.matchedTags,0) AS matchedtags,
a.title,
IFNull(b.matchedSubjects,0) AS matchedsubjects,
IFNull(c.matchedYears,0) AS matchedyears,
IFNull(d.matchedTypes,0) AS matchedtypes
FROM
(
SELECT
z.*,
COUNT(*) AS matchedTags
FROM products z
INNER JOIN p_tags pt ON pt.productid = z.id
WHERE
pt.tag IN('foo','test')
GROUP BY z.id
HAVING COUNT( * ) > 0
) as a
LEFT JOIN (
SELECT
y.*,
COUNT(*) AS matchedSubjects
FROM products y
WHERE
3 IN (SELECT subjectid FROM p_subjects m WHERE m.productid = y.id)
GROUP BY y.id
HAVING COUNT( * ) > 0
) as b ON b.id=a.id
LEFT JOIN (
SELECT
x.*,
COUNT(*) AS matchedYears
FROM products x
WHERE
1 IN (SELECT yearid FROM p_years n WHERE n.productid = x.id)
GROUP BY x.id
HAVING COUNT( * ) > 0
) as c ON c.id=b.id
LEFT JOIN (
SELECT
w.*,
COUNT(*) AS matchedTypes
FROM products w
WHERE
1 IN (SELECT id FROM p_types o WHERE o.productid = w.id)
GROUP BY w.id
HAVING COUNT( * ) > 0
) as d ON d.id=c.id
The query runs fine but will only match a product if the previous criteria is met. I.e if a product has a tag 'foo' then it will then gain a value for number of subjects matched as well. If the product does not contain a tag it will return 0 for all following JOINS.
I'm guessing I'm using the wrong kind of join and have looked into OUTER JOIN using LEFT AND RIGHT then UNION but don't know how to slip this into this code and whether it would work anyway.
Thanks in advance

What an idiot!! I was joining the joins on top of each other so it was only adding to the criteria above. I have changed it now and working sweeet!!
SELECT
g.title,
g.id,
IFNull(a.matchedTags,0) AS matchedtags,
IFNull(b.matchedSubjects,0) AS matchedsubjects,
IFNull(c.matchedYears,0) AS matchedyears,
IFNull(d.matchedTypes,0) AS matchedtypes
FROM
(SELECT id,title FROM products) as g
LEFT JOIN (
SELECT
z.*,
COUNT(*) AS matchedTags
FROM products z
INNER JOIN p_tags pt ON pt.productid = z.id
WHERE
pt.tag IN('test')
GROUP BY z.id
) as a on a.id=g.id
LEFT JOIN (
SELECT
y.*,
COUNT(*) AS matchedSubjects
FROM products y
WHERE
5 IN (SELECT subjectid FROM p_subjects m WHERE m.productid = y.id)
GROUP BY y.id
) as b ON b.id=g.id
LEFT JOIN (
SELECT
x.*,
COUNT(*) AS matchedYears
FROM products x
WHERE
1 IN (SELECT yearid FROM p_years n WHERE n.productid = x.id)
GROUP BY x.id
) as c ON c.id=g.id
LEFT JOIN (
SELECT
w.*,
COUNT(*) AS matchedTypes
FROM products w
WHERE
1 IN (SELECT id FROM p_types o WHERE o.productid = w.id)
GROUP BY w.id
) as d ON d.id=g.id

Related

Extend result with data that does not exist in this way

sorry but i can't say the words for my target.
maybe someone understand my problem with the example below:
stored data:
brand warehouse amount
-----------------------
ba a 1
bb a 1
ba b 1
bb c 1
i want to write a select query that get result like the following:
brand warehouse amount
-----------------------
ba a 1
bb a 1
ba b 1
bb b null
ba c null
bb c 1
brand "bb" is not in the warehouse "a" but exists in other warehouse, then the brand should be listed anyway.
It looks like we want to generate a cross product of brand and warehouse.
Here's one way to do it:
Get a distinct list of brand values:
SELECT bt.brand
FROM stored_data bt
GROUP BY bt.brand
Get a distinct list of warehouse values:
SELECT wt.warehouse
FROM stored_data wt
GROUP BY wt.warehouse
Generate a Cartesian product (cross product) of those two sets:
SELECT b.brand
, w.warehouse
FROM ( SELECT bt.brand
FROM stored_data bt
GROUP BY bt.brand
) b
CROSS
JOIN ( SELECT wt.warehouse
FROM stored_data wt
GROUP BY wt.warehouse
) w
ORDER
BY b.brand
, w.warehouse
The next step is an outer join to get the amount column. If (brand,warehouse) tuple is unique in stored_data, then we can just do:
SELECT b.brand
, w.warehouse
, a.amount
FROM ( SELECT bt.brand
FROM stored_data bt
GROUP BY bt.brand
) b
CROSS
JOIN ( SELECT wt.warehouse
FROM stored_data wt
GROUP BY wt.warehouse
) w
LEFT
JOIN stored_data a
ON a.brand = b.brand
AND a.warehouse = w.warehouse
ORDER
BY b.brand
, w.warehouse
If (brand,warehouse) is not unique, then the query has the potential to return multiple rows with the same values of brand and warehouse.
If we want to collapse the rows and get a total amount, the normative pattern would be to use GROUP BY and an aggregate function:
SELECT b.brand
, w.warehouse
, SUM(a.amount) AS amount
FROM ( SELECT bt.brand
FROM stored_data bt
GROUP BY bt.brand
) b
CROSS
JOIN ( SELECT wt.warehouse
FROM stored_data wt
GROUP BY wt.warehouse
) w
LEFT
JOIN stored_data a
ON a.brand = b.brand
AND a.warehouse = w.warehouse
GROUP
BY b.brand
, w.warehouse
ORDER
BY b.brand
, w.warehouse
There are other possible query patterns that will achieve and equivalent result.
If you don't have a Warehouse table, make one using the stored_data
SELECT
D.brand,
W.warehouse
FROM
(Select
DISTINCT warehouse
FROM stored_data
) W LEFT JOIN stored_data D on W.warehouse = D.warehouse
ORDER By D.brand, D.warehouse

i want to display all records from 2 or more queries which are not in left queries too

i have queries like this
SET #curr_date = '2017-03-23';
SELECT
curr_week.mid AS MID,
curr_week.EDC AS Merchant_Name ,
COALESCE(curr_week.amount,0) AS Total_Amount_Curr_Week,
COALESCE(curr_week.total_trx,0) AS Total_Trx_Curr_Week,
COALESCE(curr_week.total_user,0) AS Total_User_Curr_Week,
COALESCE(last_week.amount,0) AS Total_Amount_Last_Week,
COALESCE(last_week.total_trx,0) AS Total_Trx_Last_Week,
COALESCE(last_week.total_user,0) AS Total_User_Last_Week
FROM
(
SELECT a.*, b.total_user
FROM
(
SELECT a1.owner_name AS MID, m.name AS EDC,SUM(t1.amount) AS amount, COUNT(t1.id) AS total_trx
FROM members m
JOIN accounts a1 ON a1.member_id = m.id
JOIN transfers t1 ON a1.id = t1.to_account_id
WHERE DATE(t1.DATE) = (#curr_date - INTERVAL 1 DAY)
GROUP BY a1.owner_name
) AS a
JOIN
(-- get total user
SELECT COUNT(r.ecash_no) AS total_user, r.mid, r.merchant_name
FROM
(
SELECT a.`owner_name` AS ecash_no,
a1.owner_name AS MID,
m.name AS merchant_name
FROM accounts a1
JOIN transfers t1 ON a1.id = t1.to_account_id
JOIN members m ON a1.member_id = m.id
JOIN accounts a ON a.id = t1.from_account_id
WHERE DATE(t1.date) = (#curr_date - INTERVAL 1 DAY)
GROUP BY a.owner_name,m.`name`
) AS r
GROUP BY r.mid
) AS b ON a.mid = b.mid
) AS curr_week
JOIN
(
-- last week
SELECT a.*, b.total_user
FROM
(
SELECT a1.owner_name AS MID, m.name AS EDC,SUM(t1.amount) AS amount, COUNT(t1.id) AS total_trx
FROM members m
JOIN accounts a1 ON a1.member_id = m.id
JOIN transfers t1 ON a1.id = t1.to_account_id
WHERE DATE(t1.DATE) = (#curr_date - INTERVAL 1 DAY) - INTERVAL 1 WEEK
GROUP BY a1.owner_name
) AS a
JOIN
(-- get total user
SELECT COUNT(r.ecash_no) AS total_user, r.mid, r.merchant_name
FROM (
SELECT a.`owner_name` AS ecash_no,
a1.owner_name AS MID,
m.name AS merchant_name
FROM accounts a1
JOIN transfers t1 ON a1.id = t1.to_account_id
JOIN members m ON a1.member_id = m.id
JOIN accounts a ON a.id = t1.from_account_id
WHERE DATE(t1.date) = (#curr_date - INTERVAL 1 DAY) - INTERVAL 1 WEEK
GROUP BY a.owner_name,m.`name`
) AS r
GROUP BY r.mid
) AS b ON a.mid = b.mid
) AS last_week ON curr_week.mid = last_week.mid
how can i retrieve all EDC value from joined queries like that.
because if i use join , it displayed only the same values.
and if i use left join, it follows the value from the left query
is there any way to display everything with join?
You can simulate a full outer join of the two tables using the following:
SELECT COALESCE(a.ColA, b.ColA) AS ColA,
COALESCE(a.ColB, b.ColB) AS ColB
FROM tableA a
LEFT JOIN tableB b ON a.ColA = b.ColA
UNION
SELECT COALESCE(a.ColA, b.ColA) AS ColA,
COALESCE(a.ColB, b.ColB) AS ColB
FROM tableA a
RIGHT JOIN tableB b ON a.ColA = b.ColA;
Note: I've assumed that only ColA is the join column. You can add ColB as a join column as well, or use only ColB as a join column. This really depends on the design of your table, but the general approach I gave should still work.
Output:
Demo here:
Rextester

Count two different rows in mysql query

I have organizations. Each organization can have members and projects.
I want to get list of organizations with number of members and projects.
For example,
Organization | Members | Projects | Action
------------------------------------------
Org 1 | 5 | 6 | Delete - Edit
Org 2 | 2 | 9 | Delete - Edit
I am using this query,
SELECT COUNT(m.id) as members, COUNT(p.id) as projects,
o.status,o.organization_name,o.logo, o.id as id
from tbl_organizations o
LEFT JOIN tbl_organization_members m ON (o.id = m.organization_id)
LEFT JOIN tbl_projects p ON (o.id = p.organization_id)
WHERE o.status= 'active' AND o.created_by= 1
But the output of number of projects is equal to number of members.
How can I make the sample above using query?
Try this way:
SELECT o.id as id, o.organization_name, cnt_ as members, cnt_p as projects
from tbl_organizations o
LEFT JOIN (
SELECT organization_id, COUNT(id) cnt_m
FROM tbl_organization_members
GROUP BY organization_id
) m ON (o.id = m.organization_id)
LEFT JOIN (
SELECT organization_id, COUNT(id) cnt_p
FROM tbl_projects
GROUP BY organization_id
) p ON (o.id = p.organization_id)
WHERE o.status= 'active' AND o.created_by= 1
This way you JOIN to an already aggregated version of member/project tables, so as to get the count of members/projects per organization_id.
Group by the organisation columns and count distinct IDs
SELECT o.status,o.organization_name, o.logo, o.id as id,
COUNT(distinct m.id) as members, COUNT(distinct p.id) as projects,
from tbl_organizations o
LEFT JOIN tbl_organization_members m ON (o.id = m.organization_id)
LEFT JOIN tbl_projects p ON (o.id = p.organization_id)
WHERE o.status= 'active'
AND o.created_by= 1
GROUP BY o.status, o.organization_name, o.logo, o.id
You can co-related subquery:
SELECT
o.id as Organization,
(SELECT COUNT(*) FROM tbl_organization_members WHERE organization_id = o.id) as members,
(SELECT COUNT(*) FROM tbl_projects WHERE organization_id = o.id) as projects
FROM
tbl_organizations o
WHERE
o.status= 'active' AND o.created_by = 1

MySQL: How can I select a value (date) based on a result from another table and include it in said result?

I have the following MySQL tables (Simplified)
DRIVER (D)
----------
id (PK)
name
RACE (RA)
---------
id (PK)
date
RESULT (RE)
-----------
id (PK)
raceid (FK -> RACE.id)
driverid (FK -> DRIVER.id)
bestRound
averageRound
I want to be able to list all drivers with their numRaces, firstRace, lastRace, bestRound, bestAverageRound and the dates on which their bestRound and bestAverageRound happened. I'm having problems with the last two, the dates for bestRound and bestAverageRound.
This is what I have so far:
SELECT D.name, COUNT(DISTINCT RE.raceid) AS numRaces, min(RA.date) AS firstRace,
max(RA.date) AS lastRace, min(RE.bestRound) AS bestRound,
min(RE.averageRound) AS bestAverageRound
FROM DRIVER D
JOIN RESULT RE ON RE.driverid = D.id
JOIN RACE RA ON RA.id = RE.raceid
GROUP BY D.id
ORDER BY D.name
This is working correctly. But how do I proceed to select the dates from the RACE table on which the bestRound and bestAverageRound occurred? Thanks for your time.
Try this solution:
SELECT
a.*, b.date AS bestRoundDate, c.date AS bestAverageRoundDate
FROM
(
SELECT
d.id,
d.name,
COUNT(DISTINCT re.raceid) AS numRaces,
MIN(ra.date) AS firstRace,
MAX(ra.date) AS lastRace,
MIN(re.bestRound) AS bestRound,
MIN(re.averageRound) AS bestAverageRound
FROM driver d
INNER JOIN result re ON d.id = re.driverid
INNER JOIN race ra ON re.raceid = ra.id
GROUP BY d.id, d.name
) a
INNER JOIN
(
SELECT aa.driverid, aa.bestRound, bb.date
FROM result aa
INNER JOIN race bb ON aa.raceid = bb.id
) b ON a.id = b.driverid AND a.bestRound = b.bestRound
INNER JOIN
(
SELECT aa.driverid, aa.bestAverageRound, bb.date
FROM result aa
INNER JOIN race bb ON aa.raceid = bb.id
) c ON a.id = c.driverid AND a.bestAverageRound = c.bestAverageRound
ORDER BY
a.name

Select row with max value in one column

I have a select statement that returns two columns: office names and total per office:
select o.OfficeName, c.Total
from Offices o
left join
( select OfficeID, count(*) Total
from Customers c
group by OfficeID
) c on o.OfficeID = c.OfficeID
where o.ClusterID = 29
How can I get the row that has max total?
"Customers" table has an "OfficeID" colummn. For a given "ClusterID", I select all offices within the cluster identified by cluster id (e.g. 29) and count the customers belongin to those offices.
There are a number of approaches:
SELECT OfficeName, Total
FROM ( SELECT o.OfficeName, c.Total, MAX(Total) OVER() [MaxTotal]
FROM Offices o
LEFT JOIN
( SELECT OfficeID, COUNT(*) Total
FROM Customers
GROUP BY OfficeID
) c
ON o.OfficeID = c.OfficeID
WHERE o.ClusterID = 29
) c
WHERE Total = MaxTotal
OR
WITH CTE AS
( SELECT o.OfficeName, c.Total
FROM Offices o
LEFT JOIN
( SELECT OfficeID, COUNT(*) Total
FROM Customers
GROUP BY OfficeID
) c
ON o.OfficeID = c.OfficeID
WHERE o.ClusterID = 29
)
SELECT *
FROM CTE
WHERE Total = (SELECT MAX(Total) FROM CTE)
OR
SELECT TOP 1 o.OfficeName, c.Total
FROM Offices o
LEFT JOIN
( SELECT OfficeID, COUNT(*) Total
FROM Customers
GROUP BY OfficeID
) c
ON o.OfficeID = c.OfficeID
WHERE o.ClusterID = 29
ORDER BY Total DESC
Although using TOP 1 may not be what you are after, with the other methods if there are 2 offices with the same number of customers they will both be returned, whereas TOP 1 will only return 1 of these (probably in order of office name). If you only ever want 1 record, then this is the best method
SELECT TOP 1 o.OfficeName, c.Total
FROM Offices o
LEFT JOIN
(SELECT OfficeID, count(*) Total
FROM Customers c
GROUP BY OfficeID
) c ON o.OfficeID = c.OfficeID
WHERE o.ClusterID = 29
ORDER BY c.Total DESC
WITH TIES offers a cleaner way to get all offices sharing the top count:
with a as (
select o.OfficeID,Total=COUNT(*)
from Offices o
inner join Customers c on c.OfficeID=o.OfficeID
group by o.OfficeID
)
select top 1 WITH TIES t.OfficeName, a.Total
from a
inner join Offices t on t.OfficeID=a.OfficeID
where t.ClusterID=29
order by a.Total desc