Cara sederhana untuk mengubah urutan kolom dan baris dalam SQL?


110

Bagaimana cara mengganti kolom dengan baris di SQL? Apakah ada perintah sederhana untuk mengubah urutan?

yaitu putar hasil ini:

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

ke dalam ini:

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

PIVOT tampaknya terlalu rumit untuk skenario ini.


1
stackoverflow.com/questions/10699997/… adalah pertanyaan yang agak mirip.
Tim Dearborn

Jawaban:


145

Ada beberapa cara untuk mengubah data ini. Di posting asli Anda, Anda menyatakan bahwa PIVOTtampaknya terlalu rumit untuk skenario ini, tetapi dapat diterapkan dengan sangat mudah menggunakan fungsi UNPIVOTdanPIVOT di SQL Server.

Namun, jika Anda tidak memiliki akses ke fungsi tersebut, ini dapat direplikasi menggunakan UNION ALLke UNPIVOTdan kemudian fungsi agregat dengan CASEpernyataan ke PIVOT:

Buat tabel:

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, Agregat dan Versi CASE:

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

Lihat SQL Fiddle dengan Demo

Itu UNION ALLmelakukan UNPIVOTdata dengan mengubah kolom Paul, John, Tim, Ericmenjadi baris terpisah. Kemudian Anda menerapkan fungsi agregat sum()dengan casepernyataan tersebut untuk mendapatkan kolom baru untuk masing-masing color.

Versi Statis Unpivot dan Pivot:

Baik fungsi UNPIVOTdan PIVOTdi server SQL membuat transformasi ini jauh lebih mudah. Jika Anda mengetahui semua nilai yang ingin diubah, Anda dapat melakukan hard-code pada nilai tersebut menjadi versi statis untuk mendapatkan hasilnya:

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

Lihat SQL Fiddle dengan Demo

Kueri dalam dengan UNPIVOTmenjalankan fungsi yang sama seperti UNION ALL. Ini mengambil daftar kolom dan mengubahnya menjadi baris, PIVOTkemudian melakukan transformasi terakhir menjadi kolom.

Versi Pivot Dinamis:

Jika Anda memiliki jumlah kolom yang tidak diketahui ( Paul, John, Tim, Ericdalam contoh Anda) dan kemudian jumlah warna yang tidak diketahui untuk diubah, Anda dapat menggunakan sql dinamis untuk membuat daftar UNPIVOTdan kemudian 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)

Lihat SQL Fiddle dengan Demo

Kueri versi dinamis keduanya yourtabledan kemudian sys.columnstabel untuk menghasilkan daftar item ke UNPIVOTdan PIVOT. Ini kemudian ditambahkan ke string kueri untuk dieksekusi. Kelebihan dari versi dinamis adalah jika Anda memiliki daftar yang berubah colorsdan / atau namesini akan menghasilkan daftar pada saat run-time.

Ketiga kueri tersebut akan menghasilkan hasil yang sama:

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

Saya harus menekankan bahwa saya menginginkan metode untuk mengubah urutan hasil kueri pada database. Perintah tipe transpose sederhana yang dapat dieksekusi pada kumpulan hasil dari kueri tanpa harus mengetahui apa pun tentang kueri itu sendiri, atau kolom, tabel, dll. Yang digunakan untuk mengekstrak info. Sesuatu seperti: (TRANSPOSE (SELECT ......)) Dalam contoh saya yang diberikan di atas, bayangkan bahwa tabel pertama adalah kumpulan hasil yang dihasilkan dari kueri yang sangat kompleks yang melibatkan banyak tabel, unions, pivot, dll. Namun hasilnya adalah a tabel sederhana. Saya hanya ingin membalik kolom dengan baris di hasil ini.
edezzie

Saya meneruskan hasilnya ke ac # DataTable. Saya bisa membangun metode sederhana 'Transpose (Datatable dt)' untuk melakukannya di sana, tetapi apakah ada sesuatu di SQL untuk melakukan ini.
edezzie

@edezzie tidak ada cara sederhana dalam SQL untuk melakukan ini. Anda mungkin dapat mengubah versi dinamis untuk meneruskan nama tabel dan menarik info dari tabel sistem.
Taryn

Adakah alasan mengapa Anda tidak menandai jawaban brilian ini sebagai JAWABAN? Beri @bluefeet medali!
Fandango68

Ini berfungsi dengan baik, pilih * dari Table unpivot (nilai untuk nama di ([Paul], [John], [Tim], [Eric])) up pivot (max (value) untuk warna di ([Red], [Green] , [Blue])) p tetapi apakah mungkin untuk menulis ini pilih * dari Table unpivot (nilai untuk nama dalam ([Paul], [John], [Tim], [Eric])) up pivot (max (value) untuk color in (SELECT COLOR FROM TABLENAME)) p alih-alih memasukkan nilai kutipan tidak bisa mengambil nilai secara langsung menggunakan kueri pemilihan.
Mogli

