Get the middle value from query build on two select subqueries - mysql

I have the following query:
select
qry1.product, qry1.date, qry2.monthly_income, qry1.monthly_cost,
round(qry2.monthly_income / qry1.monthly_cost) as x_ratio
from
(select
ap.product,
DATE_FORMAT(min(ai.date), '%m-%Y') as date,
ROUND(SUM(ai.ad_spend)) as monthly_cost
from
ad_insight ai
join
ads_products ap on ai.ad_id = ap.ad_id
group by
ap.product, YEAR(ai.date), MONTH(ai.date)) as qry1
inner join
(select
pi.name,
round(sum(pi.value)) as monthly_income,
DATE_FORMAT(min(pi.date), "%m-%Y") as date
from
product_insight pi
where
pi.name in (select distinct anp.product from ads_products anp)
group by
pi.name, YEAR(pi.date), MONTH(pi.date)) as qry2 on qry1.product = qry2.name and qry1.date = qry2.date;
I want to find the middle value of x_ratio(median of x_ratio) in the upper query.
Up till now, I have done something like this:
select qry1.product, round(avg(qry2.monthly_income/qry1.monthly_cost)) as x_ratio, count(qry1.date) as occurrences
from (
SELECT ap.product,
DATE_FORMAT(min(ai.date), '%m-%Y') as date,
ROUND(SUM(ai.ad_spend)) as monthly_cost
FROM ad_insight ai
JOIN ads_products ap ON ai.ad_id = ap.ad_id
GROUP BY ap.product, YEAR(ai.date), MONTH(ai.date)
) as qry1
inner join (
select pi.name,
round(sum(pi.value)) as monthly_income,
DATE_FORMAT(min(pi.date), "%m-%Y") as date
from product_insight pi
where pi.name in (select distinct anp.product from ads_products anp)
group by pi.name, YEAR(pi.date), MONTH(pi.date)
) as qry2 on qry1.product=qry2.name and qry1.date=qry2.date
group by qry1.product
order by x_ratio asc, occurrences desc;
As you can see I have used avg function, but in my case is not helpful, because x_ratio has a very wide spectrum of values.
Here you have fiddle
I want to result table look like this:
+---------+----------------+-------------+
| product | middle_x_ratio | occurrences |
+---------+----------------+-------------+
| prod2 | 1 | 1 |
| prod1 | 4 | 5 |
+---------+----------------+-------------+

Related

Joining two tables produces no result despite common values

My fiddle is here
What I am trying to achieve?
I need to sum up income and sum up ad spend for each product in each month.
So the first query is summing ad cost for each product each month.
The second query is summing up income for each product during each month.
And the third query should merge this result and produce this table:
My first query looks like this:
SELECT ap.product, DATE_FORMAT(min(ai.date), '%m-%Y') as date, ROUND(SUM(ai.ad_spend)) as monthly_cost
FROM ad_insight ai
JOIN (SELECT ad_id, product FROM ads_products anp) ap ON ai.ad_id = ap.ad_id
GROUP BY ap.product, YEAR(ai.date), MONTH(ai.date)
ORDER BY min(ai.date) asc;
My second query looks like this:
select pi.name, round(sum(pi.value)) as monthly_income, DATE_FORMAT(min(pi.date), "%m-%Y") as date
from product_insight pi
where pi.name in (select distinct anp.product from ads_products anp)
group by pi.name, YEAR(pi.date), MONTH(pi.date)
order by min(pi.date) asc;
And now I want to join this queries, so I do something like this:
SELECT ap.product, DATE_FORMAT(min(ai.date), '%m-%Y') as date, ROUND(SUM(ai.ad_spend)) as monthly_cost
FROM ad_insight ai
JOIN (SELECT ad_id, product FROM ads_products anp) ap ON ai.ad_id = ap.ad_id
join (
select pi.name, round(sum(pi.value)) as monthly_income, DATE_FORMAT(min(pi.date), "%m-%Y") as date
from product_insight pi
where pi.name in (select distinct anp.product from ads_products anp)
group by pi.name, YEAR(pi.date), MONTH(pi.date)
) jpi on jpi.name = ap.product and jpi.date = ai.date
GROUP BY ap.product, YEAR(ai.date), MONTH(ai.date)
ORDER BY min(ai.date) asc;
But this joined query produces no rows. Why?
The final query should produce this table:
| product | date | montly_cost | monthly_income |
|---------|---------|-------------|----------------|
| prod1 | 04-2021 | 11 | 38 |
| prod2 | 04-2021 | 17 | 16 |

