COUNT on joined rows - mysql

I would like to make an SQL call where i count the rows on the joined call for each row
SELECT *, COUNT(id_feed.id) as price_count, prices.id as id FROM prices
LEFT JOIN id_feed ON id_feed.id_prices = prices.id
WHERE prices.id_user = :id_user
prices table:
id name id_user
1 name1 21
2 name2 21
3 name3 4
id_feed table:
id id_prices price
1 1 30
2 1 30
3 1 30
4 2 30
5 2 30
6 3 30
Result
id id_prices price_count
1 name1 3
2 name2 2

With a join and aggregation:
select p.id, p.name, count(*) price_count
from prices p inner join id_feed i
on i.id_prices = p.id
where p.id_user = :id_user
group by p.id, p.name
See the demo.
Results for id_user = 21:
> id | name | price_count
> -: | :---- | ----------:
> 1 | name1 | 3
> 2 | name2 | 2

You are describing window functions. Something like this:
SELECT p.*, f.*, COUNT(f.id) OVER () as price_count
FROM prices p LEFT JOIN
id_feed f
ON f.id_prices = p.id
WHERE p.id_user = :id_user

You seems to want window function :
SELECT prices.*, prices.id as id ,
count(*) over (partition by id_prices) as price_count
FROM prices LEFT JOIN
id_feed
ON id_feed.id_prices = prices.id
WHERE prices.id_user = :id_user

Related

MySQL: left join only one row and sum

It's possible left join only one row without sub query?
I need to get product statistics and some of products have multiple groups.
Therefore, the amount of products is incorrect.
SELECT COUNT(p.id) AS total_product, SUM(p.price) AS total_price
FROM product p
LEFT JOIN attribute_group a ON
a.product_id = p.id
WHERE p.created_at >= "2018-01-01" AND (a.id = 1 OR a.id = 2)
GROUP BY p.id
LIMIT 0, 30;
product
id | price
1 | 100
2 | 150
3 | 250
attribute_group
id | product_id | title
1 | 1 | a1
2 | 1 | a2
3 | 2 | a3
4 | 3 | a4
Should be:
1| 100
But i get:
2 | 200
You appear to want all products or the counts/sum of them that have attributes of both 1 and 2. Here is one method:
SELECT COUNT(*) as num_products, SUM(p.price) as total_price
FROM product p
WHERE p.created_at >= '2018-01-01' AND
EXISTS (SELECT 1
FROM attribute_group ag
WHERE ag.product_id = p.id AND ag.id = 1
) AND
EXISTS (SELECT 1
FROM attribute_group ag
WHERE ag.product_id = p.id AND ag.id = 2
);

MySQL query resulting in the substraction of the sum of 2 columns in different tables

I have a MySQL DB with the following Tables:
Products:
Product_ID | Product_Name
1 | Blaster
2 | Faser
3 | BFG
Orders:
Order_ID | Product_ID | Order_Product_Qnt
1 | 1 | 10
2 | 2 | 5
3 | 3 | 7
4 | 2 | 10
Sells:
Sell_ID | Product_ID | Sel_Product_Qnt
1 | 2 | 5
2 | 1 | 1
3 | 3 | 2
What I want to do is a query that lists all the products followed by their amount.
The result should be:
Product_Name | Quantity
BFG | 5
Blaster | 9
Faser | 10
Following Barnar's suggestion I got to this piece of code:
SELECT
Products.Product_Name,
COALESCE (SUM(Orders.Order_Product_Qnt), 0) - COALESCE (SUM(Sells.Sells_Product_Qnt), 0) AS Quantity
FROM
Products
LEFT JOIN
Orders ON Products.Product_ID = Orders.Product_ID
LEFT JOIN
Sells ON Products.Product_ID = Sells.Product_ID
GROUP BY
Products.Product_Name
The query works but it returns wrong values.
For example, I have a product that has 6 orders, and 1 sell, logic dictates that 6-1=5, but that query gives me 4 as a result.
Or another one with 18 Orders and 6 Sells, returns 60 (should be 12).
Any advise is appreciated.
Maybe something like this?
SELECT
product_name,
orders_cnt - sales_cnt AS Quantity
FROM (
SELECT product_name,
SUM(orders) AS orders_cnt,
SUM(sales) AS sales_cnt
FROM (
SELECT products.product_name,
ifnull(orders.order_product_qnt, 0) orders,
ifnull(sells.sells_product_qnt,0) sales
FROM products
LEFT JOIN orders ON products.product_id = orders.product_id
LEFT JOIN sells ON products.product_id = sells.product_id
) t1
GROUP BY product_name ) t2
Finally got it working, forgot to post my solution here:
SELECT
Products.Product_ID,
Products.Product_Name,
IFNULL(b.SB - c.SC, 0) AS Quantity,
FROM Produtos_Table
LEFT JOIN (
SELECT
Product_ID,
SUM(Quantity) AS SB
FROM
Orders
GROUP BY Product_ID
) b
ON Products.Product_ID = b.Product_ID
LEFT JOIN (
SELECT
Product_ID,
SUM(Sell_Product_Qnt) AS SC
FROM
Sells
GROUP BY
Product_ID
) c
ON Products.Product_ID = c.Product_ID
GROUP BY
Products.Product_Name

