Jak zaktualizować z SELECT w SQL Server?


3694

W SQL Server możliwe jest INSERTprzejście do tabeli za pomocą SELECTinstrukcji:

INSERT INTO Table (col1, col2, col3)
SELECT col1, col2, col3 
FROM other_table 
WHERE sql = 'cool'

Czy można również aktualizować za pomocą SELECT? Mam tymczasową tabelę zawierającą wartości i chciałbym zaktualizować inną tabelę przy użyciu tych wartości. Być może coś takiego:

UPDATE Table SET col1, col2
SELECT col1, col2 
FROM other_table 
WHERE sql = 'cool'
WHERE Table.id = other_table.id

Odpowiedzi:


5368
UPDATE
    Table_A
SET
    Table_A.col1 = Table_B.col1,
    Table_A.col2 = Table_B.col2
FROM
    Some_Table AS Table_A
    INNER JOIN Other_Table AS Table_B
        ON Table_A.id = Table_B.id
WHERE
    Table_A.col3 = 'cool'

16
Jeśli edytujesz łącze między tabelami ( SET Table.other_table_id = @NewValue), zmień instrukcję ON na coś w styluON Table.id = @IdToEdit AND other_table.id = @NewValue
Trisped

11
@RogerRay, to pytanie dotyczy Microsoft SQL Server. Niestety składnia między różnymi implementacjami SQL może się różnić.
Charles Wood,

2
@CharlesWood tak. Mam to samo pytanie w MySQL. Byłoby wspaniale, gdyby ktoś wiedział, jak zaimplementować go w MySQL i dzielić się nim ze wszystkimi. Jestem pewien, że wiele osób szuka rozwiązania w wersji MySQL
Roger Ray

1
Jak korzystać z aliasu w zestawie? aktualizuj zestaw tabel a.col1 = b.col2 z tabeli wewnętrzna tabela łączenia 2 b na a.id = b.id; Zamiast tego muszę użyć zestawu aktualizacji tabeli table.col1 = b.col2 z tabeli wewnętrznego połączenia table2 b na a.id = b.id;
ThinkCode

11
Nieco spokrewnione, często lubię zapisywać moje zapytania UPDATE jako instrukcje SELECT, aby zobaczyć dane, które zostaną zaktualizowane przed wykonaniem. Sebastian omawia technikę tego w niedawnym poście na blogu: sqlity.net/en/2867/update-from-select
dennislloydjr

777

W SQL Server 2008 (lub nowszym) użyj MERGE

MERGE INTO YourTable T
   USING other_table S 
      ON T.id = S.id
         AND S.tsql = 'cool'
WHEN MATCHED THEN
   UPDATE 
      SET col1 = S.col1, 
          col2 = S.col2;

Alternatywnie:

MERGE INTO YourTable T
   USING (
          SELECT id, col1, col2 
            FROM other_table 
           WHERE tsql = 'cool'
         ) S
      ON T.id = S.id
WHEN MATCHED THEN
   UPDATE 
      SET col1 = S.col1, 
          col2 = S.col2;

122
MERGEmoże być również użyty do zapisu „Upserting”; to znaczy, UPDATEjeśli istnieje pasujący rekord, INSERTnowy rekord, jeśli nie znaleziono pasującego
brichins

16
To było około 10 razy szybsze niż odpowiednik aktualizacji ... oświadczenie dołączenia dla mnie.
Paul Suart

17
MERGE można również użyć do USUNIĘCIA. Należy jednak zachować ostrożność przy scalaniu, ponieważ stół TARGET nie może być stołem zdalnym.
Möoz,


16
@ SimonD: wybierz dowolne słowo kluczowe SQL Server, a znajdziesz błędy. Twój punkt? Założę się, że jest więcej błędów (i bardziej fundamentalne nich zbyt) związane z UPDATEniż MERGE, ludzie mają po prostu nauczyli się żyć z nimi, a oni stają się częścią krajobrazu ( „cechami”). Pomyśl, że blogi nie istniały, kiedy UPDATEbył nowy dzieciak w bloku.
dniu

671
UPDATE YourTable 
SET Col1 = OtherTable.Col1, 
    Col2 = OtherTable.Col2 
FROM (
    SELECT ID, Col1, Col2 
    FROM other_table) AS OtherTable
