Pivot on duplicate column name - sql

The query below is doing what I want, except a given accessruleId could have more than one businessArea. So, the current query is just grabbing the max one instead of all of them.
I get businessArea column to list all of the values perhaps comma delimited like the screenshot below.
I am using SQL Server 2016.
AccessRuleId EffectiveDate TermDate CreatedByUser CreateDateTime LastUpdatedUser LastUpdatedDateTime
1 2019-12-13 2020-01-22 User1 2019-12-11 User2 2019-12-12
RuleFieldId FieldName
1 BusinessArea
2 ProviderTaxId
3 VendorName
RuleOperationId AccessRuleId Fieldid Value
1 1 1 ABC
2 1 2 1234537890
3 1 3 Vendor1
30 1 4 XYZ
SELECT *
FROM (
SELECT ar.AccessRuleId
,ar.EffectiveDate
,ar.TermDate
,ar.CreatedByUser
,ar.LastUpdatedUser
,rf.FieldName
,ro.Value
FROM AccessRule.AccessRule ar
JOIN AccessRule.RuleOperation ro ON ar.AccessRuleId = ro.AccessRuleId
JOIN AccessRule.RuleField rf ON ro.FieldId = rf.RuleFieldId
) AS t
pivot(max([value]) FOR [FieldName] IN (
[BusinessArea]
,[ProviderTaxId]
,[VendorName]
)) AS pt

You need to merge the value for the field before the pivoting:
;WITH cte_raw(AccessRuleId,EffectiveDate,TermDate,CreatedByUser,LastUpdatedUser,FieldName,Value)
AS
(
SELECT ar.AccessRuleId
,ar.EffectiveDate
,ar.TermDate
,ar.CreatedByUser
,ar.LastUpdatedUser
,rf.FieldName
,ro.Value
FROM AccessRule.AccessRule ar
JOIN AccessRule.RuleOperation ro ON ar.AccessRuleId = ro.AccessRuleId
JOIN AccessRule.RuleField rf ON ro.FieldId = rf.RuleFieldId
),
cte_merged(AccessRuleId,EffectiveDate,TermDate,CreatedByUser,LastUpdatedUser,FieldName,Value)
AS
(
SELECT r.AccessRuleId,r.EffectiveDate,r.TermDate,r.CreatedByUser,r.LastUpdatedUser,r.FieldName,
STUFF((SELECT N','+v.Value
FROM cte_raw v
WHERE v.AccessRuleId=r.AccessRuleId
AND v.EffectiveDate=r.EffectiveDate
AND v.TermDate=r.TermDate
AND v.CreatedByUser=r.CreatedByUser
AND v.LastUpdatedUser=r.LastUpdatedUser
AND v.FieldName=r.FieldName
ORDER BY v.Value
FROM XML PATH (N''),TYPE).value('.','nvarchar(4000)')
,1,1,N'') AS Value
FROM cte_raw r
GROUP BY r.AccessRuleId
,r.EffectiveDate
,r.TermDate
,r.CreatedByUser
,r.LastUpdatedUser
,r.FieldName
)
SELECT *
FROM cte_merged t
pivot(max([value]) FOR [FieldName] IN (
[BusinessArea]
,[ProviderTaxId]
,[VendorName]
)) AS pt

I figured out how to do this in an a more simplified way.
SELECT ar.AccessRuleId
,ar.EffectiveDate
,ar.TermDate
,(select Value from AccessRule.RuleOperation ro where ro.AccessRuleId = ar.AccessRuleId and ro.FieldId = 2) as ProviderTaxId
,(select Value from AccessRule.RuleOperation ro where ro.AccessRuleId = ar.AccessRuleId and ro.FieldId = 3) as VendorName
,(SELECT STUFF( (SELECT ',' + value FROM AccessRule.RuleOperation ro where ro.AccessRuleId = ar.AccessRuleId and ro.FieldId = 1 FOR XML PATH('')),1, 1, '')) AS BusinessAreas
FROM AccessRule.AccessRule ar

Related

Cross Apply remove NULL values

