DAX Query - EARLIER() - Compare with previous Quarter - powerpivot

I tried to do a comparison in DAX between multiple Quarter.
In our case we use Power Pivot with a flat table as source. (No star schema)
Here is a small example:
Our source looks like this:
That's what we have now:
Here is our expected result:
I tried to do something with EARLIER() and EARLIEST() function, but it is definitively not good. (Like an MDX LAG() function).
In our specific case, we don't have a clear date and I'm not sure if I can use a ParallelPeriod function
SumValuePrevious:=CALCULATE
(
SUM(Data[Value]);
FILTER(ALL(Data[Quarter]);
SUMX
(
FILTER(Data; EARLIEST(Data[Quarter]) = Data[Quarter] )
;
[Value]
)
)
)
But I get the same result:
Do you have something to help me?
Thank you very much for your help,
Arnaud

The best way to work with dates is to add a date table.
then you can use the default date functions
Previous Quarter:= CALCULATE(SUM(myTable[myColumn]), PREVIOUSQUARTER('Date'[Date]))
Adding a date table:
Date =
ADDCOLUMNS (
CALENDAR (DATE(2000;1;1); DATE(2025;12;31));
"DateAsInteger"; FORMAT ( [Date]; "YYYYMMDD" );
"Year"; YEAR ( [Date] );
"Monthnumber"; FORMAT ( [Date]; "MM" );
"YearMonthnumber"; FORMAT ( [Date]; "YYYY/MM" );
"YearMonthShort"; FORMAT ( [Date]; "YYYY/mmm" );
"MonthNameShort"; FORMAT ( [Date]; "mmm" );
"MonthNameLong"; FORMAT ( [Date]; "mmmm" );
"DayOfWeekNumber"; WEEKDAY ( [Date] );
"DayOfWeek"; FORMAT ( [Date]; "dddd" );
"DayOfWeekShort"; FORMAT ( [Date]; "ddd" );
"Quarter"; "Q" & FORMAT ( [Date]; "Q" );
"YearQuarter"; FORMAT ( [Date]; "YYYY" ) & "/Q" & FORMAT ( [Date]; "Q" )
)
Mark your date table as date table
Replace your column Quarter in your source, with the first date of the quarter.
Create a relationship between both tables.

Related

How can I create a column of dates using the start and end dates from a table in Teradata using SQL?

The table columns are "Product_Number", "Product_Name", "Start_Date", "End_Date"
Table in Teradata:
Output Needed:
There's proprietary syntax, expand on, in Teradata to create time series using periods:
select Product_Number, Product_Name
-- extract the start date of the period value
, begin(pd) as new_Date
from tab
-- create a period on the fly and return one row per day
-- periods include the start, but exclude the end, thus end_date+1
expand on period(start_date, end_date+1) as pd
This assumes your example dates are in mm-dd-yyyy format, if it's dd-mm-yyyy you need to expand by month:
select Product_Number, Product_Name, begin(pd) as new_Date
from tab
expand on period(start_date, end_date+1) as pd by interval '1' month
Or to return always the 1st of month:
select Product_Number, Product_Name, begin(pd) as new_Date
from tab
expand on period(start_date, end_date+1) as pd by anchor period month_begin
with cte as (
SELECT [PRoduct_number]
,[Product_name]
,[Start_date]
,[End_date]
FROM table_name
union all
select
[PRoduct_number]
,[Product_name]
,ADD_MONTHS(cte.[Start_date], 1 )
,[End_date]
from
cte
where StartDate < End_Date
)
select * from cte

BigQuery wildcard date range with subselect seems to return null

I'm trying to create a quarterly report where some of the dates are generated from a lookup query. The input is start_date = 20181001 and end date = 20191231. While I could just query the whole range, I don't need Q1/2/3 so I'm dynamically generating the in-between dates.
The problem comes when I use them in the subquery with the table_suffix.
The dynamically generated ones don't work; it looks like it returns null and queries the entire table rather than date partitioned. But when I just hard code the values in a subquery, they work fine.
If I you query both date lookup tables, they look identical
Results of both dynamically_created and hard_coded table. So I have no idea where this error is coming from.
CREATE TEMP FUNCTION start_end() AS ( [parse_date('%Y%m%d','{start_date}'), parse_date('%Y%m%d','{end_date}')] );
CREATE TEMP FUNCTION wildcard_format(date_object date) as (replace(cast(date_object as string),"-",""));
-- create a calendar table 1 column "day" and one row for each day in the desired timeframe
WITH
calendar AS (
SELECT
extract(quarter from day) quarter
,extract(year from day) year
,day
FROM
UNNEST(GENERATE_DATE_ARRAY( start_end()[OFFSET(0)], start_end()[OFFSET(1)], INTERVAL 1 DAY) ) AS day
),
dynamically_created as (
select
wildcard_format(min(day)) start_py
,wildcard_format(max(case when year = extract (year from parse_date('%Y%m%d','{start_date}')) then day else null end)) end_py
,wildcard_format(min(case when year = extract (year from parse_date('%Y%m%d','{end_date}')) then day else null end)) start_cy
,wildcard_format(max(day)) end_cy
from
calendar
where quarter = extract (quarter from parse_date('%Y%m%d','{end_date}'))
),
hard_coded as (
SELECT
'20181001' as start_py
,'20181231' as end_py
,'20191001' as start_cy
,'20191231' as end_cy
),
sesh_data as (
select
*
from
`projectid.datasetid.summary_*`
where
(SELECT _table_suffix between start_py AND end_py FROM dynamically_created) #not working
(SELECT _table_suffix between start_py AND end_py FROM hard_coded) #working
),
select * from sesh_data