WHERE 
    OtherTable.ID = YourTable.ID

7
Zdecydowanie najprostszy! Jednak brakuje pola ID z wewnętrznego WYBORU. Będziesz tego potrzebować, aby klauzula WHERE zadziałała
John Doherty

12
Będzie to działało prawie we wszystkich systemach DBMS, co oznacza naukę raz, wykonywanie wszędzie. Jeśli jest to dla Ciebie ważniejsze niż wydajność, możesz preferować tę odpowiedź, zwłaszcza jeśli aktualizacja jest jednorazowa, aby poprawić niektóre dane.
Alan Macdonald

2
Jeśli musisz ustawić pierwszą tabelę z agregatami z drugiej, możesz umieścić agregaty w wybranym podzapytaniu, jak nie możesz tego zrobić SET Table_A.col1 = SUM(Table_B.col1)(ani żadnej innej funkcji agregującej). Więc lepiej niż odpowiedź Robin Day na ten cel.
Jason S

280

Zmodyfikowałbym doskonałą odpowiedź Robin na następujące:

UPDATE Table
SET Table.col1 = other_table.col1,
 Table.col2 = other_table.col2
FROM
    Table
INNER JOIN other_table ON Table.id = other_table.id
WHERE
    Table.col1 != other_table.col1
OR Table.col2 != other_table.col2
OR (
    other_table.col1 IS NOT NULL
    AND Table.col1 IS NULL
)
OR (
    other_table.col2 IS NOT NULL
    AND Table.col2 IS NULL
)

Bez klauzuli WHERE wpłyniesz nawet na wiersze, których nie trzeba zmieniać, co może (ewentualnie) spowodować ponowne obliczenie indeksu lub wyzwalacze ognia, które tak naprawdę nie powinny były zostać uruchomione.


7
Zakłada się jednak, że żadna z kolumn nie ma wartości zerowej.
Martin Smith

4
Masz rację, pisałem przykład ręcznie. Dodałem trzecią i czwartą klauzulę do instrukcji where, aby sobie z tym poradzić.
quillbreaker

46
WHERE EXISTS(SELECT T1.Col1, T1.Col2 EXCEPT SELECT T2.Col1, T2.Col2))jest bardziej zwięzły.
Martin Smith

5
czy zdanie nie powinno również zawierać tych dwóch w klauzuli where? (other_table.col1 jest pusty, a table.col1 nie jest pusty) lub (other_table.col2 jest pusty, a table.col2 nie jest pusty)
Barka

4
Zależy, czy chcesz zastąpić wartości null w miejscu docelowym wartościami null ze źródła. Często nie. Ale jeśli tak, najlepiej zastosować klauzulę gdzie Martin.
quillbreaker

204

Jednokierunkowa

UPDATE t 
SET t.col1 = o.col1, 
    t.col2 = o.col2
FROM 
    other_table o 
  JOIN 
    t ON t.id = o.id
WHERE 
    o.sql = 'cool'

165

Inną nie wspomnianą jeszcze możliwością jest po prostu wrzucenie SELECTsamej instrukcji do CTE, a następnie zaktualizowanie CTE.

;WITH CTE
     AS (SELECT T1.Col1,
                T2.Col1 AS _Col1,
                T1.Col2,
                T2.Col2 AS _Col2
         FROM   T1
                JOIN T2
                  ON T1.id = T2.id
         /*Where clause added to exclude rows that are the same in both tables
           Handles NULL values correctly*/
         WHERE EXISTS(SELECT T1.Col1,
                             T1.Col2
                       EXCEPT
                       SELECT T2.Col1,
                              T2.Col2))
UPDATE CTE
SET    Col1 = _Col1,
       Col2 = _Col2

Ma to tę zaletę, że łatwe jest samodzielne uruchomienie SELECTinstrukcji w celu sprawdzenia poprawności wyników, ale wymaga aliasu kolumn jak wyżej, jeśli mają one takie same nazwy w tabelach źródłowej i docelowej.

Ma to również takie samo ograniczenie jak zastrzeżona UPDATE ... FROMskładnia pokazana w czterech innych odpowiedziach. Jeśli tabela źródłowa znajduje się po wielu stronach złączenia jeden do wielu, nie jest deterministyczne, który z możliwych dopasowanych połączonych rekordów zostanie użyty w Update(problem, którego można MERGEuniknąć, zgłaszając błąd przy próbie aktualizacji ten sam rząd więcej niż raz).


