PIVOT و UNPIVOT در SQL Server – قسمت اول

PIVOT و UNPIVOT در SQL Server – قسمت اول

نوشته شده توسط: مهدی شیشه بری
تاریخ انتشار: ۲۵ خرداد ۱۳۹۵
آخرین بروزرسانی: ۲۳ آبان ۱۴۰۲
زمان مطالعه: 8 دقیقه
۴.۴
(۱۹)

مقدمه PIVOT و UNPIVOT در SQL Server

فرض کنید جدولی با نام sale و مطابق با چنین ساختاری داریم. این جدول شامل اطلاعات تعداد فروش (amount) بر اساس سال (year) و به ازای هر فصل (quarter) است.
PIVOT و UNPIVOT در SQL Serverحال می خواهیم به عنوان مثال، اطلاعات فروش هر سال را به تفکیک هر فصل داشته باشیم.
برای این کار از Aggregation Functionها استفاده کرده و اسکریپت زیر را اجرا می کنیم:

SELECT year,quarter,SUM(amount) AS amountSum
FROM sale
GROUP BY YEAR,quarter
ORDER BY year
GO

PIVOT و UNPIVOT در SQL Serverخروجی کوئری بالا، اطلاعات فروش هر سال را به تفکیک هر فصل و در قالب یک رکورد نمایش می‌دهد.
در ادامه اگر بخواهیم اطلاعات فروش به ازای هر سال و بر اساس تمامی فصل¬ها صرفا در قالب یک رکورد یا یک سطر نمایش داده شود، باید چه کار کنیم؟
با استفاده از Sub Queryها این کار امکان‌پذیر است!

SELECT DISTINCT year
,(SELECT SUM(amount)FROM sale s2 WHERE s2.year=s1.year AND s2.quarter='spring') AS spring
FROM sale s1
GO

PIVOT و UNPIVOT در SQL Server

 به خروجی کوئری اجرا شده توجه کنید! این کوئری، صرفا جهت نمایش اطلاعات فروش فصل بهار است. بنابراین برای نمایش اطلاعات سایر فصل ها، می‌بایست آنها را در کوئری شرکت داد:

SELECT DISTINCT year
,(SELECT SUM(amount)FROM sale s2 WHERE s2.year=s1.year AND s2.quarter='spring') AS spring
,(SELECT SUM(amount)FROM sale s2 WHERE s2.year=s1.year AND s2.quarter='summer') AS summer
,(SELECT SUM(amount)FROM sale s2 WHERE s2.year=s1.year AND s2.quarter='autumn') AS autumn
,(SELECT SUM(amount)FROM sale s2 WHERE s2.year=s1.year AND s2.quarter='winter') AS winter
FROM sale s1
GO

PIVOT و UNPIVOT در SQL Serverهمان طور که می‌بینید، توانستیم اطلاعات فروش در هر سال و به تفکیک هر فصل را در قالب یک رکورد نمایش دهیم اما نکته قابل تامل این است که اگر تنوع بازه زمانی اطلاعات فروش بر اساس ماه های مختلف در نظر گرفته شده بود آن گاه می‌بایست تمامی ماه های سال را در کوئری شرکت می‌دادیم!
این موضوع در خصوص موجودیت هایی متنوع، قطعا چالش برانگیز خواهد بود و روش بهینه ای به حساب نمی‌آید.
اکنون برای رفع این مشکل چه باید کرد؟ پاسخ SQL Server استفاده از PIVOT Tableها است.

دوره کوئری نویسی نیک آموز

PIVOT Table چیست؟

همان طور که در شکل پایین می‌بینید، خواسته ما، چرخش مقادیر داده ها از درون ستون های جدول به سمت Header گزارش است و این قابلیت به کمک PIVOT Tableها در SQL Server تامین می‌شود. به عبارت دیگر زمانی از PIVOT Tableها استفاده می‌کنیم که بخواهیم گزارش هایی از نوع Cross-Tab داشته باشیم.
 PIVOT و UNPIVOT در SQL Serverالگوی استفاده از PIVOT Tableها در SQL به شکل زیر می‌باشد:

SELECT <non-pivoted column>,
    [first pivoted column] AS <column name>,
    ...
    [last pivoted column] AS <column name>
FROM
    (<SELECT query that produces the data>)
    AS <alias for the source query>
