Jak zrobić zamianę wyrażenia regularnego w MySQL?


515

Mam tabelę z ~ 500 tys. Rzędów; varchar (255) kolumna UTF8 filenamezawiera nazwę pliku;

Próbuję usunąć różne dziwne znaki z nazwy pliku - pomyślałem, że użyję klasy znaków: [^a-zA-Z0-9()_ .\-]

Czy w MySQL jest funkcja, która pozwala zastąpić wyrażenie regularne ? Szukam funkcji podobnej do funkcji REPLACE () - uproszczony przykład poniżej:

SELECT REPLACE('stackowerflow', 'ower', 'over');

Output: "stackoverflow"

/* does something like this exist? */
SELECT X_REG_REPLACE('Stackoverflow','/[A-Zf]/','-'); 

Output: "-tackover-low"

Wiem o REGEXP / RLIKE , ale sprawdzają one tylko, czy istnieje dopasowanie, a nie to , co to jest dopasowanie.

( Mógłbym zrobić „ SELECT pkey_id,filename FROM foo WHERE filename RLIKE '[^a-zA-Z0-9()_ .\-]'” ze skryptu PHP, zrobić preg_replacea potem „ UPDATE foo ... WHERE pkey_id=...”, ale to wygląda na ostateczny powolny i brzydki hack)


8
To prośba o dodanie funkcji od 2007 roku: bugs.mysql.com/bug.php?id=27389 . Jeśli naprawdę chcesz tę funkcję, zaloguj się i kliknij przycisk „Wpływa na mnie”. Mam nadzieję, że uzyska wystarczającą liczbę głosów.
TMS

4
@Tomas: Zrobiłem to ... w 2009 roku, kiedy się rozglądałem. Ponieważ postęp był zerowy - najwyraźniej nie jest to taka ważna cecha. (btw Postgres ma to: stackoverflow.com/questions/11722995/... )
Piskvor opuścił budynek

1
Powiązana, prostsza wersja tego pytania: stackoverflow.com/questions/6942973/…
Kzqai

2
Stworzyłem regexp_split(funkcja + procedura) i regexp_replace, które są zaimplementowane z REGEXPoperatorem. W przypadku prostych wyszukiwań wystarczy. Możesz go znaleźć tutaj - tak jest w przypadku kodu przechowywanego w MySQL, bez UDF. Jeśli znajdziesz jakieś błędy, które nie są objęte znanymi ograniczeniami - możesz otworzyć problem.
Alma Do

1
Znalazłem tę bibliotekę w innym wątku SO: github.com/mysqludf/lib_mysqludf_preg działa idealnie.
Kyle

Odpowiedzi:


77

Z MySQL 8.0+ możesz używać REGEXP_REPLACEfunkcji natywnej .

12.5.2 Wyrażenia regularne :

REGEXP_REPLACE(expr, pat, repl[, pos[, occurrence[, match_type]]])

Zamienia wystąpienia w wyrażeniu ciągu pasujące do wyrażenia regularnego określonego przez pat wzoru na zastępujący ciąg repl i zwraca łańcuch wynikowy. Jeśli expr , pat lub repl jest NULL, zwracana jest wartość NULL.

i obsługa wyrażeń regularnych :

Wcześniej MySQL korzystał z biblioteki wyrażeń regularnych Henry Spencer do obsługi operatorów wyrażeń regularnych ( REGEXP, RLIKE).

Obsługa wyrażeń regularnych została ponownie zaimplementowana przy użyciu International Components for Unicode (ICU), który zapewnia pełną obsługę Unicode i jest bezpieczny dla wielu bajtów. REGEXP_LIKE()Funkcja wykonuje regularne dostosowywanie wyraz w sposobie porozumiewania się REGEXPi RLIKEpodmiotów, które obecnie są synonimami dla tej funkcji. Ponadto REGEXP_INSTR(), REGEXP_REPLACE()i REGEXP_SUBSTR() działa w celu znalezienia dopasowania położenia i wykonywać podciąg podstawienia i ekstrakcji, odpowiednio.

SELECT REGEXP_REPLACE('Stackoverflow','[A-Zf]','-',1,0,'c'); 
-- Output:
-tackover-low

DBFiddle Demo


146

MySQL 8.0+ :