3
czy jest jakieś znaczenie imienia CTE?
Raptor,

19
@ShivanRaptor - jest to skrót od Common Table Expression . Po prostu arbitralny alias w tym przypadku.
Martin Smith

3
Działa to również dobrze z wieloma CTE:;WITH SomeCompexCTE AS (...), CTEAsAbove AS (SELECT T1.Col1,... FROM T1 JOIN SomeComplexCTE...) UPDATE CTEAsAbove SET Col1=_Col1, ...
VeeTheSecond

117

Dla przypomnienia (i innych szukających tak jak ja) możesz to zrobić w MySQL w następujący sposób:

UPDATE first_table, second_table
SET first_table.color = second_table.color
WHERE first_table.id = second_table.foreign_id

Najprostszy? Dzięki ...
MarcoZen

96

Korzystanie z aliasu:

UPDATE t
   SET t.col1 = o.col1
  FROM table1 AS t
         INNER JOIN 
       table2 AS o 
         ON t.id = o.id

73

Najprostszym sposobem na to jest:

UPDATE
    table_to_update,
    table_info
SET
    table_to_update.col1 = table_info.col1,
    table_to_update.col2 = table_info.col2

WHERE
    table_to_update.ID = table_info.ID

2
Twój jest lepiej sformatowany; Ponadto, gdy używasz podselekcji, twoja (i Adriana) działa bardziej niezawodnie niż inny format. Dziękujemy za opublikowanie odpowiedzi.
Ben West

20
To nie jest składnia serwera
SQl

Działa to świetnie w SQL Alchemy. Może nie to, co zadało pytanie, ale na pewno mi pomogło. :)
JGTaylor

61

Może to być niszowy powód do przeprowadzenia aktualizacji (na przykład głównie używany w procedurze) lub może być oczywisty dla innych, ale należy również stwierdzić, że można wykonać instrukcję select-select bez użycia łączenia (w przypadku tabele, między którymi aktualizujesz, nie mają wspólnego pola).

update
    Table
set
    Table.example = a.value
from
    TableExample a
where
    Table.field = *key value* -- finds the row in Table 
    AND a.field = *key value* -- finds the row in TableExample a

59

Oto kolejna przydatna składnia:

UPDATE suppliers
SET supplier_name = (SELECT customers.name
                     FROM customers
                     WHERE customers.customer_id = suppliers.supplier_id)
WHERE EXISTS (SELECT customers.name
              FROM customers
              WHERE customers.customer_id = suppliers.supplier_id);

Sprawdza, czy ma wartość zero, czy nie, używając „GDZIE ISTNIEJE”.


52

Dodam to tylko, abyś mógł zobaczyć, jak to szybko napisać, abyś mógł sprawdzić, co zostanie zaktualizowane przed wykonaniem aktualizacji.

UPDATE Table 
SET  Table.col1 = other_table.col1,
     Table.col2 = other_table.col2 
--select Table.col1, other_table.col,Table.col2,other_table.col2, *   
FROM     Table 
INNER JOIN     other_table 
    ON     Table.id = other_table.id 

52

Jeśli używasz MySQL zamiast SQL Server, składnia jest następująca:

UPDATE Table1
INNER JOIN Table2
ON Table1.id = Table2.id
SET Table1.col1 = Table2.col1,
    Table1.col2 = Table2.col2

50

AKTUALIZACJA od WYBIERZ z WEJŚCIEM WEWNĘTRZNYM w bazie danych SQL

Ponieważ jest zbyt wiele odpowiedzi na ten post, z których większość jest bardzo pozytywnie oceniona, pomyślałem, że również tutaj przedstawię moją sugestię. Chociaż pytanie jest bardzo interesujące, widziałem na wielu stronach forum i stworzyłem rozwiązanie za pomocą INNER JOIN ze zrzutami ekranu.

Na początku stworzyłem tabelę o nazwie schoolold i wstawiłem kilka rekordów w odniesieniu do nazw kolumn i wykonuję ją.

Następnie wykonałem polecenie SELECT, aby wyświetlić wstawione rekordy.

