SQL Server Pivoting based off of a column to be delimited - sql

I'm using SQL Server Management Studio and I have a table of survey results for project managers and I'm aggregating by question/score at the project manager RepID level:
SELECT Lower(A.RepID) as 'HHRepID'
, YEAR(A.ProjectEndDate) AS 'Year'
, MONTH(A.ProjectEndDate) AS 'Month'
, DATENAME(mm,A.ProjectEndDate) AS 'MonthName'
, SUM(CASE WHEN A.SatisfactionWithCommunication >= 4 THEN 1 ELSE 0 END) as 'AgreeStronglyAgreeCommunicationCount'
, COUNT(A.SatisfactionWithCommunication) as 'CommunicationCount'
, SUM(CASE WHEN A.InteractionConnectionWithClient >= 4 THEN 1 ELSE 0 END) as 'AgreeStronglyAgreeInteractionCount'
, COUNT(A.InteractionConnectionWithClient) as 'InteractionCount'
, SUM(CASE WHEN A.OverallSatisfactionWithEngagement >= 4 THEN 1 ELSE 0 END) as 'AgreeStronglyAgreeOverallSatisfactionCount'
, COUNT(A.OverallSatisfactionWithEngagement) as 'OverallSatisfactionCount'
, COUNT(A.ResponseID) as 'SurveysReturned'
, 'SalesOps' as 'Grouping'
FROM
SurveyData.dbo.SalesSurvey as A with(nolock)
WHERE
A.ResponseID IS NOT NULL AND A.IsExcludedFromReporting IS NULL
GROUP BY
YEAR(A.ProjectEndDate), MONTH(A.ProjectEndDate), DATENAME(mm,A.ProjectEndDate), A.RepID
ORDER BY
A.RepID
The output would look something like this:
Everything is great. Here's the problem. For each response for a project manager, there could be multiple Project Assistants. The project assistants for each project are aggregated (separated by ;) in one column:
What I need to do is pivot/delimit these results so each projectassistantID will be 1 row with the same grouped data as if it was a project manager. So for example, let's say that that row from the first screenshot had (HHRepID = jdoe) had 2 project assistants to it (call them Michael Matthews and Sarah Boyd): mmathews; sboyd. Via pivot/delimit, the output of the 2nd query would look like this:
In the actual table, it's just 1 record. But b/c there're multiple names in the ProjectAssistantID column, I need to pivot/delimit those out and essentially get the same results for each instance, just with ProjectAssistants rather than Project Managers.
I've been able to find some stuff on pivoting but this is pivoting based on delimiting values which adds an extra layer of complexity. It's entirely possible that there could be only 1 project assistant per project or as many as 6.

You can find several string split functions when you google for that, I will just assume this one which returns a table with one column named Item.
Then you can use cross apply as follows:
SELECT assist.Item as 'HHRepID'
, YEAR(A.ProjectEndDate) AS 'Year'
, MONTH(A.ProjectEndDate) AS 'Month'
, DATENAME(mm,A.ProjectEndDate) AS 'MonthName'
, SUM(CASE WHEN A.SatisfactionWithCommunication >= 4 THEN 1 ELSE 0 END) as 'AgreeStronglyAgreeCommunicationCount'
, COUNT(A.SatisfactionWithCommunication) as 'CommunicationCount'
, SUM(CASE WHEN A.InteractionConnectionWithClient >= 4 THEN 1 ELSE 0 END) as 'AgreeStronglyAgreeInteractionCount'
, COUNT(A.InteractionConnectionWithClient) as 'InteractionCount'
, SUM(CASE WHEN A.OverallSatisfactionWithEngagement >= 4 THEN 1 ELSE 0 END) as 'AgreeStronglyAgreeOverallSatisfactionCount'
, COUNT(A.OverallSatisfactionWithEngagement) as 'OverallSatisfactionCount'
, COUNT(A.ResponseID) as 'SurveysReturned'
, 'SalesOps' as 'Grouping'
FROM
SurveyData.dbo.SalesSurvey as A with(nolock)
CROSS APPLY Split(Lower(A.RepID), ';') AS assist
WHERE
A.ResponseID IS NOT NULL AND A.IsExcludedFromReporting IS NULL
GROUP BY
YEAR(A.ProjectEndDate), MONTH(A.ProjectEndDate), DATENAME(mm,A.ProjectEndDate), assist.Item
ORDER BY
assist.Item

