SQLServer 2008 PIVOT on multiple fields issue - sql

I am trying to using PIVOT on 3 fields/Columns i.e Site, Value, Date.I am using SQLServer 2008.Please check below my actual table and desired output table and the pivot query i used
Actual Table:
**tblReference**
**ID** **Refer_code** **Site** **Value** **Date**
1 9290 CA 12.5 2014-01-01 20:20:41
5 9290 TX 12.6 2014-01-05 18:20:30
2 6651 CA 13.5 2014-01-01 21:20:21
3 7442 TX 14.5 2014-01-05 19:15:14
4 8093 CA 15.5 2014-01-01 19:20:20
6 8093 TX 16.5 2014-01-05 20:20:20
**Desired output table:**
**Refer_code** **Site_1** **Site_2** **Val_1** **Val_2** **StartDate** **EndDate**
9290 CA TX 12.5 12.6 2014-01-01 2014-01-05
20:20:41 18:20:30
6651 CA NULL 13.5 NULL 2014-01-01 NULL
21:20:21
7442 NULL TX NULL 14.5 NULL 2014-01-05
19:15:14
8093 CA TX 15.5 16.5 2014-01-01 2014-01-05
19:20:20 20:20:20
**Query:**
SELECT Refer_code, [Site_1], [Site_2], [Date_1] AS StartDate, [Date_2] AS EndDate, [Val_1], [Val_2]
FROM
(
SELECT Refer_code, Site, 'Site_'+ cast(row_number() over(partition by Refer_code order by Date) as nvarchar(50)) SiteVal,Date,'Date_'+cast(row_number() over(partition by Refer_code order by Date) as nvarchar(50)) DateVal,Value,'Val_'+cast(row_number() over(partition by Refer_code order by Date) as nvarchar(50)) Val
FROM tblReference
) x
pivot
(
min(Site)
for SiteVal in ([Site_1], [Site_2])
) p
pivot
(
min(Date)
for DateVal in ([Date_1], [Date_2])
) s
pivot
(
min(Value)
for Val in ([Val_1], [Val_2])
) t
The above query is not returning the result as expected.Please help me on the solution.

There are several ways to get the desired result. The easiest way might be to use an aggregate function with a CASE expression but you can also use the PIVOT function.
Aggregate with CASE:
I would take the following steps, first create a unique sequence for each refer_code using row_number():
select refer_code, site,
row_number() over(partition by refer_code
order by date) seq,
value,
date
from tblReference;
See SQL Fiddle with Demo.
Once you have the unique sequence you can easily convert your existing data into multiple columns by applying an aggregate function with a CASE expression:
;with cte as
(
select refer_code, site,
row_number() over(partition by refer_code
order by date) seq,
value,
date
from tblReference
)
select refer_code,
max(case when seq = 1 then site end) site1,
max(case when seq = 2 then site end) site2,
max(case when seq = 1 then value end) value1,
max(case when seq = 2 then value end) value2,
max(case when seq = 1 then date end) startdate,
max(case when seq = 2 then date end) enddate
from cte
group by refer_code;
See SQL Fiddle with Demo
PIVOT:
If you want to use the PIVOT function and you need to pivot multiple columns I would suggest looking at unpivoting those columns first, then applying the pivot function. You will need to create the unique sequence which is used for your new column names, then you need to unpivot the site, date and value columns. Since you are using SQL Server 2008+ you can use CROSS APPLY with VALUEs to unpivot the multiple columns into multiple rows:
;with cte as
(
select refer_code, site,
row_number() over(partition by refer_code
order by date) seq,
value,
date
from tblReference
)
select *
from
(
select refer_code,
col = col + cast(seq as varchar(10)),
val
from cte
cross apply
(
values
('Site', site),
('Value', cast(value as varchar(10))),
('Date', convert(varchar(10), date, 120))
) c (col, val)
) d;
See SQL Fiddle with Demo. This gets your data into the format of:
| REFER_CODE | COL | VAL |
|------------|--------|------------|
| 6651 | Site1 | CA |
| 6651 | Value1 | 13.50 |
| 6651 | Date1 | 2014-01-01 |
| 7442 | Site1 | TX |
| 7442 | Value1 | 14.50 |
| 7442 | Date1 | 2014-01-05 |
Now you can easily apply the pivot function to get the final result, so the entire code will be:
;with cte as
(
select refer_code, site,
row_number() over(partition by refer_code
order by date) seq,
value,
date
from tblReference
)
select refer_code,
site1, site2,
startdate = date1, enddate = date2,
value1, value2
from
(
select refer_code,
col = col + cast(seq as varchar(10)),
val
from cte
cross apply
(
values
('Site', site),
('Value', cast(value as varchar(10))),
('Date', convert(varchar(10), date, 120))
) c (col, val)
) d
pivot
(
max(val)
for col in (site1, site2, date1, date2,
value1, value2)
) piv;
See SQL Fiddle with Demo. Both will give a result of:
| REFER_CODE | SITE1 | SITE2 | STARTDATE | ENDDATE | VALUE1 | VALUE2 |
|------------|-------|--------|------------|------------|--------|--------|
| 6651 | CA | (null) | 2014-01-01 | (null) | 13.50 | (null) |
| 7442 | TX | (null) | 2014-01-05 | (null) | 14.50 | (null) |
| 8093 | CA | TX | 2014-01-01 | 2014-01-05 | 15.50 | 16.50 |
| 9290 | CA | TX | 2014-01-01 | 2014-01-05 | 12.50 | 12.60 |
Edit:
Based on your comment that you need the columns to be CA as site1, etc. then the easiest way to get the result would be using an aggregate function with CASe:
select refer_code,
max(case when site = 'CA' then site end) site1,
max(case when site = 'TX' then site end) site2,
max(case when site = 'CA' then value end) value1,
max(case when site = 'TX' then value end) value2,
max(case when site = 'CA' then date end) startdate,
max(case when site = 'TX' then date end) endate
from tblReference
group by refer_code;
See SQL Fiddle with Demo. This gives a result:
| REFER_CODE | SITE1 | SITE2 | VALUE1 | VALUE2 | STARTDATE | ENDATE |
|------------|--------|--------|--------|--------|--------------------------------|--------------------------------|
| 6651 | CA | (null) | 13.5 | (null) | January, 01 2014 21:20:21+0000 | (null) |
| 7442 | (null) | TX | (null) | 14.5 | (null) | January, 05 2014 19:15:14+0000 |
| 8093 | CA | TX | 15.5 | 16.5 | January, 01 2014 19:20:20+0000 | January, 05 2014 20:20:20+0000 |
| 9290 | CA | TX | 12.5 | 12.6 | January, 01 2014 20:20:41+0000 | January, 05 2014 18:20:30+0000 |

