How to convert column to row in sql without using pivot - sql-server

I have been assigned to get the data in required format from two tables.
TableStaff :
StaffID | Staff Name
--------+-----------
1 | John
2 | Jack
and TableLead
LeadID | LeadValue | LeadStaus | StaffID
-------+-----------+-----------+--------
1 | 5000 | New | 1
2 | 8000 | Qualified | 1
3 | 3000 | New | 2
As you will notice StaffID is a foreign key referencing TableStaff.
I have to represent the data in following format
StaffID | StaffName | NewLeadCount | QualifiedLeadCount
--------+-----------+--------------+-------------------
1 | John | 1 | 1
2 | Jack | 1 | 0
What I have tried till now is :
SELECT
COUNT([LeadID ]) AS LdCount, 'New' AS StageName
FROM
[dbo].[TableLead]
WHERE
[LeadStaus] = 'New'
UNION
SELECT
COUNT([LeadID ]) AS LdCount, 'Qualified' AS StageName
FROM
[dbo].[TableLead]
WHERE
[LeadStaus] = 'Qualified '
Any NULL spots should be replaced by 0. Can anyone show me the right direction to approach the problem ?

I would recommend conditional aggregation:
select s.staffid, s.staffname,
sum(case when l.leadstatus = 'New' then 1 else 0 end) as newLeadCount,
sum(case when l.leadstatus = 'Qualified' then 1 else 0 end) as qualifiedLeadCount
from TableStaff s
inner join TableLead l on l.staffid = s.staffid
group by s.staffid, s.staffname

Related

SQL Server: how count from value from dynamic columns?

SQL Server: how count from value from dynamic columns?
I have data:
+ Subject
___________________
| SubID | SubName |
|-------|---------|
| 1 | English |
|-------|---------|
| 2 | Spanish |
|-------|---------|
| 3 | Korean |
|_______|_________|
+ Student
______________________________________
| StuID | StuName | Gender | SubID |
|---------|---------|--------|--------|
| 1 | David | M | 1,2 |
|---------|---------|--------|--------|
| 2 | Lucy | F | 2,3 |
|_________|_________|________|________|
I want to query result as:
____________________________________
| SubID | SubName | Female | Male |
|--------|---------|--------|------|
| 1 | English | 0 | 1 |
|--------|---------|--------|------|
| 2 | Spanish | 1 | 1 |
|--------|---------|--------|------|
| 3 | Koean | 1 | 0 |
|________|_________|________|______|
This is my query:
SELECT
SubID, SubName, 0 AS Female, 0 AS Male
FROM Subject
I don't know to replace 0 with real count.
Because you made the mistake of storing CSV data in your tables, we will have to do some SQL Olympics to get your result set. We can try joining the two tables on the condition that the SubID from the subject table appears somewhere in the CSV list of IDs in the student table. Then, aggregated by subject and count the number of males and females.
SELECT
s.SubID,
s.SubName,
COUNT(CASE WHEN st.Gender = 'F' THEN 1 END) Female,
COUNT(CASE WHEN st.Gender = 'M' THEN 1 END) Male
FROM Subject s
LEFT JOIN Student st
ON ',' + CONVERT(varchar(10), st.SubID) + ',' LIKE
'%,' + CONVERT(varchar(10), s.SubID) + ',%'
GROUP BY
s.SubID,
s.SubName;
Demo
But, you would be best off refactoring your table design to normalize the data better. Here is an example of a student table which looks a bit better:
+---------+---------+--------+--------+
| StuID | StuName | Gender | SubID |
+---------+---------+--------+--------+
| 1 | David | M | 1 |
+---------+---------+--------+--------+
| 1 | David | M | 2 |
+---------+---------+--------+--------+
| 2 | Lucy | F | 2 |
+---------+---------+--------+--------+
| 2 | Lucy | F | 3 |
+---------+---------+--------+--------+
We can go a bit further, and even store the metadata separately from the StuID and SubID relationship. But even using just the above would have avoided the ugly join condition.
If the version of your SQL Server is SQL Server or above, you could use STRING_split function to get expected results.
create table Subjects
(
SubID int,
SubName varchar(30)
)
insert into Subjects values
(1,'English'),
(2,'Spanish'),
(3,'Korean')
create table student
(
StuID int,
StuName varchar(30),
Gender varchar(10),
SubID varchar(10)
)
insert into student values
(1,'David','M','1,2'),
(2,'Lucy','F','2,3')
--Query
;WITH CTE AS
(
SELECT
S.Gender,
S1.value AS SubID
FROM student S
CROSS APPLY STRING_SPLIT(S.SubID,',') S1
)
select
T.SubID,
T.SubName,
COUNT(CASE T1.Gender WHEN 'F' THEN 1 END) AS Female,
COUNT(CASE T1.Gender WHEN 'M' THEN 1 END) AS Male
from Subjects T
LEFT JOIN CTE T1 ON T.SubID=T1.SubID
GROUP BY T.SubID,T.SubName
ORDER BY T.SubID
--Output
/*
SubID SubName Female Male
----------- ------------------------------ ----------- -----------
1 English 0 1
2 Spanish 1 1
3 Korean 1 0
*/