Możesz użyć natywnego REGEXP_REPLACE funkcji .

Starsza wersja:

Możesz użyć funkcji zdefiniowanej przez użytkownika ( UDF ), takiej jak mysql-udf-regexp .


3
REGEXP_REPLACE jako funkcja zdefiniowana przez użytkownika? Wygląda obiecująco, przyjrzy się temu. Dzięki!
Piskvor opuścił budynek

15
Niestety mysql-udf-regexp nie wydaje się obsługiwać znaków wielobajtowych. regexp_replace („äöõü”, „ä”, „”) zwraca długi ciąg liczbowy zamiast prawdziwego tekstu.
lkraav

3
Sam MySQL nie obsługuje znaków wielobajtowych dzięki funkcjom RegEx.
Brad

4
Użytkownicy systemu Windows: dołączona tutaj biblioteka UDF nie wydaje się mieć dobrej obsługi systemu Windows. Przedstawiona metoda instalacji systemu Windows nie działała dla mnie dobrze.
Jonathan

2
@lkraav powinieneś wypróbować bibliotekę lib_mysqludf_preg poniżej, ponieważ działa świetnie. Jest to pełna wersja, ponieważ domyślnie zwraca kroplę i nie wiem, czy masz domyślnie zestaw znaków wielobajtowych: wybierz obsadę (TR jako znak) ZBIERZ utf8_unicode_ci z (wybierz preg_replace ('/ ä /', '', „öõüä”) R) T
gillyspy

124

Zamiast tego użyj MariaDB. Ma funkcję

REGEXP_REPLACE(col, regexp, replace)

Zobacz dokumenty MariaDB i rozszerzenia wyrażeń regularnych PCRE

Pamiętaj, że możesz także użyć grupowania wyrażeń regularnych (uważam, że to bardzo przydatne):

SELECT REGEXP_REPLACE("stackoverflow", "(stack)(over)(flow)", '\\2 - \\1 - \\3')

zwroty

over - stack - flow

12
pochodzi z mariadb 10
Nick

6
Następnym razem będę go potrzebować, oto składnia do zmiany całej kolumny: UPDATE table SET Name = REGEXP_REPLACE(Name, "-2$", "\\1")Usuwa to -2 z abcxyz-2 z całej kolumny na raz.
Josiah

27
Zmiana całej platformy nie jest realistycznym rozwiązaniem.
David Baucum

3
@DavidBaucum MariaDB to drop-in zamiennik dla MySQL. Nie jest to więc „zmiana platformy”, ale raczej wybór innej linii lotniczej na tę samą podróż
Benvorth,


113

Moja metoda brutalnej siły, aby to zadziałało, to:

  1. Zrzuć stół - mysqldump -u user -p database table > dump.sql
  2. Znajdź i zamień kilka wzorców - find /path/to/dump.sql -type f -exec sed -i 's/old_string/new_string/g' {} \;Istnieją oczywiście inne wyrażenia regularne perla, które możesz wykonać na pliku.
  3. Zaimportuj tabelę - mysqlimport -u user -p database table < dump.sql

Jeśli chcesz się upewnić, że ciąg nie znajduje się w innym miejscu w zestawie danych, uruchom kilka wyrażeń regularnych, aby upewnić się, że wszystkie występują w podobnym środowisku. Tworzenie kopii zapasowej nie jest również trudne przed uruchomieniem zastępowania, na wypadek, gdyby przypadkowo zniszczyłeś coś, co traci głębię informacji.


33
Okej, to też powinno działać; Nie rozważałem zamiany offline. Fajne, nieszablonowe myślenie!
Piskvor opuścił budynek

10
Wydaje mi się dziwne, że użyłbyś takiego znajdowania, skróciłbym polecenie do sed -i 's / old_string / new_string / g' /path/to/dump.sql
speshak

36
Bardzo ryzykowne i niepraktyczne w przypadku dużych zbiorów danych lub z zachowaniem integralności referencyjnej: w celu usunięcia danych, a następnie włożenia ich ponownie, należy wyłączyć integralność referencyjną, pozostawiając w praktyce również bazę danych.
Raul Luna

5
Korzystając z tej metody w przeszłości, kłócę się z Raulem, jest to bardzo ryzykowne. Musisz również mieć absolutną pewność, że Twój ciąg nie znajduje się w innym miejscu w zbiorze danych.
eggmatters,