20

Ini biasanya mengharuskan Anda untuk mengetahui SEMUA label kolom DAN baris sebelumnya. Seperti yang Anda lihat dalam kueri di bawah ini, semua label terdaftar seluruhnya di operasi UNPIVOT dan (ulang) PIVOT.

Penataan Skema MS SQL Server 2012 :

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;

Pertanyaan 1 :

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

Hasil :

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

Catatan tambahan:

  1. Diberikan nama tabel, Anda bisa menentukan semua nama kolom dari sys.columns atau tipuan FOR XML menggunakan nama-lokal () .
  2. Anda juga bisa menyusun daftar warna berbeda (atau nilai untuk satu kolom) menggunakan FOR XML.
  3. Di atas dapat digabungkan menjadi batch sql dinamis untuk menangani tabel apa pun.

11

Saya ingin menunjukkan beberapa solusi lagi untuk mengubah urutan kolom dan baris dalam SQL.

Yang pertama adalah - menggunakan CURSOR. Meskipun konsensus umum dalam komunitas profesional adalah menjauh dari SQL Server Cursor, masih ada contoh di mana penggunaan kursor direkomendasikan. Bagaimanapun, Cursor memberi kita opsi lain untuk mengubah urutan baris menjadi kolom.

  • Ekspansi vertikal

    Mirip dengan PIVOT, kursor memiliki kemampuan dinamis untuk menambahkan lebih banyak baris saat kumpulan data Anda meluas untuk menyertakan lebih banyak nomor kebijakan.

  • Ekspansi horizontal

    Berbeda dengan PIVOT, kursor unggul di area ini karena dapat diperluas untuk menyertakan dokumen yang baru ditambahkan, tanpa mengubah skrip.

  • Rincian kinerja

    Batasan utama dalam mentransposisi baris menjadi kolom menggunakan CURSOR adalah kerugian yang terkait dengan penggunaan kursor secara umum - biaya kinerja yang signifikan. Ini karena Cursor menghasilkan kueri terpisah untuk setiap operasi FETCH NEXT.

Solusi lain untuk mengubah baris menjadi kolom adalah dengan menggunakan XML.

Solusi XML untuk mengubah baris menjadi kolom pada dasarnya adalah versi optimal dari PIVOT karena mengatasi batasan kolom dinamis.

Versi XML dari skrip mengatasi batasan ini dengan menggunakan kombinasi XML Path, T-SQL dinamis dan beberapa fungsi built-in (yaitu STUFF, QUOTENAME).

  • Ekspansi vertikal

    Mirip dengan PIVOT dan Cursor, kebijakan yang baru ditambahkan dapat diambil dalam versi XML dari skrip tanpa mengubah skrip asli.

  • Ekspansi horizontal

    Berbeda dengan PIVOT, dokumen yang baru ditambahkan dapat ditampilkan tanpa mengubah skrip.

  • Rincian kinerja

    Dalam hal IO, statistik skrip versi XML hampir mirip dengan PIVOT - satu-satunya perbedaan adalah XML memiliki pemindaian kedua dari tabel dtTranspose tetapi kali ini dari cache data-baca yang logis.

Anda dapat menemukan lebih banyak lagi tentang solusi ini (termasuk beberapa contoh T-SQL aktual) di artikel ini: https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/


8

Berdasarkan solusi dari bluefeet berikut ini adalah prosedur tersimpan yang menggunakan sql dinamis untuk menghasilkan tabel yang dialihkan. Ini mensyaratkan bahwa semua bidang adalah numerik kecuali untuk kolom yang dialihkan (kolom yang akan menjadi tajuk dalam tabel yang dihasilkan):

