t-sql - Apply function to a column without sqlFunction - sql-server

I've to transform a numeric code in an alphabetic code like this:
1234 -> ABCD
where 0 = 0, 1 = A, 2 = b, etc..
This is my function and how to use:
Create function dbo.DecodeNumToChar
(
#change varchar(10),
#foo varchar(10)
) returns varchar(10)
as
begin
DECLARE #II int = 1,
#result varchar(10)
;WITH x AS
(
SELECT #II as ii, STUFF(#foo,#II,1,SUBSTRING(#change,CAST(SUBSTRING(#foo,#II,1) AS INT)+1,1)) AS AA
UNION ALL
--SELECT #II+1
SELECT ii+1, STUFF(AA,ii+1,1,SUBSTRING(#change,CAST(SUBSTRING(#foo,ii+1,1) AS INT)+1,1)) AS AA
FROM x
where ii+1 <= LEN(#foo)
)
select top 1 #result = AA from x order by ii desc
return #result
end
--------------------------------------------
select brand_code, dbo.DecodeNumToChar('0ABCDEFGHI', brand_code)
from (VALUES('1234'),('5834'),('9905'),('0250')) as t(brand_code)
This function working well, but in production DB we do not have permission to create functions.
I've tryed to transform this function into CTE like this
declare #change varchar(9) = 'ABCDEFGHI'
DECLARE #II int = 0
;WITH x AS
(
SELECT TOP (10) n = ROW_NUMBER() OVER (ORDER BY Number)
FROM master.dbo.spt_values ORDER BY Number
),
innerCTE as
(
SELECT x.n, SUBSTRING(t.brand_code, x.n, 1) chnum,
case SUBSTRING(t.brand_code, x.n, 1)
when '0' then '0'
else char(65-1+SUBSTRING(t.brand_code, x.n, 1))
end chalfa, t.brand_code
FROM x INNER JOIN (VALUES('1234'),('5834'),('9905'),('0250')) as t(brand_code)
ON x.n <= LEN(t.brand_code)
),
CTE as
(
select n, chnum, chalfa, brand_code, stuff(brand_code, n, 1, chalfa) as code
from innerCTE
union all
select n+1, chnum, chalfa, brand_code, STUFF(code, n+1, 1, chalfa) as code
from cte
where n+1 <= LEN(cte.brand_code)
)
--select * from innerCTE
select * from CTE;
or using CROSS APPLY like this example: Example_1
or using CROSS APPLY with PIVOT like this example: Example_2
but my experience on SQL is low and I have not been able to get the correct result.
I'd like to have this:
brand_code decoded_code
1234 ABCD
5834 EHCD
9905 II0E
0250 0BE0
thanks

If using SQL 2017+
DECLARE #integerValues TABLE ([I] INT);
INSERT INTO #integerValues ([I])
VALUES
(1234),
(6485834),
(99084705),
(1124601);
SELECT
T.[I],
STRING_AGG(
CASE SUBSTRING(T.[S], V.[number] + 1, 1)
WHEN '9' THEN 'I'
WHEN '8' THEN 'H'
WHEN '7' THEN 'G'
WHEN '6' THEN 'F'
WHEN '5' THEN 'E'
WHEN '4' THEN 'D'
WHEN '3' THEN 'C'
WHEN '2' THEN 'B'
WHEN '1' THEN 'A'
ELSE '0'
END,
'') WITHIN GROUP (ORDER BY T.[I]) [S]
FROM
(SELECT [I], CAST([I] AS VARCHAR(10)) [S] FROM #integerValues) T
JOIN
[master]..[spt_values] V ON V.[number] < LEN(T.[S])
WHERE
V.[type] = 'P';
or, if 2016 or earlier
DECLARE #integerValues TABLE ([I] INT);
INSERT INTO #integerValues ([I])
VALUES
(1234),
(6485834),
(99084705),
(1124601);
SELECT
[I],
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
CAST([I] AS VARCHAR(10)),
'1',
'A'),
'2',
'B'),
'3',
'C'),
'4',
'D'),
'5',
'E'),
'6',
'F'),
'7',
'G'),
'8',
'H'),
'9',
'I') [S]
FROM
#integerValues;

