I need to group my dates as Quarters, April to June as Q1, Jul to Sep as Q2, Oct to Dec as Q3 and Jan to March as Q4
I need to add another column besides close_dates showing Quarters. I cannot find any date function i can use.
Any ideas on this.
You can extract the month part and use a case expression:
select
close_date,
case
when 0 + strftime('%m', close_date) between 1 and 3 then 'Q4'
when 0 + strftime('%m', close_date) between 4 and 6 then 'Q1'
when 0 + strftime('%m', close_date) between 7 and 9 then 'Q2'
when 0 + strftime('%m', close_date) between 10 and 12 then 'Q3'
end as quarter
from mytable
The addition of 0 is there to force the conversion of the result of strftime() to a number.
This could also be expressed using date artihmetics (which lets you generate the fiscal year too):
select
close_date,
strftime('%Y', close_date, '-3 months')
|| 'Q' || ((strftime('%m', close_date, '-3 months') - 1) / 4) as year_quarter
from mytable
The format of your dates is not YYYY-MM-DD which is the only valid date format for SQLite.
So if you want to extract the month of a date, any date function that SQLite supports will fail.
You must use the string function SUBSTR() to extract the month and then other functions like NULLIF() and COALESCE() to adjust the quarter to your requirement.
Assuming that the format of your dates is DD/MM/YYYY:
SELECT Close_Date,
'Q' || COALESCE(NULLIF((SUBSTR(Close_Date, 4, 2) - 1) / 3, 0), 4) AS Quarter
FROM tablename
If the format is MM/DD/YYYY then change SUBSTR(Close_Date, 4, 2) to SUBSTR(Close_Date, 1, 2) or just Close_Date because SQLite will implicitly convert the date to a number which will be the starting digits of the date.
See the demo.
Results:
> Close_Date | Quarter
> :--------- | :------
> 01/04/2019 | Q1
> 01/05/2019 | Q1
> 01/10/2019 | Q3
> 01/09/2019 | Q2
> 01/06/2019 | Q1
> 01/09/2019 | Q2
> 01/04/2019 | Q1
> 01/07/2019 | Q2
I would do it with arithmetic rather than a case expression:
select floor( (strftime('%m', close_date) + 2) / 3 ) as quarter
Related
I have a table with some columns. Unfortunately, I don't have column with classic date format like "YYYY-MM-DD".
I have columns year and month, like:
2021 | 7
2021 | 10
2021 | 1
I want to build date from these columns and extract quarter in format (Q3'21) from this date. Can I do it with PostgreSQL?
I expect two new columns date and quarter:
2021 | 7 | 2021-07-01 | Q3'21
2021 | 10 | 2021-10-01 | Q4'21
2021 | 1 | 2021-01-01 | Q1'21
I tried to build date with simple concatenation gr."year" || '-0' || gr."month" || '-01' as custom_date, but i got wrong values like:
2021-010-01
You an use make_date() to create a date, then use to_char() to format that date:
select t.year,
t.month,
make_date(t.year, t.month, 1),
to_char(make_date(t.year, t.month, 1), '"Q"Q''YY')
from the_table t;
Use MAKE_DATE:
select
year, month,
make_date(year, month, 1) as first_day_of_month,
'Q' || to_char(make_date(year, month, 1), 'Q') || '''' ||
to_char(make_date(year, month, 1), 'YY') as quarter
from mytable;
I have a SQLite database and sales table is like the following,
| Id | quantity | dateTime |
------------------------------------
| 1 | 10 | 2019-12-25 12:55 |
| 2 | 05 | 2019-12-30 12:55 |
| 3 | 25 | 2020-08-23 12:55 |
| 4 | 25 | 2020-08-24 12:55 |
| 5 | 56 | 2020-08-25 12:55 |
| 6 | 25 | 2020-08-26 12:55 |
| 7 | 12 | 2020-08-27 12:55 |
| 8 | 30 | 2020-08-28 12:55 |
| 9 | 40 | 2020-08-29 12:55 |
I need to get the Current Week data (Mon to Sun) and the Current Year data from (Jan to Dec). So if I pass today date I need to get only the Current Week sales data group by days like the following,
If I pass today date and time (2020-08-28 13:55) the query should give me Current Week data like this,
Day Sold Items (SUM(quantity))
Monday 20
Tuesday 25
Wednesday 10
Thursday 50
Friday 60
Saturday 0 (If the date hasn't come yet I need to get 0)
Sunday 0
And same as the Current Year data when I pass the Current Date,
Month Sold Items (SUM(quantity))
JAN 20
FEB 25
MAR 10
APR 50
MAY 60
JUN 0 (If the month hasn't come yet I need to get 0)
JUL 0
... ...
I tried with multiple queries in SQLite but couldn't get what I need. Here are the queries I tried,
Weekly Data (This one gave me past week data also)
SELECT SUM(quantity) as quantity, strftime('%w', dateTime) as Day
From sales
Group by strftime('%w', dateTime)
Monthly Data
SELECT SUM(quantity) as quantity, strftime('%m', dateTime) as Month
From sales
Group by strftime('%m', dateTime)
So anybody can help me to achieve this? Thanks in advance.
For the totals of the current week you need a CTE that returns the names of the days and the another one that returns the Monday of the current week.
You must cross join these CTEs and left join your table to aggregate:
with
days as (
select 1 nr, 'Monday' day union all
select 2, 'Tuesday' union all
select 3, 'Wednesday' union all
select 4, 'Thursday' union all
select 5, 'Friday' union all
select 6, 'Saturday' union all
select 7, 'Sunday'
),
weekMonday as (
select date(
'now',
case when strftime('%w', 'now') <> '1' then '-7 day' else '0 day' end,
'weekday 1'
) monday
)
select d.day,
coalesce(sum(t.quantity), 0) [Sold Items]
from days d cross join weekMonday wm
left join tablename t
on strftime('%w', t.dateTime) + 0 = d.nr % 7
and date(t.dateTime) between wm.monday and date(wm.monday, '6 day')
group by d.nr, d.day
order by d.nr
For the totals of the current year you need a CTE that returns the month names and then left join the table to aggregate:
with
months as (
select 1 nr, 'JAN' month union all
select 2 nr, 'FEB' union all
select 3 nr, 'MAR' union all
select 4 nr, 'APR' union all
select 5 nr, 'MAY' union all
select 6 nr, 'JUN' union all
select 7 nr, 'JUL' union all
select 8 nr, 'AUG' union all
select 9 nr, 'SEP' union all
select 10 nr, 'OCT' union all
select 11 nr, 'NOV' union all
select 12 nr, 'DEC'
)
select m.month,
coalesce(sum(t.quantity), 0) [Sold Items]
from months m
left join tablename t
on strftime('%m', t.dateTime) + 0 = m.nr
and date(t.dateTime) between date('now','start of year') and date('now','start of year', '1 year', '-1 day')
group by m.nr, m.month
order by m.nr
You can use the below query to get the weekly date, I am assuming that everydate has single entry and hence not grouping otherwise you can add group by.
First we will get the weekly calendar based on the input date (I have taken current date)
and then left join with calendar to get the required sold items info.
WITH seq(n) AS
(
SELECT 0 UNION ALL SELECT n + 1 FROM seq
WHERE n < DATEDIFF(DAY, (SELECT DATEADD(DAY, 2 - DATEPART(WEEKDAY, GETDATE()), CAST(GETDATE() AS DATE)) [Week_Start_Date]), (Select DATEADD(DAY, 8 - DATEPART(WEEKDAY, GETDATE()), CAST(GETDATE() AS DATE)) [Week_End_Date]))
),
CALENDAR(d) AS
(
SELECT DATEADD(DAY, n, (SELECT DATEADD(DAY, 2 - DATEPART(WEEKDAY, GETDATE()), CAST(GETDATE() AS DATE)) [Week_Start_Date])) FROM seq
)
SELECT coalesce(QUANTITY, 0) sold_items ,DATENAME(WEEKDAY, d) week_day FROM CALENDAR a left outer join Table_WEEKDAY b
on (a.d = convert(date, b.dateTime))
ORDER BY d
OPTION (MAXRECURSION 0);
You can try the below - DEMO
select day,coalesce(sum(quantity),0) as quantity
from
(select 0 as day union all select 1 union all select 2 union all select 3 union all select 4
union all select 5 union all select 6) as d
left join sales on cast(strftime('%w', dateTime) as int)=day
group by strftime('%w', dateTime),day
order by day
I have MS SQL function DATEDIFF
SELECT DATEDIFF(QQ, 0, '2018-09-05')
that returns 474(integer).
What is PostgreSQL equivalent of this function ?
MSSQL 0 is the date '1900-01-01' in DATEDIFF(QQ, 0, '2018-09-05'), that function will get the number of QUARTER from 1900-01-01 to 2018-09-05
But PostgreSQL does not have a QUARTER number function.
You can try to use
EXTRACT(QUARTER FROM TIMESTAMP '2018-09-05') to get this month QUARTER number.
date_part('year',age('2018-09-05','1900-01-01')) get year number between
'2018-09-05' and '1900-01-01'.
then do some calculation.
select (EXTRACT(QUARTER FROM TIMESTAMP '2018-09-05') +
date_part('year',age('2018-09-05','1900-01-01')) * 4) -1 QUARTER
Results:
| quarter |
|---------|
| 474 |
I think the current accepted is incorrect.
e.g. if you change date '1900-01-01' to '2017-07-01' you actually get 6 quarter diff.
But expected result should be:
include first and last quarter: 5
exclude first and last quarter: 3
exclude last quarter: 4
select
age('2018-09-05','2017-07-01') age,
(EXTRACT(QUARTER FROM TIMESTAMP '2018-09-05') +
date_part('year',age('2018-09-05','2017-07-01')) * 4) -1 QUARTER
Result:
age |quarter|
--------------------|-------|
1 year 2 mons 4 days| 6|
The reason is age('2018-09-05','2017-07-01') return 1 year 2 mons 4 days (maybe more than 12 month).
The answer i use to calculate number of quarter:
with params as (
select
'2017-07-01 00:00:00'::timestamp date_from,
'2018-09-05 00:00:00'::timestamp date_to
)
select
extract( quarter from date_from)::int date_from_quarter,
(extract( quarter from date_to))::int date_to_quarter,
age(date_to, date_from) date_diff,
(extract(year from date_to) - extract(year from date_from))::int how_many_yr,
-- exclude begin and end
(extract(year from date_to) - extract(year from date_from))::int* 4 - extract( quarter from date_from)::int + (extract( quarter from date_to))::int - 1 quarter_diff,
-- exclude begin quarter
(extract(year from date_to) - extract(year from date_from))::int* 4 - extract( quarter from date_from)::int + (extract( quarter from date_to))::int quarter_diff_include_current_quarter
from params
;
Result:
date_from_quarter|date_to_quarter|date_diff |how_many_yr|quarter_diff|quarter_diff_include_current_quarter|
-----------------|---------------|--------------------|-----------|------------|------------------------------------|
3| 3|1 year 2 mons 4 days| 1| 3| 4|
Its complicated on Postgres:
( (DATE_PART('year', '2018-09-05') - DATE_PART('year', '1900-01-01')) * 12
+ (DATE_PART('month', '2018-09-05') - DATE_PART('month', '1900-01-01'))
) / 4
Additional resources: topic
Let's say you have two dates in the example table: start_date and end_date
Here is how to get the difference between these dates in number of quarters:
select * (year(end_date)*4 + quarter(end_date)) - (year(start_date)*4 + quarter(start_date)) as quarter_diff
from example
I have monthly targets defined for the different category of items for the complete year.
Example:
January Target for A Category - 15,000
January Target for R Category - 10,000
January Target for O Category - 5,000
Actual Sales for A Category January - 18,400
Actual Sales for R Category January - 8,500
Actual Sales for O Category January - 3,821
The SQL query to compare actual sales with target will be simple as follows:
SELECT TO_CHAR (Sales_Date, 'MM') Sales_Month,
Sales_Category,
SUM (Sales_Value) Sales_Val_Monthly,
Target_Month,
Target_Category,
Target_Value
FROM Sales_Data, Target_Data
WHERE TO_CHAR (Sales_Date, 'MM') = Target_Month
AND Sales_Category = Target_Category
GROUP BY TO_CHAR (Sales_Date, 'MM'),
Target_Month,
Target_Category,
Sales_Category,
Target_Value;
Now I have a requirement that user will input FROM_DATE and TILL_DATE in the report parameter and the starting/ending date can be random, it will not represent a complete month or week, the start date can be 12/01/2018 and end date can be 15/01/2018, i.e., data for 4 days. The result should calculate the actual data for 4 days, calculate the target for 4 days considering the fact that there will be 6 working days (Sunday is a holiday) and if the date range includes Sunday, it should not be considered.
Also, the number of days in a month should be considered and the date parameters may contain some days from one month and some days from another month or maybe more than one month.
Target_Table (Target_Data)
Target_Year Target_Month Target_Category Target_Value
2018 01 A 15000
2018 02 A 8500
2018 03 A 9500
2018 01 R 15000
2018 02 R 8500
2018 03 R 9500
2018 01 O 15000
2018 02 O 8500
2018 03 O 9500
Sales Table (Sales_Data)
Inv_Txn Inv_No Sales_Date Item_Code Sales_Category Qty Rate Sales_Value Inv_Locn Inv_SM_ID
A21 2018000001 02/01/2018 XXXX A 2 5.5 11 O001 XXXX
R32 2018000001 27/02/2018 XXXX R 3 9.5 28.5 O305 XXXX
O98 2018000001 12/03/2018 XXXX O 12 12.5 150 O901 XXXX
U76 2018000001 18/01/2018 XXXX A 98 5.5 539 O801 XXXX
B87 2018000001 19/02/2018 XXXX R 2 9.5 19 O005 XXXX
A21 2018000002 13/03/2018 XXXX R 45 9.5 427.5 O001 XXXX
B87 2018000002 14/03/2018 XXXX O 12 12.5 150 O005 XXXX
Desired Output (From Date: 27/02/2018 Till Date: 06/03/2018)
Target_Category Target_Value Sales_Value
A 87.52 21.88
A 96.25 24.06
A 74.25 18.56
R 100.25 25.06
R 800.2 200.05
R 25.1 6.28
O 75.5 18.88
O 98.1 24.53
O 25.5 6.38
The first step might be to see whether we can get the number of Sundays in a given month. As it turns out, we can - and we don't have to use any SQL tricks or PL/SQL:
SELECT EXTRACT( DAY FROM LAST_DAY(SYSDATE) ) AS month_day_cnt
, CEIL( ( LAST_DAY(TRUNC(SYSDATE, 'MONTH')) - NEXT_DAY(TRUNC(SYSDATE, 'MONTH')-1, 'SUN') + 1 ) / 7 ) AS sunday_cnt
FROM dual;
This will give us the number of days in a given month as well as the number of Sundays. All we need to do is subtract the latter number from the former to get the number of working days. We can work that into your initial query (by the way, I suggest using TRUNC() instead of TO_CHAR() since your users might want a date range that spans more than one calendar year):
SELECT TRUNC(s.Sales_Date, 'MONTH') AS Sales_Month
, EXTRACT( DAY FROM LAST_DAY( TRUNC(s.Sales_Date, 'MONTH') ) ) - CEIL( ( LAST_DAY(TRUNC(s.Sales_Date, 'MONTH')) - NEXT_DAY(TRUNC(s.Sales_Date, 'MONTH')-1, 'SUN') + 1 ) / 7 ) AS working_day_cnt
, s.Sales_Category, SUM(s.Sales_Value) AS Sales_Val_Monthly
, t.Target_Value -- Target_Month and Target_Category are superfluous
FROM Sales_Data s INNER JOIN Target_Data t
ON TO_CHAR(s.Sales_Date, 'MM') = t.Target_Month
AND TO_CHAR(s.Sales_Date, 'YYYY') = t.Target_Year
AND s.Sales_Category = t.Target_Category
GROUP BY TRUNC(s.Sales_Date, 'MONTH'), Sales_Category, Target_Value;
Now given a start date and an end date, we can generate the number of working days for all the months in between those dates as follows:
SELECT TRUNC(range_dt, 'MONTH'), COUNT(*) FROM (
SELECT start_dt + LEVEL - 1 AS range_dt
FROM dual
CONNECT BY start_dt + LEVEL - 1 < end_dt
) WHERE TO_CHAR(range_dt, 'DY') != 'SUN'
GROUP BY TRUNC(range_dt, 'MONTH');
where start_dt and end_dt are parameters supplied by the user. Putting this all together, we'll have something like the following:
WITH rd ( range_month, range_day_cnt ) AS (
SELECT TRUNC(range_dt, 'MONTH'), COUNT(*) FROM (
SELECT start_dt + LEVEL - 1 AS range_dt
FROM dual
CONNECT BY start_dt + LEVEL - 1 < end_dt
) WHERE TO_CHAR(range_dt, 'DY') != 'SUN'
GROUP BY TRUNC(range_dt, 'MONTH')
)
SELECT range_month, Sales_Category, Sales_Val_Monthly
, range_day_cnt, working_day_cnt, Target_Value
, Target_Value*range_day_cnt/working_day_cnt AS prorated_target_value
FROM (
SELECT r.range_month, r.range_day_cnt
, EXTRACT( DAY FROM LAST_DAY( TRUNC(s.Sales_Date, 'MONTH') ) ) - CEIL( ( LAST_DAY(TRUNC(s.Sales_Date, 'MONTH')) - NEXT_DAY(TRUNC(s.Sales_Date, 'MONTH')-1, 'SUN') + 1 ) / 7 ) AS working_day_cnt
, s.Sales_Category, SUM(s.Sales_Value) AS Sales_Val_Monthly
, t.Target_Value -- Target_Month and Target_Category are superfluous
FROM rd INNER JOIN Sales_Data s
ON rd.range_month = TRUNC(s.Sales_Date, 'MONTH')
INNER JOIN Target_Data t
ON TO_CHAR(s.Sales_Date, 'MM') = t.Target_Month
AND TO_CHAR(s.Sales_Date, 'YYYY') = t.Target_Year
AND s.Sales_Category = t.Target_Category
WHERE s.Sales_Date >= TRUNC(start_dt)
AND s.Sales_Date < TRUNC(end_dt+1)
GROUP BY r.range_month, r.range_day_cnt, s.Sales_Category, t.Target_Value
) ORDER BY range_month;
If you have a table of public holidays, then those will have to be factored in somewhere as well - both in the rd common table expression and from the calculation of working days. If the above doesn't give you a start on that then I can take a look again in a bit and see how the other holidays might be worked in.
You can calculate the number of working days between two dates using below query. I added a nonworking date via a table named: holiday_dates and created a series of dates from 12/01/2018 to 15/01. I remove those dates that are either Sunday or holiday. Please let me know if it works for you. Thanks.
create table holiday_dates(holiday_dte date, holiday_desc varchar(100));
insert into holiday_dates values(TO_DATE('13/01/2018','DD-MM-YYYY'), 'Not a Working Day');
With tmp as (
select count(*) as num_of_working_days
from ( select rownum as rn
from all_objects
where rownum <= to_date('15/01/2018','DD-MM-YYYY') - to_date('12/01/2018','DD-MM-YYYY')+1 )
where to_char( to_date('12/01/2018','DD-MM-YYYY')+rn-1, 'DY' ) not in ( 'SUN' )
and not exists ( select null from holiday_dates where holiday_dte = trunc(to_date('12/01/2018','DD-MM-YYYY') + rn - 1)))
SELECT TO_CHAR (Sales_Date, 'MM') Sales_Month,
Sales_Category,
SUM (Sales_Value) Sales_Val_Monthly,
Target_Month,
Target_Category,
Target_Value,
tmp.num_of_working_days
FROM Sales_Data, Target_Data, tmp
WHERE Sales_Date between to_date('12/01/2018','DD-MM-YYYY') and to_date('15/01/2018','DD-MM-YYYY')
AND Sales_Category = Target_Category
GROUP BY TO_CHAR (Sales_Date, 'MM'),
Target_Month,
Target_Category,
Sales_Category,
Target_Value;
I have the following table Sales:
Date Store Sales
1/1/2015 St01 12123
1/1/2015 St02 3123
1/1/2016 St01 4213
1/1/2016 St03 2134
When I try to self join to get this year and last year sales the closed store is not showing up.
The result should be like this:
Date Store This year Sales Last Year Sales
1/1/2016 St01 4213 1212
1/1/2016 St02 0 3123
1/1/2016 St03 2134 0
My query as follows:
SELECT CY.DATE,
CY.store cy.Sales,
LY.sales
FROM sales CY,
sales LY
WHERE CY.store(+) = LY.store(+)
AND LY.DATE = CY.DATE - 365
Oracle Setup:
CREATE TABLE sales ( "DATE", Store, Sales ) AS
SELECT DATE '2015-01-01', 'St01', 12123 FROM DUAL UNION ALL
SELECT DATE '2015-01-01', 'St02', 3123 FROM DUAL UNION ALL
SELECT DATE '2016-01-01', 'St01', 4213 FROM DUAL UNION ALL
SELECT DATE '2016-01-01', 'St03', 2134 FROM DUAL;
Query:
SELECT TRUNC( SYSDATE, 'YY' ) AS "DATE",
Store,
SUM( CASE WHEN "DATE" = TRUNC( SYSDATE, 'YY' )
THEN sales END )
AS "This year sales",
SUM( CASE WHEN "DATE" = ADD_MONTHS( TRUNC( SYSDATE, 'YY' ), -12 )
THEN sales END )
AS "Last year sales"
FROM sales
GROUP BY store
ORDER BY store;
Output:
DATE STORE This year sales Last year sales
------------------- ----- --------------- ---------------
2016-01-01 00:00:00 St01 4213 12123
2016-01-01 00:00:00 St02 3123
2016-01-01 00:00:00 St03 2134
What you need is called Pivoting Table. Although Oracle has specific clauses to do it, you can use just plain and pure SQL to do so, like this:
SELECT store,
SUM(CASE WHEN Extract(year FROM DATE) = Extract(year FROM SYSDATE) THEN
sales
ELSE 0
END) AS "This year Sales",
SUM(CASE WHEN Extract(year FROM DATE) = Extract(year FROM SYSDATE) - 1 THEN
sales
ELSE 0
END) AS "Last year Sales"
FROM sales
WHERE Extract(year FROM DATE) >= Extract(year FROM SYSDATE) - 1
GROUP BY store
ORDER BY store
It would show:
Store This year Sales Last year Sales
St01 4213 12123
St02 0 3123
St03 2134 0
Note that makes no sense to have to column date as the first column. You couldn't group by it to show the output you want.
See the equivalent of this query here on fiddle: http://sqlfiddle.com/#!15/7662d8/6
Since I want the query to return day by day sales I used MT0 answer and added the dates, this way I can get the data for all year days.
WITH AllYear AS
(select to_date('2016-01-01', 'yyyy-mm-dd') + level - 1 AS dobs
from dual
connect by level <= 366)
SELECT dobs AS "DATE",
Store,
nvl(SUM(CASE
WHEN t.Date = dobs THEN
t.sales
END),
0) AS "This Year Sales",
nvl(SUM(CASE
WHEN t.Date = dobs-365 THEN
t.sales
END),
0) AS "Last Year Sales"
FROM Sales t,AllYear
where dobs='01-Jan-2016'
GROUP BY Store
ORDER BY Store;
The general solution is a full outer join, which includes all records from both joined tables. I don't know the Oracle syntax, but in MS SQL Server it would be something like this:
SELECT ISNULL(CY.DATE, LY.DATE) as DATE,
ISNULL(CY.store, LY.store) as STORE,
isnull(cy.Sales, 0),
isnull(LY.sales, 0)
FROM sales CY FULL OUTER JOIN sales LY
ON CY.store = LY.store
AND (CY.DATE IS NULL OR
DATEPART(year, LY.DATE) = DATEPART(year, CY.DATE) - 1
ISNULL(a, b) gives a if A IS NOT NULL, else b. DATEPART extracts specified part of a date; I'm comparing a difference of exactly one year rather than 365 days, in case "last year" is a leap year/
I only work with SQL Server. If anything is different, try to apply the same logic.
Declaring a temporary table to test the query:
DECLARE #Sales TABLE (
[Date] DATE,
Store NVARCHAR(10),
Sales INT
)
INSERT INTO #Sales VALUES
('1/1/2015','St01',12123),
('1/1/2015','St02',3123),
('1/1/2016','St01',4213),
('1/1/2016','St03',2134);
SELECT * FROM #Sales;
The actual query:
SELECT
CY_Date = CASE
WHEN CY.Date IS NULL THEN DATEADD(YEAR, 1, LY.Date)
ELSE CY.Date
END,
LY_Date = CASE
WHEN LY.Date IS NULL THEN DATEADD(YEAR, -1, CY.Date)
ELSE LY.Date
END,
Store = CASE
WHEN CY.Store IS NULL THEN LY.Store
ELSE CY.Store
END,
ISNULL(CY.Sales, 0) AS CY_Sales,
ISNULL(LY.Sales, 0) AS LY_Sales
FROM #Sales CY
FULL JOIN #Sales LY ON (CY.Store = LY.Store AND LY.Date = DATEADD(YEAR, -1, CY.Date))
WHERE (CY.Date = '1/1/2016' OR CY.Date IS NULL)
AND (LY.Date = DATEADD(YEAR, -1, '1/1/2016') OR LY.Date IS NULL);
Result:
CY_Date LY_Date Store CY_Sales LY_Sales
2016-01-01 2015-01-01 St01 4213 12123
2016-01-01 2015-01-01 St03 2134 0
2016-01-01 2015-01-01 St02 0 3123
How it works:
The FULL JOIN will will combine by the Store and the lines from the current and the year before.
The WHERE clause will filter by the current date '1/1/2016'. The NULLs are allowed because sometimes you don't have lines for the current or for the last year.
On the columns, CASES are used to create the dates if they are null (If the current date is null, get the last year + 1 year, and vice versa), to create the store if they are null and to place a zero instead of a null on the sales columns.