Copy prior month value and insert into new row - sql

Here is an example of the current table I have:
1) Table name: TotalSales
Name Year Month Sales
------ ---- ----- -----
Alfred 2011 1 100
What I want to do is create a table like this, add a new row(Prior month sales):
2) Table name: TotalSales
Name Year Month Sales Prior month sales
------ ---- ----- ----- -----------------
Alfred 2011 2 110 100
Not sure how to this, but this is what I have been working on:
SELECT Name, Year, Month, Sales, Sales as [Prior Month sales]
FROM TotalSales
WHERE
DATEPART(month, [Prior Month sales]) = DATEPART(month, DATEADD(month, -1, getdate()))
Thanks for any help

I believe this should work...you need to join to itself on name/prior month, but you have 2 test cases for prior month since year/month are stored separately.
select c.Name, c.Year, c.Month, c.Sales, p.Sales
from TotalSales c
left join TotalSales p
on c.Name = p.Name and (
(c.Month > 1 and c.Year = p.Year and c.Month = p.Month + 1)
or (c.Month = 1 and c.Year = p.Year + 1 and p.Month = 12))

To select the given data you need to join the table to itself:
SELECT
TS.name,
TS.year,
TS.month,
TS.sales,
COALESCE(TS2.sales, 0) AS prior_month_sales
FROM
TotalSales TS
LEFT OUTER JOIN TotalSales TS2 ON
TS2.name = TS.name AND
(
(TS2.year = TS.year AND TS2.month = TS.month - 1) OR
(TS.month = 1 AND TS2.month = 12 AND TS2.year = TS.year - 1)
)
The LEFT OUTER JOIN is an outer join in case they didn't have any sales the previous month (or this is their first month with the company).

Try something like this to just update the table with the values you want...
UPDATE TotalSales
SET PriorMonthSales =
(
SELECT TS.Sales
FROM TotalSales TS
WHERE
(TotalSales.Month = TS.Month + 1 AND TotalSales.Year = TS.Year)
OR
(TotalSales.Month = 1 AND TS.Month = 12 AND TS.Year = TotalSales.Year -1)
)

Related

How to combine 3 separate SQL statements into 1 set of results