PIVOT
(
    <aggregation function>(<column being aggregated>)
FOR
[<column that contains the values that will become column headers>]
    IN ( [first pivoted column], [second pivoted column],
    ... [last pivoted column])
) AS <alias for the pivot table>
<optional ORDER BY clause>;

بنابراین در ابتدای کار می‌بایست تکلیف سه مورد زیر را مشخص کنیم

۱- Aggregate Column: همان فیلدی است که قرار است بر روی آن عملیات Aggregation انجام شود که در مثال فرضی ما، فیلد amount خواهد بود.
۲- PIVOT Column: فیلدی که قرار است از درون رکوردها به سمت Header گزارش چرخش داشته باشد که در مثال فرضی ما، فیلد quarter خواهد بود. این فیلد در جلو عبارت FOR قرار می‌گیرد.
3- لیستی که قرار است گزارش براساس آن تهیه شود که در مثال فرضی ما، مقادیر فیلد quarter خواهد بود که همان spring,summer,autumn,winter خواهند بود.

 اکنون با تشخیص موارد بالا، همه چیز برای ایجاد کوئری فراهم شده است:

SELECT * FROM sale
PIVOT
(SUM (amount) FOR quarter
IN ([spring],[summer],[autumn],[winter]))pTable

PIVOT و UNPIVOT در SQL Serverشکل زیر مقایسه میان Plan اجرایی این کوئری (استفاده از PIVOT) و کوئری قبلی (استفاده از Sub Query) را نشان می‌دهد و شما می‌بینید که به لحاظ کارآیی، استفاده از PIVOT Tableها به چه میزان تاثیر گذار خواهند بود. مقایسه دیاگرام Plan اجرایی این دو کوئری هم در نوع خودش جالب توجه است. از طرفی میزان خطوط نوشته شده در هر کوئری هم جای تامل دارد!
PIVOT و UNPIVOT در SQL Serverضمنا باید به این نکته هم توجه داشته باشید که با استفاده از ایندکس گذاری مناسب، قطعا می‌توانیم به کارآیی بیشتر این گونه کوئری ها کمک کنیم.
در ادمه می‌خواهیم تغییراتی بر روی جدول sale اعمال کنیم. این تغییرات شامل افزودن یک فیلد از نوع INT و با خصوصیت IDENTITY است:

ALTER TABLE sale
ADD id INT IDENTITY

مجددا همان کوئری ای را که در آن از PIVOT استفاده شده بود، اجرا می‌کنیم.
خروجی کوئری، مطابق با آنچه که ما انتظارش را داشتیم، نیست!
PIVOT و UNPIVOT در SQL Serverآیا می‌توان چنین استنباط کرد که قابلیت PIVOT صرفا برای جداول سه فیلدی ایجاد شده است؟ پاسخ مثبت و چنین برداشتی، قطعا موجب رنجش خاطر تیم توسعه دهنده Microsoft SQL Server خواهد شد!

اما بیایید با هم بررسی کنیم که چرا چنین اتفاقی افتاده و راه برون رفت از آن چیست؟

دوباره به کوئری زیر توجه کنید. فرض می‌کنیم هنوز به جدول مان فیلد id را اضافه نکرده ایم. کوئری زیر را اجرا می‌کنیم:

SELECT * FROM sale
PIVOT
(SUM (amount) FOR quarter
IN ([spring],[summer],[autumn],[winter]))pTable

PIVOT و UNPIVOT در SQL Serverدر این کوئری، SQL نتایج را بر اساس سال فروش (year) تفکیک کرده است. اما SQL از کجا تشخیص داده است که باید چنین کاری را انجام بدهد؟ پاسخ آن است که در این حالت تمامی فیلد های یک جدول به غیر از Aggregate Column و PIVOT Column، توسط SQL در GROUP BY شرکت داده می‌شوند که در این جا شامل فیلد year می‌شود.
این موضوع در Plan اجرایی کوئری، به وضوح قابل مشاهده است.
 PIVOT و UNPIVOT در SQL Serverالبته این قاعده در برخی از موارد به ضرر ما تمام می‌شود و این همان جایی است که مثلا به جدول sale یک فیلد id اضافه شود. آن گاه علاوه بر فیلد year، فیلد id هم در GROUP BY شرکت داده می‌شود و نتایج مورد انتظارمان حاصل نخواهد شد.
