retrieve matching and non matching rows from 2 tables - mysql

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

Related

Get all rows from table - with the latest row from another table, with another table based on the latest row

I need to get all the details from the orders table, with the latest status ID in the orders statuses table, and then the name of that status from the states table.
orders
id | customer | product
-----------------------
1 | David | Cardboard Box
Order_to_statuses
id | order | status | updated_at
--------------------------------
1 | 1 | 1 | 2017-05-30 00:00:00
2 | 1 | 3 | 2017-05-28 00:00:00
3 | 1 | 4 | 2017-05-29 00:00:00
4 | 1 | 2 | 2017-05-26 00:00:00
5 | 1 | 5 | 2017-05-05 00:00:00
order_states
id | name
---------
1 | Pending
2 | Paid
3 | Shipped
4 | Refunded
In this instance, I would need to get the customer and product, with the latest status ID from the order statuses table, and then the name of that state.
How can I do this?
I'd break this down by first getting the max(updated_at) for each order, then work to everything else you need. You can get the max date for each order by using subquery:
select
s.`order`,
s.`status`,
s.updated_at
from order_to_statuses s
inner join
(
select
`order`,
max(updated_at) as updated_at
from order_to_statuses
group by `order`
) m
on s.`order` = m.`order`
and s.updated_at = m.updated_at
Once you get this you now have the order, the status id, and the most recent date. Using this you can then JOIN to the other tables, making your full query:
select
o.customer,
o.product,
ots.updated_at,
os.name
from orders o
inner join
(
select
s.`order`,
s.`status`,
s.updated_at
from order_to_statuses s
inner join
(
select
`order`,
max(updated_at) as updated_at
from order_to_statuses
group by `order`
) m
on s.`order` = m.`order`
and s.updated_at = m.updated_at
) ots
on o.Id = ots.`order`
inner join order_states os
on ots.`status` = os.id;
See a demo
It may have some typo, but the idea of the query should be something like this:
select orders.id, orders.customer, orders.product,
order_to_status.status, staus.name
from orders, order_to_status, status
where orders.id = order_to_status.order
and order_to_status.status = status.id
and order_to_status.updated_at in (
SELECT MAX(order_to_status.updated_at)
FROM order_to_status
where order_to_status.order = orders.id
group by order_to_status.order
);
I ussually don't use joins but with joins it should be like this:
select orders.id, orders.customer, orders.product,
order_to_status.status, staus.name
from orders
JOIN order_to_status ON orders.id = order_to_status.order
JOIN status ON order_to_status.status = status.id
where
order_to_status.updated_at in (
SELECT MAX(order_to_status.updated_at)
FROM order_to_status
where order_to_status.order = orders.id
group by order_to_status.order
);
Note I added a group by I had missed.
EDIT 2
I had an error in the subquery condition.
changed to where order_to_status.order = orders.id
also moved the group by after the where clause.

Sum columns from two tables in sql

