如何在MSSQL中获取字符串列的不同字符?

4

给定:

 | name
-+---------------------------
 | Josef Knoller
 | Josef Somos
 | KFZ Wiesauer

wanted result:

JOSEFKNMLRZWIAU

(结果中的大小写不重要 - 只是在编写时更容易按住UPPER键)

有没有办法在 T-SQL 中实现这个功能?


抱歉...我把列和行搞混了... 它只有1列和n行

MLRZWIAU

  • M 来自于 Somos
  • L 来自于 Knoller
  • R 来自于 Knoller
  • ...

更清晰了吗?


嗯,我觉得我不理解假设输入的结果逻辑。 - Jeff Norman
这是三个不同的行,您想要从所有三个行中获取不同的字符吗? - Martin Smith
你想要实现什么目标? - Srinivas Reddy Thatiparthy
1
你是怎么想到使用那种特定类型的字符串的?MLRZWIAU 是从哪里来的? - Pavan
3个回答

3

这是一个相当常见的SQL难题。您需要一个数字表,我将使用CTE在此处生成(假定SQL Server 2005或更高版本)。

declare @Names table (
    name varchar(100)
)

insert into @Names
    (name)
    select 'Josef Knoller' union all
    select 'Josef Somos' union all
    select 'KFZ Wiesauer'

;With Numbers As (
    Select Row_Number() Over ( Order By c1.object_id ) As Value
    From sys.columns As c1
        Cross Join sys.columns As c2
)
Select Distinct '' + case when Substring(nm.name, N.Value, 1)<>' ' then upper(Substring(nm.name, N.Value, 1)) else '' end
    From Numbers N
        Cross Join @Names nm
    Where N.Value <= Len(nm.name)
    For Xml Path('')

完成需要25秒。这不是非常长的时间吗? - Kashif
@Muhammad: 由于我的生成数字表的技术,它可能取决于您在哪个数据库中运行此代码。尝试在像Model这样相对较小的数据库中运行,我在其中只需1秒即可运行。我喜欢 Martin's answer 中的技巧,可以生成完全正确大小的数字表。 - Joe Stefanelli
2
@Joe - 我刚刚对此进行了一些测试,因为我一直想做这个事情,然后发现了这个答案,它与我的结论相同。也就是说,Itzik Ben Gan的交叉连接CTE方法是生成非永久数字表的最佳方式。在我的测试中,它比冷缓存中的永久表更快,但在表被缓存后被永久表击败。 - Martin Smith

2
DECLARE @result VARCHAR(MAX)
SET @result = ''

DECLARE  @t TABLE(name VARCHAR(400))

INSERT INTO @t 
SELECT 'Josef Knoller' UNION ALL SELECT 'Josef Somos' UNION ALL SELECT 'KFZ Wiesauer'

;WITH 
L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
L1 AS (SELECT 1 AS c FROM L0 A CROSS JOIN L0 B),
L2 AS (SELECT 1 AS c FROM L1 A CROSS JOIN L1 B),
L3 AS (SELECT 1 AS c FROM L2 A CROSS JOIN L2 B),
L4 AS (SELECT 1 AS c FROM L3 A CROSS JOIN L3 B),
Nums AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS i FROM L4),
FilteredNums AS (SELECT i FROM Nums WHERE i<= 400),
Letters AS(
SELECT UPPER(SUBSTRING(name,i,1)) AS L, ROW_NUMBER() OVER (ORDER BY name,i) AS RN
FROM @t
JOIN FilteredNums ON FilteredNums.i <= LEN(name))

SELECT @result = @result + L 
FROM Letters
GROUP BY L
ORDER BY MIN(RN)

SELECT @result

1
谈论一个用大锤砸小坚果的行为,真是太过分了。 - smirkingman
3
@smirkingman - 等待你更好的解决方案 :-). Andreas - 那个错误不是由我的代码引起的。你是如何使用它的? - Martin Smith
嗨,马丁,你能否解释一下或给我一个链接,让我可以阅读CTE中逗号后面的部分,即“Letters”?我不明白它是如何工作的。 - Kashif
@Muhammad - 如果你将 SELECT @result = ... ORDER BY MIN(RN) 注释掉并替换为 SELECT * FROM Letters,会更清晰吗? - Martin Smith
@Martin,感谢您的反馈。我已经阅读了一些关于CTE的好文章,现在我完全明白了。我也使用了您的答案来回答这个问题,只是想问一下为什么您要使用多个表来生成FilteredNums表中的数字列表?请看看我的答案,并评论一下使用这种方法生成数字的问题所在。 - Kashif
@Muhammad- 交叉连接的CTE方法比递归的CTE方法更快。请参考此答案以获取基准测试 https://dev59.com/anVD5IYBdhLWcg3wWKLc#2663232 - Martin Smith

1
DECLARE @result VARCHAR(MAX)
SET @result = ''

DECLARE  @t TABLE(name VARCHAR(400))

INSERT INTO @t 
SELECT 'Josef Knoller' UNION ALL SELECT 'Josef Somos' UNION ALL SELECT 'KFZ Wiesauer'

;with nums(i) as
(
    select i=1
    union all
    select i=i+1 from nums where i < 400
),
Letters AS(
SELECT UPPER(SUBSTRING(name,i,1)) AS L, ROW_NUMBER() OVER (ORDER BY name,i) AS RN
FROM @t JOIN nums ON nums.i <= LEN(name))

SELECT @result = @result + L 
FROM Letters
GROUP BY L
ORDER BY MIN(RN)
OPTION (MAXRECURSION 400)

SELECT @result

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接