retrieve matching and non matching rows from 2 tables

Here is the scenario
Table income has following column
ID Inc_amt Trans_date
1 100 9/24/2014
2 200 9/24/2014
3 300 9/25/2015
Table expense has following column
ID exp_amt Trans_date
2 100 9/24/2014
3 200 9/24/2014
4 400 9/25/2014
What I need is that for say trans_date = 9/24/2014, I need the following output. Here ID is the common key for both tables
ID inc_amt exp_amt trans_date
1 100 null 9/24/2014
2 200 100 9/24/2014
3 null 200 9/24/2014
I am confused as to what join to do . I am able to get both matched and mismatched rows , but when I add date check condition , then it gives only matching rows for that date of 9/24/2014
you need to left join the two tables to a base table that has the id's from both tables.
when you left join a table it will join to any existing id's so you need to union the two tables to get all id's then join to them, then filter by date.
so something like this
Query:
SELECT t.id, i.inc_amt, e.exp_amt, t.trans_date
FROM
( SELECT id, trans_date FROM income
UNION
SELECT id, trans_date FROM expense
) t
LEFT JOIN income i ON i.id = t.id AND i.trans_date = '9/24/2014'
LEFT JOIN expense e ON t.id = e.id AND e.trans_date = '9/24/2014'
WHERE t.trans_date = '9/24/2014';
Output:
+----+---------+---------+------------+
| id | inc_amt | exp_amt | trans_date |
+----+---------+---------+------------+
| 1 | 100 | null| 9/24/2014 |
| 2 | 200 | 100 | 9/24/2014 |
| 3 | null| 200 | 9/24/2014 |
+----+---------+---------+------------+
This can be achieved by union all income|expense left outer join and conditional right outer join.
SELECT inc.id
,inc.inc_amt
,exp.exp_amt
,inc.trans_date
FROM income inc
LEFT OUTER JOIN expense exp ON inc.id = exp.id
WHERE (inc.trans_date = '2014-09-24')
UNION ALL
(
SELECT exp.id
,CASE
WHEN inc.trans_date <> '2014-09-24'
THEN NULL
END
,exp.exp_amt
,exp.trans_date
FROM income inc
RIGHT OUTER JOIN expense exp ON inc.id = exp.id
WHERE (
exp.trans_date = '2014-09-24'
AND inc.trans_date <> '2014-09-24'
)
)
ORDER BY id;
link to sqlfiddle

Compare variation between first and second row for group

I got a "Empresas" table
dbo.empresas
id | name | delegacion_id
-------------------------
1 | A | 3
2 | B | 3
3 | C | 3
4 | D | 4
a "pagos" table
dbo.pagos
id | id_empresa | monto | periodo
----------------------------------
1 | 1 | 120 | 2012-11-01
2 | 1 | 125 | 2012-12-01
3 | 2 | 150 | 2012-11-01
4 | 1 | 200 | 2013-01-01
5 | 2 | 151 | 2012-12-01
I have a value X that is a percentage.
I need to show the "empresas" that, comparing the "montos" of their two last "pagos" (ordered by periodo), have changed at least +X% or -X%, from an especific id_delegacion
For example, if we run this query with these example values, considering
X = 10
id_delegacion = 3
the output expected will be:
name | periodo | monto
---------------------------
A | 2012-12-01 | 125
A | 2013-01-01 | 200
empresa A is from delegacion_id = 3, and the comparison between the last two pagos, ordered by periodo desc (200 => 125) is bigger than 10%.
B is not showed because the comparison is smaller than 10%.
C is not showed because has no row in "pagos" table
D is from another delegation.
How can I get this desired output? For the record, using MySQL 5.5.8.
What I've done
I got this
select P.id_empresa, max(periodo) as periodo from
pagos P
where id_empresa in(
select e.id
from empresa E
where E.id_delegacion = 3
)
group by p.id_empresa, p.periodo
having count(*) > 1
with these I got the "empresas" that have more than one "pago" row, and got id_delegation = 3.
Also get the first period (the maximum), but I don't know how to get the second for each empresa, and compare them.
thanks
This is my query:
SELECT
empresas.name,
pagos.periodo,
pagos.monto
FROM
pagos INNER JOIN (
SELECT
lst.id id1,
prc.id id2
FROM (
SELECT
p1.id_empresa,
MAX(p1.periodo) last_p,
MAX(p2.periodo) prec_p
FROM
pagos p1 INNER JOIN pagos p2
ON p1.id_empresa = p2.id_empresa
AND p2.periodo < p1.periodo
GROUP BY
id_empresa) latest
INNER JOIN
pagos lst ON lst.id_empresa = latest.id_empresa AND lst.periodo=latest.last_p
INNER JOIN
pagos prc ON prc.id_empresa = latest.id_empresa AND prc.periodo=latest.prec_p
WHERE
lst.monto > prc.monto * 1.1) ids
ON pagos.id IN (ids.id1, ids.id2)
INNER JOIN
empresas
ON pagos.id_empresa = empresas.id
WHERE
delegacion_id=3
I think it can be simplified if you want to have values on the same row, e.g.
name | ultimo_periodo | ultimo_monto | anterior_periodo | anterior_monto
Please see fiddle here.
I still wondering if it can be simplified a little, but I am not sure if it is. Here's another solution:
SELECT
empresas.name,
pagos.periodo,
pagos.monto
FROM
pagos INNER JOIN empresas
ON pagos.id_empresa = empresas.id
INNER JOIN (
SELECT
id_empresa,
MAX(CASE WHEN row=1 THEN monto END) lst_monto,
MAX(CASE WHEN row=2 THEN monto END) prc_monto,
MAX(id) id1, MIN(id) id2
FROM (
SELECT
p1.*, COUNT(*) row
FROM
pagos p1 INNER JOIN pagos p2
ON p1.id_empresa = p2.id_empresa
AND p1.periodo <= p2.periodo
INNER JOIN empresas
ON p1.id_empresa = empresas.id
WHERE
empresas.delegacion_id = 3
GROUP BY
p1.id, p1.id_empresa, p1.monto, p1.periodo
HAVING
COUNT(*)<=2
ORDER BY
p1.id_empresa, p1.periodo desc
) s
GROUP BY
id_empresa
HAVING
lst_monto>prc_monto*1.1
) l ON pagos.id IN (l.id1, l.id2)
Please see fiddle here.