Następnie stworzyłem nową tabelę o nazwie schoolnew i podobnie wykonałem na niej powyższe akcje.

Następnie, aby wyświetlić wstawione w nim rekordy, wykonuję polecenie SELECT.

Teraz chcę wprowadzić zmiany w trzecim i czwartym rzędzie, aby zakończyć tę akcję, wykonuję polecenie UPDATE za pomocą INNER JOIN .

Aby wyświetlić zmiany, wykonuję polecenie SELECT .

Możesz zobaczyć, jak Trzeci i Czwarty rekord szkolnej tabeli łatwo zastąpiono szkolną tabelą , używając INNER JOIN z instrukcją UPDATE.


43

A jeśli chcesz dołączyć do stołu sam (co nie zdarza się zbyt często):

update t1                    -- just reference table alias here
set t1.somevalue = t2.somevalue
from table1 t1               -- these rows will be the targets
inner join table1 t2         -- these rows will be used as source
on ..................        -- the join clause is whatever suits you

8
+1, ale powinieneś użyć odpowiednich nazw aliasów, takich jak targett1i sourcet1zamiast (lub również) komentarzy.
Mark Hurd

42

W poniższym przykładzie użyto tabeli pochodnej, instrukcji SELECT po klauzuli FROM, aby zwrócić stare i nowe wartości dla dalszych aktualizacji:

UPDATE x
SET    x.col1 = x.newCol1,
       x.col2 = x.newCol2
FROM   (SELECT t.col1,
               t2.col1 AS newCol1,
               t.col2,
               t2.col2 AS newCol2
        FROM   [table] t
               JOIN other_table t2
                 ON t.ID = t2.ID) x

41

Aktualizacja poprzez CTEjest bardziej czytelna niż inne odpowiedzi tutaj:

;WITH cte
     AS (SELECT col1,col2,id
         FROM   other_table
         WHERE  sql = 'cool')
UPDATE A
SET    A.col1 = B.col1,
       A.col2 = B.col2
FROM   table A
       INNER JOIN cte B
               ON A.id = B.id

39

Jeśli używasz programu SQL Server, możesz zaktualizować jedną tabelę z drugiej bez określania łączenia i po prostu połącz dwie z whereklauzuli. To znacznie ułatwia zapytanie SQL:

UPDATE Table1
SET Table1.col1 = Table2.col1,
    Table1.col2 = Table2.col2
FROM
    Table2
WHERE
    Table1.id = Table2.id

25

Skonsolidowanie wszystkich różnych podejść tutaj.

  1. Wybierz aktualizację
  2. Zaktualizuj za pomocą wspólnego wyrażenia tabelowego
  3. Łączyć

Przykładowa struktura tabeli znajduje się poniżej i zostanie zaktualizowana z Product_BAK do tabeli produktów.

Produkt

CREATE TABLE [dbo].[Product](
    [Id] [int] IDENTITY(1, 1) NOT NULL,
    [Name] [nvarchar](100) NOT NULL,
    [Description] [nvarchar](100) NULL
) ON [PRIMARY]

Product_BAK

    CREATE TABLE [dbo].[Product_BAK](
        [Id] [int] IDENTITY(1, 1) NOT NULL,
        [Name] [nvarchar](100) NOT NULL,
        [Description] [nvarchar](100) NULL
    ) ON [PRIMARY]

1. Wybierz aktualizację

    update P1
    set Name = P2.Name
    from Product P1
    inner join Product_Bak P2 on p1.id = P2.id
    where p1.id = 2

2. Zaktualizuj za pomocą wspólnego wyrażenia tabelowego

    ; With CTE as
    (
        select id, name from Product_Bak where id = 2
    )
    update P
    set Name = P2.name
    from  product P  inner join CTE P2 on P.id = P2.id
    where P2.id = 2

3. Scal

    Merge into product P1
    using Product_Bak P2 on P1.id = P2.id

    when matched then
    update set p1.[description] = p2.[description], p1.name = P2.Name;

W tej instrukcji Merge możemy wprowadzić wstawkę, jeśli nie znajdzie pasującego rekordu w celu, ale istnieje w źródle i znajduje składnię:

    Merge into product P1
    using Product_Bak P2 on P1.id = P2.id;

    when matched then
    update set p1.[description] = p2.[description], p1.name = P2.Name;

    WHEN NOT MATCHED THEN
    insert (name, description)
    values(p2.name, P2.description);