For a strictly sql solution, you could use recursive cte to get your names split and then join it back to you query..
Try the below query
DECLARE #col varchar(20)='a;b;c;g;y;u;i;o;p;';
WITH CTE as
(
SELECT SUBSTRING(#COL,CHARINDEX(';',#COL)+1, LEN(#COL)-CHARINDEX(';', #COL)) col
,SUBSTRING(#COL,0, CHARINDEX(';',#COL)) Split_Names
, 1 i
union ALL
SELECT SUBSTRING(COL,CHARINDEX(';',COL)+1, LEN(COL)-CHARINDEX(';', COL)) col
,SUBSTRING(COL,0, CHARINDEX(';',COL)) Split_Names
, i+1
from CTE
WHERE CHARINDEX(';', col)>1
)
SELECT * FROM CTE
You will need to use your column in place of #col and change the code to refer to your table in the first union statement.
Hope this helps...
So you would do something like this...
WITH ProjectAssistants as
(
SELECT SUBSTRING(PROJECTASSISTANTID,CHARINDEX(';',PROJECTASSISTANTID)+1, LEN(PROJECTASSISTANTID)-CHARINDEX(';', PROJECTASSISTANTID)) col
,SUBSTRING(PROJECTASSISTANTID,0, CHARINDEX(';',PROJECTASSISTANTID)) Assistnames_Names
, ProjectManagerID
FROM YOUR_PROJECT_ASSISTANT_TABLE_NAME
union ALL
SELECT SUBSTRING(COL,CHARINDEX(';',COL)+1, LEN(COL)-CHARINDEX(';', COL)) col
,SUBSTRING(COL,0, CHARINDEX(';',COL)) Assistnames_Names
, ProjectManagerID
from ProjectAssistants
WHERE CHARINDEX(';', col)>1
)
SELECT Lower(A.RepID) as 'HHRepID'
, YEAR(A.ProjectEndDate) AS 'Year'
, MONTH(A.ProjectEndDate) AS 'Month'
, DATENAME(mm,A.ProjectEndDate) AS 'MonthName'
, SUM(CASE WHEN A.SatisfactionWithCommunication >= 4 THEN 1 ELSE 0 END) as 'AgreeStronglyAgreeCommunicationCount'
, COUNT(A.SatisfactionWithCommunication) as 'CommunicationCount'
, SUM(CASE WHEN A.InteractionConnectionWithClient >= 4 THEN 1 ELSE 0 END) as 'AgreeStronglyAgreeInteractionCount'
, COUNT(A.InteractionConnectionWithClient) as 'InteractionCount'
, SUM(CASE WHEN A.OverallSatisfactionWithEngagement >= 4 THEN 1 ELSE 0 END) as 'AgreeStronglyAgreeOverallSatisfactionCount'
, COUNT(A.OverallSatisfactionWithEngagement) as 'OverallSatisfactionCount'
, COUNT(A.ResponseID) as 'SurveysReturned'
, 'SalesOps' as 'Grouping'
FROM
SurveyData.dbo.SalesSurvey as A with(nolock)
LEFT JOIN ProjectAssistants as B
ON A.ProjectManagerID=B.ProjectManagerID
WHERE
A.ResponseID IS NOT NULL AND A.IsExcludedFromReporting IS NULL
GROUP BY
YEAR(A.ProjectEndDate), MONTH(A.ProjectEndDate), DATENAME(mm,A.ProjectEndDate), A.RepID
ORDER BY
A.RepID
Assumptions:
Your Surveydata table has a ProjectManagerid column
Left Join-ed the ProjectManager CTE considering you still want to show data when there arent any assistants.

Related

Trying to combine multiples of a key ID into single row, but with different values in columns

TSQL - SQL Sever
I'm building a report to very specific requirements. I'm trying to combine multiples of a key ID into single rows, but there's different values in some of the columns, so GROUP BY won't work.
SELECT count(tt.Person_ID) as CandCount, tt.Person_ID,
CASE e.EthnicSuperCategoryID WHEN CandCount > 1 THEN 10 ELSE e.EthnicSuperCategoryID END as EthnicSuperCategoryID,
CASE e.Ethnicity_Id WHEN 1 THEN 1 ELSE 0 END as Black ,
CASE e.Ethnicity_Id WHEN 2 THEN 1 ELSE 0 END as White ,
CASE e.Ethnicity_Id WHEN 3 THEN 1 ELSE 0 END as Asian,
etc
FROM T_1 TT
JOINS
WHERE
GROUP
Msg 102, Level 15, State 1, Line 4
Incorrect syntax near '>'.
Here's the results (without the first CASE). Note person 3 stated multiple ethnicities.
SELECT count(tt.Person_ID) as CandCount, tt.Person_ID,
CASE e.Ethnicity_Id WHEN 1 THEN 1 ELSE 0 END as Black ,
CASE e.Ethnicity_Id WHEN 2 THEN 1 ELSE 0 END as White ,
CASE e.Ethnicity_Id WHEN 3 THEN 1 ELSE 0 END as Asian,
etc
FROM T_1 TT
JOINS
WHERE
GROUP
That’s expected, but the goal would be to assign multiple ethnicities to Ethnicity_Id of 10 (multiple). I also want them grouped on a single line.
So the end result would look like this:
So my issue is two fold. If the candidate has more than 2 ethnicities, assign the records to Ethnicity_Id of 10. I also need duplicated person IDs grouped into a single row, while displaying all of the results of the columns.
This should bring your desired result:
SELECT Person_ID
, ISNULL(ID_Dummy,Ethnicity_ID) Ethnicity_ID
, MAX(Black) Black
, MAX(White) White
, MAX(Asian) Asian
FROM #T T
OUTER APPLY(SELECT MAX(10) FROM #T T2
WHERE T2.Person_ID = T.Person_ID
AND T2.Ethnicity_ID <> T.Ethnicity_ID
)EthnicityOverride(ID_Dummy)
GROUP BY Person_ID, ISNULL(ID_Dummy,Ethnicity_ID)
You want conditional aggregation. Your query is incomplete, but the idea is:
select
person_id,
sum(case ethnicity_id = 1 then 1 else 0 end) as black,
sum(case ethnicity_id = 2 then 1 else 0 end) as white,
sum(case ethnicity_id = 3 then 1 else 0 end) as asian
from ...
where ...
group by person_id
You might want max() instead of sum(). Also I did not get the logic for column the second column in the desired results - maybe that's just count(*).
This would be my approach
SELECT
person_id,
CASE WHEN flag = 1 THEN Ethnicity_Id ELSE 10 END AS Ethnicity_Id,
[1] as black,
[2] as white,
[3] as asian
FROM
(
SELECT
person_id,
Ethnicity_Id as columns,
1 as n,
MAX(Ethnicity_Id) over(PARTITION BY person_id) as Ethnicity_Id,
COUNT(Ethnicity_Id) over(PARTITION BY person_id) as flag
FROM
#example
) AS SourceTable
PIVOT
(
MAX(n) FOR columns IN ([1], [2], [3])
) AS PivotTable;
Pivot the Ethnicity_Id column into multiples columns, Using constant
1 to make it complain with your expected result.
Using Max(Ethnicity_Id) with Partition By to get the original
Ethnicity_Id
Using Count(Ethnicity_Id) to flag if a need to raplace Ethnicity_Id
with 10 bc there is more that 1 row for that person_id
If you need to add more Ethnicitys add the ids in ... IN ([1], [2], [3]) ... and in the select

How to Count Distinct on Case When?

I have been building up a query today and I have got stuck. I have two unique Ids that identify if and order is Internal or Web. I have been able to split this out so it does the count of how many times they appear but unfortunately it is not providing me with the intended result. From research I have tried creating a Count Distinct Case When statement to provide me with the results.
Please see below where I have broken down what it is doing and how I expect it to be.
Original data looks like:
Company Name Order Date Order Items Orders Value REF
-------------------------------------------------------------------------------
CompanyA 03/01/2019 Item1 Order1 170 INT1
CompanyA 03/01/2019 Item2 Order1 0 INT1
CompanyA 03/01/2019 Item3 Order2 160 WEB2
CompanyA 03/01/2019 Item4 Order2 0 WEB2
How I expect it to be:
Company Name Order Date Order Items Orders Value WEB INT
-----------------------------------------------------------------------------------------
CompanyA 03/01/2019 4 2 330 1 1
What currently comes out
Company Name Order Date Order Items Orders Value WEB INT
-----------------------------------------------------------------------------------------
CompanyA 03/01/2019 4 2 330 2 2
As you can see from my current result it is counting every line even though it is the same reference. Now it is not a hard and fast rule that it is always doubled up. This is why I think I need a Count Distinct Case When. Below is my query I am currently using. This pull from a Progress V10 ODBC that I connect through Excel. Unfortunately I do not have SSMS and Microsoft Query is just useless.
My Current SQL:
SELECT
Company_0.CoaCompanyName
, SopOrder_0.SooOrderDate
, Count(DISTINCT SopOrder_0.SooOrderNumber) AS 'Orders'
, SUM(CASE WHEN SopOrder_0.SooOrderNumber IS NOT NULL THEN 1 ELSE 0 END) AS 'Order Items'
, SUM(SopOrderItem_0.SoiValue) AS 'Order Value'
, SUM(CASE WHEN SopOrder_0.SooParentOrderReference LIKE 'INT%' THEN 1 ELSE 0 END) AS 'INT'
, SUM(CASE WHEN SopOrder_0.SooParentOrderReference LIKE 'WEB%' THEN 1 ELSE 0 END) AS 'WEB'
FROM
SBS.PUB.Company Company_0
, SBS.PUB.SopOrder SopOrder_0
, SBS.PUB.SopOrderItem SopOrderItem_0
WHERE
SopOrder_0.SopOrderID = SopOrderItem_0.SopOrderID
AND Company_0.CompanyID = SopOrder_0.CompanyID
AND SopOrder_0.SooOrderDate > '2019-01-01'
GROUP BY
Company_0.CoaCompanyName
, SopOrder_0.SooOrderDate
I have tried using the following line but it errors on me when importing:
, Count(DISTINCT CASE WHEN SopOrder_0.SooParentOrderReference LIKE 'INT%' THEN SopOrder_0.SooParentOrderReference ELSE 0 END) AS 'INT'
Just so know the error I get when importing at the moment is syntax error at or about "CASE WHEN sopOrder_0.SooParentOrderRefer" (10713)
Try removing the ELSE:
COUNT(DISTINCT CASE WHEN SopOrder_0.SooParentOrderReference LIKE 'INT%' THEN SopOrder_0.SooParentOrderReference END) AS num_int
You don't specify the error, but the problem is probably that the THEN is returning a string and the ELSE a number -- so there is an attempt to convert the string values to a number.
Also, learn to use proper, explicit, standard JOIN syntax. Simple rule: Never use commas in the FROM clause.
count distinct on the SooOrderNumber or the SooParentOrderReference, whichever makes more sense for you.
If you are COUNTing, you need to make NULL the thing that your are not counting. I prefer to include an else in the case because it is more consistent and complete.
, Count(DISTINCT CASE WHEN SopOrder_0.SooParentOrderReference LIKE 'INT%' THEN SopOrder_0.SooParentOrderReference ELSE null END) AS 'INT'
Gordon Linoff is correct regarding the source of your error, i.e. datatype mismatch between the case then value else value end. null removes (should remove) this ambiguity - I'd need to double check.
Editing my earlier answer...
Even though it looks, as you say, like count distinct is not supported in Pervasive PSQL, CTEs are supported. So you can do something like...
This is what you are trying to do but it is not supported...
with
dups as
(
select 1 as id, 'A' as col1 union all select 1, 'A' union all select 1, 'B' union all select 2, 'B'
)
select id
,count(distinct col1) as col_count
from dups
group by id;
Stick another CTE in the query to de-duplicate the data first. Then count as normal. That should work...
with
dups as
(
select 1 as id, 'A' as col1 union all select 1, 'A' union all select 1, 'B' union all select 2, 'B'
)
,de_dup as
(
select id
,col1
from dups
group by id
,col1
)
select id
,count(col1) as col_count
from de_dup
group by id;
These 2 versions should give the same result set.
There is always a way!!
I cannot explain the error you are getting. You are mistakenly using single quotes for alias names, but I don't actually think this is causing the error.
Anyway, I suggest you aggregate your order items per order first and only join then:
SELECT
c.coacompanyname
, so.sooorderdate
, COUNT(*) AS orders
, SUM(soi.itemcount) AS order_items
, SUM(soi.ordervalue) AS order_value
, COUNT(CASE WHEN so.sooparentorderreference LIKE 'INT%' THEN 1 END) AS int
, COUNT(CASE WHEN so.sooparentorderreference LIKE 'WEB%' THEN 1 END) AS web
FROM sbs.pub.company c
JOIN sbs.pub.soporder so ON so.companyid = c.companyid
JOIN
(
SELECT soporderid, COUNT(*) AS itemcount, SUM(soivalue) AS ordervalue
FROM sbs.pub.soporderitem
GROUP BY soporderid
) soi ON soi.soporderid = so.soporderid
GROUP BY c.coacompanyname, so.sooorderdate
ORDER BY c.coacompanyname, so.sooorderdate;

Joining two tables and while pivoting sql server 2008

I have two tables like so
Table1
SegmentNo PassingPotencies
1 8
2 10
Table2
BatchAvg Total TotalSegments TotalPassed
106.22 20 2 18
I want to join two tables with a simple login. If the Passingpotencies in table 1 is not equal to 10 then the segment is failed and vice versa. The final result should look something like this
TableResult
BatchAvg Total TotalSegments TotalPassed Segment1 Segment2
106.22 20 2 18 Fail Pass
Any help is greatly appreciated. Thanks.
With your current design, this is what you can achieve (something closest).
See a demo here http://sqlfiddle.com/#!3/e86f5/5
select distinct BATCHAVG,
TOTAL,
TOTALSEGMENTS,
TOTALPASSED,
SegmentNo,
case when PASSINGPOTENCIES <> 10 then 'Failed'
else 'Passed' end as SegmentStatus
from
(
select * from table2,table1
) X
Above query will results in
This will work in your actual scenario. See a Demo here: SQLFiddle
First, join both tables:
SELECT
T2.BATCHAVG
, T2.TOTAL
, T2.TOTALSEGMENTS
, T2.TOTALPASSED
, T1.SEGMENTNO
, (CASE WHEN T1.PASSINGPOTENCIES >= 10 THEN 'PASSED' ELSE 'FAILED' END) AS SEGMENT
INTO TABLE3
FROM TABLE1 T1
CROSS JOIN TABLE2 T2
Then, select this table like this. It's some kind of PIVOT:
SELECT
T.BATCHAVG
, T.TOTAL
, T.TOTALSEGMENTS
, T.TOTALPASSED
, MAX(T.SEGMENT1) AS SEGMENT1
, MAX(T.SEGMENT2) AS SEGMENT2
FROM (
SELECT
T1.BATCHAVG
, T1.TOTAL
, T1.TOTALSEGMENTS
, T1.TOTALPASSED
, (CASE WHEN T1.SEGMENTNO = '1' THEN T1.SEGMENT END) AS SEGMENT1
, (CASE WHEN T1.SEGMENTNO = '2' THEN T1.SEGMENT END) AS SEGMENT2
FROM TABLE3 T1
) T
GROUP BY
T.BATCHAVG
, T.TOTAL
, T.TOTALSEGMENTS
, T.TOTALPASSED

How do I fix my SQL statement?

I am using Microsoft SQL Server 2008. I have a table called "Tools" with the following columns [ID], [QtyOnHand], [SizeUS], [PlantID]. A typical small data sample may be something like this:
I would like to group the data by size and have a column with a sum of qty grouped by each plant that would look something like this:
I tried the following query but it's not correct.
SELECT
T.SizeUS
,(
SELECT SUM(T.QtyOnHand)
From Tools T1
WHERE T1.PlantID=1 AND T1.SizeUS=T.SizeUS
) As QtyPlant1
,(
SELECT SUM(T.QtyOnHand)
From Tools T2
WHERE T2.PlantID=2 AND T2.SizeUS=T.SizeUS
) As QtyPlant2
,(
SELECT SUM(T.QtyOnHand)
From Tools T5
WHERE T5.PlantID=5 AND T5.SizeUS=T.SizeUS
) As QtyPlant5
FROM
Tools T
GROUP BY
T.SizeUS;
Try this:
SELECT T.SizeUS,
, sum(case when T1.PlantID = 1 then 1 else 0 end) as QtyPlant1
, sum(case when T1.PlantID = 2 then 1 else 0 end) as QtyPlant2
, sum(case when T1.PlantID = 5 then 1 else 0 end) as QtyPlant5
FROM Tools T
Group By T.SizeUS

SQL, How to use temp table and join multiple table

Actually, I am using GridView at vb.net. this is the sql in the datasource.
SELECT SE.shipperID
, SE.sName
, SE.consigneeID
, SE.conName
, SE.agentID
, SE.aName
, SEDetail.bolId
, SE.masterBOLno
, SE.coloaderBOLno
, SE.NumOBOL
, SE.polName
, SE.podName
, CONVERT(VARCHAR ,SE.onboardDate ,111)
, SUM(SEDetail.quantity)
, SUM(SEDetail.totalSize)
, SUM(SEDetail.totalWeight)
FROM SE
INNER JOIN SEDetail ON SE.id = SEDetail.bolId
WHERE SE.id = SEDetail.bolId
GROUP BY SE.shipperID, SE.sName, SE.consigneeID, SE.conName, SE.agentID,
SE.aName, SE.masterBOLno, SE.coloaderBOLno, SE.NumOBOL, SEDetail.bolId,
SE.polName, SE.podName, SE.onboardDate
I want use the TemplateField to add 3 column , but GridView can not use multiple datasource
column 1. count(SEDetail.containerId) (where b.SEDetail.containerId between 1 and 5 )
column 2. count(SEDetail.containerId) (where b.SEDetail.containerId between 6 and 10 )
column 3. count(SEDetail.containerId) (where b.SEDetail.containerId = 11 )
So, how to use the temp table or another way to group up to one statement.
Not sure what flavor of SQL you are using but could you not add the following to your select statement:
SUM(CASE WHEN b.SEDetail.containerId BETWEEN 1 AND 5
THEN 1 ELSE 0 END) AS column1
SUM(CASE WHEN b.SEDetail.containerId BETWEEN 6 AND 10
THEN 1 ELSE 0 END) AS column2
SUM(CASE WHEN b.SEDetail.containerId = 11
THEN 1 ELSE 0 END) AS column3