Mysql left join style sum() - mysql

I believe the answer is already there at stackoverflow but I cannot find the right keywords. So please help.
Table sales looks like this:
state | sales-representative | product | sales
NY | Mike B. | prod-A | 90
FL | David J. | prod-B | 120
FL | Mike B. | prod-A | 15
I need to get the total sales by such sales representative. Expected results for Mike B. look at this:
state | product | sales
NY | prod-A | 90
FL | prod-A | 15
NY | prod-B | 0 <--How can I get this record as well?
FL | prod-B | 0
A regular sum query returns the first 2 records. How can I get the last 2 records as well?
select state, product, sum(sales)
from sales
where sales-representative = 'Mike B.'
group by state, product

SELECT ss.state, sp.product, SUM(sr.sales)
FROM (SELECT DISTINCT state FROM sales) AS ss
CROSS JOIN (SELECT DISTINCT product FROM sales) AS sp
LEFT JOIN sales AS r
AS sr ON ss.state = s.state
AND sp.product = s.product
AND r.`sales-representative` = 'Mike B.'
GROUP BY ss.state, sp.product;
The cross join gets you every combination of state and product, and the left join gets you the specified representative's associated sales.

You can use a left join on subquery for stated and product
select t.state, t.product, sum(sales)
from table1
left join (
select t1.state, t2.product
from t1
cross join (
select product
from t1
) t2
) t on t.state = table1.state and t.product = table1.product
grooup by t.state, t.product

Related

MySQL nearest date without duplicated data

So I need to display all my customers and with the associated booking number (null if there is no booking) without duplicated custome. If the customer has lot of bookings I need to display only the nearest booking date. I don't understand why my query doesn't work.
Here is what is did : http://sqlfiddle.com/#!9/df0455/19
SELECT c.name, x.number, x.start_date
FROM customer c
LEFT JOIN
(SELECT b.customer_id, b.number, b.start_date
FROM booking b
INNER JOIN (
SELECT customer_id, MIN(ABS(TIME_TO_SEC(TIMEDIFF(NOW(), start_date)))) as mindiff
FROM booking
GROUP BY customer_id
) nearest ON b.customer_id = nearest.customer_id AND ABS(TIME_TO_SEC(TIMEDIFF(NOW(), start_date))) = mindiff
) AS x ON c.id = x.customer_id
Actually Paul is displayed three times and what is need is to display Paul just once with the nearest booking number who is booking-1 2019-11-05 21:45:00
I hope you can help me
You can filter with a row-limiting correlated subquery:
select c.name, b.number, b.start_date
from customer c
inner join booking b on b.customer_id = c.id
where b.start_date = (
select b1.start_date
from booking b1
where b1.customer_id = b.customer_id
order by abs(timestampdiff(second, now(), b1.start_date))
limit 1
)
In your DB Fiddle, this produces:
name number start_date
Paul booking-1 2019-11-05T21:45:00Z
John booking-3 2019-09-27T21:45:00Z
Morgan booking-5 2019-09-27T21:45:00Z
If you wanted to also display customers without bookings, then you would left join and move the filtering to the on clause of the join:
select c.name, b.number, b.start_date
from customer c
left join booking b
on b.customer_id = c.id
and b.start_date = (
select b1.start_date
from booking b1
where b1.customer_id = b.customer_id
order by abs(timestampdiff(second, now(), b1.start_date))
limit 1
)
You can use NOT EXISTS to get the nearest booking and join to customer:
SELECT c.id, c.name, t.number, t.start_date
FROM customer c
LEFT JOIN (
SELECT b.* FROM booking b
WHERE NOT EXISTS (
SELECT 1 FROM booking
WHERE customer_id = b.customer_id
AND ABS(TIMESTAMPDIFF(SECOND, NOW(), start_date)) < ABS(TIMESTAMPDIFF(SECOND, NOW(), b.start_date))
)
) t ON t.customer_id = c.id
See the demo.
Results:
| id | name | number | start_date |
| --- | ------ | --------- | ------------------- |
| 1 | Paul | booking-1 | 2019-11-05 21:45:00 |
| 2 | John | booking-3 | 2019-09-27 21:45:00 |
| 3 | Morgan | booking-5 | 2019-09-27 21:45:00 |
| 4 | Jane | | |
| 5 | Mike | | |

Set Alias in SELECT with Inner Join

I have a 2 tables:
People table:
id | name | date
1 | Mika | 2013
2 | Rose | 2015
Work table:
id | user_id | work_name | registers
1 | 1 | rugby | 10
2 | 1 | golf | 3
I use this query to join tables:
SELECT * FROM work INNER JOIN people ON work.user_id = people.id WHERE work_name= :work_name
This work it, but I get duplicate column ID and in php when I try to print the camp id, this show the last column id..
I try with this but dont work it:
SELECT *, id AS 'work_id'
FROM work
INNER JOIN people ON work.user_id = people.id
WHERE work_name= :work_name
That is because you are using * in the SELECT that mean will bring all the fields from boths tables instead you can use
SELECT work.id as work_id,
work.name as work_name,
work.date as work_date,
people.id as people_id,
people.name as people_name,
people.date as people_date
Try to manually list all of columns you need to display, for instance
SELECT w.user_id, w.work_name, w.registers, p.name, p.date FROM work as w INNER JOIN people as p ON work.user_id = people.id WHERE work_name= :work_name