23

Innym sposobem jest użycie tabeli pochodnej:

UPDATE t
SET t.col1 = a.col1
    ,t.col2 = a.col2
FROM (
SELECT id, col1, col2 FROM @tbl2) a
INNER JOIN @tbl1 t ON t.id = a.id

Przykładowe dane

DECLARE @tbl1 TABLE (id INT, col1 VARCHAR(10), col2 VARCHAR(10))
DECLARE @tbl2 TABLE (id INT, col1 VARCHAR(10), col2 VARCHAR(10))

INSERT @tbl1 SELECT 1, 'a', 'b' UNION SELECT 2, 'b', 'c'

INSERT @tbl2 SELECT 1, '1', '2' UNION SELECT 2, '3', '4'

UPDATE t
SET t.col1 = a.col1
    ,t.col2 = a.col2
FROM (
SELECT id, col1, col2 FROM @tbl2) a
INNER JOIN @tbl1 t ON t.id = a.id

SELECT * FROM @tbl1
SELECT * FROM @tbl2

23
UPDATE TQ
SET TQ.IsProcessed = 1, TQ.TextName = 'bla bla bla'
FROM TableQueue TQ
INNER JOIN TableComment TC ON TC.ID = TQ.TCID
WHERE TQ.IsProcessed = 0

Aby upewnić się, że aktualizujesz to, co chcesz, wybierz najpierw

SELECT TQ.IsProcessed, 1 AS NewValue1, TQ.TextName, 'bla bla bla' AS NewValue2
FROM TableQueue TQ
INNER JOIN TableComment TC ON TC.ID = TQ.TCID
WHERE TQ.IsProcessed = 0

22

Istnieje jeszcze krótsza metoda i może być dla Ciebie zaskakująca:

Przykładowy zestaw danych:

CREATE TABLE #SOURCE ([ID] INT, [Desc] VARCHAR(10));
CREATE TABLE #DEST   ([ID] INT, [Desc] VARCHAR(10));

INSERT INTO #SOURCE VALUES(1,'Desc_1'), (2, 'Desc_2'), (3, 'Desc_3');
INSERT INTO #DEST   VALUES(1,'Desc_4'), (2, 'Desc_5'), (3, 'Desc_6');

Kod:

UPDATE #DEST
SET #DEST.[Desc] = #SOURCE.[Desc]
FROM #SOURCE
WHERE #DEST.[ID] = #SOURCE.[ID];

1
TAK - celowo nie ma JOIN i NIE - nie można tego zastosować do zmiennych tabeli.
Bartosz X

1
Myślę, że jeśli użyjesz [_id] na swoim #SOURCE, a nie [ID] tak samo jak w # DESTINATION, mogą pozwolić ci DOŁĄCZYĆ. "na # DESTINATION.ID = # SOURCE._id. Lub nawet użyj zmiennej tabeli jak @tbl," na PermTable.ID=@memorytbl._id ". Próbowałeś? Używam telefonu, aby odpowiedzieć na to pytanie, nie ma komputera, aby spróbować ,
Jenna Leaf,

2
Co to ma wspólnego z aktualizacją z SELECT?
Martin Smith

2
Jest to ten sam pomysł, ale inna metoda - wcale nie musisz wstawiać „select”, aby osiągnąć JOIN i WHERE w instrukcji update - która jest rodzajem zapytania SELECT bez pisania SELECT
Bartosz X

19

Posługiwać się:

drop table uno
drop table dos

create table uno
(
    uid int,
    col1 char(1),
    col2 char(2)
)
create table dos
(
    did int,
    col1 char(1),
    col2 char(2),
    [sql] char(4)
)
insert into uno(uid) values (1)
insert into uno(uid) values (2)
insert into dos values (1,'a','b',null)
insert into dos values (2,'c','d','cool')

select * from uno 
select * from dos

ZARÓWNO:

update uno set col1 = (select col1 from dos where uid = did and [sql]='cool'), 
col2 = (select col2 from dos where uid = did and [sql]='cool')

LUB:

update uno set col1=d.col1,col2=d.col2 from uno 
inner join dos d on uid=did where [sql]='cool'

select * from uno 
select * from dos