/****** 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

Anda dapat mengujinya dengan tabel yang disediakan dengan perintah ini:

exec SQLTranspose 'yourTable', 'color'

argg. tapi saya ingin satu di mana semua bidangnya nvarchar (maks)
hamish

1
Untuk semua bidang nvarchar (max) cukup ubah jumlah (nilai) dengan max (nilai) di baris ke-6 dari akhir
Paco Zarate

2

Saya lakukan UnPivotpertama kali dan menyimpan hasil dalam CTEdan menggunakan CTEdalam Pivotoperasi.

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

Menambahkan jawaban hebat @Paco Zarate di atas, jika Anda ingin mengubah urutan tabel yang memiliki beberapa jenis kolom, tambahkan ini ke akhir baris 39, sehingga hanya mengubah posisi intkolom:

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

Berikut adalah kueri lengkap yang sedang diubah:

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, '')

Untuk menemukan yang lain system_type_id, jalankan ini:

select name, system_type_id from sys.types order by name

1

Saya ingin membagikan kode yang saya gunakan untuk mengubah urutan teks yang dipisahkan berdasarkan jawaban + bluefeet. Dalam pendekatan ini saya diimplementasikan sebagai prosedur di MS SQL 2005

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

Saya mencampur solusi ini dengan informasi tentang cara memesan baris tanpa urutan oleh ( SQLAuthority.com ) dan fungsi pemisahan di MSDN ( social.msdn.microsoft.com )

Saat Anda menjalankan produk

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

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

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

Anda mendapatkan hasil selanjutnya

  name       rowid  1      2          3
  col_value  1      hello  beautiful  world

1

Dengan cara ini Mengkonversi semua Data Dari Filelds (Kolom) Dalam Tabel Untuk Merekam (Baris).

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

Saya dapat menggunakan solusi Paco Zarate dan itu bekerja dengan baik. Saya memang harus menambahkan satu baris ("SET ANSI_WARNINGS ON"), tetapi itu mungkin sesuatu yang unik untuk cara saya menggunakannya atau menyebutnya. Ada masalah dengan penggunaan saya dan saya harap seseorang dapat membantu saya:

Solusinya hanya berfungsi dengan tabel SQL yang sebenarnya. Saya mencobanya dengan tabel sementara dan juga tabel dalam memori (dideklarasikan) tetapi tidak berhasil dengan tabel tersebut. Jadi dalam kode panggilan saya, saya membuat tabel di database SQL saya dan kemudian memanggil SQLTranspose. Sekali lagi, ini bekerja dengan baik. Itu yang saya inginkan. Inilah masalah saya:

Agar solusi keseluruhan menjadi benar-benar dinamis, saya perlu membuat tabel itu di mana saya menyimpan sementara informasi yang telah disiapkan yang saya kirim ke SQLTranspose "dengan cepat", dan kemudian menghapus tabel itu setelah SQLTranspose dipanggil. Penghapusan tabel menimbulkan masalah dengan rencana implementasi akhir saya. Kode harus dijalankan dari aplikasi pengguna akhir (tombol pada formulir / menu Microsoft Access). Ketika saya menggunakan proses SQL ini (membuat tabel SQL, panggil SQLTranspose, hapus tabel SQL) aplikasi pengguna akhir mengalami kesalahan karena akun SQL yang digunakan tidak memiliki hak untuk menjatuhkan tabel.

Jadi saya pikir ada beberapa kemungkinan solusi:

  1. Temukan cara untuk membuat SQLTranspose berfungsi dengan tabel sementara atau variabel tabel yang dideklarasikan.

  2. Cari tahu metode lain untuk transposisi baris dan kolom yang tidak memerlukan tabel SQL yang sebenarnya.

  3. Cari tahu metode yang sesuai untuk mengizinkan akun SQL yang digunakan oleh pengguna akhir saya untuk menghapus tabel. Ini adalah satu akun SQL bersama yang dikodekan ke dalam aplikasi Access saya. Tampaknya izin adalah hak istimewa tipe dbo yang tidak dapat diberikan.

Saya menyadari bahwa beberapa di antaranya mungkin memerlukan utas dan pertanyaan lain yang terpisah. Namun, karena ada kemungkinan bahwa satu solusi mungkin hanyalah cara yang berbeda untuk melakukan pengalihan baris dan kolom, saya akan membuat posting pertama saya di sini di utas ini.

EDIT: Saya juga mengganti jumlah (nilai) dengan max (nilai) di baris ke-6 dari akhir, seperti yang disarankan Paco.

EDIT:

Saya menemukan sesuatu yang berhasil untuk saya. Saya tidak tahu apakah itu jawaban terbaik atau bukan.

Saya memiliki akun pengguna hanya-baca yang digunakan untuk menjalankan prosedur terstruktur dan oleh karena itu menghasilkan keluaran pelaporan dari database. Karena fungsi SQLTranspose yang saya buat hanya akan bekerja dengan tabel "sah" (bukan tabel yang dideklarasikan dan bukan tabel sementara) saya harus mencari cara untuk akun pengguna hanya-baca untuk membuat (dan kemudian menghapus) tabel .

Saya beralasan bahwa untuk tujuan saya, tidak masalah bagi akun pengguna untuk diizinkan membuat tabel. Namun, pengguna tetap tidak dapat menghapus tabel. Solusi saya adalah membuat skema di mana akun pengguna diotorisasi. Kemudian setiap kali saya membuat, menggunakan, atau menghapus tabel itu, referensikan dengan skema yang ditentukan.

Saya pertama kali mengeluarkan perintah ini dari akun 'sa' atau 'sysadmin': CREATE SCHEMA ro AUTHORIZATION

Kapan pun saya merujuk ke tabel "tmpoutput" saya, saya menetapkannya seperti contoh ini:

drop tabel ro.tmpoutput


Saya menemukan sesuatu yang berhasil untuk saya. Saya tidak tahu apakah itu jawaban terbaik atau bukan.
CraigD
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.