I'm creating a query which will display projected qty versus qty sold on a monthly basis. Below are my two tables and third one is what i want as an output. The two table is almost the same, except that the date and creation date is in different format (i have converted this to output e.g 201301, so no worries about it).
As you have noticed in my output table, qty sold become 25.00 because it adds the qty if the date falls under the same month and year. I also need to consider these three important conditions:
to display rows with projected qty but without sales (ex. item 206)
To display rows without projected qty but with sales (ex. item 312)
to display row with projection and with sales (ex. item001 & 040)
I don't know if i have to used join or union to be able to achieve my desired output.
Table A (Sales Table)
item code Sold date
001 cust001 10.00 2013-01-20
001 cust001 15.00 2013-01-25
040 cust045 16.00 2013-04-07
312 cust001 20.00 2013-03-13
Table B (Projection Table)
item Custcode ProjectedQty Creation Date
001 cust001 20.00 2013-01-01
040 cust045 50.00 2013-04-01
206 cust121 60.00 2013-04-01
Output
item Custcode sold Date ProjectedQty Creation Date
001 cust001 25.00 201301 20.00 201301
312 cust001 20.00 201303 null null
040 cust045 16.00 201304 50.00 201304
206 cust121 null null 60.00 201304
Thanks guys for helping.
Try
SELECT COALESCE(p.item, s.item) item,
COALESCE(p.code, s.code) code,
sold,
CONCAT(s.year, LPAD(s.month, 2, '0')) date,
ProjectedQty,
CONCAT(p.year, LPAD(p.month, 2, '0')) creationdate
FROM
(
SELECT item, code, YEAR(`date`) year, MONTH(`date`) month
FROM sales
UNION
SELECT item, custcode, YEAR(`creationdate`), MONTH(`creationdate`)
FROM projection
) i LEFT JOIN
(
SELECT item, code, SUM(sold) sold, YEAR(`date`) year, MONTH(`date`) month
FROM sales
GROUP BY item, code, YEAR(`date`), MONTH(`date`)
) s ON i.item = s.item
AND i.code = s.code
AND i.year = s.year
AND i.month = s.month LEFT JOIN
(
SELECT item, custcode code, SUM(ProjectedQty) ProjectedQty, YEAR(`creationdate`) year, MONTH(`creationdate`) month
FROM projection
GROUP BY item, custcode, YEAR(`creationdate`), MONTH(`creationdate`)
) p ON i.item = p.item
AND i.code = p.code
AND i.year = p.year
AND i.month = p.month
ORDER BY code, item
Output:
| ITEM | CODE | SOLD | DATE | PROJECTEDQTY | CREATIONDATE |
------------------------------------------------------------------
| 001 | cust001 | 25 | 201301 | 20 | 201301 |
| 312 | cust001 | 20 | 201303 | (null) | (null) |
| 040 | cust045 | 16 | 201304 | 50 | 201304 |
| 206 | cust121 | (null) | (null) | 60 | 201304 |
SQLFiddle
Why not use a simple join for this?
SELECT * FROM `sales`, `projection` WHERE `code`=`custcode`;
Make a view of the query above, and using WHERE clause, you can alter the data.
Create a view using union. and then you can use the below query:
select *, sum(sold) as sold from view group by item
another is:
select *, sum(sold) from (
select item, code as Custcode, sold, date, 'ProjectedQty' as ProjectedQty, 'Creation Date' as Creation Date from table 1 union select item, Custcode, sold, Date, ProjectedQty, Creation Date, from table 2) group by item
Related
A more generic title for this post would be
MySql Sum different columns in same table based on value of another row, group by yet another row
I have a table of employee expenses:
id | employee_id | expense_cat_id | expense_amount |
1 | 11 | 1 | 100 |
2 | 11 | 1 | 200 |
3 | 12 | 1 | 120 |
4 | 12 | 1 | 140 |
5 | 11 | 2 | 5 |
6 | 12 | 2 | 8 |`
and I want to produce a report like this:
Employee Id | Expense Cat 1 Total Amount | Expense Cat 2 Total Amount
11 | 300 | 5
12 | 260 | 8
So initially I thought I could use 2 table aliases for the same table like this:
SELECT
employee_id,
sum(expense_cat_1.expense_amount) as expense_1_total,
sum(expense_cat_2.expense_amount) as expense_2_total
FROM
expenses as expense_cat_1 where expense_cat_1.expense_cat_id=1 ,
expenses as expense_cat_2 where expense_cat_2.expense_cat_id=2
group by employee_id
but this was not correct Sql Syntax, which makes sense to me.
So I thought I could do two joins on between employee table and the expenses table:
SELECT
employees.id as employee_id,
sum(expenses_cat_1.expense_amount) as expense_1_total,
sum(expenses_cat_2.expense_amount) as expense_2_total
FROM employees
join expenses as expenses_cat_1 on employees.id = expenses_cat_1.employee_id and expenses_cat_1.expense_cat_id=1
join expenses as expenses_cat_2 on employees.id = expenses_cat_2.employee_id and expenses_cat_2.expense_cat_id=2
group by employees.id
Which comes close, but is wrong:
employee_id | expense_1_total | expense_2_total
11 | 300 | 10
12 | 260 | 16
as the expense 2 total is doubled! I think this is because the join on shows up two rows for each of the two expenses with category 1, and sums them.
I also tried a sub-query approach:
SELECT (SELECT sum(expense_amount)
FROM expenses
WHERE expense_cat_id = 1) AS sum1 ,
(SELECT sum(expense_amount)
FROM expenses
WHERE expense_cat_id = 2) AS sum2,
employee_id
FROM expenses group by employee_id
but this has the same problem as the join approach - totals for cat 2 are doubled.
How do I make the second join only include the expense_2_total once ???
I have a personal dislike of sql case statements as they seem more of a procedural language construct (and sql is declarative), but am happy to consider their use in this case - but I put the challenge out there for sql experts to solve this elegantly.
You are looking for conditional aggregation:
SELECT employee_id,
sum(case when expense_cat_id = 1 then expense_amount else 0 end) as expense_1_total,
sum(case when expense_cat_id = 2 then expense_amount else 0 end) as expense_2_total
FROM expenses e
GROUP BY employee_id;
I have two tables: invoices and items.
invoices
id | timest
items
id | invoice_id | price | qty
It is apparent an invoice may have several items - items.invoice_id = invoices.id.
I have the following query that selects all invoices with the total sum of theirs items:
SELECT id, DATE_FORMAT(FROM_UNIXTIME(inv.time), "%Y-%m" ) AS _period,
(SELECT SUM(it.price*it.quantity) FROM items AS it WHERE it.invoice_id=inv.id) as total
FROM `invoices` `inv`
This generates something like:
id| _period | total
-------------------
1 | 2014-06 | 100
4 | 2014-06 | 200
5 | 2014-07 | 660
6 | 2014-07 | 300
7 | 2014-07 | 30
9 | 2015-02 | 225
Now I want to group it by the period to have output as:
_period | qty | total_price
---------------------------
2014-06 | 2 | 300
2014-07 | 3 | 990
2015-02 | 1 | 224
I can easily do it for the quantity field as
SELECT DATE_FORMAT(FROM_UNIXTIME(inv.time), "%Y-%m" ) AS _period,
COUNT(inv.id) as qty
FROM `invoices` `inv`
GROUP BY _period
But I can't figure out how the similar thing could be done for the total_price field, which results from a subquery virtual field? Does anyone have any idea?
Thank you!
You should do this using a LEFT JOIN and GROUP BY:
SELECT DATE_FORMAT(FROM_UNIXTIME(i.time, '%Y-%m') AS _period,
COUNT(DISTINCT i.id) as num_invoices
SUM(i.price * it.quantity) as total
FROM invoices i LEFT JOIN
items it
ON it.invoice_id = i.id
GROUP BY _period
ORDER BY _period;
try this
SELECT InnerTable._period, Count(InnerTable.id) as id, Sum(InnerTable.total) as total FROM
(SELECT id, DATE_FORMAT(FROM_UNIXTIME(inv.time), "%Y-%m" ) AS _period,
(SELECT SUM(it.price*it.quantity) FROM items AS it WHERE it.invoice_id=inv.id) as total
FROM `invoices` `inv`) as InnerTable FROM GROUP BY InnerTable._period.
Making sub table from the query and then put group by on it.
I would like to find out the average number of days between orders grouping by account_id in the database.
Let's say I have the following table named 'orders' with this data.
id account_id account_name order_date
1 555 Acme Fireworks 2015-06-15
2 342 Kent Brewery 2015-09-12
3 555 Acme Fireworks 2015-09-15
4 342 Kent Brewery 2015-10-12
5 342 Kent Brewery 2015-11-12
6 342 Kent Brewery 2015-12-12
7 555 Acme Fireworks 2015-12-15
8 900 Plastic Inc. 2015-12-20
I would like a query to produce the following results
account_id account_name average_days_between_orders
342 Kent Brewery 30.333
555 Acme Fireworks 91.5
900 Plastic Inc. (unsure of what value would go here since there's 1 order only)
I checked the following questions to get an idea, but still couldn't figure out the problem:
Average difference between two dates, grouped by a third field?
Thanks!
You need a query that produces the difference between the previous purchase for a given (null if there is no previous purchase) and take the average of these values.
I would self-join the above table to get for each order the maximum order date of any previous order in a subquery. In the avg() function calculate the difference between the calculated date and the current order date:
SELECT o3.account_id, o3.account_name, avg(diff) as average_days_between_orders
FROM
(select o1.id,
o1.account_id,
o1.account_name,
datediff(o1.order_date, max(o2.order_date)) as diff
from orders o1
left join orders o2 on o1.account_id=o2.account_id and o1.id>o2.id
group by o1.id, o1.account_id, o1.account_name, o1.order_date) o3
GROUP BY o3.account_id, o3.account_name
As an alternative to joins, you can use a user defined variable in the subquery or a correlated subquery in the select list to calculate the differences. You can check mysql running total solutions to get a hang of this solution, such as this SO topic. Specifically, check out the solution provided by Andomar.
If your orders table is huge, then the alternative aprroaches described in that topic may be better from a performance point of view.
Note: Please test it carefully and use it as you wish. I couldn't find an easy query for it. I don't guarantee to work for all cases :) If you just want the answer, the complete query is shown in the end.
The goal is that I'll try to get a table with start and end dates in one row, and then I'll simply calculate average difference between two dates. Something like this.
id | account_id | account_name | start_date | end_date
------------------------------------------------------------
1 | 342 | Kent Brewery | 2015-09-12 | 2015-10-12
2 | 342 | Kent Brewery | 2015-10-12 | 2015-11-12
3 | 342 | Kent Brewery | 2015-11-12 | 2015-12-12
4 | 555 | Acme Fireworks | 2015-06-15 | 2015-09-15
5 | 555 | Acme Fireworks | 2015-09-15 | 2015-12-15
I'll create few temporary tables to make it a bit more clear. First query for start_date:
QUERY:
create temporary table uniq_start_dates
select (#sid := #sid + 1) id, tmp_uniq_start_dates.*
from
(select distinct o1.account_id, o1.account_name, o1.order_date start_date
from orders o1
join orders o2 on o1.account_id=o2.account_id and o1.order_date < o2.order_date
order by o1.account_id, o1.order_date) tmp_uniq_start_dates
join (select #sid := 0) AS sid_generator
OUTPUT: temporary table - uniq_start_dates
id | account_id | account_name | start_date
-----------------------------------------------
1 | 342 | Kent Brewery | 2015-09-12
2 | 342 | Kent Brewery | 2015-10-12
3 | 342 | Kent Brewery | 2015-11-12
4 | 555 | Acme Fireworks | 2015-06-15
5 | 555 | Acme Fireworks | 2015-09-15
Do the same thing for end_date:
QUERY:
create temporary table uniq_end_dates
select (#eid := #eid + 1) id, tmp_uniq_end_dates.*
from
(select distinct o2.account_id, o2.account_name, o2.order_date end_date
from orders o1
join orders o2 on o1.account_id=o2.account_id and o1.order_date < o2.order_date
order by o2.account_id, o2.order_date) tmp_uniq_end_dates
join (select #eid := 0) AS eid_generator
OUTPUT: temporary table - uniq_end_dates
id | account_id | account_name | end_date
-----------------------------------------------
1 | 342 | Kent Brewery | 2015-10-12
2 | 342 | Kent Brewery | 2015-11-12
3 | 342 | Kent Brewery | 2015-12-12
4 | 555 | Acme Fireworks | 2015-09-15
5 | 555 | Acme Fireworks | 2015-12-15
If you notice, I created new auto id for each view so that I can join them back to one table (like the very first table). Let's join uniq_start_dates and uniq_end_dates.
QUERY:
create temporary table uniq_start_end_dates
select uniq_start_dates.*, uniq_end_dates.end_date
from uniq_start_dates
join uniq_end_dates using (id)
OUTPUT: temporary table - uniq_start_end_dates
(the same one as the first table)
Now it's an easy part. Just aggregate and get average date time difference.
QUERY:
select account_id, account_name, avg(timestampdiff(day, start_date, end_date)) average_days
from uniq_start_end_dates
group by account_id, account_name
OUTPUT:
account_id | account_name | average_days
--------------------------------------------
342 | Kent Brewery | 30.3333
555 | Acme Fireworks | 91.5000
If you may notice, Plastic Inc. is not in the result. If you care about "null" average_days. Here it is:
QUERY:
select all_accounts.account_id, all_accounts.account_name, accounts_with_average_days.average_days
from
(select distinct account_id, account_name from orders) all_accounts
left join
(select account_id, account_name, avg(timestampdiff(day, start_date, end_date)) average_days
from uniq_start_end_dates
group by account_id, account_name) accounts_with_average_days
using (account_id, account_name)
OUTPUT:
account_id | account_name | average_days
--------------------------------------------
342 | Kent Brewery | 30.3333
555 | Acme Fireworks | 91.5000
900 | Plastic Inc. | null
Here is a complete messy query:
select all_accounts.account_id, all_accounts.account_name, accounts_with_average_days.average_days
from
(select distinct account_id, account_name from orders) all_accounts
left join
(select uniq_start_dates.account_id, uniq_start_dates.account_name, avg(timestampdiff(day, start_date, end_date)) average_days
from
(select (#sid := #sid + 1) id, tmp_uniq_start_dates.*
from
(select distinct o1.account_id, o1.account_name, o1.order_date start_date from orders o1
join orders o2 on o1.account_id=o2.account_id and o1.order_date < o2.order_date order by o1.account_id, o1.order_date) tmp_uniq_start_dates join (select #sid := 0) AS sid_generator
) uniq_start_dates
join
(select (#eid := #eid + 1) id, tmp_uniq_end_dates.*
from
(select distinct o2.account_id, o2.account_name, o2.order_date end_date from orders o1
join orders o2 on o1.account_id=o2.account_id and o1.order_date < o2.order_date order by o2.account_id, o2.order_date) tmp_uniq_end_dates join (select #eid := 0) AS eid_generator
) uniq_end_dates
using (id)
group by uniq_start_dates.account_id, uniq_start_dates.account_name) accounts_with_average_days
using (account_id, account_name)
I have an apartment calendar table for displaying prices to users for each day.
On this table, some of the apartments include rooms (id_room) to rent:
table columns / records example:
id_apart | id_room | date | price | promo_price
1 | 1 | 03-03-2013 | 20.00 | 0
1 | 1 | 04-03-2013 | 20.00 | 0
1 | 2 | 03-03-2013 | 50.00 | 45.00
1 | 2 | 04-03-2013 | 50.00 | 45.00
I want to get as a result, a string that has 'price' and 'promo_price' concatenated with total SUM of the lowest prices found for the apartment / rooms between two dates.
This query is summing all room prices for the apartment and i can't figure out how to use MIN on this one, to sum ONLY the prices of the CHEAPEST room:
select
concat(sum(if(promo_price>0,promo_price,price)),
"---",
sum(price))
from
apart
where
id_apart=215
and date>= "2013-03-03"
and date<"2013-03-05"
The result from this query is:
90---140
Legend: first string number is sum of 'promo_price'; second number sum of 'price'
select concat(promo_price, "--", price) from apart
where id_apart=215 and date >= "2013-03-03" and date < "2013-03-05"
and (promo_price + price)) = (select min(promo_price + price) from apart);
Please try this
select concat(if(promo_price>0, 'true', sum(promo_price)),'---', sum(price)) from aprt where id_apart = 215 and date between "2013-03-03" and "2013-03-05"
i think is this what you looking for
select if(promo_price = 0,concat(sum(price),"---", sum(price)) ,sum(price)) as pri
from apart
where id_apart=1 and `date`>= "03-03-2013" and `date` < "05-03-2013"
and promo_price = 0
OUTPUT.
PRIS
40---40
obs note that i made id_apart=1 from your above example .
Is this answer u looking for?
select
concat(sum(if(promo_price>0,promo_price,price)),
"---",
sum(price)) from apart group by id_room
having sum(price) + sum(promo_price) =
(select min(sum(price) + sum(promo_price)) from apart where id_apart =1
group by id_room);
Output :40 --- 40
I'm doing an inner join where i select between a date range (say, BETWEEN '2011-01-01' AND '2011-02-01'), and grouping by an enumerated value. is there a way to do this for each month as a column for a range of months? I'm currently doing this by hand for each month.
Example:
vehicle_type | January | February | March
----------------------------------------------
sedan | 12 | 10 | 4
coupe | 5 | 7 | 23
truck | 0 | 0 | 9
electric | 22 | 10 | 13
hybrid | 0 | 12 | 0
You could create a calendar table...
CREATE TABLE calendar
(
description VARCHAR2(100 BYTE),
when_start DATE,
when_end DATE
)
then use a pivot query
e.g.
SELECT
vehicle_type,
SUM(jan),SUM(feb),
--add the other months here
SUM(nov),SUM(dece)
FROM
(
SELECT v.vehicle_type,
CASE WHEN c.description='Jan' THEN
count(*)
END AS jan,
case when c.description='Feb' THEN
count(*)
END AS feb,
-- Add the rest of the months here too
CASE WHEN c.description='Nov' THEN
COUNT(*)
END AS nov,
CASE WHEN c.description='Dec' THEN
COUNT(*)
END AS dece
FROM calendar c
INNER JOIN vehicles v ON v.when >= c.when_start AND v.when <= c.when_end
GROUP BY v.vehicle_type
)
GROUP BY vehicle_type
ORDER BY vehicle_type