2015-06-02 18 views
11

Próbuję zrozumieć nasz sposób mogę zastąpić ciąg wykorzystując dane z innej tabeliSQL zastąpić z listy

Mam tabeli, który wygląda tak:

 
Id Translation 
1 Peter 
2 Sandra 
3 Olga 

Teraz chcę, aby wybrać wszystko i zastąpić tłumaczenia na podstawie listy, który wygląda tak:

 
Original New 
e  # 
r  ? 
lg  *% 

Tak, że lista wyboru wygląda następująco:

 
Id Translation 
1 P#t#? 
2 Sand?a 
3 O*%a 

Tak więc, dla każdego tłumaczenia potrzebuję WYMIENIAĆ (Tłumaczenie, Oryginał, Nowy). Lub innymi słowy: Muszę przejść przez każde "tłumaczenie" na mojej pierwszej liście i zrobić kolejną pętlę w moim zastępczym stole, aby zobaczyć, co zastąpić

Bare pamiętać, że pierwsza lista ma 25'000 wierszy i drugi ma 50'000, więc nie można po prostu wpisać go ręcznie :)

EDIT

prostu do wyjaśnienia: The Original i Nowy z moim spojrzeniem w górę tabeli mogą być zarówno litery i słowa, więc tabela może wyglądać tak:

 
Original New 
one  two 
three fifty 
sun  moon 
+0

Jest czas i miejsce na użycie kursorów, myślę, że właśnie znalazłeś.Nie sądzę, aby połączenie krzyżowe i korzystanie z funkcji opartej na zestawie działałyby tutaj, ponieważ każda zamiana byłaby inna. i istnieje możliwość nakładania się zamienników, na przykład, jeśli wymieniane są 'er' i' e', co ma precedens? Więc myślę, że chcesz kursora, zamawiając od maksymalnej długości w tabeli B do najkrótszej. – xQbert

+2

@ Marcus Ohlsson: Jak byś przetłumaczył "Peter", jeśli "et" = "§" i "te" = "$"? –

+1

I czy gwarantuje się, że New nie zawiera żadnych liter, które istnieją w oryginale? (Nawet prosty przykład, taki jak "S" => "r" i "ra" => "#", doprowadziłby już do pytania, czy "Sandra" musi stać się "rand #" lub "# nd #".) –

Odpowiedz

8

Aby zrobić to w jednym zapytaniu, należy użyć rekurencyjnego CTE. Coś jak:

with trans as (
     select t.original, t.new, row_number() over (order by t.original) as seqnum, 
      count(*) over() as cnt 
     from translations 
    ), 
    t as (
     select tt.id, tt.string, replace(tt.string, trans.original, trans.new) as replaced, 
      seqnum + 1 as seqnum, cnt 
     from totranslate tt join 
      trans 
      on trans.id = 1 
     union all 
     select t.id, t.string, replace(t.string, trans.original, trans.new), 
      seqnum + 1 as seqnum, cnt 
     from t join 
      trans 
      on t.seqnum = trans.id 
     where t.seqnum <= t.cnt 
    ) 
select t.id, t.string, t.replaced 
from t 
where seqnum = cnt; 
+0

Prawidłowa odpowiedź. Tylko drobne spory. dodaj 'OPCJA (maxrecursion 0)' – ughai

+0

Dziękuję @Gordon Linoff i ughai, Próbowałem zaimplementować kod, ale myślę, że ogranicza się do użycia tylko jednej litery w tym czasie. Potrzebuję go również do pracy ze słowami, przepraszam za nie wyjaśnienie, że w moim pytaniu, zaktualizuję moje pytanie :) –

+0

@MarcusOhlsson. . . Nic w tej logice nie ogranicza żadnego z łańcuchów do posiadania tylko jednej postaci. –

5

również rekurencyjne CTE:

DECLARE @translations TABLE 
    (
     Id INT , 
     Translation NVARCHAR(20) 
    ) 
INSERT INTO @translations 
VALUES (1, 'Peter'), 
     (2, 'Sandra'), 
     (3, 'Olga') 

DECLARE @replacements TABLE 
    (
     Original VARCHAR(2) , 
     New VARCHAR(2) 
    ) 
INSERT INTO @replacements 
VALUES ('e', '#'), 
     ('r', '?'), 
     ('lg', '*%'); 


WITH cte1 AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY id ORDER BY (SELECT 1)) rn 
       FROM @translations CROSS JOIN @replacements), 
     cte2 AS (SELECT Id, rn, REPLACE(Translation, Original, New) AS NTranslation 
       FROM cte1 
       WHERE rn = 1 
        UNION ALL 
       SELECT c2.Id, c2.rn + 1, REPLACE(c2.NTranslation, c1.Original, c1.New) 
       FROM cte1 c1 
       JOIN cte2 c2 ON c2.Id = c1.Id AND c2.rn + 1 = c1.rn) 
SELECT * FROM cte2 
WHERE rn = (SELECT COUNT(*) FROM @replacements) 
ORDER BY Id 

EDIT:

WITH cte1 AS (SELECT t.*, p.Id AS Old, p.Code, ROW_NUMBER() OVER (PARTITION BY t.id ORDER BY (SELECT 1)) rn 
       FROM translations t CROSS JOIN Property p), 
     cte2 AS (SELECT Id, rn, REPLACE(Trans, Old, Code) AS NTranslation 
       FROM cte1 
       WHERE rn = 1 
        UNION ALL 
       SELECT c2.Id, c2.rn + 1, REPLACE(c2.NTranslation, c1.Old, c1.Code) 
       FROM cte1 c1 
       JOIN cte2 c2 ON c2.Id = c1.Id AND c2.rn + 1 = c1.rn) 
SELECT * FROM cte2 
WHERE rn = (SELECT COUNT(*) FROM Property) 
ORDER BY Id 
+0

Dziękuję za odpowiedź, czy można uzyskać tłumaczenie i dane zastępcze z tabel, więc nie muszę wpisywać 50'000 linii w funkcji? –

+0

Oczywiście, po prostu usuń wszystko przed 'WITH' i zastąp' @ translations' i '@ replaceements' odpowiednimi tabelami. –

+0

Dziękuję @Giorgi Nakeuri Jestem całkiem nowy w tego rodzaju scenariuszu, czy możesz mi doradzić, jak by wyglądało, gdyby tłumaczenie pochodziło z kolumny "Trans" w dbo.translations, a Replacement pochodzi z "Id" i "Code" w dbo.Property? –

3

Można użyć UDF:

CREATE FUNCTION [dbo].[Translate] 
(
-- Add the parameters for the function here 
@Str nvarchar(max) 
) 
RETURNS nvarchar(max) 
AS 
BEGIN 
    DECLARE @Result nvarchar(max) = @Str; 

    SELECT @Result = replace(@Result,Original,New) from dbo.Mappings order BY Pos; 

    RETURN @Result; 
END 

Tu przyjęto ta ble zawierające tłumaczenia nazywa dbo.Mappings a obok kolumny Original i New trzeba inną kolumnę Pos int który będzie używany do określenia kolejności, w jakiej są stosowane tłumaczenia (w celu rozwiązania problemów wymienionych przez @Thorsten Kettner w komentarzach)

+0

Dzięki, nie jestem ekspertem od SQL, ale wygląda na to, że twój kod znajdzie tylko pierwszą wymianę. Naprawdę muszę znaleźć i zastąpić każdą linię w tabeli "mapowania". –

+0

Nie, zastąpi wszystkie wystąpienia. Przede wszystkim sama funkcja 'replace' zamienia wszystkie wystąpienia' Original' na 'New' dla pojedynczego mapowania. Następnie 'select' wokół niego wywołuje' replace' dla każdego mapowania. Po prostu daj temu szansę. – B0Andrew

+2

@MarcusOhlsson, jeśli nie jesteś ekspertem, dlaczego miałbyś odrzucić pomysł, nie próbując go najpierw? –

0

Tutaj jest coś, co wymyśliłem, co pozwoli ci zastąpić wiele znaków jednym określonym ciągiem.

[Split2] został skradziony z https://blogs.msdn.microsoft.com/amitjet/2009/12/11/convert-comma-separated-string-to-table-4-different-approaches/

USE <Your Database> 
GO 

CREATE FUNCTION [dbo].[Split2] 
( 
@strString varchar(4000) 
) 
RETURNS @Result TABLE 
(
RID INT IDENTITY(0,1) Primary Key 
,Value varchar(4000) 
) 
AS 
    BEGIN 
     WITH StrCTE(start, stop) AS 
     (
     SELECT 1, CHARINDEX(',' , @strString) 
     UNION ALL 
     SELECT stop + 1, CHARINDEX(',' ,@strString , stop + 1) 
      FROM StrCTE 
      WHERE stop > 0 
     ) 

     INSERT INTO @Result 
     SELECT SUBSTRING(@strString , start, CASE WHEN stop > 0 THEN stop - start ELSE 4000 END) AS stringValue 
      FROM StrCTE 

     RETURN 
    END 
GO 

USE <Your Database> 
GO 

CREATE FUNCTION [dbo].[MultiReplace] 
(
@MyString varchar(MAX) 
,@RepChars varchar(4000) 
,@NewChars varchar(4000) 
) 
RETURNS varchar(MAX) 
AS 
    BEGIN 
     DECLARE @CurRow int = 0 
     DECLARE @MaxRow int 

     SELECT @MaxRow = MAX(RID) 
      FROM dbo.split2 (@RepChars) 

     WHILE @CurRow <= @MaxRow 
      BEGIN 
       SELECT @MyString = REPLACE(@MyString,VALUE,@NewChars) 
        FROM dbo.split2 (@RepChars) 
        WHERE RID = @CurRow 

       SET @CurRow = @CurRow + 1 
      END 

     RETURN (@MyString); 

    END 
GO 

W tym przykładzie zastąpić każdy znak bez spacji

SELECT [dbo].[MultiReplace]('6th month 2016-06 (test/requested)',',1st,2nd,3rd,4th,5th,6th,0,1,2,3,4,5,6,7,8,9,(,),/,-,+, ','') 

Wynik: monthtestrequested

Mam nadzieję, że jest to przydatne dla Ciebie .

Powiązane problemy