Related
This question already has answers here:
Using pivot on multiple columns of an Oracle row
(3 answers)
Closed 10 months ago.
I have a question, is there any method that convert colum to row.
for example,I have a table like this:
CREATE TABLE mytable(u_id, month, offer, revenue) as
SELECT 1, 'January', 'Offer_1', 45 FROM dual
UNION ALL
SELECT 1, 'February','Offer_2', 40 FROM dual
UNION ALL
SELECT 1, 'March' ,'Offer_1', 35 FROM dual
UNION ALL
SELECT 2, 'January' ,'Offer_2', 40 FROM dual
UNION ALL
SELECT 2, 'February','Offer_3', 40 FROM dual
UNION ALL
SELECT 2, 'March' ,'Offer_1', 50 FROM dual;
and my expected table is
(There should be one row per user)
u_id
january_offer
january_revenue
february_offer
february_revenue
march_offer
march_revenue
1
Offer_1
45
Offer_2
40
Offer_1
35
2
Offer_2
40
Offer_3
40
Offer_1
50
I tried:
SELECT t1.u_id,
t1.january_offer,
t1.january_revenue,
t2.february_offer,
t2.february_revenue,
t3.march_offer,
t3.march_revenue
FROM (SELECT u_id, offer AS january_offer,
revenue AS january_revenue
FROM mytable
WHERE month = 'January') t1
LEFT JOIN (SELECT u_id,
offer AS february_offer,
revenue AS february_revenue
FROM mytable t1
WHERE month = 'February') t2
ON t1.u_id = t2.u_id
LEFT JOIN (SELECT u_id, offer AS march_offer,
revenue AS march_revenue
FROM mytable t1
WHERE month = 'March') t3
ON t2.u_id = t3.u_id
but,real, I have lots of data and a long SQL script and this method aggravates my script.
This is what PIVOT is designed for:
SELECT *
FROM package
PIVOT (
MAX(offer) AS offer, MAX(revenue) AS revenue
FOR month IN ( 'January' AS january, 'February' AS feburary, 'March' AS march )
)
Which, for the sample data:
CREATE TABLE package (U_id, month, offer,revenue) AS
SELECT 1, 'January', 'offer_1', 45 FROM DUAL UNION ALL
SELECT 1, 'February', 'offer_2', 40 FROM DUAL UNION ALL
SELECT 1, 'March', 'offer_1', 35 FROM DUAL UNION ALL
SELECT 2, 'January', 'offer_2', 40 FROM DUAL UNION ALL
SELECT 2, 'February', 'offer_3', 40 FROM DUAL UNION ALL
SELECT 2, 'March', 'offer_1', 50 FROM DUAL;
Outputs:
U_ID
JANUARY_OFFER
JANUARY_REVENUE
FEBURARY_OFFER
FEBURARY_REVENUE
MARCH_OFFER
MARCH_REVENUE
1
offer_1
45
offer_2
40
offer_1
35
2
offer_2
40
offer_3
40
offer_1
50
db<>fiddle here
An alternative pattern that might be more efficient is to use a case expression:
SELECT t1.u_id
, CASE month WHEN 'January' then offer END AS january_offer
, CASE month WHEN 'January' then revenue END AS january_revenue
, CASE month WHEN 'February' then offer END AS february_offer
, ...
From there you can use an aggregate function to eliminate null rows:
SELECT u_id
, MAX(january_offer) AS january_offer
, MAX(january_revenue) AS january_revenue
, MAX(february_offer) AS february_offer
, ...
FROM (
SELECT t1.u_id
, CASE month WHEN 'January' then offer END AS january_offer
, CASE month WHEN 'February' then offer END AS february_offer
, ...
) AS t
GROUP BY u_id
In general, this kind of operation is better handled in the presentation layer of the application than in the database layer.
You can use conditional aggregation along with CASE..WHEN expressions such as
SELECT u_id,
MAX(CASE WHEN month = 'January' THEN offer END) AS january_offer,
MAX(CASE WHEN month = 'January' THEN revenue END) AS january_revenue,
MAX(CASE WHEN month = 'February' THEN offer END) AS february_offer,
MAX(CASE WHEN month = 'February' THEN revenue END) AS february_revenue,
MAX(CASE WHEN month = 'March' THEN offer END) AS march_offer,
MAX(CASE WHEN month = 'March' THEN revenue END) AS march_revenue
FROM t
GROUP BY u_id
or by using DECODE() function such as
SELECT u_id,
MAX(DECODE (month , 'January', offer)) AS january_offer,
MAX(DECODE (month , 'January', revenue)) AS january_revenue,
MAX(DECODE (month , 'February', offer)) AS february_offer,
MAX(DECODE (month , 'February', revenue)) AS february_revenue,
MAX(DECODE (month , 'March', offer)) AS march_offer,
MAX(DECODE (month , 'March', revenue)) AS march_revenue
FROM t
GROUP BY u_id
Demo
My table has records as below for different Id's and different start and end dates
ID, Startdate, Enddate
1, 2017-02-14, 2018-11-05
I want to write an SQL without using date dimension table that gives below output: Basically one record for each month between start and end date.
1, 2017, 02
1, 2017, 03
1, 2017, 04
1, 2017, 05
1, 2017, 06
1, 2017, 07
1, 2017, 08
1, 2017, 09
1, 2017, 10
1, 2017, 11
1, 2017, 12
1, 2018, 01
1, 2018, 02
1, 2018, 03
1, 2018, 04
1, 2018, 05
1, 2018, 06
1, 2018, 07
1, 2018, 09
1, 2018, 10
1, 2018, 11
Please use below query example:
set #start_date = '2017-02-14';
set #end_date = LAST_DAY('2018-11-05');
WITH RECURSIVE date_range AS
(
select MONTH(#start_date) as month_, YEAR(#start_date) as year_, DATE_ADD(#start_date, INTERVAL 1 MONTH) as next_month_date
UNION
SELECT MONTH(dr.next_month_date) as month_, YEAR(dr.next_month_date) as year_, DATE_ADD(dr.next_month_date, INTERVAL 1 MONTH) as next_month_date
FROM date_range dr
where next_month_date <= #end_date
)
select month_, year_ from date_range
order by next_month_date desc
This is what I did and it worked like a charm:
-- sample data
WITH table_data
AS (
SELECT 1 AS id
,cast('2017-08-14' AS DATE) AS start_dt
,cast('2018-12-16' AS DATE) AS end_dt
UNION ALL
SELECT 2 AS id
,cast('2017-09-14' AS DATE) AS start_dt
,cast('2019-01-16' AS DATE) AS end_dt
)
-- find minimum date from the data
,starting_date (start_date)
AS (
SELECT min(start_dt)
FROM TABLE_DATA
)
--get all months between min and max dates
,all_dates
AS (
SELECT last_day(add_months(date_trunc('month', start_date), idx * 1)) month_date
FROM starting_date
CROSS JOIN _v_vector_idx
WHERE month_date <= add_months(start_date, abs(months_between((
SELECT min(start_dt) FROM TABLE_DATA), (SELECT max(end_dt) FROM TABLE_DATA))) + 1)
ORDER BY month_date
)
SELECT id
,extract(year FROM month_date)
,extract(month FROM month_date)
,td.start_dt
,td.end_dt
FROM table_data td
INNER JOIN all_dates ad
ON ad.month_date > td.start_dt
AND ad.month_date <= last_day(td.end_dt)
ORDER BY 1
,2
You have to generate date and from that have to pick year and month
select distinct year(date),month( date) from
(select * from (
select
date_add('2017-02-14 00:00:00.000', INTERVAL n5.num*10000+n4.num*1000+n3.num*100+n2.num*10+n1.num DAY ) as date
from
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n1,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n2,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n3,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n4,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n5
) a
where date >'2017-02-14 00:00:00.000' and date < '2018-11-05'
) as t
I have a user table where data is about 20 years old.
I want to fetch month wise data from CreatedOn column that is (2014-07-08 17:44:00) and if data is not available for any month then I want 0 for this month
Example
Month data year
jan 34 2014
feb 56 2014
march 0 2014
apr 23 2014
I am using the following query but it's not working:
with cte(monno , monname ) as(
select 1, 'Jan' union all
select 2, 'Feb' union all
select 3, 'Mar' union all
select 4, 'Apr' union all
select 5, 'May' union all
select 6, 'Jun' union all
select 7, 'Jul' union all
select 8, 'Aug' union all
select 9, 'Sep' union all
select 10, 'Oct' union all
select 11, 'Nov' union all
select 12, 'Dec'
)
SELECT DISTINCT monname
,monno
,count(CreatedOn) OVER (PARTITION BY datepart(year, CreatedOn),DATEPART(MONTH, CreatedOn))
FROM Contributors_tbl a
RIGHT JOIN cte b ON DATEPART(MONTH, CreatedOn) = b.monno
AND datepart(year, CreatedOn) = 2014
ORDER BY monno
Try this Way to find Month Number AND Month Name
CREATE TABLE #TempMonth
(
MonthNum int,
MonthNames varchar(50)
)
INSERT INTO #TempMonth
SELECT STR(MONTH(DATEADD(mm, number, GETDATE())), 2) AS MonthNum,
DATENAME(month, DATEADD(month, MONTH(DATEADD(mm, number, GETDATE())), 0) - 1) AS MonthNames
FROM master.dbo.spt_values
WHERE (name IS NULL) AND (number BETWEEN 0 AND 11) ORDER BY STR(MONTH(DATEADD(mm, number, GETDATE())), 2)
SELECT * FROM #TempMonth
select distinct MonthNames,MonthNum,
count(CreatedOn) over(partition by datepart(year,CreatedOn),DATEPART(MONTH,CreatedOn))
from Contributors_tbl a
right join #TempMonth b on DATEPART(MONTH,CreatedOn) = b.MonthNum and datepart(year,CreatedOn) = 2014
order by MonthNum
DROP Table #TempMonth
Try This
with cte(monno , monname ) as(
select 1, 'Jan' union all
select 2, 'Feb' union all
select 3, 'Mar' union all
select 4, 'Apr' union all
select 5, 'May' union all
select 6, 'Jun' union all
select 7, 'Jul' union all
select 8, 'Aug' union all
select 9, 'Sep' union all
select 10, 'Oct' union all
select 11, 'Nov' union all
select 12, 'Dec'
)
select monname,
monno,
coalesce(cnt,0) as cnt
from cte c
outer apply (
select count(*) as cnt
from Contributors_tbl
where CreatedOn >= DATEADD(mm,c.monno - 1,'20140101')
And CreatedOn < DATEADD(mm,c.monno,'20140101')
)t
order by monno
Is there a way to make this work? change static, "13MonthsAgo"
into January?
Original
> COUNT(CASE WHEN dateadd(MONTH, - 13, getdate()) >
> HireDate AND dateadd(MONTH, - 13, getdate()) <
> TerminationDate OR
>TerminationDate IS NULL THEN 1 ELSE NULL END) AS 13Monthsago
Preferred
> COUNT(CASE WHEN dateadd(MONTH, - 13, getdate()) >
> HireDate AND dateadd(MONTH, - 13, getdate()) <
> TerminationDate OR
>TerminationDate IS NULL THEN 1 ELSE NULL END) AS
>DATENAME(month, dateadd(MONTH,-13,getdate()))
A lengthy approach but will work..
declare #MonthName varchar(20)
select 1 num, 'January' name into #Months
union
select 2 num, 'February' name
union
select 3 num, 'March' name
union
select 4 num, 'April' name
union
select 5 num, 'May' name
union
select 6 num, 'June' name
union
select 7 num, 'July' name
union
select 8 num, 'August' name
union
select 9 num, 'September' name
union
select 10 num, 'October' name
union
select 11 num, 'November' name
union
select 12 num, 'December' name
select #MonthName = name from #Months where datepart(mm,getdate()) = num
--Add the other columns to the dataset here
--This is just an example
select HireDate, TerminationDate,
COUNT(CASE WHEN dateadd(MONTH, - 13, getdate()) >
HireDate AND dateadd(MONTH, - 13, getdate()) <
TerminationDate OR
TerminationDate IS NULL THEN 1 ELSE NULL END) AS 13Monthsago
into #Dataset
FROM SomeTable GROUP BY HireDate, TerminationDate
use tempdb
EXEC sp_RENAME '#Dataset.13Monthsago' , #MonthName, 'COLUMN'
SELECT * FROM #Dataset
This could be one way to go about it.
;with cte_Dates AS
(
SELECT CAST('20150101' as DATEtime) as DateStr UNION ALL
SELECT '20150201' UNION ALL
SELECT '20150202' UNION ALL
SELECT '20150203' UNION ALL
SELECT '20150204' UNION ALL
SELECT '20150301' UNION ALL
SELECT '20150401' UNION ALL
SELECT '20150501' UNION ALL
SELECT '20150601' UNION ALL
SELECT '20150701' UNION ALL
SELECT '20150801' UNION ALL
SELECT '20150901' UNION ALL
SELECT '20151001' UNION ALL
SELECT '20151101' UNION ALL
SELECT '20151201'
)
SELECT *
FROM
(SELECT DateStr,DATENAME(MONTH,DateStr) As MONTHS
FROM
cte_Dates
)P
PIVOT
(
count(DateStr)
FOR MONTHS IN ([January], [February],[March],[April],[May],[June],[July],[August],[September],[October],[November],[December])
)AS PVT
Quick question about summary data:
I have the below code which will pull sales information and put it into a month/year grid, which is terrific (http://sqlfiddle.com/#!3/9d79e/1):
WITH
months AS (SELECT 1 AS mon UNION ALL SELECT mon + 1 FROM months WHERE mon < 12),
years AS (SELECT 2011 AS yr UNION ALL SELECT yr + 1 FROM years WHERE yr < 2015),
invoices AS (
SELECT CAST('2013-06-27' AS date) AS InvoiceDate, 40 AS MarginAmount
UNION
SELECT CAST('2013-07-29' AS date) AS InvoiceDate, 40 AS MarginAmount
UNION
SELECT CAST('2013-10-30' AS date) AS InvoiceDate, 40 AS MarginAmount
)
-- End data setup, real work begins here
SELECT * FROM
(
SELECT
months.mon, years.yr, COALESCE(SUM(inv.MarginAmount), 0) AS MarginAmount
FROM
months
CROSS JOIN years
LEFT OUTER JOIN invoices inv ON ( (YEAR(inv.InvoiceDate) = years.yr) AND (MONTH(inv.InvoiceDate) = months.mon) )
GROUP BY
months.mon, years.yr
) AS source
PIVOT
(
MAX(MarginAmount)
FOR yr in ([2011], [2012], [2013], [2014], [2015])
)
AS pvt
ORDER BY mon
I was wondering how I could change two things:
Replace the numbers 1 - 11 with the names of the months of the year and
Create a line at the bottom of the table summarizing the information above it, where the mon column would have the word 'Total'
Any help would be greatly appreciated
e.g The sum of all sales in 2012 would be displayed at the bottom of the 2012 column
Question 1. Replace numbers
To replace the numbers, you can for instance change this:
months AS (SELECT 1 AS mon UNION ALL SELECT mon + 1 FROM months WHERE mon < 12)
to
months AS (SELECT 1 AS mon, 'Jan' name UNION ALL SELECT mon + 1, months.name FROM months WHERE mon < 12)
Question 2. Grand totals
To create a bottom line with totals you can either use grouping sets (your query seems to be SQL Server, don't know whether SQL Server supports that, please specify):
group
by grouping sets
( ()
, (full list)
)
or add a union to the query:
with myresults as (the whole thing)
select 1 ordering
, myresults.columns-minus-total
, myresults.something subtotal
from myresults
union all
select 2 ordering
, myresults.columns-minus-total
, sum(something) grandtotal
from myresults
order
by 1
, ...other...
Complete example using Microsoft SQL Server 2008 R2
Original code was prettified and without dependencies on tables:
with months as
( select 1 as mon
, 'Jan' monname
union all
select 2
, 'Feb'
union all
select 3
, 'Mar'
union all
select 4
, 'Apr'
union all
select 5
, 'May'
union all
select 6
, 'Jun'
union all
select 7
, 'Jul'
union all
select 8
, 'Aug'
union all
select 9
, 'Sep'
union all
select 10
, 'Oct'
union all
select 11
, 'Nov'
union all
select 12
, 'Dec'
)
, years as
( select 2011 as yr
union all
select 2012
union all
select 2013
union all
select 2014
)
, invoices as
( select cast('2013-06-27' as date) as invoicedate
, 40 as marginamount
union
select cast('2013-07-29' as date) as invoicedate
, 40 as marginamount
union
select cast('2013-10-30' as date) as invoicedate
, 40 as marginamount
)
select *
from ( select months.mon
, years.yr
, coalesce(sum(inv.marginamount), 0) as marginamount
from months
cross
join years
left
outer
join invoices inv
on year(inv.invoicedate) = years.yr
and month(inv.invoicedate) = months.mon
group
by months.mon
, years.yr
) source
pivot ( max(marginamount)
for yr
in ( [2011], [2012], [2013], [2014], [2015]
)
) pvt
order
by mon
Adding the text and grand totals leads to:
with months as
( select 1 as mon
, 'Jan' monname
union all
select 2
, 'Feb'
union all
select 3
, 'Mar'
union all
select 4
, 'Apr'
union all
select 5
, 'May'
union all
select 6
, 'Jun'
union all
select 7
, 'Jul'
union all
select 8
, 'Aug'
union all
select 9
, 'Sep'
union all
select 10
, 'Oct'
union all
select 11
, 'Nov'
union all
select 12
, 'Dec'
)
, years as
( select 2011 as yr
union all
select 2012
union all
select 2013
union all
select 2014
)
, invoices as
( select cast('2013-06-27' as date) as invoicedate
, 40 as marginamount
union
select cast('2013-07-29' as date) as invoicedate
, 40 as marginamount
union
select cast('2013-10-30' as date) as invoicedate
, 40 as marginamount
)
select case
when mon is null
then 'Total'
else cast(mon as varchar)
end
, monname
, [2011]
, [2012]
, [2013]
, [2014]
, [2015]
from ( select months.mon
, months.monname
, years.yr
, coalesce(sum(inv.marginamount), 0) as marginamount
from months
cross
join years
left
outer
join invoices inv
on year(inv.invoicedate) = years.yr
and month(inv.invoicedate) = months.mon
group
by grouping sets
( (months.mon, months.monname, years.yr)
, (years.yr)
)
) source
pivot ( max(marginamount)
for yr
in ( [2011], [2012], [2013], [2014], [2015]
)
) pvt
order
by coalesce(mon, 100)