The following solution uses a FOR XML subquery and some mod (%) operations against the integer value to split each digit, then a mapping table to relate each digit with a character. The FOR XML returns the digits back in order.
DECLARE #IntegerValues TABLE (Integer INT)
INSERT INTO #IntegerValues (Integer)
VALUES
(1234),
(6485834),
(99084705),
(1124601)
SELECT
T.Integer,
Conversion = (
SELECT
'' + M.Character -- Must have no declared alias (for xml)
FROM
(VALUES
(1, T.Integer % 10),
(2, T.Integer / 10 % 10),
(3, T.Integer / 100 % 10),
(4, T.Integer / 1000 % 10),
(5, T.Integer / 10000 % 10),
(6, T.Integer / 100000 % 10),
(7, T.Integer / 1000000 % 10),
(8, T.Integer / 10000000 % 10),
(9, T.Integer / 100000000 % 10),
(10, T.Integer / 1000000000 % 10),
(11, T.Integer / 10000000000 % 10)
) AS X(OrdinalPosition, SplitDigit)
INNER JOIN (VALUES
(0, '0'),
(1, 'A'),
(2, 'B'),
(3, 'C'),
(4, 'D'),
(5, 'E'),
(6, 'F'),
(7, 'G'),
(8, 'H'),
(9, 'I')
) AS M(Digit, Character) ON X.SplitDigit = M.Digit
WHERE
X.OrdinalPosition <= FLOOR(LOG10(T.Integer)) + 1 -- This expression returns the number of digits
ORDER BY
X.OrdinalPosition DESC
FOR XML
PATH('')
)
FROM
#IntegerValues AS T
Result:
Integer Conversion
1234 ABCD
6485834 FDHEHCD
99084705 II0HDG0E
1124601 AABDF0A
There is probably a cleaner way to write the mappings and the mod operations, but this should give an idea.