I want to remove the row with NULL values on Crit_Value column. Appreciate your help.
Query:
SELECT DISTINCT GP.PRIORITY_CD, CODE_Val
FROM TCRITERIA_GROUP_PS GP
CROSS APPLY ( SELECT PV.CODE + ','
FROM TCRITERIA_GROUP_PS_VALUE GPV
--JOIN TCRITERIA_GROUP_PS GP ON GP.CRITERIA_GROUP_PS_ID = GPV.CRITERIA_GROUP_PS_ID
JOIN TCRITERIA_PS_VALUE PV ON PV.CRITERIA_PS_VALUE_ID = GPV.CRITERIA_PS_VALUE_ID
JOIN TCRITERIA_PS CP ON PV.CRITERIA_PS_ID = CP.CRITERIA_PS_ID
WHERE
PV.PARTNER_ID = 'JETSTAR'
AND GP.PARTNER_SYS_ID = 'JETSTAR1'
AND GP.ISO_CNTRY_CD = 'AU'
AND GP.CRITERIA_GROUP_PS_ID = GPV.CRITERIA_GROUP_PS_ID
FOR XML PATH('') ) D ( CODE_Val )
RESULTSET of the Query:
PRIORITY_CD CODE_Val
-------------------------
1 NULL
1 AU,AU,AUD,OW,0_1999,
2 NULL
2 AU,AU,AUD,OW,2000_99999,
3 NULL
3 AU,AU,AUD,RT,0_1999,
4 NULL
4 AU,AU,AUD,RT,2000_99999,
5 NULL
5 AU,ALL_EX,AUD,OW,
Try changing the correlation between the main table and the subquery, or add a where clause to filter out the NULLs
SELECT DISTINCT
GP.PRIORITY_CD
, D.CODE_Val
FROM TCRITERIA_GROUP_PS GP
CROSS APPLY (
SELECT
PV.CODE + ','
FROM TCRITERIA_GROUP_PS_VALUE GPV
JOIN TCRITERIA_PS_VALUE PV ON PV.CRITERIA_PS_VALUE_ID = GPV.CRITERIA_PS_VALUE_ID
JOIN TCRITERIA_PS CP ON PV.CRITERIA_PS_ID = CP.CRITERIA_PS_ID
WHERE PV.PARTNER_ID = 'JETSTAR'
AND GP.PARTNER_SYS_ID = 'JETSTAR1'
AND GP.ISO_CNTRY_CD = 'AU'
AND GP.PRIORITY_CD = GPV.CRITERIA_GROUP_PS_ID -- changed
FOR xml PATH ('')
) D (CODE_Val)
-- WHERE D.CODE_Val IS NOT NULL

Combining multiple rows in TSQL query