I have two tables, one is the cost table and the other is the payment table, the cost table contains the cost of product with the product name.
Cost Table
id | cost | name
1 | 100 | A
2 | 200 | B
3 | 200 | A
Payment Table
pid | amount | costID
1 | 10 | 1
2 | 20 | 1
3 | 30 | 2
4 | 50 | 1
Now I have to sum the total of cost by the same name values, and as well sum the total amount of payments by the costID, like the query below
totalTable
name | sum(cost) | sum(amount) |
A | 300 | 80 |
B | 200 | 30 |
However I have been working my way around this using the query below but I think I am doing it very wrong.
SELECT
b.name,
b.sum(cost),
a.sum(amount)
FROM
`Payment Table` a
LEFT JOIN
`Cost Table` b
ON
b.id=a.costID
GROUP by b.name,a.costID
I would be grateful if somebody would help me with my queries or better still an idea as to how to go about it. Thank you
This should work:
select t2.name, sum(t2.cost), coalesce(sum(t1.amount), 0) as amount
from (
select id, name, sum(cost) as cost
from `Cost`
group by id, name
) t2
left join (
select costID, sum(amount) as amount
from `Payment`
group by CostID
) t1 on t2.id = t1.costID
group by t2.name
SQLFiddle
You need do the calculation in separated query and then join them together.
First one is straight forward.
Second one you need to get the name asociated to that payment based in the cost_id
SQL Fiddle Demo
SELECT C.`name`, C.`sum_cost`, COALESCE(P.`sum_amount`,0 ) as `sum_amount`
FROM (
SELECT `name`, SUM(`cost`) as `sum_cost`
FROM `Cost`
GROUP BY `name`
) C
LEFT JOIN (
SELECT `Cost`.`name`, SUM(`Payment`.`amount`) as `sum_amount`
FROM `Payment`
JOIN `Cost`
ON `Payment`.`costID` = `Cost`.`id`
GROUP BY `Cost`.`name`
) P
ON C.`name` = P.`name`
OUTPUT
| name | sum_cost | sum_amount |
|------|----------|------------|
| A | 300 | 80 |
| B | 200 | 30 |
A couple of issues. For one thing, the column references should be qualified, not the aggregate functions.
This is invalid:
table_alias.SUM(column_name)
Should be:
SUM(table_alias.column_name)
This query should return the first two columns you are looking for:
SELECT c.name AS `name`
, SUM(c.cost) AS `sum(cost)`
FROM `Cost Table` c
GROUP BY c.name
ORDER BY c.name
When you introduce a join to another table, like Product Table, where costid is not UNIQUE, you have the potential to produce a (partial) Cartesian product.
To see what that looks like, to see what's happening, remove the GROUP BY and the aggregate SUM() functions, and take a look at the detail rows returned by a query with the join operation.
SELECT c.id AS `c.id`
, c.cost AS `c.cost`
, c.name AS `c.name`
, p.pid AS `p.pid`
, p.amount AS `p.amount`
, p.costid AS `p.costid`
FROM `Cost Table` c
LEFT
JOIN `Payment Table` p
ON p.costid = c.id
ORDER BY c.id, p.pid
That's going to return:
c.id | c.cost | c.name | p.pid | p.amount | p.costid
1 | 100 | A | 1 | 10 | 1
1 | 100 | A | 2 | 20 | 1
1 | 100 | A | 4 | 50 | 1
2 | 200 | B | 3 | 30 | 2
3 | 200 | A | NULL | NULL | NULL
Notice that we are getting three copies of the id=1 row from Cost Table.
So, if we modified that query, adding a GROUP BY c.name, and wrapping c.cost in a SUM() aggregate, we're going to get an inflated value for total cost.
To avoid that, we can aggregate the amount from the Payment Table, so we get only one row for each costid. Then when we do the join operation, we won't be producing duplicate copies of rows from Cost.
Here's a query to aggregate the total amount from the Payment Table, so we get a single row for each costid.
SELECT p.costid
, SUM(p.amount) AS tot_amount
FROM `Payment Table` p
GROUP BY p.costid
ORDER BY p.costid
That would return:
costid | tot_amount
1 | 80
2 | 30
We can use the results from that query as if it were a table, by making that query an "inline view". In this example, we assign an alias of v to the query results. (In the MySQL venacular, an "inline view" is called a "derived table".)
SELECT c.name AS `name`
, SUM(c.cost) AS `sum_cost`
, IFNULL(SUM(v.tot_amount),0) AS `sum_amount`
FROM `Cost Table` c
LEFT
JOIN ( -- inline view to return total amount by costid
SELECT p.costid
, SUM(p.amount) AS tot_amount
FROM `Payment Table` p
GROUP BY p.costid
ORDER BY p.costid
) v
ON v.costid = c.id
GROUP BY c.name
ORDER BY c.name

Get COUNT() AND COUNT(DISTINCT()) values across 2 tables

