SQL'deki sütunları ve satırları değiştirmenin basit yolu?


110

SQL'de satırları olan sütunları nasıl basitçe değiştiririm? Değiştirilecek basit bir komut var mı?

yani bu sonucu çevirin:

        Paul  | John  | Tim  |  Eric
Red     1       5       1       3
Green   8       4       3       5
Blue    2       2       9       1

bunun içine:

        Red  | Green | Blue
Paul    1       8       2
John    5       4       2
Tim     1       3       9
Eric    3       5       1

PIVOT bu senaryo için çok karmaşık görünüyor.


Yanıtlar:


145

Bu verileri dönüştürebilmenin birkaç yolu vardır. Orijinal gönderide, aşağıdakileri beyan PIVOTbu senaryo için çok karmaşık görünüyor, ama kullanan çok kolay uygulanabilir hem UNPIVOTvePIVOT SQL Server fonksiyonları.

Bununla birlikte, bu işlevlere erişiminiz yoksa, bu, UNION ALLto UNPIVOTve ardından bir CASEifade ile bir toplama işlevi kullanılarak çoğaltılabilir PIVOT:

Tablo Oluştur:

CREATE TABLE yourTable([color] varchar(5), [Paul] int, [John] int, [Tim] int, [Eric] int);

INSERT INTO yourTable
    ([color], [Paul], [John], [Tim], [Eric])
VALUES
    ('Red', 1, 5, 1, 3),
    ('Green', 8, 4, 3, 5),
    ('Blue', 2, 2, 9, 1);

Union All, Aggregate ve CASE Sürümü:

select name,
  sum(case when color = 'Red' then value else 0 end) Red,
  sum(case when color = 'Green' then value else 0 end) Green,
  sum(case when color = 'Blue' then value else 0 end) Blue
from
(
  select color, Paul value, 'Paul' name
  from yourTable
  union all
  select color, John value, 'John' name
  from yourTable
  union all
  select color, Tim value, 'Tim' name
  from yourTable
  union all
  select color, Eric value, 'Eric' name
  from yourTable
) src
group by name

Demo ile SQL Fiddle'ı görün

UNION ALLYapar UNPIVOTsütun dönüştürerek verilerin Paul, John, Tim, Ericayrı satırlara. Daha sonra , her biri için yeni sütunları elde etmek üzere deyimle sum()birlikte toplama işlevini uygularsınız .casecolor

Unpivot ve Pivot Statik Versiyon:

Hem UNPIVOTve PIVOTSQL server fonksiyonları çok daha kolay bu dönüşümü yapmak. Dönüştürmek istediğiniz tüm değerleri biliyorsanız, sonucu almak için bunları statik bir sürüme sabit kodlayabilirsiniz:

select name, [Red], [Green], [Blue]
from
(
  select color, name, value
  from yourtable
  unpivot
  (
    value for name in (Paul, John, Tim, Eric)
  ) unpiv
) src
pivot
(
  sum(value)
  for color in ([Red], [Green], [Blue])
) piv

Demo ile SQL Fiddle'ı görün

İç sorgu ile UNPIVOTaynı işlevi yerine getirir UNION ALL. Sütun listesini alır ve satırlara dönüştürür, PIVOTardından son dönüşümü sütunlara dönüştürür.

Dinamik Pivot Sürümü:

Bilinmeyen sayıda sütununuz varsa ( Paul, John, Tim, Ericörneğinizde) ve sonra dönüştürülecek bilinmeyen sayıda renk varsa, listeyi oluşturmak için dinamik sql'yi kullanabilir UNPIVOTve ardından PIVOT:

DECLARE @colsUnpivot AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX),
    @colsPivot as  NVARCHAR(MAX)

select @colsUnpivot = stuff((select ','+quotename(C.name)
         from sys.columns as C
         where C.object_id = object_id('yourtable') and
               C.name <> 'color'
         for xml path('')), 1, 1, '')