SQL: Join with LIKE and min()

I'm using the query below to fetch the lowest priced row(s) matching the LIKE.
Suppliers
id | name
----------------------------------------
1 | sup1
2 | sup2
Prices
id | article_number | price | supplier_id
------------------------------------------------
1 | 12345678 | 312 | 1
2 | 12345678 | 219 | 2
3 | 87654321 | 312 | 1
select prices.* from prices
inner join
(
select min(price) as price,
article_number as article_number from prices group by article_number
)
min_prices on prices.price = min_prices.price
and prices.article_number = min_prices.article_number
WHERE prices.article_number LIKE '".$q."%'
Although now also want to fetch the suppliers.name from Suppliers-table:
select prices.*, suppliers.name from prices, suppliers
inner join
(
select min(price) as price,
prices.article_number as article_number from prices group by prices.article_number
)
min_prices on price = min_prices.price
and article_number = min_prices.article_number
WHERE
prices.article_number LIKE '".$q."%' AND
prices.supplier_id = suppliers.id"
This returns 18-times the amount of rows it is supposed to...?
Also the table consists of 10+ millions rows, so efficiency is very important.
For performance, add index on article_number, and use prices.article_number LIKE '".$q."%' in sub query. You'd better use JOIN not , (this is old way) to combine two tables. Like this:
select prices.*, suppliers.name
from prices
inner join (
select min(price) as price, prices.article_number as article_number
from prices
where prices.article_number like '".$q."%'
group by prices.article_number
) min_prices
on price = min_prices.price
and article_number = min_prices.article_number
inner join suppliers
on prices.supplier_id = suppliers.id
where prices.article_number like '".$q."%'

MySQL: Getting the MIN and the MAX of a table and both of their titles