I have a table with data like this
Road Item Response added_on
1 82 Yes 7/11/16
1 83 Yes 7/11/16
1 84 Yes 7/11/16
2 82 Yes 8/11/16
2 83 No 8/11/16
2 85 Yes 8/11/16
This reflects an assessment of a road where 'item' is things being assessed.
Some items will always be done during an assessment (82, 83) where others are optional (84, 85).
I want to return something that combines all of the assessment results for a road/date, returning null if that item was not assessed. And also only returning last month's results. For example
Road 82 83 84 85 added_on
1 Yes Yes Yes 7/11/16
2 Yes No Yes 8/11/16
I have tried a multiple self joins like this but it's returning nothing.
FROM assess AS A
JOIN assess AS B
ON A.road = B.road AND a.added_on = B.added on
JOIN assess AS C
ON A.road = C.road AND a.added_on = C.added on
JOIN assess AS D
ON A.road = D.road AND a.added_on = D.added on
WHERE A.item = '81'
AND B.item = '82'
AND (C.item = '83' OR C.item IS NULL)
AND (D.item = '84' OR D.item IS NULL)
AND datepart(month,A.added_on) = datepart(month,getdate()) -1
To clarify,
-no road is assessed more than once a day
-each item is only assessed once, and sometimes is NULL i.e. not applicable
-multiple roads are assessed each day
-this table has other assessments but we aren't worried about those.
Any ideas? Using SQL server 2008. Thanks.
Assuming you need to go Dynamic
Declare #SQL varchar(max)
Select #SQL = Stuff((Select Distinct ',' + QuoteName(Item) From YourTable Order By 1 For XML Path('')),1,1,'')
Select #SQL = 'Select [Road],' + #SQL + ',[added_on]
From YourTable
Pivot (max(Response) For Item in (' + #SQL + ') ) p'
Exec(#SQL);
Returns
EDIT - The SQL Generated is as follows. (just in case you can't go
dynamic)
Select [Road],[82],[83],[84],[85],[added_on]
From YourTable
Pivot (max(Response) For Item in ([82],[83],[84],[85]) ) p
Another way of achieving this is less elegant, but uses basic operations if you don't want to use pivot.
Load up test data
create table #assess ( road int, item varchar(10), response varchar(3), added_on date )
insert #assess( road, item, response, added_on )
values
(1, '82', 'Yes', '2016-07-11' )
, (1, '83', 'Yes', '2016-07-11' )
, (1, '84', 'Yes', '2016-07-11' )
, (2, '82', 'Yes', '2016-08-11' )
, (2, '83', 'No', '2016-08-11' )
, (2, '85', 'Yes', '2016-08-11' )
Process the data
-- Get every possible `item`
select distinct item into #items from #assess
-- Ensure every road/added_on combination has all possible values of `item`
-- If the combination does not exist in original data, leave `response` as blank
select road, added_on, i.item, cast('' as varchar(3)) as response into #assess2
from #items as i cross join #assess AS A
group by road, added_on, i.item
update a set response = b.response
from #assess2 a inner join #assess b on A.road = B.road AND a.added_on = B.added_on AND a.item = b.item
-- Join table to itself 4 times - inner join if `item` must exist or left join if `item` is optional
select a.road, a.added_on, a.response as '82', b.response as '83', c.response as '84', d.response as '85'
FROM #assess2 AS A
INNER JOIN #assess2 AS B ON A.road = B.road AND a.added_on = B.added_on
LEFT JOIN #assess2 AS C ON A.road = C.road AND a.added_on = C.added_on
LEFT JOIN #assess2 AS D ON A.road = D.road AND a.added_on = D.added_on
WHERE A.item = '82'
AND B.item = '83'
AND (C.item = '84' OR C.item IS NULL)
AND (D.item = '85' OR D.item IS NULL)
--AND datepart(month,A.added_on) = datepart(month,getdate()) -1
The resultset is:
road added_on 82 83 84 85
1 2016-07-11 Yes Yes Yes
2 2016-08-11 Yes No Yes
I would do this using conditional aggregation:
select road,
max(case when item = 82 then response end) as response_82,
max(case when item = 83 then response end) as response_83,
max(case when item = 84 then response end) as response_84,
max(case when item = 85 then response end) as response_85,
added_on
from t
group by road, added_on
order by road;
For the month component, you can add a where clause. One method is:
where year(date_added) * 12 + month(date_added) = year(getdate())*12 + month(getdate()) - 1
Or, you can use logic like this:
where date_added < dateadd(day, 1 - day(getdate()), cast(getdate() as date)) and
date_added >= dateadd(month, -1, dateadd(day, 1 - day(getdate()), cast(getdate() as date)))
The second looks more complicated but it is sargable, meaning that an index on date_added can be used (if one is available).

Major issues with a query

I have a query
SELECT
ZEML.ICC_CODE AS ICC_CODE
,SUM(CS.TOT_HOURS) AS TOT_HOURS
,SUM(CS.NUM_INCIDENT_ALL) AS NUM_INCIDENTS
,(VALUE(FLOAT(SUM(CS.NUM_INCIDENT_ALL)) * 200000 / SUM(TOT_HOURS)
,0)) AS INC_RATE
FROM TR.CLAIMS_SUMM CS
INNER JOIN TR.LOCATION_MASTER LM
ON LM.LOCATION = CS.LOCATION
AND CS.LOCATION < '900'
LEFT JOIN TR.LOCATION_ASSIGNMENTS DISTRICT
ON DISTRICT.LOCATION = LM.LOCATION
AND DISTRICT.ASSIGNMENT_TYPE = 'District'
LEFT JOIN TR.LOCATION_ASSIGNMENTS TERRITORY
ON TERRITORY.LOCATION = LM.LOCATION
AND TERRITORY.ASSIGNMENT_TYPE = 'Territory'
LEFT JOIN TR.EMPL_CLAIMS ZEML
ON CS.LOCATION = ZEML.LOCATION
AND ZEML.TYPE = 'WC'
AND ZEML.STATUS <> 'V'
AND ZEML.CLAIM_ACTION NOT IN ('D','F','I','H')
WHERE CS.DW_DATE BETWEEN '01/01/2014'
AND '05/31/2014'
AND (MONTH(ZEML.DATE_OF_INCIDENT) = MONTH(CS.DW_DATE)
AND YEAR(ZEML.DATE_OF_INCIDENT) = YEAR(CS.DW_DATE))
GROUP BY ZEML.ICC_CODE
UNION
SELECT
'OTHER' AS ICC_CODE
, 0 AS TOT_HOURS
, 0 AS NUM_INCIDENTS
, 0 AS INC_RATE
FROM SYSIBM.SYSDUMMY1
WHERE 1 = 1
ORDER BY 1
in my union where I made an other I want to select everything else from the tr.empl_claims table and store it in the other from the union because this is what I have many other ICC codes without incidents on them and I am doing calculations on our incident rate and hourse based off of all the data but my query right now is only selecting the ones that currently is having incidents which is throwing off my calculations.
From your use of FROM SYSIBM.SYSDUMMY1 I believe you are using DB2 database. If yes, you can use CTE (common table expression) to achieve the desired result like
WITH cte1 AS
(
SELECT
ZEML.ICC_CODE AS ICC_CODE
,SUM(CS.TOT_HOURS) AS TOT_HOURS
,SUM(CS.NUM_INCIDENT_ALL) AS NUM_INCIDENTS
,(VALUE(FLOAT(SUM(CS.NUM_INCIDENT_ALL)) * 200000 / SUM(TOT_HOURS)
,0)) AS INC_RATE
FROM TR.CLAIMS_SUMM CS
... <rest of the code> ...
)
select * from cte1
UNION ALL
SELECT
ICC_CODE
, 0 AS TOT_HOURS
, 0 AS NUM_INCIDENTS
, 0 AS INC_RATE
FROM TR.EMPL_CLAIMS
WHERE ICC_CODE NOT IN
(
SELECT distinct ICC_CODE
FROM cte1
)
ORDER BY 1
SideNote: You are joining the same table LOCATION_ASSIGNMENTS twice (as below) which is not needed.
LEFT JOIN TR.LOCATION_ASSIGNMENTS DISTRICT
ON DISTRICT.LOCATION = LM.LOCATION
AND DISTRICT.ASSIGNMENT_TYPE = 'District'
LEFT JOIN TR.LOCATION_ASSIGNMENTS TERRITORY
ON TERRITORY.LOCATION = LM.LOCATION
AND TERRITORY.ASSIGNMENT_TYPE = 'Territory'
This can be transformed to below using a IN operator
LEFT JOIN TR.LOCATION_ASSIGNMENTS DISTRICT
ON DISTRICT.LOCATION = LM.LOCATION
AND DISTRICT.ASSIGNMENT_TYPE IN ('District', 'Territory')
See more about Common Table Expression in DB2 Here.
Hope this helps.

SQL Union Query

SELECT pv.PropertyID, COUNT(pv.VisitID) AS InitialVisit
FROM tblPAppointments pa INNER JOIN tblPropertyVisit pv ON pv.AppID = pa.AppID
WHERE pv.Status = 0
GROUP BY pv.PropertyID
UNION ALL
SELECT jv.PropertyID, COUNT(jv.JobVistID) AS JobVisit
FROM tblPAppointments pa INNER JOIN tblJobVisits jv ON jv.AppID = pa.AppID
WHERE jv.VisitStatus = 1
GROUP BY jv.PropertyID
I need to get InitialVisit count and JobVisit count in two separate columns.above query returns just two columns (PropertyID,InitialVisit).
Use a NULL as a placeholder for the column that there won't be any output for:
SELECT pv.PropertyID,
COUNT(pv.VisitID) AS InitialVisit,
NULL AS jobvisit
FROM tblPAppointments pa
JOIN tblPropertyVisit pv ON pv.AppID = pa.AppID
WHERE pv.Status = 0
GROUP BY pv.PropertyID
UNION ALL
SELECT jv.PropertyID,
NULL AS initialvisit,
COUNT(jv.JobVistID) AS JobVisit
FROM tblPAppointments pa
JOIN tblJobVisits jv ON jv.AppID = pa.AppID
WHERE jv.VisitStatus = 1
GROUP BY jv.PropertyID
This will return three columns. The column alias is necessary in the first query, but not in the second -- I aliased both to make it clear what is happening.
Be aware that using NULL like this in SQL Server will require you to use CAST/CONVERT on the NULL for data types other than INT because SQL Server defaults the NULL to an INT data type (as odd as that is).
An alternate query that doesn't use UNION:
SELECT x.propertyid,
COUNT(y.visitid) AS initialvisit,
COUNT(z.jobvisitid) AS jobvisit
FROM (SELECT pv.propertyid
FROM TBLPROPERTYVISIT pv
WHERE EXISTS (SELECT NULL
FROM TBLAPPOINTMENTS a
WHERE a.appid = pv.appid)
UNION
SELECT jv.propertyid
FROM TBLJOBVISIT jv
WHERE EXISTS (SELECT NULL
FROM TBLAPPOINTMENTS a
WHERE a.appid = jv.appid)) x
LEFT JOIN TBLPROPERTYVISIT y ON y.propertyid = x.propertyid
LEFT JOIN TBLJOBVISIT z ON z.propertyid = x.propertyid
GROUP BY x.propertyid
No need for a UNION at all. And you don't use tblPAppointments either
Edited to allow for no rows in one of the tables. Still one row output though
SELECT
ISNULL(pv2.PropertyID, jv2.PropertyID),
ISNULL(pv2.InitialVisit, 0),
ISNULL(jv2.JobVisit, 0)
FROM
(
SELECT pv.PropertyID, COUNT(pv.VisitID) AS InitialVisit
FROM tblPropertyVisit pv
WHERE pv.Status = 0
GROUP BY pv.PropertyID
) pv2
FULL OUTER JOIN
(
SELECT jv.PropertyID, COUNT(jv.JobVistID) AS JobVisit
FROM tblJobVisits jv
WHERE jv.VisitStatus = 1
GROUP BY jv.PropertyID
) jv2 ON pv2.PropertyID = jv2.PropertyID

How to Optimize this SQL Query?

I have 3 tables:
CRSTasks (ID,parentID)
CRSTaskReceivers (ID,tskID,receiverID)
UserNames (id,name)
...relation between CRSTasks and CRSTaskReceivers one-to-many
between UserNames and CRSTaskReceivers one-to-one
tasks
ID parent
1 null
10 1
50 1
taskReceivers
id taskID receiverID
1 1 4(john)
1 10 2(mike)
1 50 3(brand)
I need result like that:
taskid Receivers
-------------------
1 jone,mike,brand
ONLY FOR PARENT TASKS IT WILL CONCATE RECEIVERS
SQL Server 2005+:
SELECT t.id AS taskid,
STUFF((SELECT ','+ x.name
FROM (SELECT COALESCE(pu.[ArabicName], aut.Name) AS name
FROM CRSTaskReceivers tr
JOIN AD_USER_TBL aut ON aut.id = tr.receiverid
LEFT JOIN PORTAL_USERS pu ON pu.id = aut.id
WHERE tr.crstaskid = t.id
AND tr.receivertype = 1
UNION
SELECT agt.name
FROM CRSTaskReceiver tr
JOIN AD_GROUP_TBL sgt ON agt.id = tr.receiverid
WHERE tr.receivertype = 3
AND tr.crstaskid = t.id) x
FOR XML PATH('')), 1, 1, '')
FROM CRSTasks t
Don't need the function.
Besides the odd string concatenation going on it sure looks like all that could be done in one query instead of four. It's perfectly fine to have more than one criteria in a join. Something along:
FROM CRSTaskReceiver
INNER JOIN CRSTask
ON CRSTaskReceiver.CRSTaskID = CRSTask.ID
INNER JOIN CRS_BuiltinGroup
ON CRSTaskReceiver.ReceiverID = CRS_BuiltinGroup.ID AND CRSTaskReceiver.ReceiverType = 4
WHERE CRSTask.ParentTask = #TaskID
Also the below part of the function seems to do absolutely nothing. What is it meant to do?
DECLARE #tmpLength INT
SET #tmpLength = 0
SET #tmpLength = LEN(#tmp)
IF #tmpLength > 0
BEGIN
SET #tmp = SUBSTRING(#tmp, 0, #tmpLength)
END