MySql query code to fetch unique data from single ID

How do i List the CUSTNUMs and NAMES of any customer who has only ordered chemical [NUMBER].
ORDERS TABLE
+---------+--------+------------+------+
| CUSTNUM | CHEMNO | DATE | QTY |
+---------+--------+------------+------+
| 123456 | 1234 | 2000-00-00 | 35 |
+---------+--------+------------+------+
CUSTOMER TABLE
+---------+-----------+-----------+
| CUSTNUM | NAME | LOCATION |
+---------+-----------+-----------+
| 123456 | AmChem | New York |
+---------+-----------+-----------+
You could join the CUSTOMER and ORDERS tables containing orders for a particular <chemno> with a subquery for the custnum that buy only a product:
SELECT
CUSTNUM, NAME
FROM
CUSTOMER c
INNER JOIN
ORDERS o ON o.CUSTNUM = c.CUSTNUM and o.CHEMNO = <chemno>
INNER JOIN
( SELECT
CUSTNUM
FROM
ORDERS
GROUP BY
CUSTNUM
HAVING
COUNT(DISTINCT CHEMNO) = 1 ) t ON t.CUSTNUM = o.CUSTNUM
I will approach this with one join between both tables, then grouping by the column CUSTNUM of the ORDERS table and finally adding the required conditions on the HAVING clause, like this:
SELECT
o.CUSTNUM,
c.NAME
FROM
ORDERS AS o
INNER JOIN
CUSTOMER AS c ON c.CUSTNUM = o.CUSTNUM
GROUP BY
o.CUSTNUM
HAVING
( COUNT(DISTINCT o.CHEMNO) = 1 AND MIN(o.CHEMNO) = <some_chemno> )
OK, slow day...
SELECT DISTINCT x.custnum
FROM orders x
LEFT
JOIN orders y
ON y.custnum = x.custnum
AND y.chemno <> x.chemno
WHERE x.chemno = 9377
AND y.order_id IS NULL;
The rest of this task has been left as an exercise for the reader

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

Sum columns from multiple tables in MySQL

I am trying to sum some columns from multiple tables in just one SQL query, but I seem to be getting the wrong results. I think there is a problem with the code that I have provided below. Please any help on this is appreciated.
item Names
| id | name |
| 1 | AB |
| 2 | CA |
table1
| id | interest | year |
| 1 | 20.00 | 2014 |
| 2 | 30.00 | 2013 |
| 1 | 10.00 | 2013 |
table2
| id | deposit | year |
| 1 | 10.00 | 2014 |
| 2 | 10.00 | 2014 |
This is the query that I tried:
SELECT
a.name,
b.year,
sum(b.interest) as 'total'
FROM
`table1` b
INNER JOIN
`item names` a
ON
b.id=a.id
GROUP BY
b.id
UNION ALL
SELECT
c.name,
d.year,
sum(d.deposit) as 'total'
FROM
`table2` d
INNER JOIN
`item names` c
ON
d.id=c.id
GROUP BY
d.id
EXPECTED RESULTS
UPDATE
I am trying to find the total sum of interest and deposit for a particular year and for a particular item
|name | year | total |
| AB | 2014 | 30.00 |
| AB | 2013 | 10.00 |
| CA | 2013 | 30.00 |
| CA | 2014 | 10.00 |
Perhaps... assuming table1 and table2 have same structure.
First I generate a set with the union values from one and two then we use a simple aggregate and a group by to sum the values by name and year.
SELECT I.Name, B.year, Sum(B.Total)
FROM item I
INNER JOIN
(SELECT * FROM table1 UNION select * FROM table2) B
on B.ID = I.ID
GROUP BY I.Name, B.Year
In the query you have posted, you need to group by year also to get the results. Then, you can use UNION to get all of the rows from the first set, along with all of the rows from the second set:
SELECT a.name, b.year, SUM(b.interest) AS total
FROM names a
JOIN table1 b ON b.id = a.id
GROUP BY a.name, b.year
UNION
SELECT a.name, c.year, SUM(c.deposit) AS total
FROM names a
JOIN table2 c ON c.id = a.id
GROUP BY a.name, c.year;
However, this doesn't give you your final results, as names that appear in each table ('AB' for example) will appear twice. One row for the year in deposits, one row for the year in interests. To combine those, just use the above as a subquery, sum the totals and again group by name and date:
SELECT name, year, SUM(total) AS total
FROM(
SELECT a.name, b.year, SUM(b.interest) AS total
FROM names a
JOIN table1 b ON b.id = a.id
GROUP BY a.name, b.year
UNION
SELECT a.name, c.year, SUM(c.deposit) AS total
FROM names a
JOIN table2 c ON c.id = a.id
GROUP BY a.name, c.year) temp
GROUP BY name, year;
Here is an SQL Fiddle example.