1
Wiele lat spóźniłem się na odpowiedź @speshak, ale zdecydowałem się na dostęp do pliku w ten sposób, ponieważ początkowo byłem bardzo zdenerwowany z tych samych powodów, jak wspomniano powyżej. Wówczas wydawało się, że oddzielenie części „znajdź plik” od części „zamień” ułatwiłoby odczytanie kodu przed jego przesłaniem
Ryan Ward

42

rozwiązujemy ten problem bez użycia wyrażenia regularnego to zapytanie zastępuje tylko ciąg dokładnie dopasowany.

update employee set
employee_firstname = 
trim(REPLACE(concat(" ",employee_firstname," "),' jay ',' abc '))

Przykład:

emp_id nazwa_pierwszego pracownika

1 jay

2 sójka ajay

3 jay

Po wykonaniu wyniku zapytania:

emp_id nazwa_pierwszego pracownika

1 abc

2 abc ajay

3 abc


@yellowmelon po co są dwie pary podwójnych cytatów?
codecowboy

5
Wypełnia nazwę pracownika spacjami przed i po. To pozwala mu na zamianę (spacja) nazwa_pracy (spacja), co pozwala uniknąć przechwycenia nazwy pseudonimu „jay”, jeśli jest częścią większego ciągu „ajay”. Po zakończeniu przycina przestrzenie.
Slam

42

Niedawno napisałem funkcję MySQL do zamiany ciągów za pomocą wyrażeń regularnych. Możesz znaleźć mój post w następującej lokalizacji:

http://techras.wordpress.com/2011/06/02/regex-replace-for-mysql/

Oto kod funkcji:

DELIMITER $$

CREATE FUNCTION  `regex_replace`(pattern VARCHAR(1000),replacement VARCHAR(1000),original VARCHAR(1000))
RETURNS VARCHAR(1000)
DETERMINISTIC
BEGIN 
 DECLARE temp VARCHAR(1000); 
 DECLARE ch VARCHAR(1); 
 DECLARE i INT;
 SET i = 1;
 SET temp = '';
 IF original REGEXP pattern THEN 
  loop_label: LOOP 
   IF i>CHAR_LENGTH(original) THEN
    LEAVE loop_label;  
   END IF;
   SET ch = SUBSTRING(original,i,1);
   IF NOT ch REGEXP pattern THEN
    SET temp = CONCAT(temp,ch);
   ELSE
    SET temp = CONCAT(temp,replacement);
   END IF;
   SET i=i+1;
  END LOOP;
 ELSE
  SET temp = original;
 END IF;
 RETURN temp;
END$$

DELIMITER ;

Przykładowe wykonanie:

mysql> select regex_replace('[^a-zA-Z0-9\-]','','2my test3_text-to. check \\ my- sql (regular) ,expressions ._,');

25
Po prostu wzmocnię powyższy punkt: ta funkcja zastępuje znaki pasujące do wyrażenia jednoznakowego. Mówi powyżej, że jest używany „do repalowania ciągów za pomocą wyrażeń regularnych”, co może być nieco mylące. Wykonuje swoją pracę, ale nie jest to prośba o pracę. (Nie jest to skarga - chodzi tylko o uratowanie wiodących ludzi na złej ścieżce)
Jason

2
Bardziej pomocne byłoby zamieszczenie kodu w odpowiedzi zamiast zamieszczania odsyłacza bez linku.
phobie

2
Fajnie - ale niestety nie zajmuje się takimi referencjami jak select regex_replace('.*(abc).*','\1','noabcde')(zwraca „noabcde”, a nie „abc”).
Izzy

@phobie ktoś inny zrobił to w tej odpowiedzi - tylko jako odniesienie na wypadek, gdyby link umarł;)
Izzy

Zmodyfikowałem tę metodę, aby spróbować rozwiązać niektóre z wyżej wymienionych ograniczeń i nie tylko. Proszę zobaczyć tę odpowiedź .
Steve Chambers


13

AKTUALIZACJA 2: Użyteczny zestaw funkcji wyrażeń regularnych, w tym REGEXP_REPLACE , został teraz udostępniony w MySQL 8.0. Czyni to czytanie niepotrzebnym, chyba że jesteś ograniczony do używania wcześniejszej wersji.