select @colsPivot = STUFF((SELECT  ',' 
                      + quotename(color)
                    from yourtable t
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')


set @query 
  = 'select name, '+@colsPivot+'
      from
      (
        select color, name, value
        from yourtable
        unpivot
        (
          value for name in ('+@colsUnpivot+')
        ) unpiv
      ) src
      pivot
      (
        sum(value)
        for color in ('+@colsPivot+')
      ) piv'

exec(@query)

Görmek Demo ile SQL Fiddle'ı

Dinamik versiyonu hem sorgular yourtableve sonra sys.columnstablo öğelerin listesini oluşturmak için UNPIVOTve PIVOT. Bu daha sonra yürütülecek bir sorgu dizesine eklenir. Dinamik sürümün artısı, değişen bir colorsve / veyanames bu, çalışma zamanında listeyi oluşturacaktır.

Üç sorgu da aynı sonucu verecektir:

| NAME | RED | GREEN | BLUE |
-----------------------------
| Eric |   3 |     5 |    1 |
| John |   5 |     4 |    2 |
| Paul |   1 |     8 |    2 |
|  Tim |   1 |     3 |    9 |

Bir sorgunun sonuçlarını bir veritabanına aktaracak bir yöntem istediğimi vurgulamalıydım. Bir sorgudan alınan sonuç kümesi üzerinde sorgunun kendisi veya bilgi çıkardığı sütunlar, tablolar vb. Hakkında hiçbir şey bilmeden çalıştırılabilen basit bir devrik tipi komut. Şuna benzer bir şey: (TRANSPOSE (SELECT ......)) Yukarıda verdiğim örnekte, ilk tablonun birden çok tablo, birlik, pivot vb. İçeren çok karmaşık bir sorgudan oluşturulan bir sonuç kümesi olduğunu hayal edin. Yine de sonuç bir basit tablo. Bu sonuçta sadece satırları olan sütunları çevirmek istiyorum.
edezzie

Sonucu ac # DataTable'a aktarıyorum. Orada yapmak için basit bir yöntem olan 'Transpose (Datatable dt)' oluşturabilirim, ancak SQL'de bunu yapmak için herhangi bir şey var mı?
edezzie

@edezzie SQL'de bunu yapmanın basit bir yolu yoktur. Tablo adını geçirmek ve bilgileri sistem tablolarından çekmek için dinamik sürümü büyük olasılıkla değiştirebilirsin.
Taryn

Bu parlak yanıtı CEVAP olarak işaretlememenizin herhangi bir nedeni var mı? @ Bluefeet'e bir madalya verin!
Fandango68

Bu, ([Kırmızı], [Yeşil] renk için Tablo özümlemesinden (([Paul], [John], [Tim], [Eric]) ad için değer) yukarı pivot (maks (değer)) iyi çalışıyor * , [Mavi])) p, ancak bu seçimi * Tablo pivotundan yazmak mümkün mü (([Paul], [John], [Tim], [Eric]) içindeki ad için değer) yukarı pivot (maks (değer) için (TABLENADINDAN RENK SEÇ) p içindeki renk, tırnaklı değer girmek yerine seçme sorgusu kullanılarak değerleri doğrudan alamaz.
Mogli

20

Bu normalde TÜM sütun VE satır etiketlerini önceden bilmenizi gerektirir. Aşağıdaki sorguda görebileceğiniz gibi, etiketlerin tümü hem UNPIVOT hem de (yeniden) PIVOT işlemlerinde tamamen listelenmiştir.

MS SQL Server 2012 Şema Kurulumu :

create table tbl (
    color varchar(10), Paul int, John int, Tim int, Eric int);
insert tbl select 
    'Red' ,1 ,5 ,1 ,3 union all select
    'Green' ,8 ,4 ,3 ,5 union all select
    'Blue' ,2 ,2 ,9 ,1;

Sorgu 1 :

select *
from tbl
unpivot (value for name in ([Paul],[John],[Tim],[Eric])) up
pivot (max(value) for color in ([Red],[Green],[Blue])) p

Sonuçlar :

| NAME | RED | GREEN | BLUE |
-----------------------------
| Eric |   3 |     5 |    1 |
| John |   5 |     4 |    2 |
| Paul |   1 |     8 |    2 |
|  Tim |   1 |     3 |    9 |

Ek Notlar:

  1. Bir tablo adı verildiğinde, local-name () kullanarak sys.columns veya FOR XML hilelerinden tüm sütun adlarını belirleyebilirsiniz .
  2. Ayrıca, FOR XML kullanarak farklı renklerin (veya bir sütun için değerlerin) listesini oluşturabilirsiniz.
  3. Yukarıdakiler, herhangi bir tabloyu işlemek için dinamik bir sql partisinde birleştirilebilir.

11

SQL'de sütunların ve satırların aktarılması için birkaç çözüm daha belirtmek isterim.

İlki - CURSOR kullanmaktır. Profesyonel topluluktaki genel fikir birliği SQL Server Cursors'dan uzak durmak olsa da, yine de imleç kullanımının tavsiye edildiği durumlar vardır. Her neyse, Cursors bize satırları sütunlara dönüştürmek için başka bir seçenek sunar.

  • Dikey genişleme

    PIVOT'a benzer şekilde, imleç, veri kümeniz daha fazla politika numarası içerecek şekilde genişledikçe daha fazla satır ekleme dinamik özelliğine sahiptir.

  • Yatay genişleme

    PIVOT'un aksine, imleç, komut dosyasını değiştirmeden yeni eklenen belgeyi içerecek şekilde genişleyebildiği için bu alanda üstündür.

  • Performans dökümü

    CURSOR kullanarak satırları sütunlara aktarmanın en büyük sınırlaması, genel olarak imleçleri kullanmakla bağlantılı bir dezavantajdır - önemli performans maliyetine sahiptirler. Bunun nedeni, İmlecin her FETCH NEXT işlemi için ayrı bir sorgu oluşturmasıdır.

Satırları sütunlara dönüştürmenin başka bir çözümü de XML kullanmaktır.

Satırları sütunlara dönüştürmeye yönelik XML çözümü, dinamik sütun sınırlamasına değinmesi açısından temelde PIVOT'un en uygun sürümüdür.

Komut dosyasının XML sürümü, XML Yolu, dinamik T-SQL ve bazı yerleşik işlevlerin (örneğin STUFF, QUOTENAME) bir kombinasyonunu kullanarak bu sınırlamayı giderir.

  • Dikey genişleme

    PIVOT ve İmleç'e benzer şekilde, yeni eklenen ilkeler, orijinal komut dosyasını değiştirmeden komut dosyasının XML sürümünde alınabilir.

  • Yatay genişleme

    PIVOT'dan farklı olarak, yeni eklenen belgeler komut dosyası değiştirilmeden görüntülenebilir.

  • Performans dökümü

    IO açısından, komut dosyasının XML sürümünün istatistikleri neredeyse PIVOT'a benzer - tek fark, XML'in dtTranspose tablosunun ikinci bir taramasına sahip olmasıdır, ancak bu kez mantıksal bir okuma - veri önbelleğinden.

Bu çözümler (bazı gerçek T-SQL örnekleri dahil) hakkında daha fazla bilgiyi bu makalede bulabilirsiniz: https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/


8

Buna göre çözelti gelen bluefeet burada transpoze bir tablo oluşturmak için dinamik SQL kullanan bir saklı bir işlemdir. Sırası değiştirilmiş sütun (ortaya çıkan tablodaki başlık olacak sütun) hariç tüm alanların sayısal olmasını gerektirir:

/****** Object:  StoredProcedure [dbo].[SQLTranspose]    Script Date: 11/10/2015 7:08:02 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:      Paco Zarate
-- Create date: 2015-11-10
-- Description: SQLTranspose dynamically changes a table to show rows as headers. It needs that all the values are numeric except for the field using for     transposing.
-- Parameters: @TableName - Table to transpose
--             @FieldNameTranspose - Column that will be the new headers
-- Usage: exec SQLTranspose <table>, <FieldToTranspose>
-- =============================================
ALTER PROCEDURE [dbo].[SQLTranspose] 
  -- Add the parameters for the stored procedure here
  @TableName NVarchar(MAX) = '', 
  @FieldNameTranspose NVarchar(MAX) = ''
AS
BEGIN
  -- SET NOCOUNT ON added to prevent extra result sets from
  -- interfering with SELECT statements.
  SET NOCOUNT ON;

  DECLARE @colsUnpivot AS NVARCHAR(MAX),
  @query  AS NVARCHAR(MAX),
  @queryPivot  AS NVARCHAR(MAX),
  @colsPivot as  NVARCHAR(MAX),
  @columnToPivot as NVARCHAR(MAX),
  @tableToPivot as NVARCHAR(MAX), 
  @colsResult as xml

  select @tableToPivot = @TableName;
  select @columnToPivot = @FieldNameTranspose


  select @colsUnpivot = stuff((select ','+quotename(C.name)
       from sys.columns as C
       where C.object_id = object_id(@tableToPivot) and
             C.name <> @columnToPivot 
       for xml path('')), 1, 1, '')

  set @queryPivot = 'SELECT @colsResult = (SELECT  '','' 
                    + quotename('+@columnToPivot+')
                  from '+@tableToPivot+' t
                  where '+@columnToPivot+' <> ''''
          FOR XML PATH(''''), TYPE)'

  exec sp_executesql @queryPivot, N'@colsResult xml out', @colsResult out

  select @colsPivot = STUFF(@colsResult.value('.', 'NVARCHAR(MAX)'),1,1,'')

  set @query 
    = 'select name, rowid, '+@colsPivot+'
        from
        (
          select '+@columnToPivot+' , name, value, ROW_NUMBER() over (partition by '+@columnToPivot+' order by '+@columnToPivot+') as rowid
          from '+@tableToPivot+'
          unpivot
          (
            value for name in ('+@colsUnpivot+')
          ) unpiv
        ) src
        pivot
        (
          sum(value)
          for '+@columnToPivot+' in ('+@colsPivot+')
        ) piv
        order by rowid'
  exec(@query)
END

Bu komutla sağlanan tabloyla test edebilirsiniz:

exec SQLTranspose 'yourTable', 'color'

argg. ama tüm alanların nvarchar (maks.) olduğu bir tane istiyorum
hamish

1
Tüm alanlar için nvarchar (max) sadece toplamı (değeri)
Paco Zarate

2

Yaptığım olduğum UnPivotilk ve sonuçları saklamak CTEve kullanma CTEiçinde Pivotoperasyon.

Demo

with cte as
(
select 'Paul' as Name, color, Paul as Value 
 from yourTable
 union all
 select 'John' as Name, color, John as Value 
 from yourTable
 union all
 select 'Tim' as Name, color, Tim as Value 
 from yourTable
 union all
 select 'Eric' as Name, color, Eric as Value 
 from yourTable
 )

select Name, [Red], [Green], [Blue]
from
(
select *
from cte
) as src
pivot 
(
  max(Value)
  for color IN ([Red], [Green], [Blue])
) as Dtpivot;

2

@Paco Zarate'nin yukarıdaki müthiş cevabına ekleyerek, birden fazla sütun türü olan bir tabloyu değiştirmek istiyorsanız, bunu 39. satırın sonuna ekleyin, böylece yalnızca intsütunları dönüştürür:

and C.system_type_id = 56   --56 = type int

İşte değiştirilmekte olan tam sorgu:

select @colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id(@tableToPivot) and
      C.name <> @columnToPivot and C.system_type_id = 56    --56 = type int
for xml path('')), 1, 1, '')

Başkalarını bulmak için şunu system_type_idçalıştırın:

select name, system_type_id from sys.types order by name

1

Bölünmüş bir metni + bluefeet cevabına göre değiştirmek için kullandığım kodu paylaşmayı seviyorum. Bu yaklaşımda MS SQL 2005'te bir prosedür olarak uygulanıyorum

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:      ELD.
-- Create date: May, 5 2016.
-- Description: Transpose from rows to columns the user split function.
-- =============================================
CREATE PROCEDURE TransposeSplit @InputToSplit VARCHAR(8000)
    ,@Delimeter VARCHAR(8000) = ','
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @colsUnpivot AS NVARCHAR(MAX)
        ,@query AS NVARCHAR(MAX)
        ,@queryPivot AS NVARCHAR(MAX)
        ,@colsPivot AS NVARCHAR(MAX)
        ,@columnToPivot AS NVARCHAR(MAX)
        ,@tableToPivot AS NVARCHAR(MAX)
        ,@colsResult AS XML

    SELECT @tableToPivot = '#tempSplitedTable'

    SELECT @columnToPivot = 'col_number'

    CREATE TABLE #tempSplitedTable (
        col_number INT
        ,col_value VARCHAR(8000)
        )

    INSERT INTO #tempSplitedTable (
        col_number
        ,col_value
        )
    SELECT ROW_NUMBER() OVER (
            ORDER BY (
                    SELECT 100
                    )
            ) AS RowNumber
        ,item
    FROM [DB].[ESCHEME].[fnSplit](@InputToSplit, @Delimeter)

    SELECT @colsUnpivot = STUFF((
                SELECT ',' + quotename(C.NAME)
                FROM [tempdb].sys.columns AS C
                WHERE C.object_id = object_id('tempdb..' + @tableToPivot)
                    AND C.NAME <> @columnToPivot
                FOR XML path('')
                ), 1, 1, '')

    SET @queryPivot = 'SELECT @colsResult = (SELECT  '','' 
                    + quotename(' + @columnToPivot + ')
                  from ' + @tableToPivot + ' t
                  where ' + @columnToPivot + ' <> ''''
          FOR XML PATH(''''), TYPE)'

    EXEC sp_executesql @queryPivot
        ,N'@colsResult xml out'
        ,@colsResult OUT

    SELECT @colsPivot = STUFF(@colsResult.value('.', 'NVARCHAR(MAX)'), 1, 1, '')

    SET @query = 'select name, rowid, ' + @colsPivot + '
        from
        (
          select ' + @columnToPivot + ' , name, value, ROW_NUMBER() over (partition by ' + @columnToPivot + ' order by ' + @columnToPivot + ') as rowid
          from ' + @tableToPivot + '
          unpivot
          (
            value for name in (' + @colsUnpivot + ')
          ) unpiv
        ) src
        pivot
        (
          MAX(value)
          for ' + @columnToPivot + ' in (' + @colsPivot + ')
        ) piv
        order by rowid'

    EXEC (@query)

    DROP TABLE #tempSplitedTable
END
GO

Bu çözümü, sıra olmadan satırların nasıl sıralanacağı ( SQLAuthority.com ) ve MSDN'deki ( social.msdn.microsoft.com ) bölme işlevi hakkındaki bilgilerle karıştırıyorum

Prodecure'u çalıştırdığınızda

DECLARE @RC int
DECLARE @InputToSplit varchar(MAX)
DECLARE @Delimeter varchar(1)

set @InputToSplit = 'hello|beautiful|world'
set @Delimeter = '|'

EXECUTE @RC = [TransposeSplit] 
   @InputToSplit
  ,@Delimeter
GO

sonraki sonucu onayladın

  name       rowid  1      2          3
  col_value  1      hello  beautiful  world

1

Bu şekilde, Tablodaki Dosyalardan (Sütunlardan) Tüm Verileri Kayda (Satır) Dönüştürün.

Declare @TableName  [nvarchar](128)
Declare @ExecStr    nvarchar(max)
Declare @Where      nvarchar(max)
Set @TableName = 'myTableName'
--Enter Filtering If Exists
Set @Where = ''

--Set @ExecStr = N'Select * From '+quotename(@TableName)+@Where
--Exec(@ExecStr)

Drop Table If Exists #tmp_Col2Row

Create Table #tmp_Col2Row
(Field_Name nvarchar(128) Not Null
,Field_Value nvarchar(max) Null
)

Set @ExecStr = N' Insert Into #tmp_Col2Row (Field_Name , Field_Value) '
Select @ExecStr += (Select N'Select '''+C.name+''' ,Convert(nvarchar(max),'+quotename(C.name) + ') From ' + quotename(@TableName)+@Where+Char(10)+' Union All '
         from sys.columns as C
         where (C.object_id = object_id(@TableName)) 
         for xml path(''))
Select @ExecStr = Left(@ExecStr,Len(@ExecStr)-Len(' Union All '))
--Print @ExecStr
Exec (@ExecStr)

Select * From #tmp_Col2Row
Go

0

Paco Zarate'nin çözümünü kullanabildim ve çok güzel çalışıyor. Bir satır eklemem gerekiyordu ("SET ANSI_WARNINGS ON"), ancak bu, onu kullanma veya adlandırmama özgü bir şey olabilir. Kullanımımda bir sorun var ve umarım birisi bana yardımcı olabilir:

Çözüm yalnızca gerçek bir SQL tablosu ile çalışır. Bunu geçici bir tabloyla ve ayrıca bellek içi (bildirilmiş) bir tabloyla denedim ama onlarla çalışmıyor. Bu yüzden çağıran kodumda SQL veritabanımda bir tablo oluşturup ardından SQLTranspose çağırıyorum. Yine harika çalışıyor. Tam istediğim şey bu. İşte benim sorunum:

Genel çözümün gerçekten dinamik olması için, SQLTranspose'a gönderdiğim hazırlanmış bilgileri geçici olarak depoladığım tabloyu oluşturmam ve ardından SQLTranspose çağrıldıktan sonra bu tabloyu silmem gerekiyor. Tablonun silinmesi, nihai uygulama planımla ilgili bir sorun teşkil ediyor. Kodun bir son kullanıcı uygulamasından (Microsoft Access formundaki / menüsündeki bir düğme) çalıştırılması gerekir. Bu SQL işlemini kullandığımda (bir SQL tablosu oluşturma, SQLTranspose çağırma, SQL tablosunu silme) son kullanıcı uygulaması bir hataya çarpıyor çünkü kullanılan SQL hesabı bir tablo bırakma hakkına sahip değil.

Bu yüzden birkaç olası çözüm olduğunu düşündüm:

  1. SQLTranspose'un geçici bir tabloyla veya bildirilmiş bir tablo değişkeniyle çalışmasını sağlamanın bir yolunu bulun.

  2. Gerçek bir SQL tablosu gerektirmeyen satırların ve sütunların aktarımı için başka bir yöntem bulun.

  3. Son kullanıcılarımın kullandığı SQL hesabının bir tabloyu bırakmasına izin verecek uygun bir yöntem bulun. Access uygulamama kodlanmış tek bir paylaşılan SQL hesabı. İznin, verilemeyen dbo tipi bir ayrıcalık olduğu anlaşılıyor.

Bunların bir kısmının başka, ayrı bir konu ve soruyu gerektirebileceğini kabul ediyorum. Bununla birlikte, bir çözümün satırların ve sütunların aktarımını yapmanın farklı bir yolu olabileceği ihtimali olduğundan, bu konudaki ilk yazımı burada yapacağım.

DÜZENLEME: Paco'nun önerdiği gibi, sondan 6. satırdaki toplamı (değeri) maksimum (değer) ile değiştirdim.

DÜZENLE:

Benim için işe yarayan bir şey buldum. Bunun en iyi cevap olup olmadığını bilmiyorum .

Sıralı prosedürleri yürütmek ve bu nedenle bir veritabanından raporlama çıktısı oluşturmak için kullanılan salt okunur bir kullanıcı hesabım var. Oluşturduğum SQLTranspose işlevi yalnızca "meşru" bir tabloyla çalışacağından (bildirilen bir tablo değil ve geçici bir tablo değil), salt okunur bir kullanıcı hesabının bir tablo oluşturması (ve daha sonra silmesi) için bir yol bulmalıydım .

Amaçlarım için kullanıcı hesabının bir tablo oluşturmasına izin verilmesinin uygun olmadığını düşündüm. Kullanıcı yine de tabloyu silemedi. Çözümüm, kullanıcı hesabının yetkilendirildiği bir şema oluşturmaktı. Sonra o tabloyu her oluşturduğumda, kullandığımda veya sildiğimde, ona belirtilen şemayla başvur.

Bu komutu ilk olarak bir 'sa' veya 'sysadmin' hesabından verdim: CREATE SCHEMA ro AUTHORIZATION

"Tmpoutput" tabloma başvurduğumda, bunu şu örnek gibi belirtirim:

tablo ro.tmpoutput bırak


Benim için işe yarayan bir şey buldum. Bunun en iyi cevap olup olmadığını bilmiyorum .
CraigD
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.