How can I calculate daily snapshots of my total sales on SQL? - sql

I have a table (let's call it DiodeSales) that tells me the total number of diode sales I made, grouped by date, diode color, and country. This is a sample of this schema:
Date Color Country Sales
June, 20 2016 00:00:00 Green US 1
June, 20 2016 00:00:00 Red Japan 1
June, 20 2016 00:00:00 Red US 1
June, 21 2016 00:00:00 Red US 1
June, 22 2016 00:00:00 Green US 1
June, 22 2016 00:00:00 Red US 1
June, 23 2016 00:00:00 Green US 1
June, 23 2016 00:00:00 Red Japan 1
June, 23 2016 00:00:00 Red US 1
June, 24 2016 00:00:00 Green US 1
June, 24 2016 00:00:00 Red US 1
I want to be able to have have an additional column that tells me how many diodes we've sold up until that point. So, for example, using the above data, the {June 23, Red, 1, US} row would have a total sales value of 4, because we've sold 4 red diodes in the US at that point.
I initially thought a cumulative sum would do the trick. So I wrote this: (sqlfiddle here)
SELECT
t1.Date,
t1.Color,
t1.Country,
t1.Sales,
SUM(t2.Sales) AS CumulativeSales
FROM DiodeSales AS t1
INNER JOIN DiodeSales AS t2
ON t1.Date >= t2.Date
AND t1.Color = t2.Color
AND t1.Country = t2.Country
GROUP BY
t1.Date,
t1.Color,
t1.Country
This gives me the cumulative sum, as expected, but it does not give me the total sales for a given color in a given country on a given day. In particular, because some specific days may have 0 sales in some country, they will not have a cumulative value associated to it. For example, consider the results of the previous table:
Date Color Country Sales CumulativeSales
June, 20 2016 00:00:00 Green US 1 1
June, 20 2016 00:00:00 Red Japan 1 1
June, 20 2016 00:00:00 Red US 1 1
June, 21 2016 00:00:00 Red US 1 2
June, 22 2016 00:00:00 Green US 1 2
June, 22 2016 00:00:00 Red US 1 3
June, 23 2016 00:00:00 Green US 1 3
June, 23 2016 00:00:00 Red Japan 1 2
June, 23 2016 00:00:00 Red US 1 4
June, 24 2016 00:00:00 Green US 1 4
June, 24 2016 00:00:00 Red US 1 5
If I were to look for the column corresponding to Japan on June 24, I'd find nothing (because there was no Japan sale that day, so there is no Japan row for that day). I don't think there's a way to do this in SQL, but is it possible to populate this resulting table with values on days in which some countries had no sales? The starting table will always have at least one column for each day for some country.
I am aware I could just write a simple
SELECT SUM(Sales) FROM DiodeSales
WHERE Date &lt= #someDate AND Color = #someColor AND Country = #someCountry
to get this information, but this is for a table that has to be formatted in that way for it to be used by another piece of already-made software.
EDIT: Someone mentioned this as a potential duplicate of Calculate a Running Total in SQL Server, but that post only addresses efficiency while calculating a running sum. I already have various ways of calculating this sum, but I'm looking for a way to fix the issue of missing day/country combinations for days when there were no sales in that country. For the above example, the fixed query would return this:
Date Color Country Sales CumulativeSales
June, 20 2016 00:00:00 Green US 1 1
June, 20 2016 00:00:00 Red Japan 1 1
June, 20 2016 00:00:00 Red US 1 1
June, 21 2016 00:00:00 Green US 0 1
June, 21 2016 00:00:00 Red Japan 0 1
June, 21 2016 00:00:00 Red US 1 2
June, 22 2016 00:00:00 Green US 1 2
June, 22 2016 00:00:00 Red Japan 0 1
June, 22 2016 00:00:00 Red US 1 3
June, 23 2016 00:00:00 Green US 1 3
June, 23 2016 00:00:00 Red Japan 1 2
June, 23 2016 00:00:00 Red US 1 4
June, 24 2016 00:00:00 Green US 1 4
June, 24 2016 00:00:00 Red Japan 0 2
June, 24 2016 00:00:00 Red US 1 5

Try this:
SELECT [Date], Color, Country, Sales,
SUM(Sales) OVER(PARTITION BY Color, Country ORDER BY [Date] rows unbounded preceding) as RunningTotal
FROM YourTable
ORDER BY [Date], Color
It produces the output as expected.
[EDIT]
If you're looking for solution for missing dates, countries and colors, try this (replace #tmp with the name of your table):
SELECT A.[Date], A.Color, A.Country, COALESCE(B.Sales, 0) AS Sales
, SUM(COALESCE(B.Sales, 0)) OVER(PARTITION BY A.Color, A.Country ORDER BY A.[Date] rows unbounded preceding) as RunningTotal
FROM (
SELECT [Date], Color, Country
FROM (SELECT DISTINCT [Date] FROM #tmp) AS q1 CROSS JOIN
(SELECT DISTINCT Color FROM #tmp) AS q2 CROSS JOIN
(SELECT DISTINCT Country FROM #tmp) AS q3
) AS A
LEFT JOIN #tmp AS B ON A.[Date] = B.[Date] AND A.Color= B.Color AND A.Country = B.Country
ORDER BY A.[Date], A.Color
Above query produces:
Date Color Country Sales RunningTotal
2016-06-20 Green Japan 0 0
2016-06-20 Green US 1 1
2016-06-20 Red Japan 1 1
2016-06-20 Red US 1 1
2016-06-21 Green US 0 1
2016-06-21 Green Japan 0 0
2016-06-21 Red US 1 2
2016-06-21 Red Japan 0 1
2016-06-22 Green Japan 0 0
2016-06-22 Green US 1 2
2016-06-22 Red Japan 0 1
2016-06-22 Red US 1 3
2016-06-23 Green US 1 3
2016-06-23 Green Japan 0 0
2016-06-23 Red US 1 4
2016-06-23 Red Japan 1 2
2016-06-24 Green Japan 0 0
2016-06-24 Green US 1 4
2016-06-24 Red Japan 0 2
2016-06-24 Red US 1 5

I think you should use left join instead of inner join
SELECT
t.Date,
t.Color,
t.Country,
t.CumulativeSales
from DiodeSales t
left join
(SELECT
t1.Date,
t1.Color,
t1.Country,
t1.Sales,
SUM(t2.Sales) AS CumulativeSales
FROM DiodeSales AS t1
GROUP BY
t1.Date,
t1.Color,
t1.Country) t2
on
t.Date=t2.date
and t.Color=t2.color
and t.Country=t2.country

Try this
Select distinct Date into SalesDate From DiodeSales
SELECT S.Date,t.Color,t.Country,t.CumulativeSales
from DiodeSales t left join
(SELECt t1.Date,t1.Color,t1.Country,t1.Sales,
SUM(t2.Sales) AS CumulativeSales FROM DiodeSales AS t1
GROUP BY
t1.Date,
t1.Color,
t1.Country) t2 on
S.Date=t2.date
and t.Color=t2.color
and t.Country=t2.country
join
SalesDate S
on t.date=S.date

Related

Include "0" results in COUNT(*) aggregate

Good morning, I've searched in the forum one doubt that I have but the results that I've seen didn't give me a solution.
I have two tables.
CARS:
Id Model
1 Seat
2 Audi
3 Mercedes
4 Ford
BREAKDOWNS:
IdBd Description Date Price IdCar
1 Engine 01/01/2020 500 € 3
2 Battery 05/01/2020 0 € 1
3 Wheel's change 10/02/2020 110,25 € 4
4 Electronic system 15/03/2020 100 € 2
5 Brake failure 20/05/2020 0 € 4
6 Engine 25/05/2020 400 € 1
I wanna make a query that shows the number of breakdowns by month with 0€ of cost.
I have this query:
SELECT Year(breakdowns.[Date]) AS YEAR, StrConv(MonthName(Month(breakdowns.[Date])),3) AS MONTH, Count(*) AS [BREAKDOWNS]
FROM cars LEFT JOIN breakdowns ON (cars.Id = breakdowns.IdCar AND breakdowns.[Price]=0)
GROUP BY breakdowns.[Price], Year(breakdowns.[Date]), Month(breakdowns.[Date]), MonthName(Month(breakdowns.[Date]))
HAVING ((Year([breakdowns].[Date]))=[Insert a year:])
ORDER BY Year(breakdowns.[Date]), Month(breakdowns.[Date]);
And the result is (if I put year '2020'):
YEAR MONTH BREAKDOWNS
2020 January 1
2020 May 1
And I want:
YEAR MONTH BREAKDOWNS
2020 January 1
2020 February 0
2020 March 0
2020 May 1
Thanks!
The HAVING condition should be in WHERE (otherwise it changes the Outer to an Inner join). But as long as you don't use columns from cars there's no need to join it.
To get rows for months without a zero price you should switch to conditional aggregation (Access doesn't support Standard SQL CASE, but IIF?).
SELECT Year(breakdowns.[Date]) AS YEAR,
StrConv(MonthName(Month(breakdowns.[Date])),3) AS MONTH,
SUM(CASE WHEN breakdowns.[Price]=0 THEN 1 ELSE 0 END) AS [BREAKDOWNS]
FROM breakdowns
JOIN cars
ON (cars.Id = breakdowns.IdCar)
WHERE ((Year([breakdowns].[Date]))=[Insert a year:])
GROUP BY breakdowns.[Price], Year(breakdowns.[Date]), Month(breakdowns.[Date]), MonthName(Month(breakdowns.[Date]))
ORDER BY Year(breakdowns.[Date]), Month(breakdowns.[Date]

SQL Server running balance with Partition by month

I have the following scenario where a user has some allowance taken every month up to a yearly capping.
I have successfully implemented this as shown here
I have stumbled into a problem for if the user gets promoted during the year the yearly capping needs to be ratified accordingly.
The following query gave these results. (Using sql server 2012)
SELECT *,
RemainingBalance = AnnualCapping - Sum(amount)
OVER (
partition BY userid, year, annualcapping
ORDER BY userid, year, month)
FROM exampleTx
WHERE userid = 1
AND year = 2015
data
userId year month monthname name surname annualCapping amount RemainingBalance
1 2015 1 January Joe Black 500,00 40,00 460,00
1 2015 2 February Joe Black 500,00 40,00 420,00
1 2015 3 March Joe Black 500,00 40,00 380,00
1 2015 4 April Joe Black 500,00 40,00 340,00
1 2015 5 May Joe Black 500,00 40,00 300,00
1 2015 6 June Joe Black 500,00 40,00 260,00
1 2015 7 July Joe Black 500,00 40,00 220,00
1 2015 8 August Joe Black 500,00 40,00 180,00
1 2015 9 September Joe Black 1000,00 40,00 **960,00**
1 2015 10 October Joe Black 1000,00 40,00 **920,00**
1 2015 11 November Joe Black 1000,00 40,00 **880,00**
1 2015 12 December Joe Black 1000,00 40,00 **840,00**
In September the monthly allowance should have been proportional to remaining of year.
4 months = 1000 * 4/12 = 333.33
and remaining balance of 293.33, 253.33, 213.33,173.33.
Could I achieve this without modifying the annual capping field. ie.e would have been simpler if annual capping was reduced to 333.33 but this is the data I have.
A change in capping of previous month would indicate a promotion has taken place. It can occur during any month. Hence the new capping should be proportional.
You could use following query
Select *,
RemainingBalance = AnnualCapping - SUM(amount) OVER (
partition by userid ,year ORDER BY userid, year,month)
from
exampleTx
where userid = 1 and year = 2015
Remove the Annual Capping from the Partition by clause.

Merging old table with new table with different structure

I am using SQL Server 2012. I have two tables which I need to 'merge'. The two tables are called tblOld and tblNew.
tblOld has data from say 2012 to 2013
tblNew has data from 2013 onwards and has a different structure
The dates do not overlap between the tables.
Simple example of the tables:
Old table
t_date region sub_region sales
------------------------------------------
1 Jan 2012 US QR 2
1 Jan 2012 US NT 3
1 Jan 2012 EU QR 5
2 Jan 2012 US QR 4
2 Jan 2012 US NT 6
2 Jan 2012 EU QR 10
...
31 Dec 2013 US QR 8
31 Dec 2013 US NT 9
31 Dec 2013 EU QR 15
New table
t_date region sales
-----------------------------
1 Jan 2014 US 20
1 Jan 2014 EU 50
2 Jan 2014 US 40
2 Jan 2014 EU 100
...
31 Dec 2014 US 80
31 Dec 2014 EU 150
Result I'm looking for:
t_date US QR US NT EU
-------------------------------------
1 Jan 2012 2 3 5
2 Jan 2012 4 6 10
...
31 Dec 2013 8 9 15
1 Jan 2014 20 50
2 Jan 2014 40 100
...
31 Dec 2014 80 150
So I'm trying to create a query which will give me the results above although I'm not sure how to do this or if it can be done?
SELECT t_date,
SUM(CASE WHEN region='US' AND (sub_region='QR' OR sub_region IS NULL) THEN sales ELSE 0 END) 'US QR',
SUM(CASE WHEN region='US' AND sub_region='NT' THEN sales ELSE 0 END) 'US NT',
SUM(CASE WHEN region='EU' THEN sales ELSE 0 END) 'EU'
FROM (
SELECT t_date
,region
,sub_region
,sales
FROM tblOLD
UNION ALL
SELECT t_date
,region
,NULL
,sales
FROM tblNEW
) t
GROUP BY t_date
You are looking for a UNION of the two tables:
SELECT t_date
,region
,sales
,sub_region
FROM tblOLD
UNION ALL
SELECT t_date
,region
,NULL
,sales
FROM tblNEW

return the last row that meets a condition in sql

I have two tables:
Meter
ID SerialNumber
=======================
1 ABC1
2 ABC2
3 ABC3
4 ABC4
5 ABC5
6 ABC6
RegisterLevelInformation
ID MeterID ReadValue Consumption PreviousReadDate ReadType
============================================================================
1 1 250 250 1 jan 2015 EST
2 1 550 300 1 feb 2015 ACT
3 1 1000 450 1 apr 2015 EST
4 2 350 350 1 jan 2015 EST
5 2 850 500 1 feb 2015 ACT
6 2 1000 150 1 apr 2015 ACT
7 3 1500 1500 1 jan 2015 EST
8 3 2500 1000 1 mar 2015 EST
9 3 5000 2500 4 apr 2015 EST
10 4 250 250 1 jan 2015 EST
11 4 550 300 1 feb 2015 ACT
12 4 1000 450 1 apr 2015 EST
13 5 350 350 1 jan 2015 ACT
14 5 850 500 1 feb 2015 ACT
15 5 1000 150 1 apr 2015 ACT
16 6 1500 1500 1 jan 2015 EST
17 6 2500 1000 1 mar 2015 EST
18 6 5000 2500 4 apr 2015 EST
I am trying to group by meter serial and return the last actual read date for each of the meters but I am unsure as to how to accomplish this. Here is the sql I have thus far:
select a.SerialNumber, ReadTypeCode, MAX(PreviousReadDate) from Meter as a
left join RegisterLevelInformation as b on a.MeterID = b.MeterID
where ReadType = 'ACT'
group by a.SerialNumber,b.ReadTypeCode, PreviousReadDate
order by a.SerialNumber
I can't seem to get the MAX function to take effect in returning only the latest actual reading row and it returns all dates and the same meter serial is displayed several times.
If I use the following sql:
select a.SerialNumber, count(*) from Meter as a
left join RegisterLevelInformation as b on a.MeterID = b.MeterID
group by a.SerialNumber
order by a.SerialNumber
then each serial is shown only once. Any help would be greatly appreciated.
Like #PaulGriffin said in his comment you need to remove PreviousReadDate column from your GROUP BY clause.
Why are you experiencing this behaviour?
Basically the partition you have chosen - (SerialNumber,ReadTypeCode,PreviousReadDate) for each distinct pair of those values prints you SerialNumber, ReadTypeCode, MAX(PreviousReadDate). Since you are applying a MAX() function to each row of the partition that includes this column you are simply using an aggregate function on one value - so the output of MAX() will be equal to the one without it.
What you wanted to achieve
Get MAX value of PreviousReadDate for every pair of (SerialNumber,ReadTypeCode). So this is what your GROUP BY clause should include.
select a.SerialNumber, ReadTypeCode, MAX(PreviousReadDate) from Meter as a
left join RegisterLevelInformation as b on a.MeterID = b.MeterID
where ReadType = 'ACT'
group by a.SerialNumber,b.ReadTypeCode
order by a.SerialNumber
Is the correct SQL query for what you want.
Difference example
ID MeterID ReadValue Consumption PreviousReadDate ReadType
============================================================================
1 1 250 250 1 jan 2015 EST
2 1 550 300 1 feb 2015 ACT
3 1 1000 450 1 apr 2015 EST
Here if you apply the query with grouping by 3 columns you would get result:
SerialNumber | ReadTypeCode | PreviousReadDate
ABC1 | EST | 1 jan 2015 -- which is MAX of 1 value (1 jan 2015)
ABC1 | ACT | 1 feb 2015
ABC1 | EST | 1 apr 2015
But instead when you only group by SerialNumber,ReadTypeCode it would yield result (considering the sample data that I posted):
SerialNumber | ReadTypeCode | PreviousReadDate
ABC1 | EST | 1 apr 2015 -- which is MAX of 2 values (1 jan 2015, 1 apr 2015)
ABC1 | ACT | 1 feb 2015 -- which is MAX of 1 value (because ReadTypeCode is different from the row above
Explanation of your second query
In this query - you are right indeed - each serial is shown only once.
select a.SerialNumber, count(*) from Meter as a
left join RegisterLevelInformation as b on a.MeterID = b.MeterID
group by a.SerialNumber
order by a.SerialNumber
But this query would produce you odd results you don't expect if you add grouping by more columns (which you have done in your first query - try it yourself).
You need to remove PreviousReadDate from your Group By clause.
This is what your query should look like:
select a.SerialNumber, ReadTypeCode, MAX(PreviousReadDate) from Meter as a
left join RegisterLevelInformation as b on a.MeterID = b.MeterID
where ReadType = 'ACT'
group by a.SerialNumber,b.ReadTypeCode
order by a.SerialNumber
To understand how the group by clause works when you mention multiple columns, follow this link: Using group by on multiple columns
You will understand what was wrong with your query and why it returns all dates and the same meter serial is displayed several times.
Good luck!
Kudos! :)

SQL Select rows based on date

I have a two tables (Table1 and Table2):
Table1
Date Name Other
2014-02-08 Alex 1
2014-06-15 Bob 1
Table2
Date Name Count
2014-02-07 Alex 1
2014-01-31 Alex 2
2014-02-09 Alex 4
2014-02-08 Alex 10
2014-02-10 Alex 0
2014-02-01 Alex 4
2014-01-08 Alex 5
2014-03-08 Alex 4
2014-06-01 Bob 22
2014-06-02 Bob 0
2014-06-10 Bob 9
2014-06-15 Bob 3
2014-06-16 Bob 3
2014-06-20 Bob 5
2014-06-14 Bob 18
2014-07-11 Bob 1
2014-08-15 Bob 2
I am having a difficult time constructing a query that accomplishes the following:
From Table1, run through each "Date" and "Name"
For a given "Date" and "Name" in Table1, go through Table2 and grab all rows that also have the same "Name" and that have dates that are 10 days before the "Date" (in Table1) and 5 days after "Date" (in Table1).
So, for Table1, "Alex" on "2014-02-08", I want to grab all rows in Table2 that also say "Alex" but whose date is between "2014-01-29" (10 days before 2014-02-08) and "2014-02-13" (5 days after 2014-02-08).
For "Bob" on "2014-06-15", I want to grab all rows in Table2 that also say "Bob" but whose date is between "2014-06-05" (10 days before 2014-06-15) and "2014-06-20" (5 days after 2014-06-15).
The expected output is:
Date Name Count
2014-02-07 Alex 1
2014-01-31 Alex 2
2014-02-09 Alex 4
2014-02-08 Alex 10
2014-02-10 Alex 0
2014-02-01 Alex 4
2014-06-10 Bob 9
2014-06-15 Bob 3
2014-06-16 Bob 3
2014-06-20 Bob 5
2014-06-14 Bob 18
In my real work, the number of rows in Table1 is much larger and the number of days I'd like to grab before/after the reference date can vary.
I think you can do something like this:
select t2.*
from table1 t2
where exists (select 1
from table1 t1
where t1.name = t2.name and t1.date >= t2.date - 'interval 10 day' and
t1.date <= t2.date + 'interval 10 day'
);
See:
http://sqlfiddle.com/#!4/82e1e/10
Assuming Oracle, but you get the picture:-). You would need to format the date in the results. That is left for your perusal.
In case, the above link cannot be opened, the sql is:
select t2.*
from
table2 t2, table1 t1
where
t1.name = t2.name
and t2.date1 > t1.date1 -10
and t2.date1 <= t1.date1 +5;
The result is:
DATE1 NAME COUNT
February, 07 2014 00:00:00+0000 Alex 1
January, 31 2014 00:00:00+0000 Alex 2
February, 09 2014 00:00:00+0000 Alex 4
February, 08 2014 00:00:00+0000 Alex 10
February, 10 2014 00:00:00+0000 Alex 0
February, 01 2014 00:00:00+0000 Alex 4
June, 10 2014 00:00:00+0000 Bob 9
June, 15 2014 00:00:00+0000 Bob 3
June, 16 2014 00:00:00+0000 Bob 3
June, 20 2014 00:00:00+0000 Bob 5
June, 14 2014 00:00:00+0000 Bob 18