AKTUALIZACJA 1: Zrobiłem to z postu na blogu: http://stevettt.blogspot.co.uk/2018/02/a-mysql-regular-expression-replace.html


Poniższe rozwinięcie funkcji Rasika Godawatte, ale przeszukuje wszystkie niezbędne podłańcuchy, a nie tylko testuje pojedyncze znaki:

-- ------------------------------------------------------------------------------------
-- USAGE
-- ------------------------------------------------------------------------------------
-- SELECT reg_replace(<subject>,
--                    <pattern>,
--                    <replacement>,
--                    <greedy>,
--                    <minMatchLen>,
--                    <maxMatchLen>);
-- where:
-- <subject> is the string to look in for doing the replacements
-- <pattern> is the regular expression to match against
-- <replacement> is the replacement string
-- <greedy> is TRUE for greedy matching or FALSE for non-greedy matching
-- <minMatchLen> specifies the minimum match length
-- <maxMatchLen> specifies the maximum match length
-- (minMatchLen and maxMatchLen are used to improve efficiency but are
--  optional and can be set to 0 or NULL if not known/required)
-- Example:
-- SELECT reg_replace(txt, '^[Tt][^ ]* ', 'a', TRUE, 2, 0) FROM tbl;
DROP FUNCTION IF EXISTS reg_replace;
DELIMITER //
CREATE FUNCTION reg_replace(subject VARCHAR(21845), pattern VARCHAR(21845),
  replacement VARCHAR(21845), greedy BOOLEAN, minMatchLen INT, maxMatchLen INT)
