How to pivot on two levels - sql-server

I am using SQL Server 2012 and am trying to construct a pivot table from TSQL based on the table below which has been generated by joining multiple tables.
INCIDENT ID | Department | Priority | Impact
--------------------------------------------
1 | IT | Urgent | High
2 | IT | Retrospective | Medium
3 | Marketing | Normal | Low
4 | Marketing | Normal | High
5 | Marketing | Normal | Med
6 | Finance | Normal | Med
From this table, want it to be displayed in following format:
Priority | Normal | Urgent | Retrospective |
| Department | Low | Medium | High | Low | Medium | High | Low | Medium | High |
--------------------------------------------------------------------------------
| IT | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 0 |
| Finance | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 0 |
| Marketing | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 0 |
I have the following code which successfully Pivots on the "Priority" level.
SELECT *
FROM (
SELECT
COUNT(incident.incident_id) OVER(PARTITION BY serv_dept.serv_dept_n) Total,
serv_dept.serv_dept_n Department,
ImpactName.item_n Impact,
PriorityName.item_n Priority
FROM -- ommitted for brevity
WHERE -- ommitted for brevity
) AS T
PIVOT (
COUNT(Priority)
FOR Priority IN ("Normal", "Urgent", "Retrospective")
) PIV
ORDER BY Department ASC
How can I get this query to pivot on two levels like the second table I pasted?
Any help would be appreciated.

The easiest way may be conditional aggregation:
select department,
sum(case when priority = 'Normal' and target = 'Low' then 1 else 0 end) as Normal_low,
sum(case when priority = 'Normal' and target = 'Med' then 1 else 0 end) as Normal_med,
sum(case when priority = 'Normal' and target = 'High' then 1 else 0 end) as Normal_high,
. . .
from t
group by department;

