2014-10-17 12 views
6

Mam kolumnę NVARCHAR(10) w tabeli. Może przechowywać dowolny typ ciągów UNICODE.Serwer SQL: Zastąp znaki inne niż znak w łańcuchu bez użycia tabeli tymczasowej.

Chcę zastąpić każdy znak różny od "1" za pomocą "0".

Załóżmy, że mam ciąg "C18 *". Powinienem dostać "0100000100".

udało mi się to zrobić przy użyciu tabeli pomocnika, który zawiera indeksy od 1 do rozmiaru mojego kolumnie (10), jak ten:

CREATE TABLE HELP(Idx INT) 
INSERT INTO HELP 
    SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT  7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 10 

DECLARE @myStr VARCHAR(10) 
SET @myStr = 'C18*' 

SELECT STUFF((SELECT '' + CASE(B.Ch) WHEN '1' THEN '1' ELSE '0' END FROM (
    SELECT SUBSTRING(A.Val,H.Idx,1) AS Ch 
    FROM 
    (SELECT @myStr AS Val) A 
    CROSS JOIN HELP H 
)B FOR XML PATH('')),1,0,'') 

To działa, ale można to zrobić w ładniejszy droga? To wydaje się brzydkie dla prostej aktualizacji, ignorując fakt, że rozmiar kolumny może się zmieniać z czasem. Musi również działać pod SQL> = 2005.

SQL Fiddlehere

Dzięki!

+0

Można to zrobić tylko za pomocą standardowego proc lub możesz użyć procedury .Net? Czy jest to coś, co musi działać wielokrotnie lub tylko raz, aby wyczyścić dane? –

+0

@MauriceReeves Musi działać tylko raz (kilka uszkodzonych danych w kilku naszych bazach danych) przeciwko dziesiątkom dużych tabel (10K + wiersze). Prosta aplikacja konsolowa rozwiązałaby to, ale zapytałam, czy ta sama sytuacja się powtórzy + nie podoba mi się ta tabela temp. – darkdante

+2

Tabela numerów jest bardzo przydatna. Wielu ma go jako stały stół. –

Odpowiedz

5

Oto sposób, aby to zrobić za pomocą cte. W moim systemie faktycznie mam ctes jako nazwę widoku cteTally. Ta technika generuje widok 10 000 wierszy z zerowymi odczytami. ;) Twój kod jako wysłany działa całkiem dobrze. W tym przykładzie przeniosłem ciąg znaków do tabeli, ponieważ to jest to, z czym pracujesz w rzeczywistym systemie.

declare @myStrings table(MyVal varchar(10)); 

insert @myStrings 
select 'C18*'; 

WITH 
    E1(N) AS (select 1 from 
    (
     select (1) union all 
     select (1) union all 
     select (1) union all 
     select (1) union all 
     select (1) union all 
     select (1) union all 
     select (1) union all 
     select (1) union all 
     select (1) union all 
     select (1))dt(n)), 
    E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows 
    E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max 
    cteTally(N) AS 
    (
     SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4 
    ) 

SELECT STUFF((SELECT '' + CASE(B.Ch) WHEN '1' THEN '1' ELSE '0' END FROM (
    SELECT SUBSTRING(A.MyVal, t.N, 1) AS Ch 
    FROM 
    @myStrings A 
    CROSS JOIN cteTally t 
    where t.N < LEN(a.MyVal) 
)B FOR XML PATH('')),1,0,'') 
+1

Musiałem zmodyfikować trochę cte do pracy z sql 2005. W 2008+ można to zmienić na wartości (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) zamiast wszystkich selekcji. : D –

+0

Właśnie utworzyłem widok liczb za pomocą Twojej techniki! Dziękuję :) – darkdante

+0

Czy możesz polegać na kolejności wierszy w podzapytaniu bez wyraźnego ORDER BY? –

1

Jeśli chcesz zaktualizować całą tabelę, może okazać się przydatny UDF.

Create FUNCTION dbo.F_MakeBinary(@Param NVarchar(max)) 
RETURNS NVarchar (max) 
AS 
BEGIN  
DECLARE @a NVarchar(max) 
Set @[email protected] 
While PATINDEX(N'%[^0-1]%', @a) > 0 
begin  
    select @a=STUFF(@a, PATINDEX(N'%[^0-1]%', @a),1,'0') 
end 
Return @a 
END 

Zastosowanie:

Update aTable Set aField = dbo.F_MakeBinary(aField) 
7

Nieco inne podejście, używając rekurencyjne zapytanie:

WITH cte AS 
    (SELECT v, i = 0, 
     nv = CAST('' AS NVARCHAR(10)) 
    FROM t 
    UNION ALL 
    SELECT v, i+1, 
     CAST(nv + CASE WHEN SUBSTRING(v, i+1, 1) = '1' THEN '1' ELSE '0' END 
      AS NVARCHAR(10)) 
    FROM cte 
    WHERE i+1 <= LEN(v) 
) 
SELECT v, nv 
FROM cte 
WHERE i = LEN(v) ; 

Testowany w SQLFiddle

Powiązane problemy