Related

How can I group and sum data by day using T-SQL?

I have a table like this
datex | countx |
---------------------
2022-12-04 | 1 |
2022-12-03 | 2 |
2022-12-02 | 1 |
2022-12-01 | 3 |
2022-11-30 | 1 |
2022-11-29 | 1 |
2022-11-28 | 1 |
2022-11-27 | 2 |
I want to get this output
datex | count_sum |
-------------------------
2022-12 | 4 |
2022-12-01 | 3 |
2022-11 | 5 |
So far I tried some group by clause but I didn't succeed.
Here is test code
declare #test table
(
datex date,
countx int
)
insert into #test
values ('2022-12-04', 1),
('2022-12-03', 2),
('2022-12-02', 1),
('2022-12-01', 3),
('2022-11-30', 1),
('2022-11-29', 1),
('2022-11-28', 1),
('2022-11-27', 2)
You may use a case expression to check if the date is the first day of the month then aggregate as the following:
with check_date as
(
select case
when Day([date])=1
Then Cast([date] as varchar(10))
else Format([date], 'yyyy-MM')
end As dt,
[count]
from table_name
)
select dt, sum([count]) as count_sum
from check_date
group by dt
order by dt desc
See demo
As I understood you want to "extract" year and month from your datex column and count it. I think you can use below SQL:
with cte as(
select
concat(year(datex), '-', month(datex)) as datex,
countx
from test
where not datex in ( '2022-12-01' )
)
select
datex,
count(1)
from cte
group by datex;
Result:
date | count_sum |
-------------------------
2022-12 | 3 |
2022-11 | 4 |
Here is Fiddle.

Detect changes for each ID