SQL apply functions to multiple id rows

I'm using SQL Server 2008, and trying to gather individual customer data appearing over multiple rows in my table, an example of my database is as follows:
custID | status | type | value
-------------------------
1 | 1 | A | 150
1 | 0 | B | 100
1 | 0 | A | 153
1 | 0 | A | 126
2 | 0 | A | 152
2 | 0 | B | 101
2 | 0 | B | 103
For each custID, my task is to find a flag if status=1 for any row, if type=B for any row, and the average of value in all cases where type=B. So my solution should look like:
custID | statusFlag | typeFlag | valueAv
-------------------------------------------
1 | 1 | 1 | 100
2 | 0 | 1 | 102
I can get answers for this using lots of row_number() over (partition by .. ), to create ids, and creating subtables for each column selecting the desired id. My issue is this method is awkward and time consuming, as I have many more columns than shown above to do this over, and many tables to repeat it for. My ideal solution would be to define my own aggregate() function so I could just do:
select custID, ag1(statusFlag), ag2(typeFlag)
group by custID
but as far as I can tell custom aggregates can't be defined in SQL server. Is there a nicer general approach to this problem, which doesn't require defining lots of id's ?
use CASE WHEN to evaluate the value and apply the aggregate function accordingly
select custID,
statusFlag = max(status),
typeFlag = max(case when type = 'B' then 1 else 0 end),
valueAv = avg(case when type = 'B' then value end)
from samples
group by custID

How to update SQL rows based on other rows with shared ID?

Currently, I have a table that looks like below:
ID|Date |Val
1 |1/1/2016|1
2 |1/1/2016|0
3 |1/1/2016|0
1 |2/1/2016|0
2 |2/1/2016|1
3 |2/1/2016|1
1 |3/1/2016|0
2 |3/1/2016|0
3 |3/1/2016|0
I want to update it so that the value carries over for each ID, but not on earlier dates than when the value first appeared. Also, the value can only change 0 to 1, not vice versa. So the final product would look like:
ID|Date |Val
1 |1/1/2016|1
2 |1/1/2016|0
3 |1/1/2016|0
1 |2/1/2016|1
2 |2/1/2016|1
3 |2/1/2016|1
1 |3/1/2016|1
2 |3/1/2016|1
3 |3/1/2016|1
I've tried a few code combinations, but the conditional of carrying the value after the date where the value first appears is tripping me up. I'd appreciate any help!
In SQL Server 2012+, using the aggregate max() as a window function with over() (inside a common table expression to simplify the update):
;with cte as (
select *
, MaxVal = max(convert(int,val)) over (partition by id order by date)
from t
)
update cte
set val = maxVal
where val <> maxVal
rextester demo: http://rextester.com/ZPGWB94088
result:
+----+------------+-----+
| id | Date | Val |
+----+------------+-----+
| 1 | 2016-01-01 | 1 |
| 2 | 2016-01-01 | 0 |
| 3 | 2016-01-01 | 0 |
| 1 | 2016-02-01 | 1 |
| 2 | 2016-02-01 | 1 |
| 3 | 2016-02-01 | 1 |
| 1 | 2016-03-01 | 1 |
| 2 | 2016-03-01 | 1 |
| 3 | 2016-03-01 | 1 |
+----+------------+-----+
Prior to SQL Server 2012, you could use something like this:
update t
set Val = 1
from t
inner join (
select i.Id, min(i.Date) as Date
from t as i
where i.Val = 1
group by i.Id
) as m
on t.Id = m.Id
and t.Date >= m.Date
and t.Val = 0
rextester demo: http://rextester.com/RLEAO15622