Partitioning by specific value and then by range in Oracle

I have a table with the following columns:
CREATE TABLE CUST_HISTORY (
ID NUMBER,
PRD_CNT NUMBER,
DATE_TO DATE
)
Now, I would like to apply the following partitioning strategy:
all values where DATE_TO = '9999-12-31' should be assigned to one partition called "p_max"
all remaining values of DATE_TO should be partitioned by monthly intervals (from DATE_TO)
Any hints?
From this answer:
CREATE TABLE CUST_HISTORY (
ID NUMBER,
PRD_CNT NUMBER,
DATE_TO DATE
)
PARTITION BY RANGE (date_to)
INTERVAL (INTERVAL '1' MONTH)
(PARTITION p_first VALUES LESS THAN ( DATE '2019-01-01' ) );
db<>fiddle
If you particularly want the partition to be named as p_max then you can use a virtual column to remap the DATE_TO value from a high vale to a low value so you can name the partition and then use range intervals:
CREATE TABLE CUST_HISTORY (
ID NUMBER,
PRD_CNT NUMBER,
DATE_TO DATE CHECK ( DATE_TO >= DATE '1900-01-01' ),
remapped_date_to DATE
GENERATED ALWAYS AS
( CASE WHEN date_to = DATE '9999-12-31' THEN DATE '0001-01-01' ELSE date_to END )
VIRTUAL
)
PARTITION BY RANGE (remapped_date_to)
INTERVAL (INTERVAL '1' MONTH)
(PARTITION p_max VALUES LESS THAN ( DATE '1900-01-01' ) );
db<>fiddle
or use AUTOMATIC LIST partitioning (Oracle 12c or later) with a virtual column:
CREATE TABLE CUST_HISTORY (
ID NUMBER,
PRD_CNT NUMBER,
DATE_TO DATE,
month_to DATE
-- INVISIBLE
GENERATED ALWAYS AS
( CASE WHEN date_to = DATE '9999-12-31' THEN date_to ELSE TRUNC( date_to, 'MM' ) END )
VIRTUAL
)
PARTITION BY LIST ( month_to ) AUTOMATIC
( PARTITION p_max VALUES ( DATE '9999-12-31' ) );
db<>fiddle
(If you want you can also make the virtual column INVISIBLE)
The solution with monthly partitioning (please observe last_day function):
CREATE TABLE CUST_HISTORY (
ID NUMBER,
PRD_CNT NUMBER,
DATE_TO DATE,
month_to DATE
GENERATED ALWAYS AS
( CASE WHEN date_to = DATE '9999-12-31' THEN date_to ELSE TRUNC( last_day(date_to) ) END )
VIRTUAL
)
PARTITION BY LIST ( month_to ) AUTOMATIC
( PARTITION p_max VALUES ( DATE '9999-12-31' ) );
DB<>FIDDLE
Thank you for inspiration, #mt0!

datediff between 2 asymmetric column from same table

how can i use date difference between two asymmetric row from same table?
select
[endtime]
,[realtime]
from [prin]
where ([realtime] is not null) and ([endtime] is not null and)
order by [realtime]
between end date time and next start date time
Use the DateDiff function to get the difference between two dates.
Add the day parameter to get the difference in days.
SELECT
[endtime]
,[realtime]
,datediff (day,[endtime],lead ([realtime]) over (order by [realtime])) as days_endtime_2_next_realtime
FROM [prin]
WHERE ([realtime] is not null)
AND ([endtime] is not null)
ORDER BY [realtime]
create table #a
(
date_1 date,
date_2 date
)
insert into #a values
('1-10-16','2-10-16'),
('2-10-16','3-10-16'),
('3-10-16','4-10-16')
;with cte as
(
select
date_1,date_2
,lead(date_2,1) over (order by date_2 ) as lead_value
from #a
where (date_1 is not null) and (date_2 is not null )
)
select date_1,date_2,
datediff(Day,date_1,lead_value) as diffrence from cte

Cast string as date and use it in comparison