I have a large database of products. It has a one to many relationship to another table of prices. I can easily get, with one query, the MIN, MAX and AVG of a particular category.
SELECT
MIN(gbp.price) AS min,
ROUND(AVG(gbp.price),2) AS ave,
MAX(gbp.price) AS max
FROM sku AS s
INNER JOIN price gbp ON gbp.sid = s.id
However, I also want to be able to get the title of the product it relates to as well - I cannot get this resolved despite multiple searches and rewrites.
My data is similar to...
prod_id | title
===============
1 | prod1
2 | prod2
3 | prod3
4 | prod4
5 | prod5
6 | prod6
7 | prod7
price_id | prod_id | price | price_date
=======================================
1 | 1 | 2.99 | 2015/02/01
2 | 1 | 3.99 | 2015/02/12
3 | 2 | 12.99 | 2015/02/01
4 | 3 | 15.99 | 2015/02/01
5 | 4 | 29.99 | 2015/02/01
6 | 5 | 29.99 | 2015/02/01
7 | 5 | 24.99 | 2015/02/12
8 | 6 | 2.99 | 2015/02/01
9 | 7 | 99.99 | 2015/02/01
10 | 7 | 89.99 | 2015/02/12
I am going to presume that other people may want a query writing similar to this, so I am going to ask for two answers.
First one "simply" to return this...
min | min_title | ave | max | max_title
============================================
2.99 | prod1 | 31.39 | 99.99 | prod7
However, the real answer I want (despite the fact I cannot even solve the above) is where it gets even trickier.
The actual results I want is in the table below...
min | min_title | ave | max | max_title
============================================
2.99 | prod6 | 25.85 | 89.99 | prod7
The min is 2.99 for prod6 as the 2.99 price for prod1 has expired.
The max is 89.99 for prod7 as the 99.99 price for prod7 has expired.
The ave is 25.85 because of above and because the price for prod5 is 24.99.
I am not expecting answers for everything, just answering the first question (in bold) will likely lead me to the answer for the second part (as I have similar queries that get the latest price etc).
SELECT t1.min, s.title AS min_title, t1.ave, t1.max, s2.title AS max_title
FROM (SELECT
MIN(gbp.price) AS min,
ROUND(AVG(gbp.price),2) AS ave,
MAX(gbp.price) AS max
FROM sku AS s
INNER JOIN price gbp ON (gbp.sid = s.id)
) t1
INNER JOIN (SELECT gbp.price, MAX(gbp.prod_id) AS MaxProdID
FROM price gbp
WHERE NOT EXISTS(
SELECT p2.price_id
FROM price p2
WHERE p2.price_id > gbp.price_id
AND p2.prod_id = gpb.prod_id
)
GROUP BY gbp.price
) minprice ON (minprice.price = t1.min)
INNER JOIN sku s ON (s.id = minprice.MaxProdID)
INNER JOIN (SELECT gbp.price, MAX(gbp.prod_id) AS MaxProdID
FROM price gbp
WHERE NOT EXISTS(
SELECT p2.price_id
FROM price p2
WHERE p2.price_id > gbp.price_id
AND p2.prod_id = gpb.prod_id
)
GROUP BY gbp.price
) maxprice ON (maxprice.price = t1.max)
INNER JOIN sku s2 ON (s2.id = maxprice.MaxProdID)
To solve your first output just use join to get those values:
SELECT min, mint.title, ave, max, maxt.title
FROM (
SELECT
MIN(gbp.price) AS min,
ROUND(AVG(gbp.price),2) AS ave,
MAX(gbp.price) AS max
FROM (SELECT price
FROM price AS gbp
INNER JOIN sku s2 ON gbp.sid = s2.id
ORDER BY prdate DESC
LIMIT 0, 1) AS s
INNER JOIN price gbp ON gbp.sid = s.id
) inq
JOIN price minp ON inq.min = minp.price
JOIN price maxp on inq.max = maxp.price
JOIN prod mint ON minp.prod_id = mint.prod_id
JOIN prod maxt ON maxp.prod_id = maxt.prod_id
I don't understand the rules for your second output.
This is essentially two different queries (or three if you count the average). The cross join is just horizontally splicing together the two results for min and max. They could all obviously be separated and executed individually.
with current_prices as (
select price_id, prod_id, price
from prices
where price_date = (
select max(price_date)
from prices as prices2
where prices2.prod_id = prices.prod_id
)
),
min_current_prices as (
select price, min(prod_id) as prod_d /* arbitrary selected representative */
from current_prices
where price = (
select min(price)
from current_prices
)
group by price
),
max_current_prices as (
select price, min(prod_id) as prod_id /* arbitrary selected representative */
from current_prices
where price = (
select max(price)
from current_prices
)
group by price
)
select
m1.price, prod1.title,
(select avg(price) from current_prices) as ave,
m2.price, prod2.title
from
min_current_prices as m1 inner join products as prod1 on prod1.prod_id = m1.prod_id
max_current_prices as m2 inner join products as prod2 on prod2.prod_id = m2.prod_id
I feel that this seems too complicated and yet you're asking for something very unusual. There clearly could be products with the same min/max price so this is going to cause problems when there is more than one at either end.
If your platform doesn't support WITH then just substitute the full query instead:
select
min_current_price.price as min_price, min_prod.title as min_title,
(
select avg(price)
from prices
where price_date = (
select max(price_date)
from prices as prices2
where prices2.prod_id = prices.prod_id
)
) as ave,
max_current_price.price as max_price, max_prod.title as max_title
from
(
select price, min(prod_id) as prod_id /* arbitrarily selected representative */
from (
select *
from prices
where price_date = (
select max(price_date)
from prices as prices2
where prices2.prod_id = prices.prod_id
)
) as current_prices
where price = (
select min(price)
from prices
where price_date = (
select max(price_date)
from prices as prices2
where prices2.prod_id = prices.prod_id
)
)
group by price
) as min_current_price
cross join
(
select price, min(prod_id) as prod_id /* arbitrarily selected representative */
from (
select *
from prices
where price_date = (
select max(price_date)
from prices as prices2
where prices2.prod_id = prices.prod_id
)
) as current_prices
where price = (
select max(price)
from prices
where price_date = (
select max(price_date)
from prices as prices2
where prices2.prod_id = prices.prod_id
)
)
group by price
) as max_current_price
inner join products as min_prod on min_prod.prod_id = min_current_price.prod_id
inner join products as max_prod on max_prod.prod_id = max_current_price.
Here's a hack for doing it in mysql using limits and sorting methods:
select
minprice.price as min_price, minprod.title as min_title,
(
select avg(price)
from prices
where price_date = (
select max(price_date)
from prices as prices2
where prices2.prod_id = prices.prod_id
)
) as ave,
maxprice.price as max_price, maxprod.title as max_title
from
(
select price_id, price, prod_id
from prices
where not exists ( /* another way of excluding expired prices */
select 1 from prices as p2
where p2.prod_id = prices.prod_id and p2.price_date > prices.prod_id
)
order by price asc
limit 0, 1
) as minprice,
(
select price_id, price, prod_id
from prices
where not exists (
select 1 from prices as p2
where p2.prod_id = prices.prod_id and p2.price_date > prices.prod_id
)
order by price desc
limit 0, 1
) as maxprice
inner join prod as minprod on minprod.prod_id = minprice.prod_id
inner join prod as maxprod on min.prod_id = maxprice.prod_id