RETURNS VARCHAR(21845) DETERMINISTIC BEGIN 
  DECLARE result, subStr, usePattern VARCHAR(21845); 
  DECLARE startPos, prevStartPos, startInc, len, lenInc INT;
  IF subject REGEXP pattern THEN
    SET result = '';
    -- Sanitize input parameter values
    SET minMatchLen = IF(minMatchLen < 1, 1, minMatchLen);
    SET maxMatchLen = IF(maxMatchLen < 1 OR maxMatchLen > CHAR_LENGTH(subject),
                         CHAR_LENGTH(subject), maxMatchLen);
    -- Set the pattern to use to match an entire string rather than part of a string
    SET usePattern = IF (LEFT(pattern, 1) = '^', pattern, CONCAT('^', pattern));
    SET usePattern = IF (RIGHT(pattern, 1) = '$', usePattern, CONCAT(usePattern, '$'));
    -- Set start position to 1 if pattern starts with ^ or doesn't end with $.
    IF LEFT(pattern, 1) = '^' OR RIGHT(pattern, 1) <> '$' THEN
      SET startPos = 1, startInc = 1;
    -- Otherwise (i.e. pattern ends with $ but doesn't start with ^): Set start pos
    -- to the min or max match length from the end (depending on "greedy" flag).
    ELSEIF greedy THEN
      SET startPos = CHAR_LENGTH(subject) - maxMatchLen + 1, startInc = 1;
    ELSE
      SET startPos = CHAR_LENGTH(subject) - minMatchLen + 1, startInc = -1;
    END IF;
    WHILE startPos >= 1 AND startPos <= CHAR_LENGTH(subject)
      AND startPos + minMatchLen - 1 <= CHAR_LENGTH(subject)
      AND !(LEFT(pattern, 1) = '^' AND startPos <> 1)
      AND !(RIGHT(pattern, 1) = '$'
            AND startPos + maxMatchLen - 1 < CHAR_LENGTH(subject)) DO
      -- Set start length to maximum if matching greedily or pattern ends with $.
      -- Otherwise set starting length to the minimum match length.
      IF greedy OR RIGHT(pattern, 1) = '$' THEN
        SET len = LEAST(CHAR_LENGTH(subject) - startPos + 1, maxMatchLen), lenInc = -1;
      ELSE
        SET len = minMatchLen, lenInc = 1;
      END IF;
      SET prevStartPos = startPos;
      lenLoop: WHILE len >= 1 AND len <= maxMatchLen
                 AND startPos + len - 1 <= CHAR_LENGTH(subject)
                 AND !(RIGHT(pattern, 1) = '$' 
                       AND startPos + len - 1 <> CHAR_LENGTH(subject)) DO
        SET subStr = SUBSTRING(subject, startPos, len);
        IF subStr REGEXP usePattern THEN
          SET result = IF(startInc = 1,
                          CONCAT(result, replacement), CONCAT(replacement, result));
          SET startPos = startPos + startInc * len;
          LEAVE lenLoop;
        END IF;
        SET len = len + lenInc;
      END WHILE;
      IF (startPos = prevStartPos) THEN
        SET result = IF(startInc = 1, CONCAT(result, SUBSTRING(subject, startPos, 1)),
                        CONCAT(SUBSTRING(subject, startPos, 1), result));
        SET startPos = startPos + startInc;
      END IF;
    END WHILE;
    IF startInc = 1 AND startPos <= CHAR_LENGTH(subject) THEN
      SET result = CONCAT(result, RIGHT(subject, CHAR_LENGTH(subject) + 1 - startPos));
    ELSEIF startInc = -1 AND startPos >= 1 THEN
      SET result = CONCAT(LEFT(subject, startPos), result);
    END IF;
  ELSE
    SET result = subject;
  END IF;
  RETURN result;
END//
DELIMITER ;

Próbny

Demo Rextester

Ograniczenia

  1. Ta metoda oczywiście potrwa chwilę, gdy ciąg znaków jest duży. Aktualizacja: Dodano teraz parametry minimalnej i maksymalnej długości dopasowania w celu poprawy wydajności, gdy są one znane (zero = nieznany / nieograniczony).
  2. To nie pozwala podstawienie wstecznych (na przykład \1, \2 etc.) w celu zastąpienia grup przechwytywanie. Jeśli ta funkcja jest potrzebna, zapoznaj się z tą odpowiedzią która próbuje zapewnić obejście, aktualizując funkcję, aby umożliwić wtórne wyszukiwanie i zamianę w każdym znalezionym dopasowaniu (kosztem zwiększonej złożoności).
  3. Jeśli ^i / lub $jest używany we wzorcu, muszą znajdować się odpowiednio na samym początku i na samym końcu - np. Wzorce, (^start|end$)które nie są obsługiwane.
  4. Istnieje flaga „chciwa”, która określa, czy ogólne dopasowanie powinno być zachłanne, czy nie. Łączenie zachłannego i leniwego dopasowywania w jednym wyrażeniu regularnym (np. a.*?b.*) Nie jest obsługiwane.

Przykłady użycia

Funkcja została użyta do odpowiedzi na następujące pytania StackOverflow:


7

Możesz to zrobić ... ale nie jest to zbyt mądre ... jest to tak odważne, jak spróbuję ... o ile pełne wsparcie RegEx będzie znacznie lepsze przy użyciu Perla lub podobnego.

UPDATE db.tbl
SET column = 
CASE 
WHEN column REGEXP '[[:<:]]WORD_TO_REPLACE[[:>:]]' 
THEN REPLACE(column,'WORD_TO_REPLACE','REPLACEMENT')
END 
WHERE column REGEXP '[[:<:]]WORD_TO_REPLACE[[:>:]]'

1
Nie, to nie zadziała. Wyobraź sobie, że twoja kolumna zawiera „asdfWORD_TO_REPLACE WORD_TO_REPLACE”. Twoja metoda spowodowałaby „asdfREPLACEMENT REPLACEMENT”, gdzie poprawną odpowiedzią byłoby „asdfWORD_TO_REPLACE REPLACEMENT”.
Ryan Shillington

1
@ Ryan ... właśnie dlatego powiedziałem, że nie było to zbyt mądre ... w przypadku użycia, który podasz, zdecydowanie by się nie udało. Krótko mówiąc, złym pomysłem jest stosowanie struktury przypominającej wyrażenia regularne. Co gorsza ... jeśli upuścisz klauzulę
Eddie B,

1
Właściwie Ryan, w tym przypadku jesteś niepoprawny, ponieważ znaczniki znajdą tylko pasujące słowa „granice” o zerowej długości, więc pasują tylko słowa z granicami przed i po tym słowie ... To zły pomysł…
Eddie B,

6

Możemy użyć warunku IF w zapytaniu SELECT, jak poniżej:

Załóżmy, że dla czegokolwiek z „ABC”, „ABC1”, „ABC2”, „ABC3”, ... chcemy zastąpić „ABC”, a następnie używając warunku REGEXP i IF () w zapytaniu SELECT, możemy to osiągnąć .

Składnia:

SELECT IF(column_name REGEXP 'ABC[0-9]$','ABC',column_name)
FROM table1 
WHERE column_name LIKE 'ABC%';

Przykład:

SELECT IF('ABC1' REGEXP 'ABC[0-9]$','ABC','ABC1');

Witam, dziękuję za sugestię. Próbowałem czegoś podobnego, ale wydajność moich zestawów danych była niezadowalająca. W przypadku niewielkich zestawów może to być opłacalne.
Piskvor opuścił budynek

3

Poniższy w zasadzie znajduje pierwsze dopasowanie od lewej, a następnie zastępuje wszystkie jego wystąpienia (testowane w ).

Stosowanie:

SELECT REGEX_REPLACE('dis ambiguity', 'dis[[:space:]]*ambiguity', 'disambiguity');

Realizacja:

DELIMITER $$
CREATE FUNCTION REGEX_REPLACE(
  var_original VARCHAR(1000),
  var_pattern VARCHAR(1000),
  var_replacement VARCHAR(1000)
  ) RETURNS
    VARCHAR(1000)
  COMMENT 'Based on https://techras.wordpress.com/2011/06/02/regex-replace-for-mysql/'
BEGIN
  DECLARE var_replaced VARCHAR(1000) DEFAULT var_original;
  DECLARE var_leftmost_match VARCHAR(1000) DEFAULT
    REGEX_CAPTURE_LEFTMOST(var_original, var_pattern);
    WHILE var_leftmost_match IS NOT NULL DO
      IF var_replacement <> var_leftmost_match THEN
        SET var_replaced = REPLACE(var_replaced, var_leftmost_match, var_replacement);
        SET var_leftmost_match = REGEX_CAPTURE_LEFTMOST(var_replaced, var_pattern);
        ELSE
          SET var_leftmost_match = NULL;
        END IF;
      END WHILE;
  RETURN var_replaced;
END $$
DELIMITER ;

DELIMITER $$
CREATE FUNCTION REGEX_CAPTURE_LEFTMOST(
  var_original VARCHAR(1000),
  var_pattern VARCHAR(1000)
  ) RETURNS
    VARCHAR(1000)
  COMMENT '
  Captures the leftmost substring that matches the [var_pattern]
  IN [var_original], OR NULL if no match.
  '
BEGIN
  DECLARE var_temp_l VARCHAR(1000);
  DECLARE var_temp_r VARCHAR(1000);
  DECLARE var_left_trim_index INT;
  DECLARE var_right_trim_index INT;
  SET var_left_trim_index = 1;
  SET var_right_trim_index = 1;
  SET var_temp_l = '';
  SET var_temp_r = '';
  WHILE (CHAR_LENGTH(var_original) >= var_left_trim_index) DO
    SET var_temp_l = LEFT(var_original, var_left_trim_index);
    IF var_temp_l REGEXP var_pattern THEN
      WHILE (CHAR_LENGTH(var_temp_l) >= var_right_trim_index) DO
        SET var_temp_r = RIGHT(var_temp_l, var_right_trim_index);
        IF var_temp_r REGEXP var_pattern THEN
          RETURN var_temp_r;
          END IF;
        SET var_right_trim_index = var_right_trim_index + 1;
        END WHILE;
      END IF;
    SET var_left_trim_index = var_left_trim_index + 1;
    END WHILE;
  RETURN NULL;
END $$
DELIMITER ;

3

Myślę, że istnieje prosty sposób na osiągnięcie tego i działa dobrze dla mnie.

Aby WYBRAĆ wiersze za pomocą REGEX

SELECT * FROM `table_name` WHERE `column_name_to_find` REGEXP 'string-to-find'

Aby zaktualizować wiersze za pomocą REGEX

UPDATE `table_name` SET column_name_to_find=REGEXP_REPLACE(column_name_to_find, 'string-to-find', 'string-to-replace') WHERE column_name_to_find REGEXP 'string-to-find'

Referencje REGEXP: https://www.geeksforgeeks.org/mysql-regular-expressions-regexp/


Dziękuję :) Można to zrobić łatwo od wersji 8.
Piskvor opuścił budynek
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.