I have a CROSS JOIN query that I am using to see which combination of item quantities yield the best output.
DECLARE #last_found DECIMAL(10, 2) = 0
DECLARE #calculated DECIMAL(10, 2)
DECLARE #n_count INT
DECLARE #tbl1n INT
DECLARE #tbl2n INT
DECLARE #tbl3n INT
DROP TABLE IF EXISTS #tbl1
DROP TABLE IF EXISTS #tbl2
DROP TABLE IF EXISTS #tbl3
;WITH numbers AS (
SELECT ROW_NUMBER() OVER (ORDER BY [value]) AS n
FROM string_split('1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20', ',')
)
SELECT n, (n * 10000 * (1 + IIF(n > 1, (0.50/19.00) * (n - 1), 0))) AS price
INTO #tbl1 FROM numbers
;WITH numbers AS (
SELECT ROW_NUMBER() OVER (ORDER BY [value]) AS n
FROM string_split('1,2,3,4,5,6,7,8,9,10,11,12', ',')
)
SELECT n, (n * 15000 * (1 + IIF(n > 1, (0.50/11.00) * (n - 1), 0))) AS price
INTO #tbl2 FROM numbers
;WITH numbers AS (
SELECT ROW_NUMBER() OVER (ORDER BY [value]) AS n
FROM string_split('1,2,3,4,5,6', ',')
)
SELECT n, (n * 20000 * (1 + IIF(n > 1, (0.50/5.00) * (n - 1), 0))) AS price
INTO #tbl3 FROM numbers
SELECT
#n_count = (tbl1.n + tbl2.n + tbl3.n),
#calculated = IIF(#n_count = 10, (tbl1.price + tbl2.price + tbl3.price), 0),
#tbl1n = IIF(#calculated > #last_found, tbl1.n, #tbl1n),
#tbl2n = IIF(#calculated > #last_found, tbl2.n, #tbl2n),
#tbl3n = IIF(#calculated > #last_found, tbl3.n, #tbl3n),
#last_found = IIF(#calculated > #last_found, #calculated, #last_found)
FROM #tbl1 tbl1
CROSS JOIN #tbl2 tbl2
CROSS JOIN #tbl3 tbl3
SELECT #last_found AS highest_value, #tbl1n AS tbl1n, #tbl2n AS tbl2n, #tbl3n AS tbl3n,
t1.price AS tbl1_price, t2.price AS tbl2_price, t3.price AS tbl3_price
FROM #tbl1 t1
INNER JOIN #tbl2 t2 ON t1.n = #tbl1n AND t2.n = #tbl2n
INNER JOIN #tbl3 t3 ON t3.n = #tbl3n
As can be seen, if the query finds a value higher than the previously found highest, it is storing the combination using multiple instances of #itemN = IIF(#calculated > #last_found, tbl.n, #itemN).
Is it possible to assign all #tblXn variables in one go? I could use a CONCAT, but I think it may slow down the query, as it is a string operation.
FYI - 'n' is a value between 0 and 20.
You can use apply :
SELECT n_count, calculated, last_found,
IIF(flag = 1, tbl1.n, #tbl1n) AS tbl1n,
IIF(flag = 1, tbl2.n, #tbl2n) AS tbl2n,
IIF(flag = 1, tbl3.n, #tbl3n) AS tbl3n
FROM tbl1 CROSS JOIN
tbl2 CROSS JOIN
tb CROSS APPLY
( VALUES (tbl1.n + tbl2.n + tbl3.n)
) t(n_count) CROSS APPLY
( VALUES (IIF(n_count = 10, ( (tbl1.n * tbl1.price), (tbl2.n * tbl2.price), (tbl3.n * tbl3.price) ), 0))
) tt(calculated) CROSS APPLY
( VALUES (IIF(calculated > #last_found, calculated, #last_found))
) lst(last_found) CROSS APPLY
( VALUES (IIF(calculated > last_found, 1, 0))
) cc(flag)
Note : You can further assign values to variable.
You shouldn't be using variables for this at all.
This would be much simpler written as below (without relying on undocumented/unguaranteed behaviour of assignment to variables across multiple rows)
SELECT TOP 1 CAST(combined_price AS DECIMAL(10, 2)) AS highest_value,
tbl1.n AS tbl1n,
tbl2.n AS tbl2n,
tbl3.n AS tbl3n,
tbl1.price AS tbl1_price,
tbl2.price AS tbl2_price,
tbl3.price AS tbl3_price
FROM #tbl1 tbl1
CROSS JOIN #tbl2 tbl2
CROSS JOIN #tbl3 tbl3
CROSS APPLY (VALUES (tbl1.price + tbl2.price + tbl3.price,
tbl1.n + tbl2.n + tbl3.n)) CA(combined_price, combined_n)
WHERE combined_n = 10
ORDER BY combined_price DESC
Related
I have data in Redshift that I'm aggregating to the Year-Quarter level i.e. number of items by Year-Quarter
I need to show a continuous trend and hence I need to fill-in the gaps in Year-Quarter. The picture below should give a clearer idea of my current data and desired output.
How can I achieve this in Redshift SQL?
A query like this should do the trick:
create table test (yq int, items int);
INSERT INTO test Values (20201,10),(20204, 15),(20213, 25),(20222, 30);
with recursive quarters(q) as (
select min(yq) as q
from test
union all
select decode(right(q::text, 1), 4, q + 7, q + 1) as q
from quarters
where q < (select max(yq) from test)
)
select q as yq, decode(items is null, true,
lag(items ignore nulls) over (order by q), items) as items
from test t
right join quarters q
on t.yq = q.q
order by q;
It uses a recursive CTE to generate the quarters range needed, right joins this with the source data, and then uses a LAG() window function to populate the items if the value is NULL.
This is known as forward filling values:
CREATE TABLE #Temp
(
[YQ] nvarchar(5),
[items] int
)
INSERT INTO #Temp Values ('20201',10),('20204', 15),('20213', 25),('20222', 30)
---------------------------------------------------------------------------------
DECLARE #start int, #end int, #starty int, #endy int
SELECT #start=1, #end=4
SELECT #starty=MIN(Substring(YQ,0,5)), #endy=MIN(Substring(YQ,0,5)) from #Temp
;With cte1(y) as
(
Select #starty as y
union all
Select y + 1
from cte1
where y <= #endy + 1
)
, cte2(n) as
(
Select #start as n
union all
Select n + 1
from cte2
where n < #end
)
SELECT t1.YQ AS 'Year-Quarter',
CASE WHEN t2.items is null then (SELECT TOP 1 MAX(items) from #Temp WHERE items is not null and YQ < t1.YQ) ELSE t2.items END AS '# Items'
FROM
(
SELECT CAST(cte1.y AS nvarchar(4)) + CAST(cte2.n AS nvarchar(1)) AS YQ
FROM cte1, cte2
) t1
LEFT JOIN #Temp t2 ON t2.YQ = t1.YQ
WHERE t1.YQ <= (SELECT MAX(YQ) FROM #Temp)
ORDER BY t1.YQ, t2.items
Here is my query. My issue is that if I simply use the statement and t1.DisplayVersion >= '30.6.4.10006' it returns smaller values as it's only looking a the first character. For example, any computers with version 8.23.2500 are returned.
select distinct v1.guid
from vComputer v1
inner join Inv_AddRemoveProgram t1 on v1.Guid = t1._ResourceGuid
where t1.DisplayName like 'Cisco WebEx Meeting Center%'
and CAST(t1.DisplayVersion AS INT) >= '30.6.4.10006'
and t1.installflag = '1'
and v1.IsManaged = '1'
In string comparisons, '30' is less than '8' because it compares the individual characters in the same position and 3 is less than 8. To do a numeric comparison, you'll need to parse the text into separate fields using a splitter function, align the items and then compare them. This will not be very simple and will not likely perform very well across any significant number of rows.
It will be simpler and perform better if you can get away with comparing the digits in front of the second decimal point; 30.6 vs 8.23. Those values can be converted to decimal and directly compared.
--COMPARE JUST THE FIRST TWO
DECLARE #t TABLE (VersionNumber VARCHAR(50));
DECLARE #test VARCHAR(50) = '8.23.2500';
INSERT INTO #t (VersionNumber)
VALUES ('30.6.4.10006'),
('1.2.3.4'),
('100.200.300.400'),
('1.200.3.4'),
('100.2.3.4'),
('8.24.2500'),
('8.23.2501'),
('8.23.2499'),
('8.23');
SELECT t.VersionNumber, #test,
FirstTwo = TRY_CONVERT(decimal(20,2),LEFT(t.VersionNumber, ISNULL(NULLIF(CHARINDEX('.', t.VersionNumber, CHARINDEX('.', t.VersionNumber) + 1), 0) - 1, 8000))),
FirstTwoTest = TRY_CONVERT(decimal(20,2),LEFT(#test, ISNULL(NULLIF(CHARINDEX('.', #test, CHARINDEX('.', #test) + 1), 0) - 1, 8000)))
FROM #t AS t
WHERE TRY_CONVERT(decimal(20,2),LEFT(t.VersionNumber, ISNULL(NULLIF(CHARINDEX('.', t.VersionNumber, CHARINDEX('.', t.VersionNumber) + 1), 0) - 1, 8000)))
> TRY_CONVERT(decimal(20,2),LEFT(#test, ISNULL(NULLIF(CHARINDEX('.', #test, CHARINDEX('.', #test) + 1), 0) - 1, 8000)))
;
Here is a recursive CTE version. I THINK it will work as it returned the correct results for the limited test data. You'll need to do some more extensive testing and figure out how to incorporate it into your solution.
In summary, it splits the strings using Jeff Moden's splitter function, then compares the values based on the ordinal, using a recursive cte to drill into the results for which the new value is greater or equal to the existing value. Finally, it selects from the original source where the new value was greater then the existing value.
With my sample data, the following versions came back as newer than 8.23.2500:
30.6.4.10006
100.200.300.400
100.2.3.4
8.24.2500
8.23.2501
The following versions came back as not newer than 8.23.2500:
1.2.3.4
1.200.3.4
8.23.2499
8.23
8.23.2500
IF OBJECT_ID('tempdb..#test') IS NOT NULL
DROP TABLE #test;
GO
DECLARE #t TABLE (VersionNumber VARCHAR(50));
DECLARE #test VARCHAR(50) = '8.23.2500';
INSERT INTO #t (VersionNumber)
VALUES ('30.6.4.10006'),
('1.2.3.4'),
('100.200.300.400'),
('1.200.3.4'),
('100.2.3.4'),
('8.24.2500'),
('8.23.2501'),
('8.23.2499'),
('8.23'),
('8.23.2500');
SELECT dsk.ItemNumber,
Item = CAST(dsk.Item AS INT)
INTO #TEST
FROM Utility.DelimitedSplit8K(#test, '.') AS dsk;
WITH cte
AS (SELECT t.VersionNumber,
t2.ItemNumber,
LeftItem = dsk.Item,
RightItem = t2.Item,
Diff = dsk.Item - t2.Item
FROM #t AS t
CROSS APPLY Utility.DelimitedSplit8K(t.VersionNumber, '.') AS dsk
INNER JOIN #TEST AS t2
ON dsk.ItemNumber = t2.ItemNumber
WHERE t2.ItemNumber = 1
AND dsk.Item - t2.Item > -1
UNION ALL
SELECT t.VersionNumber,
t2.ItemNumber,
LeftItem = dsk.Item,
RightItem = t2.Item,
Diff = dsk.Item - t2.Item
FROM #t AS t
CROSS APPLY Utility.DelimitedSplit8K(t.VersionNumber, '.') AS dsk
INNER JOIN #TEST AS t2
ON dsk.ItemNumber = t2.ItemNumber
INNER JOIN cte
ON cte.ItemNumber +1 = t2.ItemNumber
AND cte.VersionNumber = t.VersionNumber
WHERE cte.Diff > -1
)
SELECT * FROM #t AS t
WHERE t.VersionNumber IN
(
SELECT cte.VersionNumber FROM cte
WHERE cte.Diff > 0
)
Try this:
select distinct v1.guid
from vComputer v1
inner join Inv_AddRemoveProgram t1 on v1.Guid = t1._ResourceGuid
where t1.DisplayName like 'Cisco WebEx Meeting Center%'
and CAST(Replace(t1.DisplayVersion,'.','') AS BIGINT) >= 306410006
and t1.installflag = '1'
and v1.IsManaged = '1'
I need a quick way to compare 2 or more values from different tables where the orders are arbitrarily stored in sql server. The data comes from a 3rd party who will not change.
Example data below shows the same item described in two ways. the remaining columns contain other data that i am joining.
table1
i j other columns...
1 2 ...
table2
i j other columns
2 1 ...
1 2 ...
right now for 2, i do a union query to cover both directions (i=i, j=j / i=j, j=i) . but if you expand to 3, that is 9 possible orders.
SELECT * FROM Table1 INNER JOIN Table2 ON Table1.i = Table2.i AND Table1.j = Table2.j
UNION
SELECT * FROM Table1 INNER JOIN Table2 ON Table1.i = Table2.j AND Table1.j = Table2.i
is there a way to order data returned from the first two columns before doing the comparison so i don't have to create all the unions?
Edit: New xml approach
I wonder how this approach performs:
select *, cast( '<c>' + cast(i as varchar) + '</c>' +
'<c>' + cast(j as varchar) + '</c>' +
'<c>' + cast(k as varchar) + '</c>'
as xml).query('for $a in /c order by $a return concat($a, "/")').value('.', 'varchar(100)')
from #Table1 o
This can be wrapped in a function and referenced in a persisted column... which should scale very well for you:
create table dbo.Table1 (pk int identity(1,1) primary key, i int, j int, k int);
insert into dbo.Table1
values(1, 2, 3), (3, 1, 2), (4, 5, 6), (9,9,9);
go
create function dbo.fn_GenerateCompare(#i int, #j int, #k int)
returns varchar(100)
with schemabinding
as
begin
return
(
select cast('<c>' + cast(#i as varchar) + '</c>' +
'<c>' + cast(#j as varchar) + '</c>' +
'<c>' + cast(#k as varchar) + '</c>'
as xml).query('for $a in /c order by $a return concat($a, "/")').value('.', 'varchar(100)')
);
end
alter table dbo.Table1
add Compare as dbo.fn_GenerateCompare(i, j, k) persisted;
select * from dbo.Table1
Returns:
pk i j k Compare
-- - - - -------
1 1 2 3 1/2/3
2 3 1 2 1/2/3
3 4 5 6 4/5/6
4 9 9 9 9/9/9
Your query should now be really simple. Slap an index on the new Compare column and it should fly.
Original Post:
I like the sorted list idea proposed by Thorsten. Heres a rough idea of how it might be done. Performance would be greatly improved by persisting this compare column on the table (trigger or persisted computed column?)
declare #Table1 table (pk int identity(1,1) primary key, i int, j int, k int)
declare #Table2 table (pk int identity(1,1) primary key, i int, j int, k int)
insert into #Table1
values(1, 2, 3), (3, 1, 2), (4, 5, 6), (9,9,9)
insert into #Table2
values (2, 1, 3), (6, 4, 5)
--since the order is unimportant, concatenate the columns into a sorted array
--note how 1,2,3 and 3,1,2 both result in the same compare value:
select *
from #Table1 o
cross
apply ( select cast(value as varchar) + '/'
from #Table1
unpivot (value for c in (i,j,k)) as u
where pk = o.pk
order
by value
for xml path('')
)d(compare)
--now, bring in the 2nd table
select [src] = 1, pk, compare
from #Table1 o
cross
apply ( select cast(value as varchar) + '/'
from #Table1
unpivot (value for c in (i,j,k)) as u
where pk = o.pk
order
by value
for xml path('')
)d(compare)
union all
select [src] = 2, pk, compare
from #Table2 o
cross
apply ( select cast(value as varchar) + '/'
from #Table2
unpivot (value for c in (i,j,k)) as u
where pk = o.pk
order
by value
for xml path('')
)d(compare)
--now just group them to find the matching rows
select min(src), min(pk), compare
from (
select [src] = 1, pk, compare
from #Table1 o
cross
apply ( select cast(value as varchar) + '/'
from #Table1
unpivot (value for c in (i,j,k)) as u
where pk = o.pk
order
by value
for xml path('')
)d(compare)
union all
select [src] = 2, pk, compare
from #Table2 o
cross
apply ( select cast(value as varchar) + '/'
from #Table2
unpivot (value for c in (i,j,k)) as u
where pk = o.pk
order
by value
for xml path('')
)d(compare)
)grouped
group
by compare
having count(*) > 1;
I have two columns: One column holds actual answers, the other one is the answer_key.
I want to compare answers to answer_key and have scores in the third column:
ID Answers Answer_key Score
1 ABCD ABCC 1110
2 ACD DCA 010
Of course, I can check the length, loop through each character to compare them individually, and get the score.
However, is there an alternative? Possibly based on XML path?
You might try binary values rather than letters.
A=0001 B=0010 C=0100 D=1000
ABCD = 0001001001001000 (0x1248)
ABCC = 0001001001000100 (0x1244)
Score = (Answers XOR Answer_key) XOR 11111111
The XOR 11111111 is optional
What you want to do is to split each char in Answers and Answers_Key into separate rows and then compare them. This can be done using a Recursive CTE. The concatenation is done using the FOR XML PATH function.
CREATE TABLE temp(
Answers VARCHAR(10),
Answer_Key VARCHAR(10)
)
INSERT INTO temp VALUES ('ABCD', 'ABCC'), ('ACD', 'DCA');
;WITH temp_numbered AS(
SELECT
ID = ROW_NUMBER() OVER(ORDER BY Answer_Key),
*
FROM temp
),
cte AS(
SELECT
ID,
Answer_Key_Char = SUBSTRING(Answer_Key, 1, 1),
Answer_Key = STUFF(Answer_Key, 1, 1, ''),
Answers_Char = SUBSTRING(Answers, 1, 1),
Answers = STUFF(Answers, 1, 1, ''),
RowID = 1
FROM temp_numbered t
UNION ALL
SELECT
ID,
Answer_Key_Char = SUBSTRING(Answer_Key, 1, 1),
Answers = STUFF(Answer_Key, 1, 1, ''),
Answers_Char = SUBSTRING(Answers, 1, 1),
Answers = STUFF(Answers, 1, 1, ''),
RowID = RowID + 1
FROM cte
WHERE LEN(Answer_Key) > 0
)
SELECT
Answers,
Answer_Key,
Score = (SELECT
CASE WHEN Answer_Key_Char = Answers_Char THEN '1' ELSE '0' END
FROM cte
WHERE ID = t.ID
ORDER BY ID, RowID
FOR XML PATH(''))
FROM temp_numbered t
DROP TABLE temp
Here is another way using a Tally Table:
;WITH tally(N) AS(
SELECT TOP 11000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM sys.columns
)
,temp_numbered AS(
SELECT
ID = ROW_NUMBER() OVER(ORDER BY Answer_Key),
*
FROM temp
)
,cte AS(
SELECT
ID,
Answer_Key_Char = SUBSTRING(Answer_Key, N, 1),
Answers_Char = SUBSTRING(Answers, N, 1),
RowID = N
FROM temp_numbered tn
CROSS JOIN Tally t
WHERE t.N <= LEN(tn.Answer_Key)
)
SELECT
Answers,
Answer_Key,
Score = (SELECT
CASE WHEN Answer_Key_Char = Answers_Char THEN '1' ELSE '0' END
FROM cte
WHERE ID = t.ID
ORDER BY ID, RowID
FOR XML PATH(''))
FROM temp_numbered t
I seems easiest to loop through each character for the whole set at once:
-- get max Answer length
declare #len int,#max_len int
select #max_len = max(len(Answers)),
#len = 1
from Answers
-- update scores
while #len <= #max_len
begin
update Answers
set Score = isnull(Score,'') + '1'
where substring(Answers,#len,1) = substring(Answer_Key,#len,1)
and len(Answers) >= #len
update Answers
set Score = isnull(Score,'') + '0'
where substring(Answers,#len,1) != substring(Answer_Key,#len,1)
and len(Answers) >= #len
set #len = #len + 1
end
-- return Scores
select * from Answers
SQL FIDDLE
Expanding on the answer from #weswesthemenace to get around cte limit.
DECLARE #Answers TABLE
(
Id INT IDENTITY(1, 1) not null,
Answers VARCHAR(MAX) not null,
Answer_Key VARCHAR(MAX) not null
)
INSERT INTO #Answers (Answers, Answer_Key) VALUES ('ABCD', 'ABCC')
INSERT INTO #Answers (Answers, Answer_Key) VALUES ('ACD', 'DCA')
INSERT INTO #Answers (Answers, Answer_Key) VALUES ('ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEGGHIJKLMNOPQRSTUVXXYZABCDEFGHIIKKMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXZZ');
WITH
E01(N) AS (SELECT 1 UNION ALL SELECT 1),
E02(N) AS (SELECT 1 FROM E01 a CROSS JOIN E01 b),
E04(N) AS (SELECT 1 FROM E02 a CROSS JOIN E02 b),
E08(N) AS (SELECT 1 FROM E04 a CROSS JOIN E04 b),
E16(N) AS (SELECT 1 FROM E08 a CROSS JOIN E08 b),
E32(N) AS (SELECT 1 FROM E16 a CROSS JOIN E16 b),
cteTally(N) AS (SELECT row_number() OVER (ORDER BY N) FROM E32)
SELECT b.Answers, b.Answer_Key,
(
SELECT CASE when SUBSTRING(a.Answer_Key, n.N, 1) = SUBSTRING(a.Answers, n.N, 1) then '1' else '0' end
FROM #Answers a
CROSS APPLY cteTally n
WHERE b.Id = a.Id AND n.N <= DATALENGTH(b.Answers)
ORDER BY ID, n.N
FOR XML PATH('')
) Score
FROM #Answers b
this can be simplified by a utility Number function in the database. Mine is called dbo.Number(start, end)
SELECT b.Answers, b.Answer_Key,
(
SELECT CASE WHEN SUBSTRING(a.Answer_Key, n.N, 1) = SUBSTRING(a.Answers, n.N, 1) THEN '1' ELSE '0' END
FROM #Answers a
CROSS APPLY dbo.Number(1, DATALENGTH(b.Answers)) n
WHERE b.Id = a.Id
ORDER BY ID, n.N
FOR XML PATH('')
) Score
FROM #Answers b
How to find the number of distinct character used in the field having multiple rows.
For example, if there are two rows having data like abcd and eaafg* then distinct character used are abcdefg*.
Try this one -
INSERT INTO #temp (txt)
VALUES ('abcd3'), ('abcdefg*')
SELECT disword = (
SELECT DISTINCT dt.ch
FROM (
SELECT ch = SUBSTRING(t.mtxt, n.number + 1, 1)
FROM [master].dbo.spt_values n
CROSS JOIN (
SELECT mtxt = (
SELECT txt
FROM #temp
FOR XML PATH(N''), TYPE, ROOT).value(N'root[1]', N'NVARCHAR(MAX)'
)
) t
WHERE [type] = N'p'
AND number <= LEN(mtxt) - 1
) dt
FOR XML PATH(N''), TYPE, ROOT).value(N'root[1]', N'NVARCHAR(MAX)'
)
Example (edited):
SET NOCOUNT ON;
DECLARE #temp TABLE (txt VARCHAR(8000))
INSERT INTO #temp (txt)
VALUES ('abcd'), ('abcdefg*'), (REPLICATE('-', 8000)), (REPLICATE('+', 8000))
DECLARE #t TABLE (i BIGINT)
DECLARE
#i BIGINT = 1
, #l BIGINT = (
SELECT SUM(LEN(txt))
FROM #temp
)
WHILE (#i <= #l) BEGIN
INSERT INTO #t (i)
VALUES (#i), (#i+1), (#i+2), (#i+3), (#i+4), (#i+5), (#i+6), (#i+7), (#i+8), (#i+9)
SELECT #i += 10
END
SELECT disword = (
SELECT DISTINCT dt.ch
FROM (
SELECT ch = SUBSTRING(t.mtxt, n.i, 1)
FROM #t n
CROSS JOIN (
SELECT mtxt = (
SELECT txt
FROM #temp
FOR XML PATH(N''), TYPE, ROOT).value(N'root[1]', N'NVARCHAR(MAX)'
)
) t
) dt
FOR XML PATH(N''), TYPE, ROOT).value(N'root[1]', N'NVARCHAR(MAX)'
)
Have a look a t this solution -
SELECT
dt.ch
, cnt = COUNT(1)
FROM (
SELECT ch = SUBSTRING(t.mtxt, n.i, 1)
FROM #t n
CROSS JOIN (
SELECT mtxt = (
SELECT txt
FROM #temp
FOR XML PATH(N''), TYPE, ROOT).value(N'root[1]', N'NVARCHAR(MAX)')
) t
) dt
WHERE dt.ch != ''
GROUP BY dt.ch
ORDER BY cnt DESC
Here are questions that may refer to what you are asking:
How do I get distinct characters of string column in mssql?
and
SQL: how to get all the distinct characters in a column, across all rows
With tallys:
DECLARE #t TABLE (s NVARCHAR(MAX))
INSERT INTO #t
VALUES ('abcd'), ('abcdefg*')
;WITH tally AS(SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) i
FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t1(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t2(n))
SELECT
( SELECT DISTINCT sb
FROM tally
CROSS APPLY ( SELECT SUBSTRING(s, i, 1) sb FROM #t ) ca
WHERE sb <> ''
FOR XML PATH(N'') , TYPE , ROOT).value(N'root[1]', N'NVARCHAR(MAX)')