MySQL - SUM/COUNT Different Columns From Different Tables

I have a peculiar problem. Given three tables.
product
- id
- title
product_rating
- product_id
- rating
product_view
- product_id
I want to grab products with a SUM of their ratings (which will either be a +1 or -1 value) and their total product count.
SELECT
p.`id`, p.`title`,
SUM( pr.`rating` ) AS rating,
COUNT( pv.`product_id` ) AS views
FROM
`product` AS p
LEFT JOIN `product_rating` AS pr ON ( pr.`product_id` = p.`id` )
LEFT JOIN `product_view` AS pv ON ( pv.`product_id` = p.`id` )
GROUP BY
p.`id`
ORDER BY rating DESC
In the table I have 1 rating, 9 views. The query, however, is returning 9 rating, 9 views. I understand why it's happening (it's summing the rating for each product_view), but I don't know how to get around it.
Any suggestions would be greatly appreciated.
Sample data:
product
------------
id | title
1 | Globber
product_rating
-------------------
product_id | rating
1 | 1
product_view
------------
product_id
1
1
1
1
1
1
1
1
1
Try
SELECT p.id, p.title, r.rating, v.views
FROM product p LEFT JOIN
(
SELECT product_id, SUM(rating) rating
FROM product_rating
GROUP BY product_id
) r ON p.id = r.product_id LEFT JOIN
(
SELECT product_id, COUNT(*) views
FROM product_view
GROUP BY product_id
) v ON p.id = v.product_id
ORDER BY r.rating DESC
Sample output:
| ID | TITLE | RATING | VIEWS |
---------------------------------
| 1 | Globber | 1 | 9 |
Here is SQLFiddle demo
How to do that?
SELECT tbl.pid,tbl.ptitle, SUM(tbl.rating) as Rate, COUNT (tbl.views) as ViewList FROM (SELECT
p.`id` as pid, p.`title` as ptitle,
pr.`rating` AS rating,
pv.`product_id` AS views
FROM
`product` AS p
LEFT JOIN `product_rating` AS pr ON ( pr.`product_id` = p.`id` )
LEFT JOIN `product_view` AS pv ON ( pv.`product_id` = p.`id` ) ) as tbl
GROUP BY
tbl.`pid`
ORDER BY tbl.Rate DESC