Suppose I have the following data
ID | year_month | Department
1233 | 2020-01-01 | A
1123 | 2020-02-01 | A
1123 | 2020-03-01 | NULL
1123 | 2020-04-01 | B
1123 | 2020-05-01 | B
1123 | 2020-06-01 | B
1123 | 2020-07-01 | NULL
9999 | 2020-01-01 | A
9999 | 2020-02-01 | A
9999 | 2020-03-01 | B
9999 | 2020-04-01 | B
9999 | 2020-05-01 | B
9999 | 2020-06-01 | A
9999 | 2020-07-01 | B
I want to identify the changes in department. , including going to NA/NULL. The desired output is:
ID | Change_year_month | Old_Department | New_Department
1123 | 2020-03-01 | A | NULL
1123 | 2020-04-01 | NULL | B
1123 | 2020-07-01 | B | NULL
9999 | 2020-03-01 | A | B
9999 | 2020-06-01 | B | A
Ideas I've already tried to pursue:
with x as(
SELECT T1.ID, T1.Department, MIN(T1.year_month) AS Change_year_month FROM dbo.Source
GROUP BY T1.ID, Department),
y as (
SELECT ID, year_month,
rown = ROW_NUMBER() OVER (PARTITION BY ID ORDER BY year_month) FROM x
)
select y.ID, T2.Department, year_month AS Change_year_month FROM y
right join (SELECT T1.ID,
MAX(Department) as Old_Department,
Min(Department) AS New_Department
FROM dbo.Source
GROUP BY T1.ID HAVING COUNT(DISTINCT(Department)) >= 2) T2 on y.ID = T2.ID
where rown = 1
However, this does not yield the desired result. Whenever a NULL is involved, the query does not see the change. Whenever I change the NULL to something else (like: 'outside the scope'), then the ordering is wrong as the Old_department is never 'outside the scope', but the New_department always is. Also, I feel like the code is inefficient and not durable.
Does anyone have suggestions how to proceed or to construct of durable query?
Here is a pretty simple method using lag():
select s.id, s.year_month, s.prev_department, s.department
from (select s.*,
lag(year_month) over (partition id order by year_month) as prev_ym,
lag(year_month) over (partition id, department order by year_month) as prev_ym_dept,
lag(department) over (partition by id order by year_month) as prev_department
from dbo.source s
) s
where prev_ym_dept <> prev_ym;
This looks at the dates for the comparison, so it just handles NULL values.
Of course, you can use more complicated comparisons:
select s.id, s.year_month, s.prev_department, s.department
from (select s.*,
lag(year_month) over (partition id order by year_month) as prev_ym,
min(year_month) over (partition by id) as min_year_month
from dbo.source s
) s
where prev_department <> department or
(department is null and
prev_department is not null
) or
(prev_department is null and
department is not null and
year_month <> min_year_month
)
But that is rather tricky to express. And that might even have a mistake in filtering out the first row.

Connecting two tables so that results are shown in multiple columns

Let's assume we have table with data like this
+----------------+------------+-------+
| Company_number | Date | Value |
+----------------+------------+-------+
| 123 | 2017-01-01 | 5 |
| 123 | 2017-02-01 | 10 |
| 123 | 2018-01-01 | 15 |
| 456 | 2018-01-05 | 33 |
+----------------+------------+-------+
What should I do to receive data in format
+----------------+------+------------+------------+
| Company_number | Mont | Value 2017 | Value 2018 |
+----------------+------+------------+------------+
| 123 | 01 | 5 | 15 |
| 123 | 02 | 10 | |
| 456 | 01 | 33 | |
+----------------+------+------------+------------+
I have no idea how to select all data from 2017 and connect it using the company number and month with data from 2018.
I have taken all of the records from 2017, put it into another table, and tried to use this select, but it doesn't show records when there are no common months (there is no record for February).
select
s.company_number
,datepart(month,s.date) as Month
,s.Value as Value_2017
,r.Value as Value_2018
from table1 as s
left join table2 as r on concat(r.company_number,datepart(month,r.date))=concat(s.company_number,datepart(month,s.date))
where datepart(year,s.date)='2018'
select results (with no February)
+----------------+-------+------------+------------+
| company_number | Month | Value_2017 | Value_2018 |
+----------------+-------+------------+------------+
| 123 | 1 | 15 | 5 |
| 456 | 1 | 33 | NULL |
+----------------+-------+------------+------------+
Try this out:
SELECT
COALESCE([t1].[company_number], [t2].[company_number]) AS [Company_Number],
MONTH(COALESCE([t1].[date], [t2].[date])) AS [Month],
[t1].[value] AS [2018 Value],
[t2].[value] AS [2017 Value]
FROM
[table1] AS [t1]
FULL OUTER JOIN
[table2] as [t2] on [t1].[company_number] = [t2].[company_number]
AND MONTH([t1].[date]) = MONTH ([t2].[date])
Just tried it with (can be copy/pasted in editor and executed) and it works fine:
DECLARE #t1 TABLE (Company_number INT, [Date] Date, Value INT)
DECLARE #t2 TABLE (Company_number INT, [Date] Date, Value INT)
INSERT INTO #t1 VALUES (123, '2017-01-01', 5), (123, '2017-02-01', 10)
INSERT INTO #t2 VALUES (123, '2018-01-01', 15), (456, '2018-01-05', 33)
SELECT
COALESCE([t1].Company_number, [t2].Company_number) AS [Company_Number],
MONTH(COALESCE([t1].[date], [t2].[date])) AS [Month],
[t1].[value] AS [2018 Value],
[t2].[value] AS [2017 Value]
FROM
#t1 AS [t1]
FULL OUTER JOIN
#t2 as [t2] on [t1].[company_number] = [t2].[company_number]
AND MONTH([t1].[date]) = MONTH ([t2].[date])
This is using CTE on the same table1 NOT TWO TABLES for 2017 and 2018.
With tmp as (Select company_id,
Datepart(year, dte) as year,
Datepart(month, dte) as month,
Sum(value) as value
from tbl
Group by company_id,
Datepart(year, dte),
Datepart(month, dte))
Select coalesce(m.company_id,n.company_id) as company_id,
coalesce(m.month,n.month) as month,
m.value as value_2017,
n.value as value_2018
From (select * from tmp
Where year=2017) m
Full outer join (select * from tmp
Where year=2018) n
On m.company_id=n.company_id
and m.month=n.month
Result:
company_number month Value_2017 Value_2018
123 1 5 5
123 2 10 (null)
456 1 (null) 23