I'll take a stab at it:
WITH PivotData AS
(
SELECT
Department
, Priority + '_' + Impact AS PriorityImpact
, Incident_ID
FROM
<table>
)
SELECT
Department
, Normal_Low
, Normal_Medium
,...
FROM
PivotData
PIVOT (COUNT(Incident_ID FOR PriorityImpact IN (<Listing all the PriorityImpact values>) ) as P;

Related

Getting Top 10 based on column value

I have a code that output a long list of the sum of count of work orders per name and sorts it by total, name and count:
;with cte as (
SELECT [Name],
[Emergency],
count([Emergency]) as [CountItem]
FROM tableA
GROUP BY [Name], [Emergency])
select Name,[Emergency],[Count],SUM([CountItem]) OVER(PARTITION BY Name) as Total from cte
order by Total desc, Name, [CountItem] desc
but I only want to get the top 10 Names with the highest total like the one below:
+-------+-------------------------------+-------+-------+
| Name | Emergency | Count | Total |
+-------+-------------------------------+-------+-------+
| PLB | No | 7 | 15 |
| PLB | No Hot Water | 4 | 15 |
| PLB | Resident Locked Out | 2 | 15 |
| PLB | Overflowing Tub | 1 | 15 |
| PLB | No Heat | 1 | 15 |
| GG | Broken Lock - Exterior | 6 | 6 |
| BOA | Broken Lock - Exterior | 2 | 4 |
| BOA | Garage Door not working | 1 | 4 |
| BOA | Resident Locked Out | 1 | 4 |
| 15777 | Smoke Alarm not working | 3 | 3 |
| FP | No air conditioning | 2 | 3 |
| FP | Flood | 1 | 3 |
| KB | No electrical power | 2 | 3 |
| KB | No | 1 | 3 |
| MEM | Noise Complaint | 3 | 3 |
| ANG | Parking Issue | 2 | 2 |
| ALL | Smoke Alarm not working | 2 | 2 |
| AAS | No air conditioning | 1 | 2 |
| AAS | Toilet - Clogged (1 Bathroom) | 1 | 2 |
+-------+-------------------------------+-------+-------+
Note: I'm not after unique values. As you can see from the example above it gets the top 10 names from a very long table.
What I want to happen is assign a row id for each name so all PLB above will have a row id of 1, GG = 2, BOA = 3, ...
So on my final select I will only add the where clause where row id <= 10. I already tried ROW_NUMBER() OVER(PARTITION BY Name ORDER BY Name) but it's assigning 1 to every unique Name it encounters.
You may try this:
;with cte as (
SELECT [Name],
[Emergency],
count([Emergency]) as [CountItem]
FROM tableA
GROUP BY [Name], [Emergency]),
ct as (
select Name,[Emergency],[Count],SUM([CountItem]) OVER(PARTITION BY PropertyName) as Total from cte
),
ctname as (
select dense_rank() over ( order by total, name ) as RankName, Name,[Emergency],[Count], total from ct )
select * from ctname where rankname < 11

What's an efficient way to count "previous" rows in SQL?

Hard to phrase the title for this one.
I have a table of data which contains a row per invoice. For example:
| Invoice ID | Customer Key | Date | Value | Something |
| ---------- | ------------ | ---------- | ------| --------- |
| 1 | A | 08/02/2019 | 100 | 1 |
| 2 | B | 07/02/2019 | 14 | 0 |
| 3 | A | 06/02/2019 | 234 | 1 |
| 4 | A | 05/02/2019 | 74 | 1 |
| 5 | B | 04/02/2019 | 11 | 1 |
| 6 | A | 03/02/2019 | 12 | 0 |
I need to add another column that counts the number of previous rows per CustomerKey, but only if "Something" is equal to 1, so that it returns this:
| Invoice ID | Customer Key | Date | Value | Something | Count |
| ---------- | ------------ | ---------- | ------| --------- | ----- |
| 1 | A | 08/02/2019 | 100 | 1 | 2 |
| 2 | B | 07/02/2019 | 14 | 0 | 1 |
| 3 | A | 06/02/2019 | 234 | 1 | 1 |
| 4 | A | 05/02/2019 | 74 | 1 | 0 |
| 5 | B | 04/02/2019 | 11 | 1 | 0 |
| 6 | A | 03/02/2019 | 12 | 0 | 0 |
I know I can do this using either a CTE like this...
(
select
count(*)
from table
where
[Customer Key] = t.[Customer Key]
and [Date] < t.[Date]
and Something = 1
)
But I have a lot of data and that's pretty slow. I know I can also use cross apply to achieve the same thing, but as far as I can tell that's not any better performing than just using a CTE.
So; is there a more efficient means of achieving this, or do I just suck it up?
EDIT: I originally posted this without the requirement that only rows where Something = 1 are counted. Mea culpa - I asked it in a hurry. Unfortunately I think that this means I can't use row_number() over (partition by [Customer Key])
Assuming you're using SQL Server 2012+ you can use Window Functions:
COUNT(CASE WHEN Something = 1 THEN CustomerKey END) OVER (PARTITION BY CustomerKey ORDER BY [Date]
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) -1 AS [Count]
Old answer before new required logic:
COUNT(CustomerKey) OVER (PARTITION BY CustomerKey ORDER BY [Date]
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) -1 AS [Count]
If you're not using 2012 an alternative is to use ROW_NUMBER
ROW_NUMBER() OVER (PARTITION BY CustomerKey ORDER BY [Date]) - 1 AS Count

Multi-Class to Multi-Label Transformation in MS SQL Server

I want to transform a Data set of labels to a binary representation via a SQL query, i.e. the following table:
|---------------------------|
| Example | Label |
|---------------------------|
| 1 | Health |
| 1 | Business |
| 1 | Science |
| 2 | Sports |
| 2 | Business |
|---------------------------|
Transforms into a new table:
|---------------------------|-----------|-----------|-----------|
| Example | Business | Health | Science | Sports |
|---------------------------|-----------|-----------|-----------|
| 1 | 1 | 1 | 1 | 0 |
| 2 | 1 | 0 | 0 | 1 |
|-----------|---------------|-----------|-----------|-----------|
via some SQL query. What would be said SQL query?
select example, sum(case when label='Business' then 1 else 0 end) 'Business'
,sum(case when label='Health' then 1 else 0 end) 'Health'
,sum(case when label='Science' then 1 else 0 end) 'Science'
,sum(case when label='Sports' then 1 else 0 end) 'Sports'
From MyTable
group by example

Optimized query for SQL Server database

I have following table in my SQL Server database:
| ID | Class | CId | PId
| 7865 | Add Class for Prop | 1043 | 1
| 82 | Advanced Carpet Spotting Advanced Carpet Spotting | 1043 | 1
| 82 | Advanced Carpet Spotting Advanced Carpet Spotting | 1042 | 1
| 7863 | aTesting | 1042 | 0
| 7218 | aUnique | 1042 | 0
| 85 | Body Mechanics | 1042 | 1
| 88 | Carpet Bonnet Cleaning | 1044 | 0
| 89 | Carpet Shampooing/Extraction | 1044 | 1
| 7829 | Class 10 | 1044 | 0
I have multiple CId and PId
If distinct CId have their corresponding PId = 1 Then,
Set CId = 1 Otherwise 0
I want output like this
| CId | Status | Count
| 1042 | 0 | 4
| 1043 | 1 | 2
| 1044 | 0 | 3
I can get required output by using multiple queries but I want more optimized one .
Please suggest a solution. Thank you.
select cid,
case when count(case when pid = 1 then 1 end) > 0
then 1
else 0
end as status,
count(*) as count
from your_table
group by cid
If PId is always 1 and 0, you can do the following.
SELECT
CID,
AVG([Status] * 1) AS [Status],
COUNT(*) AS [Count]
FROM
YourTable
GROUP BY
CID
Use a CASE expression to check whether the sum of PId is equal to count of PId.
Query
select [CId],
case when sum([Cid]) = count([CId]) then 1
else 0 end as [Status],
count([CId]) as [Count]
from [your_table_name]
group by [CId];
Also PId should have only 1 and 0 values.

How to create a cross tab (in crystal) from multiple columns (in sql)

I have 5 columns in SQL that I need to turn into a cross tab in Crystal.
This is what I have:
Key | RELATIONSHIP | DISABLED | LIMITED | RURAL | IMMIGRANT
-----------------------------------------------------------------
1 | Other Dependent | Yes | No | No | No
2 | Victim/Survivor | No | No | No | No
3 | Victim/Survivor | Yes | No | No | No
4 | Child | No | No | No | No
5 | Victim/Survivor | No | No | No | No
6 | Victim/Survivor | No | No | No | No
7 | Child | No | No | No | No
8 | Victim/Survivor | No | Yes | Yes | Yes
9 | Child | No | Yes | Yes | Yes
10 | Child | No | Yes | Yes | Yes
This is what I want the cross tab to look like (Distinct count on Key):
| Victim/Survivor | Child | Other Dependent | Total |
--------------------------------------------------------------
| DISABLED | 1 | 0 | 1 | 2 |
--------------------------------------------------------------
| LIMITED | 1 | 2 | 0 | 3 |
--------------------------------------------------------------
| RURAL | 1 | 2 | 0 | 3 |
--------------------------------------------------------------
| IMMIGRANT | 1 | 2 | 0 | 3 |
--------------------------------------------------------------
| TOTAL | 4 | 6 | 1 | 11 |
--------------------------------------------------------------
I used this formula in Crystal in an effort to combine 4 columns (Field name = {#OTHERDEMO})...
IF {TABLE.DISABLED} = "YES" THEN "DISABLED" ELSE
IF {TABLE.LIMITED} = "YES" THEN "LIMITED" ELSE
IF {TABLE.IMMIGRANT} = "YES" THEN "IMMIGRANT" ELSE
IF {TABLE.RURAL} = "YES" THEN "RURAL"
...then made the cross-tab with #OTHERDEMO as the rows, RELATIONSHIP as the Columns with a distinct count on KEY:
Problem is, once crystal hits the first "Yes" it stops counting thus not categorizing correctly in the cross-tab. So I get a table that counts the DISABILITY first and gives the correct display, then counts the Limited and gives some info, but then dumps everything else.
In the past, I have done mutiple conditional formulas...
IF {TABLE.DISABLED} = "YES" AND {TABLE.RELATIONSHIP} = "Victim/Survivor" THEN {TABLE.KEY} ELSE {#NULL}
(the #null formula is because Crystal, notoriously, gets confused with nulls.)
...then did a distinct count on Key, and finally summed it in the footer.
I am convinced there is another way to do this. Any help/ideas would be greatly appreciated.
If you unpivot those "DEMO" columns into rows it will make the crosstab super easy...
select
u.[Key],
u.[RELATIONSHIP],
u.[DEMO]
from
Table1
unpivot (
[b] for [DEMO] in ([DISABLED], [LIMITED], [RURAL], [IMMIGRANT])
) u
where
u.[b] = 'Yes'
SqlFiddle
or if you were stuck on SQL2000 compatibility level you could manually unpivot the Yes values...
select [Key], [REALTIONSHIP], [DEMO] = cast('DISABLED' as varchar(20))
from Table1
where [DISABLED] = 'Yes'
union
select [Key], [REALTIONSHIP], [DEMO] = cast('LIMITED' as varchar(20))
from Table1
where [LIMITED] = 'Yes'
union
select [Key], [REALTIONSHIP], [DEMO] = cast('RURAL' as varchar(20))
from Table1
where [RURAL] = 'Yes'
union
select [Key], [REALTIONSHIP], [DEMO] = cast('IMMIGRANT' as varchar(20))
from Table1
where [IMMIGRANT] = 'Yes'
For the crosstab, use a count on the Key column (aka row count), [DEMO] on rows, and [RELATIONSHIP] on columns.

Resources