Add two columns for sum of money by year

Below is my query that groups my customers together, and sums their total money spent over the last two years. Works like a charm. However I need a little more which goes over my sql knowledge.
Is there any way to select three more columns and populate it with the money they spent for each of those years (2010, 2011, 2012)?
SELECT SUM(price) AS money_spent_total, co.customer_id, cu.first_name, cu.last_name, cu.email_primary, cu.phone_primary, co.date_paid
FROM customer_order AS co
INNER JOIN customer AS cu ON (cu.customer_id = co.customer_id)
WHERE cu.customer_id != 32518 AND co.date_paid > "2010-1-1" AND co.date_paid < "2013-1-1"
GROUP BY co.customer_id
You need to GROUP BY YEAR(date_paid) in addition to customer_id. You should also not be selecting any columns which are not in your GROUP BY. In addition, you can use the BETWEEN operator instead of > and < for your date range.
SELECT Year(co.date_paid),
co.customer_id,
Sum(price) AS money_spent_total
FROM customer_order AS co
INNER JOIN customer AS cu
ON ( cu.customer_id = co.customer_id )
WHERE cu.customer_id != 32518
AND co.date_paid BETWEEN '2010-01-01' AND '2013-01-01'
GROUP BY Year(co.date_paid),
co.customer_id
To get the columns in your original SELECT, you would do something like (not tested):
SELECT a.date_year_paid,
a.customer_id,
a.money_spent_total,
cu.first_name,
cu.last_name,
cu.email_primary,
cu.phone_primary
FROM (SELECT Year(co.date_paid) AS date_year_paid,
co.customer_id,
Sum(price) AS money_spent_total
FROM customer_order AS co
INNER JOIN customer AS cu
ON ( cu.customer_id = co.customer_id )
WHERE cu.customer_id != 32518
AND co.date_paid BETWEEN '2010-01-01' AND '2013-01-01'
GROUP BY Year(co.date_paid),
co.customer_id) a
LEFT JOIN customer cu
ON cu.customer_id = a.customer_id
If you were doing the INNER JOIN just for customer info, then it can be dropped.
SELECT a.customer_id,
a.date_year_paid,
a.money_spent_total,
cu.first_name,
cu.last_name,
cu.email_primary,
cu.phone_primary
FROM (SELECT customer_id, YEAR(date_paid) AS date_year_paid, SUM(price) AS money_spent_total
FROM customer_order
GROUP BY customer_id, YEAR(date_paid)) a
LEFT JOIN customer cu
ON cu.customer_id = a.customer_id
Lastly, if you want to group the years into columns:
SELECT a.customer_id,
a.y2010 AS '2010_money_paid',
a.y2011 AS '2011_money_paid',
a.y2012 AS '2012_money_paid',
cu.first_name,
cu.last_name,
cu.email_primary,
cu.phone_primary
FROM (SELECT customer_id,
Sum(CASE
WHEN Year(date_paid) = 2010 THEN price
ELSE 0
end) AS 'y2010',
Sum(CASE
WHEN Year(date_paid) = 2011 THEN price
ELSE 0
end) AS 'y2011',
Sum(CASE
WHEN Year(date_paid) = 2012 THEN price
ELSE 0
end) AS 'y2012'
FROM customer_order
GROUP BY customer_id) a
LEFT JOIN customer cu
ON cu.customer_id = a.customer_id
Result
| CUSTOMER_ID | 2010_MONEY_PAID | 2011_MONEY_PAID | 2012_MONEY_PAID | FIRST_NAME | LAST_NAME | EMAIL_PRIMARY | PHONE_PRIMARY |
------------------------------------------------------------------------------------------------------------------------------
| 1 | 9000 | 3000 | 2000 | Bob | Smith | bob#smith.com | 1112223333 |
| 2 | 4000 | 5000 | 1000 | Tom | Jones | tom#jones.com | 2223334444 |
See the demo