I have 2 tables storing income:
id name date price isDel
1 abc 2012-01-26 613.90 0
2 cde 2012-01-25 530.10 0
and expenses:
id name price date isDel
1 b1 334.90 2012-01-26 0
2 b1 41.90 2012-01-25 0
3 d1 61.90 2012-01-25 0
I would like to get the result as follows:
date total income total expense profile n loss
2012-01-25 530.10 103.80 426.30
2012-01-26 613.90 344.90 26.90
Thanks. Can anyone provide SQL solutions? I did refer some member's question and answer at stackoverflow but I can't modify to want I need. Please help.
Provided you want profit/loss per day this should do:
SELECT Inc.date, TotalInc, TotalExp, TotalInc - TotalExp AS ProfitNLoss
FROM
(
SELECT date, SUM(price) as TotalInc
FROM Income
WHERE isdel = 0
GROUP BY date) as Inc
LEFT OUTER JOIN
(
SELECT date, SUM(price) as TotalExp
FROM Expense
WHERE isdel = 0
GROUP BY date) as Exp
ON Inc.date = Exp.date
UNION
SELECT Exp.date, TotalInc, TotalExp, TotalInc - TotalExp AS ProfitNLoss
FROM
(
SELECT date, SUM(price) as TotalInc
FROM Income
WHERE isdel = 0
GROUP BY date) as Inc
RIGHT OUTER JOIN
(
SELECT date, SUM(price) as TotalExp
FROM Expense
WHERE isdel = 0
GROUP BY date) as Exp
ON Inc.date = Exp.date
Argh, this is "give me the codes" question, but I'll give you few hints:
mySQL GROUP BY tutorial
mySQL GRUOP BY aggregate functions
mySQL subqueries
So what you need to do:
Create list of all unique dates (unfortunately I haven't find a way to generate list of dates within sql without table query or without long php array, if anyone knows a way to do this I'll be glad to add it to anser), so you will have to do this:
SELECT date FROM income GROUP BY date;
-- If there's a chance that you'll have record in expenses and not in income:
(SELECT date FROM income GROUP BY date)
UNION (SELECT date FROM expenses GROUP BY date;)
GROUP BY date;
-- Or create table containing just list of all dates (best option)
And than join all unique dates with expenses and income:
SELECT SUM(expenses.price) AS expenses, SUM(incomes.price) AS income, dates.date
FROM unique_dates -- or from your subquery
LEFT JOIN incomes ON incomes.date = unique_dates.date
LEFT JOIN expenses ON expenses.date = unique_dates.date
GROUP BY unique_dates.date
Oh and I have no idea what does profile n loss mean.
Thanks to Pheiberg and appreciated for your prof reply.
I just did some little change so it will return 0 when table has no record or return NULL, So that Profit calculation still able to cal even given -ve value. Here the code share to u guys.
note: im using MySQL
SELECT Inc.create_dt, IFNULL(TotalInc,0), IFNULL(TotalExp,0), IFNULL(TotalInc,0) - IFNULL(TotalExp,0) AS ProfitNLoss
FROM
(
SELECT create_dt, SUM(price) as TotalInc
FROM income
WHERE IsDel= 0
GROUP BY create_dt) as Inc
LEFT OUTER JOIN
(
SELECT create_dt, SUM(price) as TotalExp
FROM expenses
WHERE IsDel= 0
GROUP BY create_dt) as Exp
ON Inc.create_dt= Exp.create_dt
UNION
SELECT Exp.create_dt, IFNULL(TotalInc,0), IFNULL(TotalExp,0), IFNULL(TotalInc,0) - IFNULL(TotalExp,0) AS ProfitNLoss
FROM
(
SELECT create_dt, SUM(price) as TotalInc
FROM income
WHERE IsDel= 0
GROUP BY create_dt) as Inc
RIGHT OUTER JOIN
(
SELECT create_dt, SUM(price) as TotalExp
FROM expenses
WHERE IsDel= 0
GROUP BY create_dt) as Exp
ON Inc.create_dt= Exp.create_dt
Related
I'm having great difficulty writing this query and cannot find any answers online which could be applied to my problem.
I have a couple of tables which looks similar to the below with. Each purchase date corresponds with an item purchased.
Cust_ID
Purchase_Date
123
08/01/2022
123
08/20/2022
123
09/05/2022
123
10/08/2022
123
12/25/2022
123
01/26/2023
The result I am looking for should contain the customers ID, a range of the purchases, the number of consecutive months they had made a purchase (regardless of which day they purchased), and a count of how many purchases they had made in the time frame. The result should look something like the below for my example.
Cust_ID
Min Purchase Date
Max Purchase Date
Consecutive Months
No. Items Purchased
123
08/01/2022
10/08/2022
3
4
123
12/25/2022
01/26/2023
2
2
I have tried using CTEs with querys similar to
WITH CTE as
(
SELECT
PaymentDate PD,
CustomerID CustID,
DATEADD(m, -ROW_NUMBER() OVER (PARTITION BY c.CustomerID ORDER BY
DATEPART(m,PaymentDate)), PaymentDate) as TempCol1,
FROM customers as c
LEFT JOIN payments as p on c.customerid = p.customerid
GROUP BY c.CustomerID, p.PaymentDate
)
SELECT
CustID,
MIN(PD) AS MinPaymentDate,
MAX(PD) AS MaxPaymentDate,
COUNT(*) as ConsecutiveMonths,
FROM CTE
GROUP BY CustID, TempCol1
However, the above failed to properly count consecutive months. When the payment dates matched a month apart (e.g. 1/1/22 - 2/1/22), the query properly counts the consecutive months. However, if the dates do not match from month to month (e.g. 1/5/22 - 2/15/22), the count breaks.
Any guidance/help would be much appreciated!
This is just a small enhancement on the answer already given by ahmed. If your date range for this query is more than a year, then year(M.Purchase_Date) + month(M.Purchase_Date) will be 2024 for both 2022-02-01 and 2023-01-01 as YEAR() and MONTH() both return integer values. This will return incorrect count of consecutive months. You can change this to use CONCAT() or FORMAT(). Also, the COUNT(*) for ItemsPurchased should be counting the right hand side of the join, as it is a LEFT JOIN.
WITH consecutive_months AS
(
SELECT *,
DATEADD(
month,
-DENSE_RANK() OVER (
PARTITION BY CustomerID
ORDER BY YEAR(PaymentDate), MONTH(PaymentDate)
),
PaymentDate
) AS grp_date
FROM payments
)
SELECT
C.CustomerID AS CustID,
MIN(M.PaymentDate) AS MinPaymentDate,
MAX(M.PaymentDate) AS MaxPaymentDate,
COUNT(DISTINCT FORMAT(M.PaymentDate, 'yyyyMM')) AS ConsecutiveMonths,
COUNT(M.CustomerID) AS ItemsPurchased
FROM customers C
LEFT JOIN consecutive_months M
ON C.CustomerID = M.CustomerID
GROUP BY C.CustomerID, YEAR(M.grp_date), MONTH(M.grp_date)
Here's a db<>fiddle
You need to use the dense_rank function instead of the row_number, this will give the same rank for the same months and avoid breaking the grouping column. Also, you need to aggregate for 'year-month' of the grouping date column.
with consecutive_months as
(
select *,
Purchase_Date - interval
dense_rank() over (partition by Cust_ID order by year(Purchase_Date), month(Purchase_Date))
month as grp_date
from payments
)
select C.Cust_ID,
min(M.Purchase_Date) as MinPurchaseDate,
max(M.Purchase_Date) as MaxPurchaseDate,
count(distinct year(M.Purchase_Date), month(M.Purchase_Date)) as ConsecutiveMonthsNo,
count(M.Cust_ID) as ItemsPurchased
from customers C left join consecutive_months M
on C.Cust_ID = M.Cust_ID
group by C.Cust_ID, year(M.grp_date), month(M.grp_date)
See demo on MySQL
You tagged your question with MySQL, while it seems that you posted an SQL Server query syntax, for SQL Server just use dateadd(month, -dense_rank() over (partition by Cust_ID order by year(Purchase_Date), month(Purchase_Date)), Purchase_Date).
See demo on SQL Server.
I have 3 different tables called that tracks the number of pens sold, pencils sold and ink sold.
**pens table:**
date who_bought
12.03.2020 a
12.03.2020 d
13.03.2020 b
14.03.2020 c
**pencils table:**
date who_bought
12.03.2020 z
16.03.2020 r
17.03.2020 j
17.03.2020 k
**ink table:**
date who_bought
11.03.2020 h
11.03.2020 j
13.03.2020 z
17.03.2020 r
I want to aggregate data and get for each day, how many pens, inks and pencils I sold. The "who_bought" column is not relevant (I mean the values). I just want to count the number of records for each day. finally, I want to sort by date.
In the above case, I would like to get results like
11.03.2020 pens:0 pencils:0 ink: 2
12.03.2020 pens:2 pencils:1 ink: 0
13.03.2020 pens:1 pencils:0 ink:1
14.03.2020 pens:0 pencils:0 ink:0
16.03.2020 pens:0 pencils:1 ink:0
17.03.2020 pens:0 pencils:2 ink:1
How can this be achieved?
I tried something like this, but it's not working:
select
COUNT(pens.*) as pens,
COUNT(pencils.*) as pencils,
COUNT(ink.*) as ink,
DATE(date) as date
from
pens
full join pencils on pencils.date=pens.date
full join ink on ink.date=pens.date
group by
date
order by
date asc
Your attempt using full join is on the right track; unfortunately, MySQL does not support this syntax.
You could do this with union all and conditional aggregation:
select
date,
sum(what = 'pens') no_pens,
sum(what = 'pencils') no_pencils,
sum(what = 'ink') no_inks
from (
select 'pens' what, date from pens
union all select 'pencils', date from pencils
union all select 'ink', date from ink
) t
group by date
If you want all dates, including those for which no sale happened for any product, then it is a bit different. Basically, ou need a calendar table for this. Here is one way to do it with a recursive query (available in MySQL 8.0 only).
with dates as (
select min(date) date, max(date) max_date
from (
select date from pens
union all select date from pencils
union all select date from ink
) t
union all
select date + interval 1 day from cte where date < max_date
)
select
d.date,
pn.no_pens,
pl.no_pencils,
ik.no_inks
from dates d
left join (select date, count(*) no_pens from pens group by date) pn on pn.date = d.date
left join (select date, count(*) no_pencils from pencils group by date) pl on pl.date = d.date
left join (select date, count(*) no_inks from inks group by date) ik on ik.date = d.date
I have to ask my question based on the following example.
mysql table
step 1: get sales per day
SELECT SUM(amount) as sold, productnumber, DATE(date) AS date
FROM foo
GROUP BY productnumber, DATE(date)
ORDER BY date;
returns:
step 2 / wanted: get sales and latest stock per day
Latest stock = last stock update per day, e.g. for productnumber 123 it's 7 on 2017-12-06:
So how to select this?
Clearly, just adding stock to the SELECT won't work:
SELECT stock, SUM(amount) as sold, productnumber, DATE(date) AS date
FROM foo
GROUP BY productnumber, DATE(date)
ORDER BY date;
It would return 0 instead of 7 on this product and on this day:
LEFT JOIN? Subquery?
How to write this SQL properly?
Thanks in advance!
The canonical way is a join:
SELECT f.*, f2.stock
FROM foo f JOIN
(SELECT f.productnumber, SUM(f.amount) as sold, DATE(f.date) as date,
MAX(f.date) as maxdate
FROM foo f
GROUP BY f.productnumber, DATE(f.date)
) f join
foo f2
ON f2.productnumber = f.productnumber AND f2.date = f.maxdate
ORDER BY date;
I have a table with prices and dates on product:
id
product
price
date
I create a new record when price change. And I have a table like this:
id product price date
1 1 10 2014-01-01
2 1 20 2014-02-17
3 1 5 2014-03-28
4 2 25 2014-01-05
5 2 12 2014-02-08
6 2 30 2014-03-12
I want to get last price for all products. But when I group with "product", I can't get a price from a row with maximum date.
I can use MAX(), MIN() or COUNT() function in request, but I need a result based on other value.
I want something like this in final:
product price date
1 5 2014-03-28
2 30 2014-03-12
But I don't know how. May be like this:
SELECT product, {price with max date}, {max date}
FROM table
GROUP BY product
Alternatively, you can have subquery to get the latest get for every product and join the result on the table itself to get the other columns.
SELECT a.*
FROM tableName a
INNER JOIN
(
SELECT product, MAX(date) mxdate
FROM tableName
GROUP BY product
) b ON a.product = b.product
AND a.date = b.mxdate
I think the easiest way is a substring_index()/group_concat() trick:
SELECT product,
substring_index(group_concat(price order by date desc), ',', 1) as PriceOnMaxDate
max(date)
FROM table
GROUP BY product;
Another way, that might be more efficient than a group by is:
select p.*
from table t
where not exists (select 1
from table t2
where t2.product = t.product and
t2.date > t.date
);
This says: "Get me all rows from the table where the same product does not have a larger date." That is a fancy way of saying "get me the row with the maximum date for each product."
Note that there is a subtle difference: the second form will return all rows that on the maximum date, if there are duplicates.
Also, for performance an index on table(product, date) is recommended.
You can use a subquery that groups by product and return the maximum date for every product, and join this subquery back to the products table:
SELECT
p.product,
p.price,
p.date
FROM
products p INNER JOIN (
SELECT
product,
MAX(date) AS max_date
FROM
products
GROUP BY
product) m
ON p.product = m.product AND p.date = m.max_date
SELECT
product,
price,
date
FROM
(SELECT
product,
price,
date
FROM table_name ORDER BY date DESC) AS t1
GROUP BY product;
SELECT user_id, currency, SUM( price ) AS totalPrice, created
FROM accounts WHERE user_id IN ( SELECT users.id FROM users
WHERE country_id ='$con_id' )
AND created BETWEEN '2013-02-01' AND '2013-02-28' GROUP BY currency
This query give me result like
Curreny totalPrice
SEK 10
USD 10
I want to multiply only SEK from 7. I have only USD rate in the database, so i want to multiply it in the query with 7 to get SEK rate. How can I do that in this query?
Your question is a little bit confusing. If I understand your question correctly, you want to multiply the value of total value by 7 if the currency is equal to SEK.
SELECT user_id,
currency,
SUM(price) * IF(currency = 'SEK', 7, 1) totalPrice,
created
FROM accounts
WHERE user_id
IN
(
SELECT users.id
FROM users
WHERE country_id = '$con_id'
) AND
created BETWEEN '2013-02-01' AND '2013-02-28'
GROUP BY currency
I would store all conversion rates in a table. For SEK you store 7.0 als conversion rate and vor USD 1.0. Simply multiplie the totalPrice with the conversion rate (with sub query)
Here is an ugly way to implement it:
SELECT user_id, currency,
CASE WHEN currency = 'SEK' THEN SUM(price)*7
ELSE SUM(price) END totalPrice, created
FROM accounts
WHERE user_id
IN ( SELECT users.id FROM users
WHERE country_id = '$con_id')
AND created BETWEEN '2013-02-01' AND '2013-02-28'
GROUP BY currency