I've find a solution using recursive CTE.
declare #change varchar(10) = '0ABCDEFGHI'
DECLARE #II int = 1;
with BRANDS as
(
select * from (VALUES('1234'),('5834'),('9905'),('0250')) as t(brand_code)
),
CTE as
(
select #II as ii, BRAND_CODE,
STUFF(brand_code,#II,1,SUBSTRING(#change,CAST(SUBSTRING(brand_code,#II,1) AS INT)+1,1)) AS AA
from BRANDS
union all
select c.ii+1, b.BRAND_CODE,
STUFF(AA,c.ii+1,1,SUBSTRING(#change,CAST(SUBSTRING(AA,c.ii+1,1) AS INT)+1,1)) AS AA
from BRANDS b
inner join CTE c on b.BRAND_CODE = c.BRAND_CODE
where c.ii < LEN(b.BRAND_CODE)
)
select BRAND_CODE, AA as NewCode from CTE where ii = len(brand_code) order by BRAND_CODE, ii

This is the code for the suggestion from #Martin Smith to use 10 nested replace.
DECLARE #change char(10) = '0ABCDEFGHI';
--------------------------------------------
select brand_code,
REPLACE( REPLACE( REPLACE( REPLACE( REPLACE(
REPLACE( REPLACE( REPLACE( REPLACE( REPLACE( brand_code
, '0', SUBSTRING( #change, 1, 1)) , '1', SUBSTRING( #change, 2, 1))
, '2', SUBSTRING( #change, 3, 1)) , '3', SUBSTRING( #change, 4, 1))
, '4', SUBSTRING( #change, 5, 1)) , '5', SUBSTRING( #change, 6, 1))
, '6', SUBSTRING( #change, 7, 1)) , '7', SUBSTRING( #change, 8, 1))
, '8', SUBSTRING( #change, 9, 1)) , '9', SUBSTRING( #change, 10, 1))
from (VALUES('1234'),('5834'),('9905'),('0250')) as t(brand_code);

Related

I want to convert some data to pivot in SQL Server with join and dynamic

The table, at last, is my target.
This is my demo database
create database pvtestDb;
go
use pvtestDb;
go
create table custTransaction
(
id int,
custNum int,
value nvarchar(50)
)
go
create table customers
(
id int,
custName nvarchar(50)
)
insert into Customers(id, custName)
values (1, 'aaa'), (2, 'bbb'), (3, 'ccc'), (4, 'ddd'),
(5, 'eee'), (6, 'fff'), (7, 'ggg'), (8, 'hhh'), (9, 'iii')
insert into custTransaction (id, custNum, value)
values (1, 3, 'a'), (1, 4, 'b'), (1, 5, 'c'),
(2, 3, 'd'), (2, 4, 'e'), (2, 6, 'f'),
(3, 3, 'g'), (3, 8, 'h'), (3, 9, 'i')
select * from customers
select * from custTransaction
select custName, custNum, value
from customers
join custTransaction on custTransaction.id = customers.id
I tried code like this but not worked at all
SELECT
custNum, [a], [b], [c], [d]
FROM
customers
JOIN
custTransaction ON custTransaction.id = customers.id
PIVOT
(COUNT([custName])
FOR [custName] IN ([a], [b], [c], [d])) AS p
I need to join between the two tables in first.
Any hints would be appreciated as I am stuck with this situation
Here's approach with dynamic SQL
declare #customers varchar(8000)
declare #sql varchar(8000)
select #customers = stuff((
select ',' + quotename(custName)
from customers
for xml path('')
), 1, 1, '')
set #sql = 'select
id, ' + #customers + '
from (
select
ct.id, c.custName, ct.value
from
customers c
join custTransaction ct on ct.custNum = c.id
) t
pivot (
max(value) for custName in (' + #customers + ')
) p'
exec (#sql)
Output
id aaa bbb ccc ddd eee fff ggg hhh iii
----------------------------------------------------------------
1 NULL NULL a b c NULL NULL NULL NULL
2 NULL NULL d e NULL f NULL NULL NULL
3 NULL NULL g NULL NULL NULL NULL h i

How to remove redundant conditions in **IN** query Sql

I have this kind of query. But i need to optimize this query so how can omit redundant conditions with same split function.
DECLARE #Filter nvarchar(20)
SELECT #Filter ='5,22,3'
SELECT * FROM Employee e
WHERE e.code IN
(
CASE WHEN((SELECT count(*) FROM dbo.FNSPLITSTRING(SUBSTRING(#Filter,1,LEN(#Filter)-1), ',') d
WHERE d.splitdata IN (5, 16, 20, 23, 33, 49, 62, 90, 91, 92, 93, 94))>0) THEN 5 ELSE 0 END
,CASE WHEN((SELECT count(*) FROM dbo.FNSPLITSTRING(SUBSTRING(#Filter,1,LEN(#Filter)-1), ',') d
WHERE d.splitdata IN (22, 18))>0) THEN 46 ELSE 0 END
,CASE WHEN((SELECT count(*) FROM dbo.FNSPLITSTRING(SUBSTRING(#Filter,1,LEN(#Filter)-1), ',') d
WHERE d.splitdata IN (3, 28))>0) THEN 3 ELSE 0 END
)
As #Damien_The_Unbeliever said, avoid split strings as table of values. You do not need to split the same string multiple times. Instead you can use a temporary table variable.
DECLARE #SplitStrings TABLE
(
splitdata int
)
INSERT #SplitStrings
SELECT splitdata FROM dbo.FNSPLITSTRING(SUBSTRING(#Filter,1,LEN(#Filter)-1), ',') d
DECLARE #EmployeeCodes TABLE
(
splitdata INT,
code int
)
INSERT #EmployeeCodes (splitdata, code)
VALUES (5, 5), (16, 5), (20, 5), (23, 5), (33, 5), (49, 5), (62, 5), (90, 5), (91, 5), (92, 5), (93, 5), (94, 5),
(22, 46), (18, 46),
(3, 3), (28, 3)
SELECT e.*
FROM Employee e
JOIN #EmployeeCodes ec
ON e.code = ec.code
JOIN #SplitStrings ss
ON ec.splitdata = ss.splitdata
I hope this is the direction you are looking at.
Note: Assuming that you do not need 0 as employee code.
I have done new way. Less complex and omit redundant code.
DECLARE #Filter nvarchar(20)
SELECT #Filter ='5,22,3'
SELECT Distinct e.EmployeeId FROM Employee e
CROSS JOIN dbo.fnSplitString(#Filter, ',') AS d
WHERE
(e.code = 5 AND d.splitdata IN ('5', '16', '20', '23', '33', '49', '62', '90', '91', '92', '93', '94'))
OR (e.code = 46 AND d.splitdata IN ('22', '18'))
OR (e.code = 3 AND d.splitdata IN ('3', '28'))

Get parents and children given a segment in a recursion SQL

Given an ID at the beginning or middle or end, I want to look up for all the rows that are linked between them.
With this I found the child elements recursively
declare #T table(
Id int primary key,
Name nvarchar(255) not null,
ParentId int)
insert into #T values
(1, 'A', NULL),
(2, 'B', 1),
(3, 'C', 2),
(4, 'D', NULL),
(5, 'E', 1)
declare #Id int = 2
;with cte as
(
select T.*
from #T as T
where T.Id = #Id
union all
select T.*
from #T as T
inner join cte as C
on T.ParentId = C.Id
)
select *
from cte
If #Id is equals 2, I will get 2 and 3. But I want to retrieve the parent too, in this case 1.
I want to get this:
If id = 1
1 A NULL
2 B 1
3 C 2
5 E 1
If id = 2
1 A NULL
2 B 1
3 C 2
5 E 1
If id = 3
1 A NULL
2 B 1
3 C 2
5 E 1
If id = 4
4 D NULL
IF id = 5
1 A NULL
2 B 1
3 C 2
5 E 1
How can I fix this?
Ok! I've got you:
I created a separate family for testing.
The first thing you need to do is create a query to find the oldest ancestor of your chosen ID. That is the first CTE below. Then you designate that, I called it #Eve and find Eve, all children of Eve.
Here is the example you can play around with
create table UserType (
Id int,
name nvarchar(255),
parentid int)
insert into UserType values
(1, 'A', NULL),
(2, 'B', 1),
(3, 'C', 2),
(4, 'D', NULL),
(5, 'E', 1),
(6, 'F', NULL),
(7, 'G', 6),
(8, 'H', 7);
DECLARE #eve BIGINT; --ancestor
DECLARE #id BIGINT;
SET #id = 8; --This is where you choose
WITH tblParent AS --CTE for oldest ancestor
(
SELECT *
FROM UserType WHERE Id = #id
UNION ALL
SELECT UserType.*
FROM UserType JOIN tblParent ON UserType.Id = tblParent.ParentId
)
select
#eve = (select top 1 id from tblParent order by id);
WITH tblChild AS --CTE for all #eve and all children
(
SELECT *
FROM UserType WHERE id = #eve
UNION ALL
SELECT UserType.* FROM UserType JOIN tblChild ON UserType.ParentId = tblChild.Id
)
SELECT * FROM tblChild
order by id
OPTION(MAXRECURSION 32767)
Props to CodeProject which was quite helpful.
Try something like this.
declare #tbl table( --test table var
Id int,
name nvarchar(255),
parentid int)
insert into #tbl values -- test data
(1, 'A', NULL),
(2, 'B', 1),
(3, 'C', 2),
(4, 'D', NULL),
(5, 'E', 1),
(6, 'F', NULL),
(7, 'G', 6),
(8, 'H', 7);
declare #id int = 7
;with parents as (
select id,name,parentid, 0 lvl
from #tbl
where id=#id
union all
select t.id,t.name,t.parentid, lvl-1
from #tbl t
inner join parents p on t.Id=p.parentid --up the tree
)
,children as (
select id,name,parentid, 0 lvl
from #tbl
where id=#id --select single record
union all
select t.id,t.name,t.parentid, lvl+1
from #tbl t
inner join children c on c.Id=t.parentid -- down the tree
)
select * from parents
union --combine results
select * from children
order by lvl

SQL Server: String insertion by position

I want to insert a set of values into a given string at specified positions. I couldn't find an existing solution for this so I created the following query which does so. I am wondering if there is a way to simplify it, especially the portion which queries the cte and creates the NewString. Also, is there a way to construct the cte recursion without involving the ID column? The ID (and the rest) seems to work fine, just wondering if it could be tweaked to make it more organic/elegant or if there is a better solution overall. The different groups are really only for testing purposes; each group corresponds to a possible insertion position scenario. Thus, there will not be groups involved when I use it.
declare
#String varchar(200),
#StringLen int,
#GroupID int,
#PositionMax int
declare #Chars table (
ID int identity,
GroupID int,
Position int,
Value varchar(20)
)
select
#String = 'abcde',
#StringLen = len(#String),
#GroupID = 1
--Affix
--[P]refix
--[I]nfix
--[S]uffix
insert #Chars
select
GroupID,
Position,
Value
from (
values
(1, 0, 'X'), --P
(2, 2, 'Y'), --I
(3, 5, 'Z'), --S
(4, 0, 'X'), --P
(4, 2, 'Y'), --I
(5, 2, 'Y'), --I
(5, 5, 'Z'), --S
(6, 0, 'X'), --P
(6, 5, 'Z'), --S
(7, 0, 'X'), --P
(7, 2, 'Y'), --I
(7, 5, 'Z'), --S
(8, 2, 'Y1'), --I
(8, 4, 'Y2'), --I
(9, 0, 'X'), --P
(9, 2, 'Y1'), --I
(9, 4, 'Y2'), --I
(10, 2, 'Y1'), --I
(10, 4, 'Y2'), --I
(10, 5, 'Z'), --S
(11, 0, 'X'), --P
(11, 2, 'Y1'), --I
(11, 4, 'Y2'), --I
(11, 5, 'Z') --S
) as T(GroupID, Position, Value)
order by GroupID, Position
;with cte (
ID,
GroupID,
LeftString,
Value,
RightString,
Position
) as (
select
ID,
GroupID,
LeftString,
Value,
RightString,
Position
from (
select
row_number() over (partition by GroupID order by ID) as RowNumber,
ID,
GroupID,
cast(left(#String, Position) as varchar(200)) as LeftString,
Value,
cast(right(#String, #StringLen - Position) as varchar(200)) as RightString,
Position
from #Chars
) as C
where RowNumber = 1
union all
select
vc.ID,
vc.GroupID,
cast(left(RightString, vc.Position - cte.Position) as varchar(200)) as LeftString,
vc.Value,
cast(right(RightString, #StringLen - vc.Position) as varchar(200)) as RightString,
vc.Position
from #Chars vc
join cte cte
on cte.GroupID = vc.GroupID
and cte.ID + 1 = vc.ID
)
select
GroupID,
case
when LSLenSumMax < #StringLen
then NewString + RightString
else NewString
end as NewString
from (
select
GroupID,
max(LSLenSum) as LSLenSumMax,
RightString,
stuff((
select
LeftString + Value
from cte cte
where cte.GroupID = cteLR.GroupID
for xml path(''), type
).value('(./text())[1]', 'varchar(max)'), 1, 0, '') as NewString
from (
select
GroupID,
(select sum(len(LeftString)) from cte cteL where cteL.groupID = cte.groupID) as LSLenSum,
(select top 1 RightString from cte cteR where cteR.groupID = cte.groupID order by cteR.ID desc) as RightString
from cte cte
) as cteLR
group by
GroupID,
RightString
) as C
order by GroupID
You can implement a custom
aggregate function... Or try this: A recursive scalar function:
CREATE FUNCTION dbo.GetPrefixTo (#GroupID INT, #Position INT, #String CHAR(200))
RETURNS Char(200)
WITH EXECUTE AS CALLER
AS
BEGIN
DECLARE #str CHAR(200) = NULL
DECLARE #beforePosition INT = 0
IF (SELECT COUNT(*) FROM Chars WHERE GroupID = #GroupID AND Position < #Position) > 0
BEGIN
SELECT #beforePosition = MAX(Position) FROM chars WHERE GroupID = #GroupID AND Position < #Position
select #str = RTRIM(dbo.GetPrefixTo(#GroupID, #beforePosition, substring(#String, 1, #Position))) +
+ RTrim(Value) + substring(#String, #Position + 1, len(#string) + 1)
FROM Chars WHERE GroupID = #GroupID AND Position = #Position
END
ELSE
SELECT #str = substring(#String, 1, #Position) + RTrim(Value) + substring(#String, #Position + 1, len(#string) + 1) FROM Chars
WHERE GroupID = #GroupID AND Position = #Position
RETURN #str
END
And group by GroupID and aggregate max(position):
SELECT groupID, max(Position)
, dbo.GetPrefixTo(groupID, max(Position), 'abcde')
FROM Chars
GROUP BY GroupID
This is the result:
1 0 Xabcde
2 2 abYcde
3 5 abcdeZ
4 2 XabYcde
5 5 abYcdeZ
6 5 XabcdeZ
7 5 XabYcdeZ
8 4 abY1cdY2e
9 4 XabY1cdY2e
10 5 abY1cdY2eZ
11 5 XabY1cdY2eZ
========================================================================

Aggregate bitfield values with binary OR

I have a table with int values being used as bitfields (where each bit is a flag).
Now I would like to aggregate them with a binary operation (in my case OR) so that:
SELECT 1 AS bitfield
INTO #TABLE
UNION ALL SELECT 1 + 2 + 8 + 32
UNION ALL SELECT 2 + 128
UNION ALL SELECT 2 + 32
SELECT AND_AGGR(bitfield) -- Invalid because AND_AGGR doesn't exist
FROM #TABLE
DROP #TABLE
would result in the value 171
What would be a good way to do this that hopefully doesn't require a lot of | and MAX (but if you must, you must)?
I am using MS SQL Server 2008 myself, but solutions on other servers are also of interest.
On MySQL and PostgreSQL you can use BIT_OR.
I don't think SQL Server has this aggregate function.
You could do it with lots of MAX and & as you said:
MAX(x & 1) + MAX(x & 2) + ... + MAX(x & 128)
If you're expecting the result 171, surely you mean binary OR not AND?
In any case, this solution aggregates the values into a variable:
SELECT 1 AS bitfield
INTO #TABLE
UNION ALL SELECT 1 + 2 + 8 + 32
UNION ALL SELECT 2 + 128
UNION ALL SELECT 2 + 32
DECLARE #i int = 0
SELECT #i = #i | bitfield
FROM #TABLE
SELECT #i
DROP TABLE #table
This might not meet your requirements if you want to group the aggregation by another field.
It is also unlikely to perform well on a large table.
In MS SQL Server
DECLARE #agg VARCHAR(MAX) = '0001,0011,0101,0101,0101'
SELECT CONVERT(binary(4), VALUE, 2) , VALUE FROM STRING_SPLIT( #agg , ',')
DECLARE #sum AS BIGINT = 0
DECLARE #mul AS BIGINT = 0xffffffff
SELECT #sum |= v
, #mul &= v
FROM STRING_SPLIT( #agg , ',')
CROSS APPLY (VALUES (CONVERT(binary(4), VALUE, 2))) _(v)
PRINT FORMAT(#sum,'X8')
PRINT FORMAT(#mul,'X8')
Prints
VALUE
---------- ------------
0x00010000 0001
0x00110000 0011
0x01010000 0101
0x01010000 0101
0x01010000 0101
01110000
00010000
In more complex word you need:
CREATE OR ALTER FUNCTION dbo.BOR( #agg VARCHAR(MAX))
RETURNS BIGINT
AS
BEGIN
DECLARE #sum AS BIGINT = 0
SELECT #sum |= CONVERT(BIGINT, VALUE)
FROM STRING_SPLIT( #agg , ',')
RETURN #sum
END
GO
CREATE OR ALTER FUNCTION dbo.BAND( #agg VARCHAR(MAX))
RETURNS BIGINT
AS
BEGIN
DECLARE #mul AS BIGINT = 0xffffffffffffffff
SELECT #mul &= CONVERT(BIGINT, VALUE)
FROM STRING_SPLIT( #agg , ',')
RETURN #mul
END
GO
when using bitmap of payment periods
;WITH delayedPayment AS
(SELECT * FROM ( VALUES
( 123, 67, '2020-2-1')
,( 123, 67, '2020-4-1')
,( 123, 67, '2020-5-1')
,( 123, 67, '2020-6-1')
,( 123, 68, '2020-6-1') -- another agreement
,( 123, 67, '2020-12-1')
,( 456, 69, '2020-4-1')
,( 456, 69, '2020-8-1')
,( 456, 69, '2020-10-1')
,( 456, 69, '2020-11-1')) _(cuno, loan, missedDuedate)
)
, delayPattern AS
(SELECT cuno
, sum_months
, bor_months
, IIF( FORMAT( CAST(bor_months AS BIGINT), 'X16') LIKE '%111%', 'dalyad 3+ month in row', NULL) delayState
FROM (SELECT cuno
, SUM(POWER( 16.0, CONVERT( BIGINT, DATEDIFF( month, missedDuedate, '2020-12-1')))) sum_months
, dbo.BOR( STRING_AGG( CONVERT( BIGINT, POWER( 16.0, DATEDIFF( month, missedDuedate, '2020-12-1'))),',')) bor_months
FROM delayedPayment
GROUP BY cuno
) s
)
SELECT cuno
, FORMAT( CAST(sum_months AS BIGINT), 'X16') sum_months
, FORMAT( CAST(bor_months AS BIGINT), 'X16') bor_months
, delayState
FROM delayPattern
cuno sum_months bor_months delayState
123 00000*10112*000001 00000*10111*000001 dalyad 3+ month in row
456 0000000100010110 0000000100010110 NULL
But sometimes just one need to think and you can do it with SUM
, delayPattern AS -- optimal
(SELECT cuno
, bor_months
, IIF( FORMAT( CAST(bor_months AS BIGINT), 'X16') LIKE '%111%', 'dalyad 3+ month in row', NULL) delayState
FROM (SELECT cuno
, SUM(POWER( 16.0, missedmonth)) bor_months
FROM ( SELECT DISTINCT cuno
, missedmonth
FROM delayedPayment
CROSS APPLY (VALUES ( DATEDIFF( month, missedDuedate, '2020-12-1'))) _(missedmonth)
GROUP BY cuno, missedmonth
) ss
GROUP BY cuno
) s
)
SELECT cuno
, FORMAT( CAST(bor_months AS BIGINT), 'X16') bor_months
, delayState
FROM delayPattern
Will print
cuno bor_months delayState
123 0000010111000001 dalyad 3+ month in row
456 0000000100010110 NULL
NOTE: I am using HEX format and POWER(16.0, X) , just to be lazy, POWER(2.0, X) will be correct, but then you need bin->string formatter. Something like this:
CREATE OR ALTER FUNCTION dbo.toBinaryString(#p INT)
RETURNS VARCHAR(24)
AS
BEGIN
RETURN REVERSE(REPLACE( REPLACE(
REPLACE( REPLACE( REPLACE( REPLACE(
REPLACE( REPLACE( REPLACE( REPLACE(
REPLACE( REPLACE( REPLACE( REPLACE(
REPLACE( REPLACE( REPLACE( REPLACE( FORMAT(#p,'X8'),
'0', '....'), '1', '...x'),'2', '..x.'),'3', '..xx'),
'4', '.x..'), '5', '.x.x'),'6', '.xx.'),'7', '.xxx'),
'8', 'x...'), '9', 'x..x'),'A', 'x.x.'),'B', 'x.xx'),
'C', 'xx..'), 'D', 'xx.x'),'E', 'xxx.'),'F', 'xxxx'),
'.','0'),'x','1'))
END

Resources