Jeśli nazwa kolumny ID jest taka sama w obu tabelach, po prostu umieść nazwę tabeli przed tabelą do aktualizacji i użyj aliasu dla wybranej tabeli, tj .:

update uno set col1 = (select col1 from dos d where uno.[id] = d.[id] and [sql]='cool'),
col2  = (select col2 from dos d where uno.[id] = d.[id] and [sql]='cool')

14

W zaakceptowanej odpowiedzi po:

SET
Table_A.col1 = Table_B.col1,
Table_A.col2 = Table_B.col2

Dodałbym:

OUTPUT deleted.*, inserted.*

To, co zwykle robię, to umieszczanie wszystkiego w transakcji wycofanej i używanie "OUTPUT": w ten sposób widzę wszystko, co ma się wydarzyć. Kiedy jestem zadowolony z tego, co widzę, zmieniam ROLLBACKna COMMIT.

Zwykle muszę dokumentować to, co zrobiłem, więc korzystam z tej "results to Text"opcji, gdy uruchamiam zapytanie wycofane i zapisuję zarówno skrypt, jak i wynik WYJŚCIA. (Oczywiście nie jest to praktyczne, jeśli zmieniłem zbyt wiele wierszy)



12

Poniższe rozwiązanie działa z bazą danych MySQL:

UPDATE table1 a , table2 b 
SET a.columname = 'some value' 
WHERE b.columnname IS NULL ;

12

Inny sposób aktualizacji z instrukcji select:

UPDATE A
SET A.col = A.col,B.col1 = B.col1
FROM  first_Table AS A
INNER JOIN second_Table AS B  ON A.id = B.id WHERE A.col2 = 'cool'

5
Inny sposób aktualizacji z instrukcji select Jaka jest różnica w stosunku do innych odpowiedzi? Proszę opracować swoją odpowiedź. Pamiętaj: Dobra odpowiedź zawsze będzie wyjaśniać, co zostało zrobione i dlaczego zostało zrobione w taki sposób, nie tylko dla PO, ale dla przyszłych odwiedzających SO.
B001 ᛦ

Ta odpowiedź pojawiła się w kolejce przeglądu niskiej jakości, prawdopodobnie dlatego, że nie podałeś żadnego wyjaśnienia kodu. Jeśli ten kod odpowiada na pytanie, rozważ dodanie tekstu wyjaśniającego kod w swojej odpowiedzi. W ten sposób znacznie częściej zyskujesz więcej głosów pozytywnych - i pomagasz pytającemu dowiedzieć się czegoś nowego.
lmo

8

Opcja 1: Korzystanie z połączenia wewnętrznego:

UPDATE
    A
SET
    A.col1 = B.col1,
    A.col2 = B.col2
FROM
    Some_Table AS A
    INNER JOIN Other_Table AS B
        ON A.id = B.id
WHERE
    A.col3 = 'cool'

Opcja 2: powiązane zapytanie podrzędne

UPDATE table 
SET Col1 = B.Col1, 
    Col2 = B.Col2 
FROM (
    SELECT ID, Col1, Col2 
    FROM other_table) B
WHERE 
    B.ID = table.ID

Czy ci się udało? Użyłem dokładnie tego samego zapytania, ale miałem błędy, gdy użyłem sprzężenia wewnętrznego, alias nie został rozwiązany. Jednak powiązane zapytanie podrzędne działało idealnie.
Pratyush Raizada

@PratyushRaizada jaki masz błąd?
Santhana

Nie mam dokładnych dzienników błędów, ale alias A był przywoływany przed przypisaniem, co spowodowało błąd.
Pratyush Raizada

Użyłem skorelowanego zapytania podrzędnego
pat capozzi

3
UPDATE table1
SET column1 = (SELECT expression1
               FROM table2
               WHERE conditions)
[WHERE conditions];

Składnia instrukcji UPDATE podczas aktualizowania jednej tabeli danymi z innej tabeli w programie SQL Server


1

Możesz użyć tego do aktualizacji na serwerze SQL

UPDATE
    T1
SET
   T1.col1 = T2.col1,
   T1.col2 = T2.col2
FROM
   Table1 AS T1
INNER JOIN Table2 AS T2
    ON T1.id = T2.id
WHERE
    T1.col3 = 'cool'
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.