Extend result with data that does not exist in this way - mysql

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

Related

Retriving data from 2 tables

I have 2 tables with below description.
table 1: customer, columns : customer_id, source
table 2: source, columns: source, rank
one customer would have many sources, each source has a particular rank in the rank table, i need to fetch the data in such a way that for each individual customer which ever has a lowest ranked source i need to fetch those records.
Here is an example:
customer table data is
1 abc
2 efg
3 abc
1 efg
1 hij
2 hij
source table data is
abc 2
hij 1
efg 3
the result set should be:
1 hij
2 hij
3 abc
You could use either of the two queries below to satisfy your requirement.
QUERY 1
SELECT c.customer_id,
c.source
FROM customer c
INNER JOIN source s
ON c.source = s.source
WHERE s.rank = (SELECT Min(s1.rank)
FROM source s1 inner join customer c1 on s1.source = c1.source
WHERE c1.customer_id = c.customer_id)
QUERY 2
SELECT x.customer_id ,
c1.source
FROM
(SELECT c.customer_id ,
MIN(s.rank) AS MinRank
FROM customer c
INNER JOIN SOURCE s ON c.source = s.source
GROUP BY c.customer_id) x
INNER JOIN customer c1 ON x.customer_id = c1.customer_id
INNER JOIN SOURCE s1 ON s1.source = c1.source
AND s1.rank = x.MinRank;
UPDATE 1
This update is in response to your comment for 3 tables rather than 2 tables. The query below extends Query 1 when your schema is spread across 3 tables.
SELECT c.customer_id,
s.source_name
FROM customer c
INNER JOIN source s
ON c.cust_id = s.cust_id
INNER JOIN rank r
ON s.source_name = r.source_name
WHERE r.rank = (SELECT Min(r1.rank)
FROM customer c1
INNER JOIN source s1
ON s1.cust_id = c1.cust_id
INNER JOIN rank r1
ON r1.source_name = s.source_name
WHERE c1.cust_id = c.cust_id);
For Oracle:
select d.customer_id, d.source
from (
select
c.customer_id,
s.source,
row_number() over (partition by c.customer_id order by s.rank asc) as rn
from customer c
join source s
on c.source = s.source
) d
where d.rn = 1
;
A much simpler way. Try this -
select c.cid,c.sourceid,min(s.rankid)
from customer c inner join sourc s
on (c.sourceid=s.sourceid)
group by c.cid order by c.cid asc
Here's an SQLFiddle
Select a.customer_id,b.source
from
(select c.customer_id,min(s.rank) as rank
from customer c
inner join source s
on c.source=s.source
group by c.customer_id) as a
inner join source b
on a.rank = b.rank

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

How to do a subtract of 2 columns that were the results of a sub-query

I have this query and it is working fine as expected, ie, it shows all the columns as well as the 2 columns from each sub-query...
SELECT
a.group_id,
a.code,
a.description_en,
c.size_code,
(
SELECT
SUM(b.qty)
FROM
receiving_details AS b
WHERE
b.code = c.size_code
) AS in_qty,
(
SELECT
SUM(d.qty)
FROM
requisition_details AS d
WHERE
d. matl_code = c.size_code
) AS out_qty
FROM products AS a
INNER JOIN products_sizes AS c ON c.prod_code = a.code
ORDER BY a.group_id ASC, a.code ASC, c.size_code ASC
However, when I try to add this line...
(in_qty - out_qty) AS balance,
just before the 'FROM' statement, I get an error of Unknown column 'in_qty' in 'field list'.
What am I doing wrong?
EDIT:
From the accepted answer, I did a few more fix and got the result I wanted.
SELECT *, (e.in_qty - e.out_qty) AS balance FROM
(SELECT
a.group_id,
a.code,
a.description_en,
c.size_code,
(
SELECT
IFNULL(SUM(b.qty),0)
FROM
receiving_details AS b
WHERE
b.code = c.size_code
) AS in_qty,
(
SELECT
IFNULL(SUM(d.qty),0)
FROM
requisition_details AS d
WHERE
d. matl_code = c.size_code
) AS out_qty
FROM products AS a
INNER JOIN products_sizes AS c ON c.prod_code = a.code) AS e
ORDER BY e.group_id ASC, e.code ASC, e.size_code ASC
You can't reference the same field from your select statement within itself. One option would be to move the results in another subquery, and then perform the calculation:
select *, (in_qty - out_qty) AS balance
from (
SELECT
a.group_id,
a.code,
a.description_en,
c.size_code,
(
SELECT
SUM(b.qty)
FROM
receiving_details AS b
WHERE
b.code = c.size_code
) AS in_qty,
(
SELECT
SUM(d.qty)
FROM
requisition_details AS d
WHERE
d. matl_code = c.size_code
) AS out_qty
FROM products AS a
INNER JOIN products_sizes AS c ON c.prod_code = a.code
) t
ORDER BY group_id, code, size_code

MySQL multiple table search

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

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