I have a SQL query that I am running 3 times (3 changing only the date range) and want to combine the result into one table, instead of running 3 different queries and trying to join outside of SQL. I am trying to find amount of times something has occurred per day/month/year. I'm running this in SQL Server.
I have 2 tables; one has the date of the transaction, and the other has the information I need (first 3 characters of the InventoryNumber table) so I am having to join these tables. I then want to group by the first 3 characters of the inventory number, and add the count in the column.
The end goal is to have something that looks like this:
InvNum | DayCount | MonthCount | YearCount
abc | 2 | 10 | 40
def | 0 | 2 | 6
xyz | 0 | 0 | 2
Here is my query for the single day one. This works exactly like I want it to. But now, I want to add on there the counts for the Month, and then the counts for the year also. The only thing that would change between this query and the other 2 is the count column name, and then the date.:
SELECT
LEFT(LINEITEM.InventoryNumber, 3) AS InvNum,
COUNT(*) AS DailyCount
FROM
INVOICE
INNER JOIN
LINEITEM ON INVOICE.InvoiceID = LINEITEM.InvoiceID
WHERE
InventoryNumber IS NOT Null
AND InventoryNumber != 'Misc'
AND DateCreated > '5-20-2022'
GROUP BY
LEFT(LINEITEM.InventoryNumber, 3)
ORDER BY
InvNum ASC;
I have looked through some of the other questions similar to this, but their queries were much simpler and I was not able to replicate the same thing with my queries.
Any help is appreciated.
Without knowing enough here, you should consider a conditional aggregation
Declare #D Date='2022-05-20';
SELECT InvNum = LEFT(LINEITEM.InventoryNumber, 3)
,DayCount = sum( case when DateCreated = #D then 1 else 0 end )
,MonthCount = sum( case when month(DateCreated) = month(#D) then 1 else 0 end )
,YearCount = sum( case when year(DateCreated) = year(#D) then 1 else 0 end )
FROM INVOICE
INNER JOIN LINEITEM ON INVOICE.InvoiceID = LINEITEM.InvoiceID
WHERE InventoryNumber IS NOT Null
AND InventoryNumber <> 'Misc'
AND DateCreated >= format(#D,'yyyy-01-01')
GROUP BY LEFT(LINEITEM.InventoryNumber, 3)
ORDER BY InvNum ASC;
Looks like you need conditional aggregation with three different start dates.
DECLARE #D date = '2022-05-20';
DECLARE #M date = '2022-04-21';
DECLARE #Y date = '2021-05-21';
SELECT InvNum = LEFT(li.InventoryNumber, 3)
,DayCount = COUNT(CASE WHEN i.DateCreated >= #D THEN 1 END)
,MonthCount = COUNT(CASE WHEN i.DateCreated >= #M THEN 1 END)
,YearCount = COUNT(*)
FROM INVOICE i
INNER JOIN LINEITEM li ON i.InvoiceID = li.InvoiceID
WHERE li.InventoryNumber <> 'Misc'
AND i.DateCreated >= #Y
GROUP BY
LEFT(li.InventoryNumber, 3)
ORDER BY
InvNum;
Note that <> 'Misc' also excludes nulls, and that ASC is the default.
You can also calculate those start dates dynamically
DECLARE #D date = DATEADD(day, -1, CAST(GETDATE() AS date));
DECLARE #M date = DATEADD(month, -1, CAST(GETDATE() AS date));
DECLARE #Y date = DATEADD(year, -1, CAST(GETDATE() AS date));
This maybe will do the work for you:
SELECT ISNULL(DailyQuery.InvNum, ISNULL(MonthlyQuery.InvNum, YearlyQuery.InvNum)) as InvNum,
ISNULL(DailyCount,0) as DailyCount,
ISNULL(MonthlyCount,0) as MonthlyCount,
ISNULL(YearlyCount,0) as YearlyCount
FROM
(SELECT
LEFT(LINEITEM.InventoryNumber, 3) AS InvNum,
COUNT(*) AS DailyCount
FROM
INVOICE
INNER JOIN
LINEITEM ON INVOICE.InvoiceID = LINEITEM.InvoiceID
WHERE InventoryNumber != 'Misc'
AND DateCreated > DATEADD(day, DATEDIFF(day, 0, GETDATE()-1), 0) --first hour day before
GROUP BY
LEFT(LINEITEM.InventoryNumber, 3)
) DailyQuery
FULL JOIN
(SELECT
LEFT(LINEITEM.InventoryNumber, 3) AS InvNum,
COUNT(*) AS MonthlyCount
FROM
INVOICE
INNER JOIN
LINEITEM ON INVOICE.InvoiceID = LINEITEM.InvoiceID
WHERE InventoryNumber != 'Misc'
AND DateCreated > DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0) --first day of current month
GROUP BY
LEFT(LINEITEM.InventoryNumber, 3) ) MonthlyQuery ON DailyQuery.InvNum = MonthlyQuery.InvNum
FULL JOIN
(SELECT
LEFT(LINEITEM.InventoryNumber, 3) AS InvNum,
COUNT(*) AS YearlyCount
FROM
INVOICE
INNER JOIN
LINEITEM ON INVOICE.InvoiceID = LINEITEM.InvoiceID
WHERE InventoryNumber != 'Misc'
AND DateCreated > DATEADD(year, DATEDIFF(year, 0, GETDATE()), 0) --first day of current month
GROUP BY
LEFT(LINEITEM.InventoryNumber, 3) ) YearlyQuery ON MonthlyQuery.InvNum = YearlyQuery.InvNum
you need to write three queries each query results in these tables based on the column and date in the where clause
just change the column and date value in the where clause for each table
first query result
InvNum | DayCount
abc | 2
def | 0
xyz | 0
second query result:
InvNum | MonthCount
abc | 10
def | 2
xyz | 0
third query result:
InvNum | YearCount
abc | 40
def | 6
xyz | 2
and then join these three tables on InvNum column

Join table with dates list - include reference on NULLs

More brain freeze moments from me. I'm sure this will be an easy one.
I have two tables. One is a list of part usage by week. This is called TransactionsPerWeek and looks like this:
ItemPK xWeek xYear TotalQty
1234 2 2019 65
1234 4 2019 15
1234 5 2019 50
I also have a DateList table that has week numbers and years in it
xWeek xYear
1 2019
2 2019
3 2019
etc.
When I right join the two together on week and year I get
ItemPK xWeek xYear TotalQty
NULL 1 2019 0
1234 2 2019 65
NULL 3 2019 0
1234 4 2019 15
1234 5 2019 50
What I need is to have the ItemPK on every line, even if the TotalQty is 0. So in effect, I need:
ItemPK xWeek xYear TotalQty
1234 1 2019 0
1234 2 2019 65
1234 3 2019 0
1234 4 2019 15
1234 5 2019 50
This is my code...
SELECT itemfk,
dates.year,
dates.week,
isnull(transactionsperweek.TotalQty,0) as TotalQty
from (
SELECT iit.ItemFK,
year(iit.transactiondate) xYear,
datepart(wk,iit.transactiondate) xWeek,
abs(sum(iit.quantity)) TotalQty
from iteminventorytransaction iit
INNER JOIN ItemInventoryTransactionType iitt on ItemInventoryTransactionTypePK = iit.ItemInventoryTransactionTypeFK
where iit.itemfk = 5311
and iit.ItemInventoryTransactionTypeFK in (10,8)
and iit.TransactionDate BETWEEN
-- 1 year up to the sunday of last week
DateAdd(wk,-51,DATEADD(day,-1 - (DATEPART(weekday, GETDATE()) + ##DATEFIRST - 2) % 7,GETDATE()))
AND DATEADD(day,-1 - (DATEPART(weekday, GETDATE()) + ##DATEFIRST - 2) % 7,GETDATE())
AND Quantity < 0
group by iit.itemfk,
year(iit.transactiondate),
datepart(wk,iit.transactiondate)
) transactionsPerWeek
RIGHT JOIN (
select year,
week
from DatesList
where date > DateAdd(wk,-51,DATEADD(day,-1 - (DATEPART(weekday, GETDATE()) + ##DATEFIRST - 2) % 7,GETDATE()))
AND date < DATEADD(day,-1 - (DATEPART(weekday, GETDATE()) + ##DATEFIRST - 2) % 7,GETDATE())
group by year,
week
) Dates ON dates.week = transactionsPerWeek.xWeek
AND dates.year = transactionsPerWeek.xYear
where week not in (52,53)
Hope this is clear enough. Thanks in advance.
You can use recursive cte :
with cte as (
select 1 as id, max(xWeek) as maxwk
from TransactionsPerWeek
union all
select id + 1, maxwk
from cte c
where c.id < maxwk
)
select coalesce(wk.ItemPK, wk1.ItemPK) as ItemPK, c.id as xWeek, wk.xYear, wk.TotalQty
from cte c left join
TransactionsPerWeek wk
on wk.xWeek = c.id outer apply
( select top (1) wk1.ItemPK
from TransactionsPerWeek wk1
where wk1.xWeek >= c.id and wk1.xWeek is not null
order by wk1.xWeek
) wk1;
Ok, so I did what #larnu suggested and cross joined the item with the dates, then left joined it to the transactionsperweek table and it worked. Thank you.
This is my code now;
SELECT itempk, week, year
, ISNULL(transactionsPerWeek.TotalQty,0) as TotalQty
from item
CROSS JOIN
(
select year, week from DatesList where date >
DateAdd(wk,-51,DATEADD(day,-1 - (DATEPART(weekday, GETDATE()) + ##DATEFIRST - 2) % 7,GETDATE()))
AND date <
DATEADD(day,-1 - (DATEPART(weekday, GETDATE()) + ##DATEFIRST - 2) % 7,GETDATE())
group by year, week
) dates
LEFT JOIN
(
SELECT iit.ItemFK, year(iit.transactiondate) xYear, datepart(wk,iit.transactiondate) xWeek, abs(sum(iit.quantity)) TotalQty from iteminventorytransaction iit
INNER JOIN ItemInventoryTransactionType iitt on ItemInventoryTransactionTypePK = iit.ItemInventoryTransactionTypeFK
where iit.itemfk = 5311 and iit.ItemInventoryTransactionTypeFK in (10,8)
and iit.TransactionDate BETWEEN
-- 1 year up to the sunday of last week
DateAdd(wk,-51,DATEADD(day,-1 - (DATEPART(weekday, GETDATE()) + ##DATEFIRST - 2) % 7,GETDATE()))
AND
DATEADD(day,-1 - (DATEPART(weekday, GETDATE()) + ##DATEFIRST - 2) % 7,GETDATE())
AND Quantity < 0
group by iit.itemfk, year(iit.transactiondate), datepart(wk,iit.transactiondate)
) transactionsPerWeek
ON itempk = transactionsperweek.ItemFK and transactionsPerWeek.xYear = dates.year and transactionsPerWeek.xWeek = dates.week
where itempk = 5311
Use a cross join to generate the rows and a left join to bring in the results you already have.
Your question explicitly states that you have two tables. Hence, I don't know what your SQL code is doing, because it is not referencing those tables. So, based on the description:
select i.ItemPK, d.xWeek, d.xYear,
coalesce(TotalQty, 0) as TotalQty
from (select distinct itemPK from TransactionsPerWeek
) i cross join
DateList d left join
TransactionsPerWeek t
on t.itemPK = i.itemPK and
t.xWeek = d.xWeek and
t.xYear = d.xYear;
Of course if the "tables" are really subqueries, then I would recommend using CTEs and still this basic query structure.

Grouping data with step down summation

I have a table with OrderDate,TotalAmount. I want to display month and TotalAmount of month with total amount of previous month to be added in next month.
e.g.
OrderDate TotalAmount
---------- -----------
13.01.1998--- 10
15.01.1998--- 11
01.02.1998--- 12
18.02.1998--- 10
12.03.1998--- 09
Output should be
Month TotalSum
------ --------
1--- 21
2--- 43
3--- 52
If your data would only be from a single calendar year, you could use
with g as
( select month(orderdate) as ordermonth,
sum( totalamount ) as sales
from orders
group by month(orderdate)
)
select m.ordermonth, sum(t.sales) as totalsales
from g as m
join g as t on m.ordermonth >= t.ordermonth
group by m.ordermonth
order by m.ordermonth
But if there is ANY chance that your data could have two years, then you need year in there as well, so construct your month to include year.
with g as
( select format(orderdate, 'yyyy-MM') as ordermonth,
sum( totalamount ) as sales
from orders
group by format(orderdate, 'yyyy-MM')
)
select m.ordermonth, sum(t.sales) as totalsales
from g as m
join g as t on m.ordermonth >= t.ordermonth
group by m.ordermonth
order by m.ordermonth

Count records that span multiple date range

ACCOUNT Amount DATE
1 50 01-2010
1 100 03-2010
1 100 02-2011
2 100 01-2011
2 50 05-2011
2 50 09-2011
3 100 03-2012
3 100 03-2013
Is there a query structure that will allow me to count distinct accounts that has spanned current and past year? For example, account 1 has amounts in 2011 and 2010 so it should be counted once under 2011. Account 2 only has amounts in 2011 so it doesn't get counted while account 3 has amounts in 2013 and 2012, so it gets counted as 1 under 2013:
2010 2011 2012 2013
0 1 0 1
First, you need to know the years where you have data for an account:
select account, year(date) as yr
from t
group by account, year(date)
Next, you need to see if two years are in sequence. You can do this in 2012 with lag/lead. Instead, we'll just use a self join:
with ay as (
select account, year(date) as yr
from t
group by account, year(date)
)
select ay.account, ay.yr
from ay join
ay ayprev
on ay.account = ayprev.account and
ay.yr = ayprev.yr + 1
Next, if you want to count the number of accounts by year, just put this into an aggregation:
with ay as (
select account, year(date) as yr
from t
group by account, year(date)
)
select yr, count(*) as numaccounts
from (select ay.account, ay.yr
from ay join
ay ayprev
on ay.account = ayprev.account and
ay.yr = ayprev.yr + 1
) ayy
group by yr
Assuming you have a record id (call this ID)
SELECT COUNT(*),Year FROM Table t3
INNER JOIN (
SELECT record_id, Year(t1.Date) as Year FROM Table t1
INNER JOIN Table t2
WHERE Year(t1.Date)-1=Year(t2.Date) AND t1.Account == t2.Account
) x ON x.record_id = t3.record_id
GROUP BY Year
Use Below Query :
SELECT YEAR(T1.Date) AS D, COUNT(*) AS C
FROM YourTable AS T1
INNER JOIN YourTable T2 ON T2.Account = T1.Account AND YEAR(T2)=YEAR(T1)+1
GROUP BY T1.Account, YEAR(T1.Date)

Sum a subquery and group by customer info

I have three tables something like the following:
Customer (CustomerID, AddressState)
Account (AccountID, CustomerID, OpenedDate)
Payment (AccountID, Amount)
The Payment table can contain multiple payments for an Account and a Customer can have multiple accounts.
What I would like to do is retrieve the total amount of all payments on a State by State and Month by Month basis. E.g.
Opened Date| State | Total
--------------------------
2009-01-01 | CA | 2,500
2009-01-01 | GA | 1,000
2009-01-01 | NY | 500
2009-02-01 | CA | 1,500
2009-02-01 | NY | 2,000
In other words, I'm trying to find out what States paid the most for each month. I'm only interested in the month of the OpenedDate but I get it as a date for processing afterwards. I was trying to retrieve all the data I needed in a single query.
I've been trying something along the lines of:
select
dateadd (month, datediff(month, 0, a.OpenedDate), 0) as 'Date',
c.AddressState as 'State',
(
select sum(x.Amount)
from (
select p.Amount
from Payment p
where p.AccountID = a.AccountID
) as x
)
from Account a
inner join Customer c on c.CustomerID = a.CustomerID
where ***
group by
dateadd(month, datediff(month, 0, a.OpenedDate), 0),
c.AddressState
The where clause includes some general stuff on the Account table. The query won't work because the a.AccountID is not included in the aggregate function.
Am I approaching this the right way? How can I retrieve the data I require in order to calculate which States' customers pay the most?
If you want the data grouped by month, you need to group by month:
SELECT AddressState, DATEPART(mm, OpenedDate), SUM(Amount)
FROM Customer c
INNER JOIN Account a ON a.CustomerID = c.CustomerID
INNER JOIN Payments p ON p.AccountID = a.AccountID
GROUP BY AddressState, DATEPART(mm, OpenedDate)
This shows you the monthnumber (1-12) and the total amount per state. Note that this example doesn't include years: all amounts of month 1 are summed regardless of year. Add a datepart(yy, OpenedDate) if you like.
In other words, I'm trying to find out what States paid the most for each month
This one will select the most profitable state for each month:
SELECT *
FROM (
SELECT yr, mon, AddressState, amt, ROW_NUMBER() OVER (PARTITION BY yr, mon, addressstate ORDER BY amt DESC) AS rn
FROM (
SELECT YEAR(OpenedDate) AS yr, MONTH(OpenedDate) AS mon, AddressState, SUM(Amount) AS amt
FROM Customer c
JOIN Account a
ON a.CustomerID = c.CustomerID
JOIN Payments p
ON p.AccountID = a.AccountID
GROUP BY
YEAR(OpenedDate), MONTH(OpenedDate), AddressState
)
) q
WHERE rn = 1
Replace the last condition with ORDER BY yr, mon, amt DESC to get the list of all states like in your resultset:
SELECT *
FROM (
SELECT yr, mon, AddressState, amt, ROW_NUMBER() OVER (PARTITION BY yr, mon, addressstate ORDER BY amt DESC) AS rn
FROM (
SELECT YEAR(OpenedDate) AS yr, MONTH(OpenedDate) AS mon, AddressState, SUM(Amount) AS amt
FROM Customer c
JOIN Account a
ON a.CustomerID = c.CustomerID
JOIN Payments p
ON p.AccountID = a.AccountID
GROUP BY
YEAR(OpenedDate), MONTH(OpenedDate), AddressState
)
) q
ORDER BY
yr, mon, amt DESC
select
AddressState,
year(OpenedDate) as Yr,
month(OpenedDate) as Mnth,
sum(Payment) as SumPayment
from Customer c
inner join Account a
on c.CustomerID=a.CustomerID
inner join Payment p
on a.AccountID=p.AccountID
group by AddressState, month(OpenedDate)