First year spending of all customers - tableau-api

I want to get all customers' spending for first year. But all of them have different date of joining. I have transaction data which consist of columns [User ID], [Date joined], [Transaction Date], [Amount].
Can someone help? Thanks

An LOD calculation oughta do the trick.
{ FIXED [User ID] :
SUM(
IIF(
(
[Transaction Date] >= [Date Joined]
AND [Transaction Date] < DATEADD('year', 1, [Date Joined])
),
[Amount],
0
)
)
}

Related

How to get the correct LOD calculation?

in my dataset there are a few customers who churned besides that their subscription plan information. Customers can change their subscription, so here I get the max plan:
{FIXED [Customer Id]:MAX(
(IF {FIXED [Customer Id]: MAX(
IF NOT ISNULL([Subscription Plan]) THEN [Date] END)
}=[Date] THEN [Subscription Plan] END)
)}
To find customer churn:
{ FIXED DATETRUNC('month', [Date]), [Max Plan]:
COUNTD(
IF NOT ISNULL([Churn Date]) AND
DATETRUNC('month', [Date]) = DATETRUNC('month', [Churn Date])
THEN [Customer Id] END
)
I want to calculate the revenue loss by churn, i.e. for each customer churn with advanced plan costs 9, for premium 19 dollars.
IF [Max Plan] = 'advanced' THEN [Churn]*9
ELSEIF [Max Plan] = 'premium' THEN [Churn]*19
END
However, it doesn't give me the correct result.
Here is the expected result:
Here is the workbook attached: https://community.tableau.com/s/contentdocument/0694T000004aUnLQAU

Bought items in last 3 orders for each customer in tableau desktop

I am trying to find basket items (category, subcategory) in last 3 orders of each customer. So, in the end, I am hoping to cluster customers according to items or categories that were mostly bought in last 3 orders.I am stuck on finding a solution to calculate last 3 orders of each customer. I should use LOD expressions but which one and how?
I think using Fixed [Client id] is the starting point. Should I rank orders descending ( based on order date) and then filter it with "<=3".
Trying replicating your problem on sample superstore data.
creating this calculation will give you last order for each customer
{fixed [Customer Name]: max([Order Date])} = [Order Date]
creating this calculation will give you last 2 orders
{Fixed [Customer Name]:MAX(
If [Order Date] <> {fixed [Customer Name]: max([Order Date])}
then [Order Date] END
)} = [Order Date]
OR
{fixed [Customer Name]: max([Order Date])} = [Order Date]
similarly creating this calculation will give you last 3 orders
{ FIXED [Customer Name] : max( IF
{Fixed [Customer Name]:MAX(
If [Order Date] <> {fixed [Customer Name]: max([Order Date])}
then [Order Date] END
)} <> [Order Date]
AND
{fixed [Customer Name]: max([Order Date])} <> [Order Date]
THEN [Order Date] END)} = [Order Date]
OR
{Fixed [Customer Name]:MAX(
If [Order Date] <> {fixed [Customer Name]: max([Order Date])}
then [Order Date] END
)} = [Order Date]
OR
{fixed [Customer Name]: max([Order Date])} = [Order Date]
only assumption is that there aren't more than 1 order on any given date.
check it

Tableau - Creating Overlapping Date Bins

I have a report that will be updated Monday through Friday and want to display a single metric [Productivity %] across several different date [In_Date] "bins" i.e. a generated date dimension that would bin my data according to the following definitions:
Yesterday
Week to date (e.g. on Thursday, Sunday through Wednesday
data would be displayed etc.)
Last 7 days
Month to date
Last full month
Last 3 months
Last 6 months
Last 12 months
I'm not looking to create a parameter that the user would use to toggle; rather, I want a new dimension that would be dropped in the columns section that would follow the bins outlined above.
Below is an illustration of what I want to achieve:
Currently, I have the below result using this code:
IF [In Date] = DATEADD('day', -1, TODAY()) THEN 'Yesterday'
ELSEIF [In Date] < TODAY() AND [In Date] >= DATEADD('day', -ISOWEEKDAY(TODAY()), TODAY()) then 'Week to Date'
ELSEIF [In Date] < TODAY() AND [In Date] >= DATEADD('day', -7, TODAY()) THEN 'Last 7 Days'
ELSEIF [In Date] < TODAY() AND [In Date] >= DATEADD('day', -day(TODAY())+1, TODAY()) then 'Month to Date'
ELSEIF [In Date] < DATETRUNC('month', TODAY()) AND [In Date] >= DATEADD('month', -1, DATETRUNC('month', TODAY())) then 'Last Month'
ELSEIF [In Date] < DATETRUNC('month', TODAY()) AND [In Date] >= DATEADD('month', -3, DATETRUNC('month', TODAY())) then 'Last 3 Months'
ELSEIF [In Date] < DATETRUNC('month', TODAY()) AND [In Date] >= DATEADD('month', -6, DATETRUNC('month', TODAY())) then 'Last 6 Months'
ELSEIF [In Date] < DATETRUNC('month', TODAY()) AND [In Date] >= DATEADD('month', -12, DATETRUNC('month', TODAY())) then 'Last 12 Months'
ELSE 'Older' END
The overlapping date bins are not capturing all of the data: "last 12 months" omits metrics previously captured in the "last 6 months," etc. Moreover, the first 4 bins (yesterday, WTD, last 7 days, and MTD) are missing entirely.
This is a proper case of use of PARAMETERS in Tableau. I enjoyed while solving this.
Since you have not provided any data, I created a dummy data in excel by generating dates from 1-7-2020 onwards till date (DATE_GIVEN) and filling some random numbers against these as MEASURE_1. Something like this.
Now proceed like this.
Step-1 Create a parameters with 8 inputs (as listed in your question). I created for first 6 and leaving remaining for you. The parameter should be like
Step-2 Create a calculated field date bins with the following calculation
CASE [Parameter 1]
WHEN 1 THEN (
IF [Date_Given] = DATEADD('day', -1, TODAY()) THEN [Date_Given] end)
WHEN 2 THEN
( IF [Date_Given] < TODAY() AND [Date_Given] >= DATEADD('day', -ISOWEEKDAY(TODAY()), TODAY()) then [Date_Given] END )
WHEN 3 then
( IF [Date_Given] < TODAY() AND [Date_Given] >= DATEADD('day', -7, TODAY()) THEN [Date_Given] END )
WHEN 4 THEN
( IF [Date_Given] < TODAY() AND [Date_Given] >= DATEADD('day', -day(TODAY())+1, TODAY()) then [Date_Given] END )
WHEN 5 THEN
( IF [Date_Given] < DATETRUNC('month', TODAY()) AND [Date_Given] >= DATEADD('month', -1, DATETRUNC('month', TODAY())) then [Date_Given] END)
WHEN 6 THEN
( IF [Date_Given] < DATETRUNC('month', TODAY()) AND [Date_Given] >= DATEADD('month', -3, DATETRUNC('month', TODAY())) then [Date_Given] END)
END
Needless to say you have to incorporate appropriate calculation for remaining two values
Step-3 Place date bins on rows shelf and additionally on FILTERS CARD. (Filter out only null values from this .. special values tab). Add measure_1 to desired type. Show parameter 1 and your view is ready. Check the screenshots (Today's system date - 16-12-2020)
OR
OR
OR
As regards your edited question, I propose a workaround (because creation of a calculated field where one value input can result in multiple value outputs seems illogical and impossible to me given the basic rules of mathematics) as follows.
Create 8 different calculated fields as
Last 3 months M
IF [Date_Given] < DATETRUNC('month', TODAY()) AND [Date_Given] >= DATEADD('month', -3, DATETRUNC('month', TODAY())) then [Measure_1] END
similarly for other desired bins create a separate calculated field. You can thereafter build a view/viz as included in your question.

Updating a table to avoid overlapping dates

I am trying to write a query that reorders date ranges around particular spans. It should do something that looks like this
Member Rank Begin Date End Date
2275 A 9/9/14 11/17/14
2275 B 9/26/14 3/24/15
2275 B 3/25/15 12/31/15
8983 A 9/16/13 3/10/15
8983 B 2/24/15 4/28/15
8983 A 4/28/15 12/31/15
and have it become
Member Rank Begin Date End Date
2275 A 9/9/14 11/17/14
2275 B 11/18/14 3/24/15
2275 B 3/25/15 12/31/15
8983 A 9/16/13 3/10/15
8983 B 3/11/15 4/27/15
8983 A 4/28/15 12/31/15
To explain further, I am looking to update the dates. There isn't much to the ranking except A > B. And there is only A and B. Date ranges with rank A should remain untouched. Overlapping B ranked dates are okay. I am concerned with B ranked dates overlapping with A ranked dates. The table is very large (~700 members) and with several different members IDs. The 2nd line (Rank B) of member 2275 changes the begin date to 11/18/15 to not overlap with the 1st line.
I am using Microsoft SQL Server 2008 R2
Thanks
LATEST EDIT: Here's what I did for pre-2012. I don't think it's the most elegant solution.
WITH a AS (
SELECT
1 AS lgoffset
, NULL AS lgdefval
, ROW_NUMBER() OVER(PARTITION BY [Member] ORDER BY [Begin Date]) AS seq
, [Member]
, [Rank]
, [Begin Date]
, [End Date]
FROM #table
)
SELECT
a.seq
, a.[Member]
, a.[Rank]
, a.[Begin Date]
, CASE
WHEN a.[Rank] = 'B' AND a.[Begin Date] <= ISNULL(aLag.[End Date], a.lgdefval)
THEN ISNULL(aLag.[End Date], a.lgdefval)
ELSE a.[Begin Date]
END AS bdate2
, a.[End Date]
INTO #b
FROM a
LEFT OUTER JOIN a aLag
ON a.seq = aLag.seq + a.lgoffset
AND a.[Member] = aLag.[Member]
ORDER BY [Member], [Begin Date];
UPDATE #table
SET #table.bdate = CASE
WHEN #table.rnk = 'B' AND #table.bdate <= (SELECT #b.bdate2 FROM #b WHERE #b.bdate2 > #b.bdate and #table.mbr = #b.mbr)
THEN dateadd(d, 1,(SELECT bdate2 FROM #b WHERE #b.bdate2 > #b.bdate and #table.mbr = #b.mbr ))
ELSE #table.bdate
END
EDIT PS: Below was my previous answer that only applies to 2012 and later.
You may want to try the following SELECT statement to see if you get the desired results and then convert to an UPDATE:
SELECT
[Member]
, [Rank]
, CASE
WHEN [Rank] = 'B' AND [Begin Date] <= LAG([End Date],1,'12/31/2030') OVER(PARTITION BY [Member] ORDER BY [Begin Date])
THEN DATEADD(d,1,LAG([End Date],1,'12/31/2030')OVER(PARTITION BY [Member] ORDER BY [Begin Date]))
ELSE [Begin Date]
END AS [Begin Date]
, [End Date]
FROM #Table
ORDER BY [Member], [Begin Date]
EDIT: So in order to update the begin date column:
UPDATE #Table
SET [Begin Date] = (SELECT
CASE
WHEN [Rank] = 'B' AND [Begin Date] <= LAG([End Date],1,'12/31/2030') OVER(PARTITION BY [Member] ORDER BY [Begin Date])
THEN DATEADD(d,1,LAG([End Date],1,'12/31/2030')OVER(PARTITION BY [Member] ORDER BY [Begin Date]))
ELSE [Begin Date]
END AS [Begin Date]
FROM #Table)
EDIT 2: Some of my code was incorrect due to not realizing the lag function needed an OVER statement, updated select statement and update statement
Sources:Alternate of lead lag function in sql server 2008
http://blog.sqlauthority.com/2011/11/24/sql-server-solution-to-puzzle-simulate-lead-and-lag-without-using-sql-server-2012-analytic-function/

iPhone SQLite (FMDB) query takes excessive time

I am trying to execute a query that has group by clause in it.
The query executes in 0.012243 seconds
However when I execute [resultset next], it takes more than 5 seconds the first time it is called.
Example
NSMutableArray *recordsArray = [[NSMutableArray alloc] init];
NSString * query = #"select count(id), SUBSTR(purchase_date, 0,11) as purchase_date from sales where purchase_date LIKE '2012-12%' group by purchase_date order by purchase_date asc";
NSDate * date1 = [NSDate date];
FMResultSet * resultset = [database executeQuery:query];
NSDate * date2 = [NSDate date];
NSTimeInterval timeTaken = [date2 timeIntervalSinceDate:date1];
NSLog(#"TimeTaken: %f", timeTaken); //outputs around 0.012
while ([resultset next])
{
[recordsArray addObject:[resultset resultDictionary]];
}
date2 = [NSDate date];
timeTaken = [date2 timeIntervalSinceDate:date1];
NSLog(#"TimeTaken 2: %f", timeTaken); //outputs around 5.5 seconds
I have also been able to determine that all the time taken is during the first time [resultset next] is called.
I have also tried modifying the query to remove the group by clause by generating a UNION'ed query like
NSString * query2 = #"select * from
(
select count(id), SUBSTR(purchase_date, 0,11) as purchase_date from sales where purchase_date = 2012-12-01
UNION
select count(id), SUBSTR(purchase_date, 0,11) as purchase_date from sales where purchase_date = 2012-12-02
UNION
select count(id), SUBSTR(purchase_date, 0,11) as purchase_date from sales where purchase_date = 2012-12-03
....
UNION
select count(id), SUBSTR(purchase_date, 0,11) as purchase_date from sales where purchase_date = 2012-12-31
) where purcase_date is not NULL order by purchase_date asc";
Executing this query also takes 0.2 seconds but the first call to [resultset next] and the time shoots to 7+ seconds.
Other Info
The table currently has 8000+ rows but that number can go as high as 100K in case of some of my users.
I am using this data to plot a graph for the sales trends for the given month.
On the simulator, this query executes in less that 0.5 seconds but on the device it takes a lot of time.
Question
Can you guide me how to bring down the time for this query?
I determined that the largest bottleneck was the SUBSTR and Group By clauses and executing & processing a simple query like the following only took around 0.02 seconds
Select purchase_date from sales where purchase_date LIKE '2012-12%' order by purchase_date asc;
So I introduced it as an an inner query
Select count(purcahse_date) as count, SUBSTR(purchase_date, 0, 11) as purchase_date from
(
Select purchase_date from sales where purchase_date LIKE '2012-12%'
)
group by purchase_date, order by purchase_date;
Although the data generated was same as the initial query the time again sky-rocketed to around 5.5 seconds as before.
So finally I decide to bite the bullet and my final solution till now is to get all the purchase_date records for the given month and process them by myself.
So Now the code looks like this
while ([resultset next])
{
[recordsArray addObject:[resultset resultDictionary]];
}
[resultset close];
[self closeDB];
//release the db lock at this point
int array[31]; //maximum days in a month
bzero((void *)array, 31 * sizeof(int)); //initialize the month array
for(NSDictionary * d in recordsArray) //read the records received from the db and add daily sales count
{
NSRange r;
r.location = 8;
r.length = 2;
int dDate = [[[d objectForKey:#"purchase_date"] substringWithRange:r] intValue];
array[dDate-1]++;
}
[recordsArray removeAllObjects];
//refDate contains #"2012-12"
for(int i=0; i<31; i++) //now populate the final array again
{
if(array[i] > 0)
{
NSDictionary * d1 = [NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:#"%#-%02d", refDate, i+1], #"date", [NSNumber numberWithInt:array[i]], #"count", nil];
[recordsArray addObject:d1];
}
}
return recordsArray;
I hope it helps someone else also stuck in a similar situation or some db guru might suggest some better alternative than this ugly solution.