Capture first character of last group of 1s in a binary series - tsql

I have a series something like this:
Month J F M A M J J A S O N D
Status 1 0 0 1 0 1 0 0 1 1 1 1
Using t-SQL, I am trying to capture the month corresponding to the first 1 in the last group of 1s, i.e., September in this example.
Here is the code I'm using:
IF OBJECT_ID('tempdb..#Temp1') IS NOT NULL DROP TABLE #Temp1
;WITH PARTITIONED1 AS
(SELECT , t0.ID
, t0.Year_Month
, t0.Status
, LAST_VALUE(t0.Year_Month) OVER (PARTITION BY t0.ID ORDER BY t0.Year_Month) AS D_YM
, ROW_NUMBER() OVER (PARTITION BY t0.ID ORDER BY t0.Year_Month) AS rn1
FROM #Temp0 t0
However, this just returns the first occurence of a 1; January here.
I really can't figure this one out, so any help would be very much appreciated.

Carefull with
although the ordering is performed in a previous stage
The previous sorting does not guarantee the later processing!
Try something like this. It is a very simple approach where you rely on gapless IDs:
DECLARE #tbl TABLE(ID INT IDENTITY,Mnth VARCHAR(100),[Status] TINYINT);
INSERT INTO #tbl VALUES
('J',1)
,('F',0)
,('M',0)
,('A',1)
,('M',0)
,('J',1)
,('J',0)
,('A',0)
,('S',1)
,('O',1)
,('N',1)
,('D',1);
SELECT a.*
FROM #tbl AS a
WHERE a.ID=(SELECT MAX(b.ID)+1 FROM #tbl AS b WHERE b.[Status]=0)

this can also be used :
select top 1 Month from table t where Status=1
and not exists
(select id from table t1 where stat=0 and t1.id>t.id)
order by t.id

I might have overcomplicated this but not knowing the table structure I put the below together:
IF OBJECT_ID('tempdb..#Temp1') IS NOT NULL DROP TABLE #Temp1
CREATE TABLE #Temp1
(
Jan int,
Feb int,
Mar int,
Apr int,
May int,
June int,
July int ,
Aug int,
Sep int,
Oct int,
Nov int,
Dec int
)
insert into #temp1
select
1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1
IF OBJECT_ID('tempdb..#monthTranslate') IS NOT NULL DROP TABLE #monthTranslate
create table #monthTranslate
(
MonthValue varchar(50),
MonthInt int
)
insert into #monthTranslate
select 'Jan',1
union all select 'Feb',2
union all select 'Mar',3
union all select 'Apr',4
union all select 'May',5
union all select 'June',6
union all select 'July',7
union all select 'Aug',8
union all select 'Sep',9
union all select 'OCt',10
union all select 'Nov',11
union all select 'Dec',12
--find the max month w\ 0 and add 1... becareful on null, it might return January incorrectly. I'd check for that in a a case statement
select max(b.MonthInt)+1
from
(
select
MonthPassVal, months , t.MonthInt
from
(
select Jan, Feb, Mar, Apr, May, June, July, Aug, Sep, Oct, Nov, Dec
from #temp1
) as r
Unpivot
(
MonthPassVal for Months
in (Jan, Feb, Mar, Apr, May, June, July, Aug, Sep, Oct, Nov, Dec)
) as u
inner join #monthTranslate t
on t.MonthValue = months
) as b
where
MonthPassVal=0

Related

T-SQL list of years and months between two dates

I have a table with a list of individuals that have a effective date and a termination date.
example Person 1, 20171201, 20180601
For each record, I need to output a list of years and months they were "active" between the two dates.
So the output would look like
Data Output
This is in SQL Server 2016
Any help would be appreciated!
Just because I did not see it offered. Here is yet another approach which uses an ad-hoc calendar table
Note the base date of 2000-01-01 and 10,000 days ... expand or contract if needed
Example
Declare #YourTable table (Person int,startdate date, enddate date)
Insert Into #YourTable values
(1,'20171201','20180601')
Select Distinct
A.Person
,ActiveYear = year(D)
,ActiveMonth = month(D)
From #YourTable A
Join (
Select Top 10000 D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),'2000-01-01')
From master..spt_values n1,master..spt_values n2
) B on D between startdate and enddate
Returns
Person ActiveYear ActiveMonth
1 2017 12
1 2018 1
1 2018 2
1 2018 3
1 2018 4
1 2018 5
1 2018 6
Based on info you've provided, I did a little guessing. Perhaps a recursive CTE will help:
DECLARE #tab TABLE (Person INT, EffectiveDate DATE, TerminationDate DATE)
INSERT #tab VALUES
(1, '2017-12-01', '2018-05-31'),
(2, '2017-10-01', '2018-01-01'),
(3, '2018-02-01', '2018-12-01')
;WITH t AS (
SELECT Person, EffectiveDate AS Dt
FROM #tab
UNION ALL
SELECT Person, DATEADD(mm,1,Dt)
FROM t
WHERE t.Dt < (SELECT DATEADD(mm,-1,TerminationDate) FROM #tab tt WHERE tt.Person = t.Person)
)
SELECT *
FROM t
ORDER BY Dt
Then take the DATEPART()
SELECT t.Person
, t.Dt
, DATEPART(yyyy, t.Dt) ActiveYear
, DATEPART(mm, t.Dt) ActiveMonth
FROM t
ORDER BY Dt
Welcome to stackoverflow! For better help it's good to include DDL easily consumable sample data that we can use to quickly re-create what you are doing and provide a solution. Note this sample data:
DECLARE #yourtable TABLE(person VARCHAR(20), date1 DATE, date2 DATE)
INSERT #yourtable (person, date1, date2)
VALUES ('Person1','20171201', '20180601'), ('Person2','20171001', '20180101'),
('Person3','20180101', '20180301');
SELECT t.* FROM #yourtable AS t;
Returns:
person date1 date2
-------------------- ---------- ----------
Person1 2017-12-01 2018-06-01
Person2 2017-10-01 2018-01-01
Person3 2018-01-01 2018-03-01
(I added a couple rows). Here's my solution:
DECLARE #yourtable TABLE(person VARCHAR(20), date1 DATE, date2 DATE)
INSERT #yourtable (person, date1, date2)
VALUES ('Person1','20171201', '20180601'), ('Person2','20171001', '20180101'),
('Person3','20180101', '20180301');
SELECT
Person = t.person,
ActiveYear = YEAR(st.DT),
ActiveMonth = MONTH(st.Dt)
FROM #yourtable AS t
CROSS APPLY
(
SELECT TOP (DATEDIFF(MONTH,t.date1,t.date2)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS a(x)
CROSS JOIN (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS b(x)
) AS iTally(N)
CROSS APPLY (VALUES(DATEADD(MONTH, iTally.N-1, t.date1))) AS st(Dt);
Which returns:
Person ActiveYear ActiveMonth
-------------------- ----------- -----------
Person1 2017 12
Person1 2018 1
Person1 2018 2
Person1 2018 3
Person1 2018 4
Person1 2018 5
Person2 2017 10
Person2 2017 11
Person2 2017 12
Person3 2018 1
Person3 2018 2
Let me know if you have questions.

Postgres - convert null result column to zero in crosstab query

I have a crosstab query that is working fine. It is based on customers and their total transactions, split by months of the year.
The only issue is that when there is no data for a column (ie no activity for a month) then I get a null value which I would like to convert to a zero.
I have tried coalesce on the 'amount' field, but that does not work.
If anyone has any pointers to help I would be very grateful.
The query is:
select *
from crosstab(
$ct$
SELECT sa.id,
company.name,
to_char(sat.transaction_date, 'YYYY-MM') AS my,
COALESCE(sat.amount,0) AS amnt
FROM sales_account_transactions sat
JOIN sales_account sa ON sa.id = sat.sales_account
JOIN company ON sa.company = company.id
WhERE sat.financial_company = 1
AND sat.transaction_date BETWEEN '2018-01-01' AND '2018-03-31'
AND sat.reversed_by = 0
AND sat.original_id = 0
GROUP BY sa.id, company.name, my, amnt
ORDER BY company.name, to_char(sat.transaction_date, 'YYYY-MM');
$ct$,
$$VALUES
('2018-01'), ('2018-02'), ('2018-03')
$$
)
as ct(id int, name text,
"Jan 2018" int, "Feb 2018" int, "Mar 2018" int);
Instead of select *, use select coalesce("Jan 2018", 0) as "Jan 2018", ...

How to find the First, Second, Third and Fourth Saturday in month?

How do I find the first, second, third and fourth saturday of the month?
Ex.: I want to end up with this format...
Blockquote
YYYY, MM, Week#1
Blockquote
YYYY, MM, Week#2
Blockquote
Thanks,
This solution does not depend on Datefirst setting.
declare #d datetime = getdate();
select
dateadd(dd, n, firstSaturday)
from (
select
firstSaturday = dateadd(day, 7-(##datefirst+datepart(weekday, dateadd(day,-1, convert(char(6),#d,112)+'01')))%7, dateadd(day,-1, convert(char(6),#d,112)+'01'))
) t
cross apply (values (0), (7), (14), (21)) q(n)
Here is one way to do it, using a stacked cte to create an inline tally table, with another cte on top of that to generate the months calendar.
Please note that you can change the GETDATE() in the first row code to any date you want (even if it's in the middle of the month) and the code will produce all the Saturdays in that month.
-- Get the current month's start date
DECLARE #MonthStart datetime = DATEADD(MONTH, (DATEDIFF(MONTH, 0, GETDATE())), 0)
;WITH lv0 AS (SELECT 0 g UNION ALL SELECT 0)
,lv1 AS (SELECT 0 g FROM lv0 a CROSS JOIN lv0 b) -- 4
,lv2 AS (SELECT 0 g FROM lv1 a CROSS JOIN lv1 b) -- 16
,lv3 AS (SELECT 0 g FROM lv2 a CROSS JOIN lv2 b) -- 256
,Tally (n) AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM lv3)
-- gets all the dates in the current month
,CurrentMonth AS (SELECT TOP (32) dateadd(day, n-1, #MonthStart) As TheDate
FROM Tally
WHERE MONTH(dateadd(day, n-1, #MonthStart)) = MONTH(#MonthStart)
ORDER BY n)
-- gets all the Saturday dates of the current month
SELECT TheDate, DATENAME(WEEKDAY, TheDate)
FROM CurrentMonth
WHERE DATEPART(WEEKDAY, TheDate) = 7 -- Depending on server settings!
If you already have a numbers table, you can use it instead of the stacked cte. If you don't know what is a numbers table and why you should have one, read The "Numbers" or "Tally" Table: What it is and how it replaces a loop by Jeff Moden
You can try this.
SET DATEFIRST 1
DECLARE #MonthId INT = 5
DECLARE #FirstDayOfTheMonth DATE = CONCAT(YEAR(GETDATE()), RIGHT(CONCAT('00', #MonthId),2), '01')
DECLARE #SaturdayId INT = 6
SELECT
DATEADD(DAY, #SaturdayId + WK.ID - DATEPART(WEEKDAY, #FirstDayOfTheMonth), #FirstDayOfTheMonth)
FROM ( VALUES(0),(7),(14),(21),(28)) AS WK(ID)
WHERE
MONTH(DATEADD(DAY, #SaturdayId + WK.ID- DATEPART(WEEKDAY, #FirstDayOfTheMonth),#FirstDayOfTheMonth)) = #MonthId

T-SQL get months from ranges of date

I have a Table with id and start date and end date. i want insert into another table, end of each month between the start data and end date and the ID, e.g.
ID Start Date End Date
1 2012-01-01 2012-03-31
2 2012-10-01 2012-12-31
Results
ID MONTH END
1 2012-01-31
1 2012-02-29
1 2012-03-31
2 2012-10-31
2 2012-11-30
2 2012-12-31
This answer makes some assumptions - no end-dates greater than start-dates, but you should see how it works. It creates a recursive union CTE and uses that to figure out the end dates
CREATE TABLE #Dates
(
ID INT IDENTITY PRIMARY KEY,
START_DATE DATETIME2(0) NOT NULL,
END_DATE DATETIME2(0) NOT NULL
)
INSERT INTO #Dates VALUES ('2012-01-01', '2012-03-31'), ('2012-10-01','2012-12-31')
WITH MONTHS ([ID],[Month],[Date], [End])
AS
(
SELECT ID, DATEPART(m,START_DATE) AS [Month], START_DATE AS [Date], DATEADD(s,-1,DATEADD(m,DATEDIFF(m,0,START_DATE)+1,0)) as [End]
FROM #Dates
UNION ALL
SELECT D.ID, DATEPART(m,DATEADD(m,1,[Date])),DATEADD(m,1,[Date]), DATEADD(s,-1,DATEADD(m,DATEDIFF(m,0,DATEADD(m,1,[Date]))+1,0)) as [End]
FROM #Dates D
INNER JOIN MONTHS M
ON D.ID = M.ID
WHERE DATEADD(m,1,[Date]) < [END_DATE]
)
SELECT *
FROM MONTHS ORDER BY ID, Date
DROP TABLE #Dates

generate new column using other columns

I am stuck with generating a new column. The table has three columns(C_ID, C_rank, Date).
C_ID C_ Rank NewColumn(Cycle) Date
42 A 1 October 14, 2010
42 B 1 October 26, 2010
42 A 2 February 16, 2011
43 A 1 December 17, 2010
44 A 1 July 28, 2010
44 B 1 August 10, 2010
44 A 2 January 11, 2011
44 B 2 January 28, 2011
45 A 1 July 30, 2010
45 B 1 August 9, 2010
45 B 1 September 24, 2010
45 A 2 April 5, 2011
45 B 2 April 26, 2011
I want to generate one more column called Cycle in such a way that for each C_ID, it should generate the number start from one and increment the number from next C_rank = 'A' (a shown above).
I tried using row_number, but no luck.
Maybe some loop option till next C_Rank = 'A' works.
How can this be done?
You should be able to get this done using ROW_NUMBER() and PARTITION BY
;WITH YourDataCTE AS
(
SELECT
C_ID, C_Rank, Date,
ROW_NUMBER() OVER(PARTITION BY C_ID,C_Rank ORDER BY Date DESC) AS 'Cycle'
FROM
dbo.YourTable
)
SELECT *
FROM YourDataCTE
Does that do what you're looking for??
The PARTITION BY C_ID,C_Rank will cause the ROW_NUMBER to start at 1 again for each different value of C_ID,C_Rank - I didn't know what ORDER BY clause within a single partition (a single value of C_ID,C_Rank) you're looking for and just guessed it might be Date DESC (newest date first).
You could count the number of previous A's in a subquery:
select *
, (
select count(*)
from #YourTable yt2
where yt2.C_ID = yt1.C_ID
and yt2.C_Rank = 'A'
and yt2.Date <= yt1.Date
) as Cycle
from #YourTable yt1
order by
C_ID, Date
Example at ODATA.
Do a self join for all records with the same C_ID, a previous date, and a C_Rank='A' and count them.
select t1.C_ID, t1.C_Rank, count(t2.C_Rank) Cycle, t1.Date
from MyTable t1
left join MyTable t2 on t1.C_ID=t2.C_ID
and t2.Date<=t1.Date
and t2.C_Rank='A'
group by t1.C_ID, t1.C_Rank, t1.Date
order by t1.C_ID, t1.Date
Below code fulfill the requirement:
create table #Temp_Table
(
C_ID int
, C_Rank char(1)
, Date datetime
, NewColumn int
)
insert into #Temp_Table
(
C_ID
, C_Rank
, Date
)
select 42, ‘A’, ’10/14/2010′
union all
select 42, ‘B’, ’10/26/2010′
union all
select 42, ‘B’, ’10/14/2010′
union all
select 42, ‘C’, ’10/26/2010′
union all
select 42, ‘A’,’02/16/2011′
union all
select 43, ‘A’, ’12/17/2010′
union all
select 44, ‘A’, ’07/28/2010′
union all
select 44, ‘B’, ’08/10/2010′
union all
select 44, ‘A’, ’01/11/2011′
union all
select 44, ‘B’, ’01/28/2011′
union all
select 44, ‘C’, ’10/14/2010′
union all
select 44, ‘D’, ’10/26/2010′
Select ‘Original Data’ Comment
,*
from #Temp_Table
/*
This would be Actual Script to get the New ID based on information you provided
*/
Declare #Count int
,#C_ID int
,#C_Rank char(1)
,#total_Count int
,#Count_Partition int
,#Previous_ID int
Declare #Table Table (ID int IDENTITY(1,1), C_ID int, C_Rank char(1), Date datetime, NewColumn int )
Set #Count = 1
Set #Count_Partition = 0
insert into #Table
Select *
from #Temp_Table
Select #total_Count = ISNULL(MAX(ID),0)
from #Table
While #Count < = #total_Count
Begin
Select #C_ID = C_ID
,#C_Rank = C_Rank
From #Table
Where ID = #Count
If #Count = 1
Set #Previous_ID = #C_ID
If #Previous_ID != #C_ID
Set #Count_Partition = 1
Else If #C_Rank = 'A'
Set #Count_Partition = #Count_Partition + 1
update #Table
Set NewColumn = #Count_Partition
Where ID = #Count
Set #Previous_ID = #C_ID
Set #Count = #Count + 1
End
Select C_ID
, C_Rank
, [Date]
, NewColumn
from #Table
–Drop table #Temp_Table