I have a table as
NUM | TDATE
1 | 200712
2 | 200708
3 | 200704
4 | 20081210
where mytable is created as
mytable
(
num int,
tdate char(8) -- legacy
);
The format of tdate is YYYYMMDD.. sometimes the date part is optional.
So a date such as "200712" can be interpreted as 2007-12-01.
I want to write query such that i can treat tdate as a Date column and apply date comparison.
like
select num, tdate from mytable where tdate
between '2007-12-31 00:00:00' and '2007-05-01 00:00:00'
So far i tried this
select num, tdate,
CAST(LEFT(tdate,6)
+ COALESCE(NULLIF(SUBSTRING(CAST(tdate AS VARCHAR(8)),7,8),''),'01') AS Date)
from mytable
SQL Fiddle
How can I use the above converted date (3rd column ) for comparison? (needs a join?)
Also is there a better way to do this?
Edit: I have no control over the table scheme for now.. we have suggested the change to the DB team..for now have to stick with char(8) .
I think this a better way to get your fixed date:
SELECT CAST(LEFT(RTRIM(tdate) + '01',8) AS DATE)
You can create a subquery/cte with the date cast properly:
;WITH cte AS (select num, tdate,CAST(LEFT(RTRIM(tdate)+ '01',8) AS DATE)'FixedDate'
from mytable )
select num, FixedDate
from cte
where FixedDate
between '2007-12-31' and '2007-05-01'
Or you can just use your fixed date in the query directly:
select num, tdate
from mytable
where CAST(LEFT(RTRIM(tdate)+ '01',8) AS DATE) between '2007-12-31' and '2007-05-01'
Ideally you would add the fixed date field to your table so that queries can benefit from indexing the date.
Note: Be wary of BETWEEN with DATETIME as the time portion can result in undesired results if you really only care about the DATE portion.
'2007-12-31 00:00:00' > '2007-05-01 00:00:00', so your BETWEEN clause will never return any records.
This will work, with a subquery, and with the dates flipped:
select num, tdate, formattedDate
from
(
select num, tdate
,
CAST(LEFT(tdate,6) + COALESCE(NULLIF(SUBSTRING(CAST(tdate AS VARCHAR(8)),7,8),''),'01') AS Date) as formattedDate
from mytable
) a
where formattedDate between '2007-05-01 00:00:00' and '2007-12-31 00:00:00'
sqlFiddle here
I think you should avoid storing date in string type fields. If that is something you have to live with try following solution.
Since you are having yyyymmdd or yyyymm format you can first get them all in yyyymmdd format which is Culture independent ISO format and then use style 112 to convert into Date for comparison:
--Culture independent solution
;with cte as (
select num, tdate,
convert(date,left(rtrim(tdate) + '01',8),112) mydate --yyyymmdd format
from mytable
)
select num,tdate,mydate
from cte
where mydate between convert(date,'20071231',112) and --Values are in yyyymmdd format
convert(date,'20070501',112)
Yet another way to turn your string values into dates would be to use REPLACE:
SELECT num, tdate
FROM mytable
WHERE CAST(REPLACE(tdate, ' ', '01') AS date) BETWEEN #date1 AND #date2
;
If you really want to both return the converted date value and use it for filtering, you can employ CROSS APPLY to avoid repeating the logic:
SELECT t.num, t.tdate, x.date
FROM mytable AS t
CROSS APPLY (SELECT CAST(REPLACE(t.tdate, ' ', '01') AS date)) AS x (date)
WHERE x.date BETWEEN #date1 AND #date2
;
This method assumes that your char(8) strings are formatted as either YYYYMMDD or YYYYMM, although the method will work without any changes if you decide to start using values formatted as just YYYY in addition to the other two formats (to imply the beginning of a year, just like a YYYYMM implies the beginning of a month).
with date_cte(num,date)as
(select num,CAST(LEFT(tdate,6)
+ COALESCE(NULLIF(SUBSTRING(CAST(tdate AS VARCHAR(8)),7,8),''),'01') AS Date)
from mytable)
select t1.num, t1.tdate,t2.date
from mytable t1 join date_cte t2 on t1.num=t2.num
where t2.date
between '2007-12-31 00:00:00' and '2007-05-01 00:00:00'
I don't have the time to test right now, but something like this may work...
select num, tdate
from mytable
WHERE CAST(LEFT(tdate,6)
+ COALESCE(NULLIF(SUBSTRING(CAST(tdate AS VARCHAR(8)),7,8),''),'01') AS Date) BETWEEN CAST('2007-12-31 00:00:00' as smalldatetime) and CAST('2007-05-01 00:00:00' as smalldatetime)
My proposal would be to add a date field to your table. If your table is regularly updated, fill it from the legacy field through a stored proc on a regular schedule (either trigger or job).
You'll then be able to use the date as ... a date, without all these tricks, turnarounds, and other approximations which are all potential source for confusion, mistakes and questionable results.