Для цикла в SQL, чтобы найти 1 позицию в последовательности 0 и 1
Ниже приведен цикл for в C#, который возвращает позицию 1 в последовательности 0 и 1, вот мой код
public string OnesPosition(string statusBits)
{
string onePos = "";
for (int i = 0; i < statusBits.Length; i++)
{
if (statusBits[i] == '1')
{
onePos = onePos + Convert.ToSingle(i + 1) + ",";
}
}
onePos = string.IsNullOrEmpty(onePos ) ? "0," : onePos ;
return onePos;
}
var result = OnesPosition("00000000000101");
Это вернет: результат = 12,14
Как я могу сделать это в SQL-запрос или с помощью функции SQL? Использование SQL Server Management Studio v17.9
7 ответов
Просто еще один вариант
пример
Declare @S varchar(50) = '00000000000101'
Select Stuff((Select concat(',',N )
From (
Select Top (len(@S)) N=Row_Number() Over (Order By (Select NULL))
From master..spt_values
) s
Where substring(@S,N,1)='1'
Order By N
For XML Path ('')),1,1,'')
Возвращает
12,14
Циклы не очень хорошо работают в SQL Server, но здесь есть метод.
declare @ones varchar(16) = '00000000100101'
declare @pos int = 1
declare @result varchar(256) = ''
while @pos <= len(@ones)
begin
set @result = @result + case when substring(@ones,@pos,1) = 1 then ',' + cast(@pos as varchar) else '' end
set @pos = @pos + 1
end
select right(@result,len(@result) - 1)
Решение:
Другой возможный подход заключается в использовании рекурсивного CTE (который возвращает каждую цифру и цифру) и групповой конкатенации.
Использование STRING_AGG () (из SQL Server 2017):
DECLARE @ones varchar(16)
SET @ones = '1000000001001011';
WITH Digits AS (
SELECT 1 AS DigitPosition, SUBSTRING(@ones, 1, 1) AS Digit
UNION ALL
SELECT DigitPosition + 1, SUBSTRING(@ones, DigitPosition + 1, 1)
FROM Digits
WHERE DigitPosition < LEN(@ones)
)
SELECT STRING_AGG(DigitPosition, ',')
FROM Digits
WHERE Digit = '1'
Использование FOR XML:
DECLARE @ones varchar(16)
SET @ones = '1000000001001011';
WITH Digits AS (
SELECT 1 AS DigitPosition, SUBSTRING(@ones, 1, 1) AS Digit
UNION ALL
SELECT DigitPosition + 1, SUBSTRING(@ones, DigitPosition + 1, 1)
FROM Digits
WHERE DigitPosition < LEN(@ones)
)
SELECT CONVERT(varchar(max), DigitPosition) + ','
FROM Digits
WHERE Digit = '1'
FOR XML PATH('')
Выход:
1,10,13,15,16
Или это...
DECLARE @string VARCHAR(100) = '00000000000101';
WITH
cte_n1 (n) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (n)),
cte_n2 (n) AS (SELECT 1 FROM cte_n1 a CROSS JOIN cte_n1 b),
cte_Tally (n) AS (
SELECT TOP (LEN(@string))
ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM
cte_n2 a CROSS JOIN cte_n2 b
)
SELECT
STRING_AGG(t.n, ',')
FROM
cte_Tally t
WHERE
SUBSTRING(@string, t.n, 1) = '1';
Вот функция, основанная на ответе @scsimon, и она сработала.
CREATE FUNCTION [dbo].[ConvertTo1Positions]
(
@ones AS varchar(16),
@pos AS INT = 1,
@result varchar(256)
) RETURNS VARCHAR(MAX) AS BEGIN
while @pos <= len(@ones)
begin
set @result = @result + iif(substring(@ones,@pos,1) = 1,cast(@pos as char),'')
set @pos = @pos + 1
end
RETURN @result;
END
Я не думаю, что это хорошая идея, чтобы использовать цикл, но так как вы хотите его
DECLARE @Str VARCHAR(45) = '000001000000101',
@OutPut VARCHAR(45) = '',
@I INT = 1;
WHILE @I <= LEN(@Str)
BEGIN
IF (SELECT SUBSTRING(@Str, @I, 1)) = '1'
SET @OutPut = @OutPut + (SELECT CAST(@I AS VARCHAR(10))) + ',';
--Convert.ToSingle(i + 1) why +1?
SET @I = @I + 1;
END
SELECT @OutPut;
Еще один метод, использующий таблицу подсчета и просто прямой tsql:
WITH Tally(i) AS (
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS i
FROM (VALUES (0), (0), (0), (0), (0), (0), (0)) a(n)
CROSS JOIN (VALUES (0), (0), (0), (0), (0), (0)) b(n)
)
SELECT bitloc
FROM
(
SELECT SUBSTRING(x.d, i, 1) as bitset, i bitloc
FROM (VALUES ('00000000000101')) x(d)
CROSS JOIN Tally
) sub
WHERE bitset = 1