برای رفع چنین مشکلی می‌بایست به جای استفاده از SELECT ای مستقیم از جدول sale، با استفاده از یک Sub Query، فیلدهای موردنظرمان را در SELECT انتخاب کنیم.
اسکریپت زیر، نحوه انجام کار را به شما نشان می‌دهد:

SELECT * FROM
(SELECT year,quarter,amount FROM sale)s
PIVOT
(SUM(s.amount) FOR quarter
IN ([spring],[summer],[autumn],[winter])
)pTable

 

و اما حالا شما به عنوان تمرین، تلاش کنید کوئری ای بنویسید که خروجی زیر را نمایش دهد:
PIVOT و UNPIVOT در SQL Server

چه رتبه ای می‌دهید؟

میانگین ۴.۴ / ۵. از مجموع ۱۹

اولین نفر باش

title sign
معرفی نویسنده
title sign
دیدگاه کاربران

    • سلام بسیار ممنون برای مقاله بسیار خوبتون
      من از قابلیت pivot در پروسیجر استفاده کردم و وقتی میخواهم توی پروژه ام اضافه کنم میگه نوعش رو نمیشناسه
      از linq استفاده میکنم ممنون میشم اگه راهنمایی کنید
      تشکر

    •    سلام خواندم اما متاسفانه جوابم را بدست نیاوردم

      فرض کنید
      فرض کنید جدول زیر را داریم 
      CREATE TABLE [dbo].[test۳](
          [ID] [int] IDENTITY(۱,۱) NOT NULL,
          [Date] [date] NULL,
          [Name] [nvarchar](۵۰) NULL,
          [Price] [int] NULL,
       CONSTRAINT [PK_test۳] PRIMARY KEY CLUSTERED 
      (
          [ID] ASC
      )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
      ) ON [PRIMARY]
      GO
      و داده های زیر را داخل ان وارد کرده ایم 

      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۱’,’a’,۱۰۰)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۲’,’a’,۱۰۱)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۳’,’a’,۱۰۲)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۱’,’b’,۱۰۳)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۲’,’b’,۱۰۴)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۳’,’b’,۱۰۵)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۱’,’c’,۱۰۶)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۲’,’c’,۱۰۷)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۱’,’d’,۱۰۹)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۲’,’d’,۱۱۰)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۳’,’d’,۱۱۱)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۱’,’e’,۱۱۲)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۲’,’e’,۱۱۳)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۳’,’e’,۱۱۴)
      بدین صورت که به تعداد روزها سطر ایجاد شود و به تعداد داده های ستون name برای خروچی ستون ایجاد شود و قیمتهای مربوط زیر هر نام براساس ان تاریخ نمایش داده شود 
      درضمن تعداد داده های ستون name متغییر است وتعداد ثابتی ندارد 
      چگونه من باید کوئری بنویسم تا به جواب مورد نظرم برسم 
      چون من نتونستم با قسمت دوم اموزش خوب شما به جواب برسم
      اگر درست نمایش داده شود چنین چیزی می خوام

      Date    a   b   c   d   e
       
      ۲۰۱۶-۰۵-۰۱  ۱۰۰ ۱۰۳ ۱۰۶ ۱۰۹ ۱۱۲
       
      ۲۰۱۶-۰۵-۰۲  ۱۰۱ ۱۰۴ ۱۰۷ ۱۱۰ ۱۱۳
       
      ۲۰۱۶-۰۵-۰۳  ۱۰۲ ۱۰۵ ۱۰۸ ۱۱۱ ۱۱۴

      باتشکر 

    •    سلام خواندم اما متاسفانه جوابم را بدست نیاوردم

      فرض کنید
      فرض کنید جدول زیر را داریم 
      CREATE TABLE [dbo].[test۳](
          [ID] [int] IDENTITY(۱,۱) NOT NULL,
          [Date] [date] NULL,
          [Name] [nvarchar](۵۰) NULL,
          [Price] [int] NULL,
       CONSTRAINT [PK_test۳] PRIMARY KEY CLUSTERED 
      (
          [ID] ASC
      )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
      ) ON [PRIMARY]
      GO
      و داده های زیر را داخل ان وارد کرده ایم 

      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۱’,’a’,۱۰۰)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۲’,’a’,۱۰۱)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۳’,’a’,۱۰۲)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۱’,’b’,۱۰۳)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۲’,’b’,۱۰۴)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۳’,’b’,۱۰۵)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۱’,’c’,۱۰۶)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۲’,’c’,۱۰۷)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۱’,’d’,۱۰۹)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۲’,’d’,۱۱۰)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۳’,’d’,۱۱۱)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۱’,’e’,۱۱۲)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۲’,’e’,۱۱۳)
      insert into [dbo].[test۳] values(‘۲۰۱۶-۰۵-۰۳’,’e’,۱۱۴)
      بدین صورت که به تعداد روزها سطر ایجاد شود و به تعداد داده های ستون name برای خروچی ستون ایجاد شود و قیمتهای مربوط زیر هر نام براساس ان تاریخ نمایش داده شود 
      درضمن تعداد داده های ستون name متغییر است وتعداد ثابتی ندارد 
      چگونه من باید کوئری بنویسم تا به جواب مورد نظرم برسم 
      چون من نتونستم با قسمت دوم اموزش خوب شما به جواب برسم
      اگر درست نمایش داده شود چنین چیزی می خوام

      Date    a   b   c   d   e
       
      ۲۰۱۶-۰۵-۰۱  ۱۰۰ ۱۰۳ ۱۰۶ ۱۰۹ ۱۱۲
       
      ۲۰۱۶-۰۵-۰۲  ۱۰۱ ۱۰۴ ۱۰۷ ۱۱۰ ۱۱۳
       
      ۲۰۱۶-۰۵-۰۳  ۱۰۲ ۱۰۵ ۱۰۸ ۱۱۱ ۱۱۴

      باتشکر 

      •   سلام
        امیدوارم منظورتان را درست متوجه شده باشم.
        لطفا اسکریپت زیر را اجرا کنید و در صورت عدم حصول نتیجه، اسکریپت خودتان را این جا بگذارید تا ببینم ایراد کار از کجاست.
        خوب و خوش باشید.

        DECLARE @Name VARCHAR(MAX)
        SET @Name=”
        SELECT @Name=@Name+Name+’,’ FROM test3
            GROUP BY Name
        SET @Name=LEFT(@Name,LEN(@Name)-1)

        EXEC(‘SELECT * FROM
         (SELECT Date,Name,Price FROM test3)t
         PIVOT
         (SUM(Price) FOR Name
         IN (‘+@Name+’))pTable’)

        •     باسلام و سپاس از جواب خوبتان

          دو سوال 
          ۱) من کد شما را چگونه می توانم تبدیل به یک ویو یا فانکشن کنم
          ۲)کدشمارا تبدیل به ویو کرده ام و از طریق کد زیر در یک دیتا گرید نمایش میدهم ولی فقط ستون name را نمایش می دهد چگونه می توانم همه ستونها را دردیتاگرید نمایش بدهم
          private void Form1_Load(object sender, EventArgs e)
                 {
                     DataClasses1DataContext dc = new DataClasses1DataContext();
                     var staff = dc.aaaa();
                     radGridView1.DataSource = staff;
                 }
    •     سلام

      استاد ببخشید در مثال شما تعداد داده ها(quarter) مشخص بود حال اگر تعداد داده ها مشخص نباشید چگونه باید کد مربوطه را بنویسیم
      •   سلام
        لطفا قسمت دوم مقاله را مطالعه کنید. پاسخ تان را آن جا پیدا خواهید کرد.
        شاد باشید.

    •     خیلی خوب بود ، تشکر

    •     سلام استاد ، وقت بخیر

      ضمن تشکر از مقاله های مفیدتان .

      درنظر بگیرید به غیر از فیلد amount یک فیلد دیگر به عنوان Percent (درصد) نیز در جدول قرار دارد که قرار است آنرا هم بعد از چرخش در گزارش نمایش دهیم . برای این مهم چه راه کاری میشود ارایه داد.
    •     خیلی خیلی منمونم ،بسیا رسا بود.

    •    خیلی خوب بود مقالتون ممنونم

    •   با سلام و خسته نباشید

      از مقاله خیلی خوبتان بسیار ممنونم خیلی خوب و عالی بود

    •    با سلام 

      و تشکر مطلب عالی بود ممنون منتظر قسمته ای بعدی این مطلب هستیم