I am trying to get a COUNT(*) from a joined table "Table2" where the dates are common PLUS a COUNT(DISTINCT(CID)) from "Table1" and grouped by the common year-month:
Table1
---------------
cid | date |
----|---------|
321 | 2016-01 |
----|---------|
423 | 2016-01 |
----|---------|
324 | 2016-01 |
----|---------|
546 | 2015-12 |
----|---------|
Table2
---------------
id | dateEnq |
----|---------|
3 | 2016-01 |
----|---------|
6 | 2016-01 |
----|---------|
24 | 2015-12 |
----|---------|
36 | 2015-12 |
----|---------|
MySQL query:
SELECT COUNT( DISTINCT (t1.cid) ) AS users,
SUBSTR(DATE(t1.date),1,7) AS month,
COUNT(t2.dateEnq) AS enquiries
FROM table1 t1
INNER JOIN table2 t2 ON SUBSTR(t2.dateEnq, 1, 7 ) = SUBSTR(t1.date, 1, 7 )
GROUP BY SUBSTR(DATE(t1.date),1,7)
This is the result I get, but the enquiries values are just way wrong, I think it is not counting the values from Table2, they should be like 3, 10, 25 per row.
How do I get the monthly count from Table2?
users | month | enquiries|
------|----------|-----------
7237 | 2015-10 | 8374 |
12597 | 2015-11 | 30066 |
12980 | 2015-12 | 15514 |
11305 | 2016-01 | 128169 |
Your "counts" are inflated because it's a partial cross product. For a given month, every row from t1 is matched to every row for that same month from t2.
One option is to get the counts before you do the join. As an example, using inline views:
SELECT c1.users
, c1.month
, c2.enquiries
FROM ( SELECT COUNT(DISTINCT t1.cid) AS `users`
, SUBSTR(DATE(t1.date),1,7) AS `month`
FROM table1 t1
GROUP BY SUBSTR(DATE(t1.date),1,7)
) c1
JOIN ( SELECT COUNT(t2.dateEnq) AS `enquiries`
, SUBSTR(DATE(t2.date),1,7) AS `month`
FROM table2 t2
GROUP BY SUBSTR(DATE(t2.date),1,7)
) c2
ON c2.month = c1.month
ORDER BY c1.month
This isn't the only way (or even necessarily the best way) to get this result. There are other query patterns that will achieve equivalent results.
If there's a possibility to have zero enquiries for a given month, then to get that zero count returned, we can tweak that query so it's an outer join, and then replace any NULL value (for a missing row) with a zero in the outer query:
SELECT c1.users
, c1.month
, IFNULL(c2.enquiries,0) AS `enquiries`
FROM ( SELECT COUNT(DISTINCT t1.cid) AS `users`
, SUBSTR(DATE(t1.date),1,7) AS `month`
FROM table1 t1
GROUP BY SUBSTR(DATE(t1.date),1,7)
) c1
LEFT
JOIN ( SELECT COUNT(t2.dateEnq) AS `enquiries`
, SUBSTR(DATE(t2.date),1,7) AS `month`
FROM table2 t2
GROUP BY SUBSTR(DATE(t2.date),1,7)
) c2
ON c2.month = c1.month
ORDER BY c1.month

Select and summarize data from three tables

i have three tables
customer
id | name
1 | john
orders
id | customer_id | date
1 | 1 | 2013-01-01
2 | 1 | 2013-02-01
3 | 2 | 2013-03-01
order_details
id | order_id | qty | cost
1 | 1 | 2 | 10
2 | 1 | 5 | 10
3 | 2 | 2 | 10
4 | 2 | 2 | 15
5 | 3 | 3 | 15
6 | 3 | 3 | 15
i need to select data so i can get the output for each order_id the summary of the order
sample output. I will query the database with a specific customer id
output
date | amount | qty | order_id
2013-01-01 | 70 | 7 | 1
2013-02-01 | 50 | 4 | 2
this is what i tried
SELECT
orders.id, orders.date,
SUM(order_details.qty * order_details.cost) AS amount,
SUM(order_details.qty) AS qty
FROM orders
LEFT OUTER JOIN order_details ON order_details.order_id=orders.id AND orders.customer_id = 1
GROUP BY orders.date
but this returns the same rows for all customers, only that the qty and cost dont hav values
Maybe
SELECT
orders.id, orders.date,
SUM(order_details.qty * order_details.cost) AS amount,
SUM(order_details.qty) AS qty
FROM orders
LEFT JOIN order_details ON order_details.order_id=orders.id
AND orders.customer_id = 1
GROUP BY orders.date
HAVING amount is not null AND qty is not null
SQL Fiddle
NOTE: In the following query, it is assumed that the dates are stored in the database as a string in the format specified in the OP. If they are actually stored as some type of date with time then you'll want to modify this query such that the time is truncated from the date so the date represents the whole day. You can use the date or date_format functions. But then you'll need to make sure that you modify the query appropriately so the group by and select clauses still work. I added this modification as comments inside the query.
select
o.date -- or date(o.date) as date
, sum(odtc.total_cost) as amount
, sum(odtc.qty) as qty
, o.order_id
from
orders o
inner join (
select
od.id
, od.order_id
, od.qty
, od.qty * od.cost as total_cost
from
order_details od
inner join orders _o on _o.id = od.order_id
where
_o.customer_id = :customer_id
group by
od.id
, od.order_id
, od.qty
, od.cost
) odtc on odtc.order_id = o.id
where
o.customer_id = :customer_id
group by
o.date -- or date(o.date)
, o.order_id
;
I don't think you want an outer join just a simple inner join on all 3 tables:
FROM orders, order_details, customer
WHERE orders.customer_id=customer.id
AND order_details.order_id=orders.id

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.