Finding the MIN value that appears for each unique value in either of two other columns

Given the following (simplified) tables:
People p
id name registered
-----------------------------------
1 Geoff 2011-03-29 12:09:08
2 Phil 2011-04-29 09:03:54
3 Tony 2011-05-29 21:22:23
4 Gary 2011-06-21 22:56:08
...
Items i
date p1id p2id
----------------------------------------
2011-06-29 20:09:44 1 2
2011-06-26 10:45:00 1 3
2011-06-23 12:22:43 2 3
2011-06-22 13:07:12 2 4
...
I'd like:
The earliest single i.date that each p.id appears in either column p1id or p2id; or p.registered if they feature in neither.
So far, I've tried:
CREATE TEMPORARY TABLE temp (id INT);
INSERT INTO temp (id)
SELECT DISTINCT u FROM (
SELECT p1id AS u FROM Items UNION ALL
SELECT p2id AS u FROM Items
)tt;
SELECT registered,id FROM People
WHERE id NOT IN (SELECT id FROM temp);
Which gets me as far as the second part, albeit in a fairly clumsy way; and I'm stuck on the first part beyond some sort of external, scripted iteration through all the values of p.id (ugh).
Can anyone help?
I'm on MySQL 5.1 and there's ~20k people and ~100k items.
One more solution:
SELECT id, name, IF(min_date1 IS NULL AND min_date2 IS NULL, registered, LEAST(COALESCE(min_date1, min_date2), COALESCE(min_date2, min_date1))) date FROM (
SELECT p.id, p.name, p.registered, MIN(i1.date) min_date1, MIN(i2.date) min_date2 FROM people p
LEFT JOIN items i1
ON p.id = i1.p1id
LEFT JOIN items i2
ON p.id = i2.p2id
GROUP BY id
) t;
OR this:
SELECT p.id, p.name, COALESCE(MIN(i.date), p.registered) FROM people p
LEFT JOIN (
SELECT p1id id, date FROM items
UNION ALL
SELECT p2id id, date FROM items
) i
ON p.id = i.id
GROUP BY id;
Result:
+------+-------+---------------------+
| id | name | date |
+------+-------+---------------------+
| 1 | Geoff | 2011-06-26 10:45:00 |
| 2 | Phil | 2011-06-22 13:07:12 |
| 3 | Tony | 2011-06-23 12:22:43 |
| 4 | Gary | 2011-06-22 13:07:12 |
+------+-------+---------------------+
This is tested in Postgres, but I think it ought to work in MySQL with few or no changes:
SELECT p.id,COALESCE(MIN(x.date),p.registered) AS date
FROM p
JOIN (
SELECT p.id,MIN(i.date) AS date
FROM p
JOIN i ON (p.id=i.p1id)
GROUP BY p.id
UNION
SELECT p.id,MIN(i.date) AS date
FROM p
JOIN i ON (p.id=i.p2id)
GROUP BY p.id
) AS x ON x.id = p.id
GROUP BY p.id,p.registered;
Output (given your sample data):
id | date
----+---------------------
3 | 2011-06-23 12:22:43
1 | 2011-06-26 10:45:00
2 | 2011-06-22 13:07:12
4 | 2011-06-22 13:07:12
(4 rows)