Customized Pivot in sql 2014

I am trying to do PIVOT on sql, where two columns value has to be aggreagated for each year.
The below code gives perfect result.
DECLARE #TABLE TABLE
(
SKU VARCHAR(10),
YYMM VARCHAR(50),
BRAND VARCHAR(50),
AMT DECIMAL,
QTY INT
)
INSERT INTO #TABLE
SELECT '104591168', '2015-January', 'abott',200, 2 UNION ALL
SELECT '104580709', '2016-January', 'GSK',159 , 2 UNION ALL
SELECT '104720038', '2017-January', 'RANBAXCY',169, 2 UNION ALL
SELECT '10467011A', '2018-January', 'abott',185, 2 UNION ALL
SELECT '104590691', '2019-January', 'abott',256 , 10
SELECT *
FROM(
SELECT BRAND, sku, QTY, YYMM,AMT/QTY AS AVGPR
FROM #TABLE
) AS src
PIVOT(
sum(QTY)
for [YYMM] IN( [2015-January], [2016-January], [2017-January] /* add other moneths here */ )
) AS Pivoted
and result look like
But how can i see AVGPR in same pivot way as like sum(qty).
when i tried like
code:
SELECT *
FROM(
SELECT BRAND, sku, QTY, YYMM
FROM #TABLE
) AS src
PIVOT(
sum(QTY),
SUM(AVG)
for [YYMM] IN( [2015-January], [2016-January], [2017-January] /* add other moneths here */ )
) AS Pivoted
i am getting Incorrect syntax error.
Please help
I have a data like this.
SKU YYMM BRAND Sales Cost QTY AVGPRICE
101110028 1/1/2017 ABOTT 15.7 5.73 1 15.7
101110028 2/1/2017 ABOTT 16.33 5.66 1 16.33
101110028 3/1/2017 ABOTT 31.2 11.34 2 15.6
and
I AM TRYING TO DISPLAY LIKE THIS
Sum of QTY Sum of Avg Price
BRAND PNO 1/1/2017 2/1/2017 3/1/2017 1/1/2017 2/1/2017 3/1/2017
PAGID 101110028 0 2 1 15.7 16.33 15.6
Pivot on Quantity and then sum of avg for a same YYMM In a row
I would just use conditional aggregation:
SELECT brand, sku,
SUM(CASE WHEN YYMM = '2015-January' THEN QTY END) as [2015-January-QTY],
SUM(CASE WHEN YYMM = '2015-January' THEN QTY END) as [2015-January-AVG],
SUM(CASE WHEN YYMM = '2015-February' THEN QTY END) as [2015-February-QTY],
SUM(CASE WHEN YYMM = '2015-February' THEN QTY END) as [2015-February-AVG],
. . .
FROM #TABLE t
GROUP BY brand, sku;
Using two pivots and union all for the result set in your comment
select *, Remarks = 'QTY'
from (select brand, sku, qty, yymm from #table) src
pivot(sum(qty) for [yymm] in (
[2015-January], [2016-January], [2017-January] /* add other months here */
) ) as Pivoted
union all
select *, Remarks = 'AVG'
from (select brand, sku, yymm,amt/qty as avgpr from #table) src
pivot(sum(avgpr) for [yymm] in (
[2015-January], [2016-January], [2017-January] /* add other months here */
) ) as Pivoted
order by brand, sku, remarks desc;
rextester demo: http://rextester.com/ORPF1616
returns:
+----------+-----------+-------------------+------------------+------------------+---------+
| brand | sku | 2015-January | 2016-January | 2017-January | Remarks |
+----------+-----------+-------------------+------------------+------------------+---------+
| abott | 104590691 | NULL | NULL | NULL | QTY |
| abott | 104590691 | NULL | NULL | NULL | AVG |
| abott | 104591168 | 2,0000000000000 | NULL | NULL | QTY |
| abott | 104591168 | 100,0000000000000 | NULL | NULL | AVG |
| abott | 10467011A | NULL | NULL | NULL | QTY |
| abott | 10467011A | NULL | NULL | NULL | AVG |
| gsk | 104580709 | NULL | 2,0000000000000 | NULL | QTY |
| gsk | 104580709 | NULL | 79,5000000000000 | NULL | AVG |
| ranbaxcy | 104720038 | NULL | NULL | 2,0000000000000 | QTY |
| ranbaxcy | 104720038 | NULL | NULL | 84,5000000000000 | AVG |
+----------+-----------+-------------------+------------------+------------------+---------+

sum 2 fields by row and query max value by day per month

I have used SQL for simple queries, but now need to perform something a bit more complex. I am not sure how to nest the queries.
I have one table with the following columns:
Date, Daily Power, Daily Power 1, Daily power2
I need to find that max daily values and then filter by months. Also I need Daily Power 1 and Daily Power 2 summed into a new column.
Any help would be appreciated.
Part of the problem with your table is that the data is not normalized since you have 3 columns each containing a separate value for DailyPower.
One way that you can easily get the result that you need is to unpivot the data using a UNION ALL query.
This query will take the data from the multiple columns and turn it into multiple rows for use:
select date, 'DailyPower' as col, DailyPower as value
from yourtable
union all
select date, 'DailyPower1' as col, DailyPower1 as value
from yourtable
union all
select date, 'DailyPower2' as col, DailyPower2 as value
from yourtable
See SQL Fiddle with Demo. This query takes the data and converts it into the result:
| DATE | COL | VALUE |
------------------------------------
| 2012-01-01 | DailyPower | 456 |
| 2012-01-02 | DailyPower | 789 |
| 2012-02-01 | DailyPower | 23 |
| 2012-01-01 | DailyPower1 | 789 |
| 2012-01-02 | DailyPower1 | 235 |
| 2012-02-01 | DailyPower1 | 89 |
| 2012-01-01 | DailyPower2 | 65 |
| 2012-01-02 | DailyPower2 | 45 |
| 2012-02-01 | DailyPower2 | 10 |
Once the data is in the rows, then it is easier to get the max() value by date.
Your query would be similar to the following:
select date,
max(value) MaxDailyPower,
sum(case when col in ('DailyPower1', 'DailyPower2') then value end) TotalDailyPower
from
(
select date, 'DailyPower' as col, DailyPower as value
from yourtable
union all
select date, 'DailyPower1' as col, DailyPower1 as value
from yourtable
union all
select date, 'DailyPower2' as col, DailyPower2 as value
from yourtable
) src
where date >= '2012-01-01'
and date <= '2012-12-31'
group by date
See SQL Fiddle with Demo. This gives the result:
| DATE | MAXDAILYPOWER | TOTALDAILYPOWER |
------------------------------------------------
| 2012-01-01 | 789 | 854 |
| 2012-01-02 | 789 | 280 |
| 2012-02-01 | 89 | 99 |
Edit #1, if you want to GROUP BY month, then you could use:
select month(date) Month,
max(value) MaxDailyPower,
sum(case when col in ('DailyPower1', 'DailyPower2') then value end) TotalDailyPower
from
(
select date, 'DailyPower' as col, DailyPower as value
from yourtable
union all
select date, 'DailyPower1' as col, DailyPower1 as value
from yourtable
union all
select date, 'DailyPower2' as col, DailyPower2 as value
from yourtable
) src
group by month(date)
See SQL Fiddle with Demo
Is this what you want?
select date,
(case when DailyPower > DailyPower1 and DailyPower > DailyPower2 then DailyPower
when DailyPower1 > DailyPower2 then DailyPower1
else DailyPower2
) as MaxDailyPower,
coalesce(DailyPower1, 0) + Colaesce(DailyPower2) as DailyPowerSUm
from t
where date between '2012-01-01' and '2012-03-31' -- for the filter
This assumes that there is one row per date i nyour data.