How to pivot on two levels

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;

Need to help query in SQL Server

I have a question about SQL Server.
Table: patient
pn | hospid | doj
------------------------
1 | 10 | 2015-10-14
1 | 10 | 2015-05-14
1 | 10 | 2015-08-12
2nd table: patientrefs
sdate | edate | codes | descripton | pn | hospid
-------------------------------------------------------------
2015-01-01 | 2015-09-30 | 500 | active | 1 | 10
2015-01-01 | 2015-09-30 | 501 | inactive | 1 | 10
2015-10-01 | 2016-03-31 | 500 | activestill | 1 | 10
2015-10-01 | 2016-03-31 | 501 | inactive | 1 | 10
2013-03-09 | 2013-09-12 | 300 | inactive | 1 | 10
Both table common columns pn + hospid and patient tables related dos between sdate and edate of patientrefs table.
And in patientrefs table descritpton=inactive and date between condition satisfy then codes we consider inactivecodes
In patientrefs table descritpton<>inactive and date between condition satisfy then codes we consider activecodes
Based on this above table I want output like this:
pn|hospid|doj |inactivecodes| activecodes
------------------------------------------------
1 |10 |2015-05-14 | 501 | 500
1 |10 |2015-08-12 | 501 | 500
1 |10 |2015-10-14 | 501 | 500
I tried like this:
select
a.pn, a.hospid, a.doj,
case when b.descripton <> 'inactive' then b.codes end activecodes,
case when b.descripton = 'inactive' then b.codes end inactivecodes
from
patient a
left join
patientrefs b on a.pn = b.pn and a.hospid = b.hospid
and a.doj between b.sdate and b.edate
But that query is not returning the expected result.
And I tried another way
select
a.pn, a.hospid, a.doj, b.codes as inactivecodes
from
patient a
left join
patientrefs b on a.pn = b.pn and a.hospid = b.hospid
and a.doj between b.sdate and b.edate
where
b.descripton = 'inactive'
select
a.pn, a.hospid, a.doj, b.codes as activecode
from
patient a
left
patientrefs b on a.pn = b.pn and a.hospid = b.hospid
and a.doj between b.sdate and b.edate
where
b.descripton <> 'inactive'
Here the individual queries return the expected result, but I need active and inactivecodes in the above expected output format.
Please tell me how to write query to get my expected result in SQL Server
You can do this using conditional aggregation:
SELECT
p.pn,
p.hospid,
p.doj,
inactivecodes = MAX(CASE WHEN pr.description = 'inactive' THEN pr.codes END),
activecodes = MAX(CASE WHEN pr.description = 'active' THEN pr.codes END)
FROM patient p
LEFT JOIN patientrefs pr
ON p.pn = pr.pn
AND p.hospid = pr.hospid
AND p.doj BETWEEN pr.sdate AND pr.edate
GROUP BY
p.pn, p.hospid, p.doj

Resources