Unified records for database query with Sql - sql

Basic need is if a record has an Attribute of "Urgent", then the attributevalue should be displayed in the Urgent column. If the record has an attribute value of "closed", then the attributevalue must be displayed in the "Closed" column.
I have a query below. My problem is that among the results I am getting back, there are two records with the same RequesterID (one with a valid value in "Urgent" column and one with a value in "Closed" colum)
My problem is that I need these two particular records to be displayed as one record.
Any ideas?
SELECT DISTINCT
r.RequesterID,
sr.ModifiedDate,
p.FirstName + ' ' + p.LastName AS RequesterName,
CASE
WHEN sa.Attribute = 'Urgent' THEN sa.AttributeValue
ELSE NULL
END AS Urgent,
CASE
WHEN sa.Attribute = 'Closed' THEN sa.AttributeValue
ELSE NULL
END AS Closed
FROM
Requester AS r
INNER JOIN SubRequester AS sr
ON r.RequesterID = sr.RequesterID
INNER JOIN SubRequesterAttribute AS sa
ON sr.SubRequesterID = sa.SubRequesterID
CROSS JOIN Personnel AS p
WHERE
(r.UserID = p.ContractorID
OR r.UserID = p.EmployeeID)
AND
(sa.Attribute IN ('Urgent', 'Closed'))
GROUP BY r.RequesterID, sr.ModifiedDate, p.FirstName, p.LastName,
sa.Attribute, sa.AttributeValue

You will need to join to your sub requester attribute table to the query twice. One with the attribute of Urgent and one with the attribute of Close.
You will need to LEFT join to these for the instances where they may be null and then reference each of the tables in your SELECT to show the relevent attribute.
I also wouldn't reccomend the cross join. You should perform your "OR" join on the personnel table in the FROM clause rather than doing a cross join and filtering in the WHERE clause.
EDIT: Sorry, my first response was a bit rushed. Have now had a chance to look further. Due to the sub requester and the sub requester attribute both being duplicates you need to split them both up into a subquery. Also, your modified date could be different for both values. So i've doubled that up. This is completely untested, and by no means the "optimum" solution. It's quite tricky to write the query without the actual database to check against. Hopefully it will explain what I meant though.
SELECT
r.RequesterID,
p.FirstName + ' ' + p.LastName AS RequesterName,
sra1.ModifiedDate as UrgentModifiedDate,
sra1.AttributeValue as Urgent,
sra2.ModifiedDate as ClosedModifiedDate,
sra2.AttributeValue as Closed
FROM
Personnel AS p
INNER JOIN
Requester AS r
ON
(
r.UserID = p.ContractorID
OR
r.UserID = p.EmployeeID
)
LEFT OUTER JOIN
(
SELECT
sr1.RequesterID,
sr1.ModifiedDate,
sa1.Attribute,
sa1.AttributeValue
FROM
SubRequester AS sr1
INNER JOIN
SubRequesterAttribute AS sa1
ON
sr1.SubRequesterID = sa1.SubRequesterID
AND
sa1.Attribute = 'Urgent'
) sra1
ON
sra1.RequesterID = r.RequesterID
LEFT OUTER JOIN
(
SELECT
sr2.RequesterID,
sr2.ModifiedDate,
sa2.Attribute,
sa2.AttributeValue
FROM
SubRequester AS sr2
INNER JOIN
SubRequesterAttribute AS sa2
ON
sr2.SubRequesterID = sa2.SubRequesterID
AND
sa2.Attribute = 'Closed'
) sra1
ON
sra2.RequesterID = r.RequesterID
SECOND EDIT: My last edit was that there were multiple SubRequesters as well as multiple Attribute, from your last comment you want to show all SubRequesters and the two relevent attributes? You can achieve this as follows.
SELECT
r.RequesterID,
p.FirstName + ' ' + p.LastName AS RequesterName,
sr.ModifiedDate,
sa1.AttributeValue as Urgent,
sa2.AttributeValue as Closed
FROM
Personnel AS p
INNER JOIN
Requester AS r
ON
(
r.UserID = p.ContractorID
OR
r.UserID = p.EmployeeID
)
INNER JOI N
SubRequester as sr
ON
sr.RequesterID = r.RequesterID
LEFT OUTER JOIN
SubRequesterAttribute AS sa1
ON
sa1.SubRequesterID = sr.SubRequesterID
AND
sa1.Attribute = 'Urgent'
LEFT OUTER JOIN
SubRequesterAttribute AS sa2
ON
sa2.SubRequesterID = sr.SubRequesterID
AND
sa2.Attribute = 'Closed'

Generally, if you have multiple rows and want to collapse them into one, GROUP BY is the basic tool to achieve that. It looks like you tried to go in that direction but didn't quite get there. What you want to do is group by the expressions that are duplicated between the rows, and apply group functions to the other expressions that will eliminate the NULL values. I used MIN in the example below but you could just as easily use MAX; the point is that since at most one of the rows will have a value for that expression, that value is both the minimum and the maximum.
SELECT
r.RequesterID,
sr.ModifiedDate,
p.FirstName + ' ' + p.LastName AS RequesterName,
MIN(
CASE
WHEN sa.Attribute = 'Urgent' THEN sa.AttributeValue
ELSE NULL
END
) AS Urgent,
MIN(
CASE
WHEN sa.Attribute = 'Closed' THEN sa.AttributeValue
ELSE NULL
END
) AS Closed
FROM
Requester AS r
INNER JOIN SubRequester AS sr
ON r.RequesterID = sr.RequesterID
INNER JOIN SubRequesterAttribute AS sa
ON sr.SubRequesterID = sa.SubRequesterID
CROSS JOIN Personnel AS p
WHERE
(r.UserID = p.ContractorID
OR r.UserID = p.EmployeeID)
AND
(sa.Attribute IN ('Urgent', 'Closed'))
GROUP BY r.RequesterID, sr.ModifiedDate, p.FirstName + ' ' + p.LastName

Related

A proper way to filter for a status when the status is explicitly filtered out with a sql query

I have a sql query that is explicitly looking for "active" items (108) and filtering out "inactive" items (117).
Although, I have found that when I try to run this query and search for inactive items, of course, nothing comes back because we have stated WHERE COALESCE(i.item_status_id, 108) <> 117
This is the original sql:
SELECT st.scope_type,
COALESCE(Sum(0 + 1), 0)
FROM scope_inventory_view i
INNER JOIN security y
ON y.security_id = i.update_user
INNER JOIN product_info vp
ON vp.product_id = i.product_id
INNER JOIN hl_vendorproduct hvp
ON hvp.product_id = i.product_id
INNER JOIN hl_scopetype st
ON st.scope_type_id = i.scope_type_id
INNER JOIN hl_itemstatus ist
ON ist.item_status_id = COALESCE(i.item_status_id, 108)
LEFT JOIN hl_owner o
ON o.owner_id = i.owner_id
LEFT JOIN hl_location l
ON l.location_id = i.location_id
LEFT JOIN hl_locationtype lt
ON lt.location_type_id = l.location_type_id
LEFT JOIN hl_sterilizerload sl
ON sl.load_id = i.load_id
LEFT JOIN hl_facility f
ON f.facility_id = l.facility_id
LEFT JOIN clientoption co
ON co.parent_option_id = 271
AND ( co.facility_id = f.facility_id
OR co.facility_id IS NULL )
LEFT JOIN clientoption clo
ON clo.option_id = 271
AND ( clo.facility_id = f.facility_id
OR clo.facility_id IS NULL ),
gl_client
WHERE COALESCE(i.item_status_id, 108) <> 117
GROUP BY st.scope_type WITH rollup
is there a better way to write this so that status 117 items are filtered out UNLESS the user requests them? If they do the sql query ends like so: (which gives no results)
WHERE COALESCE(i.item_status_id, 108) <> 117
AND ( ist.item_status_id IN( '117' ) )
GROUP BY st.scope_type WITH rollup
Any help is appreciated!
Your query has a lot of tables and joins which are not used in the select or the where conditions. Maybe you've simplified it for the question? If not eliminate the redundant tables in the join.
For your question of including or excluding 117 I suggest that you add the following column in your SELECT, and the same expression in the GROUP BY .
CASE WHEN 117 THEN '117' ELSE 'others' END AS type_group
The end using will be presented with the 2 figures for each scope_type which can be added together whether in code or by the person using the data if they wish to include type 117.

Search table twice in SQL Server

I've come across this issue several times now and I am not sure how to solve it. I have a query that needs to access the same table twice to pick out two different persons: a consultant and the customer contact.
Consultant + Customer query looks like this
select
CRM7.contact.contact_id as CustomerID,
CRM7.contact.name + ' ' + CRM7.person.firstname + ' ' + CRM7.person.LASTNAME as CustomerName,
CRM7.person.firstname + ' ' + CRM7.person.lastname as ConsultantName,
CRM7.udcontactsmall.long08 as WriteLic,
CRM7.udcontactsmall.long17 as ReadLic,
CRM7.udcontactsmall.long09 as HasMaint,
CRM7.udlist.name as BCVer
from
CRM7.contact,
CRM7.udcontactsmall
left join
CRM7.associate on CRM7.udcontactsmall.long11 = CRM7.associate.associate_id
left join
CRM7.person on CRM7.associate.person_id = CRM7.person.person_id
left join
CRM7.udlist on CRM7.udcontactsmall.long07 = CRM7.udlist.UDList_id
where
CRM7.contact.category_idx = '2' and
CRM7.contact.userdef_id = CRM7.udcontactsmall.udcontactsmall_id
order by
CRM7.contact.name
And the one that gets the contact for each customer looks like this
SELECT
C.NAME+' Kontakt '+ P.FIRSTNAME+' '+P.LASTNAME AS CONTACT
FROM
CRM7.PERSON P
LEFT OUTER JOIN
CRM7.CONTACT C ON P.CONTACT_ID = C.CONTACT_ID
WHERE
C.CATEGORY_IDX IN ('2','5')
AND P.RETIRED = 0
ORDER BY
C.NAME, P.LASTNAME, P.FIRSTNAME
Dataset:
http://oi61.tinypic.com/2j66cjq.jpg
How do I get a query which returns both the consultant for the customer and the customer contact?
This should serve your purpose:
select
CRM7.contact.contact_id as CustomerID
,CRM7.contact.name+' '+CRM7.person.firstname+' '+CRM7.person.LASTNAME as CustomerName
,CRM7.person.firstname+' '+CRM7.person.lastname as ConsultantName
,CRM7.udcontactsmall.long08 as WriteLic
,CRM7.udcontactsmall.long17 as ReadLic
,CRM7.udcontactsmall.long09 as HasMaint
,CRM7.udlist.name as BCVer
,C.NAME+' Kontakt '+ CRM7.person.FIRSTNAME+' '+ CRM7.person.LASTNAME AS CONTACT
from
CRM7.contact
,CRM7.udcontactsmall
left join CRM7.associate on CRM7.udcontactsmall.long11=CRM7.associate.associate_id
left join CRM7.person on CRM7.associate.person_id=CRM7.person.person_id
left join CRM7.udlist on CRM7.udcontactsmall.long07=CRM7.udlist.UDList_id
LEFT OUTER JOIN (SELECT CONTACT_ID,NAME FROM CRM7.CONTACT WHERE CATEGORY_IDX in ('2','5') AND P.RETIRED=0) C ON CRM7.person.CONTACT_ID=C.CONTACT_ID
where
CRM7.contact.category_idx='2'
and CRM7.contact.userdef_id=CRM7.udcontactsmall.udcontactsmall_id
order by
CRM7.contact.name
I have used a join on contact table from the second query and added it into the first one to get customer contacts:
LEFT OUTER JOIN
(SELECT CONTACT_ID, NAME
FROM CRM7.CONTACT
WHERE CATEGORY_IDX IN ('2','5') AND P.RETIRED = 0) C ON CRM7.person.CONTACT_ID = C.CONTACT_ID

SQL Cross Tab Query

Need help figuring out how to do a cross-tabulated report within one query. There are 3-4 tables involved but the users table may not need to be included in the query since we just need a count.
I have put together a screenshot of the table schema and data as an example which can be seen below:
What I need it to return is a query result that looks like:
So I can make a report that looks like:
I've tried to do cursor loops as it's the only way I can do it with my basic knowledge, but it's way too slow.
One particular report I'm trying to generate contains 32 rows and 64 columns with about 70,000 answers, so it's all about the performance of getting it down to one query and fast as possible.
I understand this may depend on indexes and so on but if someone could help me figure out how I could get this done in 1 query (with multiple joins?), that would be awesome!
Thanks!
SELECT MIN(ro.OptionText) RowOptionText, MIN(co.OptionText) RowOptionText, COUNT(ca.AnswerID) AnswerCount
FROM tblQuestions rq
CROSS JOIN tblQuestions cq
JOIN tblOptions ro ON rq.QuestionID = ro.QuestionID
JOIN tblOptions co ON cq.QuestionID = co.QuestionID
LEFT JOIN tblAnswers ra ON ra.OptionID = ro.OptionID
LEFT JOIN tblAnswers ca ON ca.OptionID = co.OptionID AND ca.UserID = ra.UserID
WHERE rq.questionText = 'Gender'
AND cq.questionText = 'How happy are you?'
GROUP BY ro.OptionID, co.OptionID
ORDER BY ro.OptionID, co.OptionID
This should be at least close to what you asked for. Turning this into a pivot will require dynamic SQL as SQL Server requires you to specify the actual value that will be pivoted into a column.
We cross join the questions and limit the results from each of those question references to the single question for the row values and column values respectively. Then we join the option values to the respective question reference. We use LEFT JOIN for the answers in case the user didn't respond to all of the questions. And we join the answers by UserID so that we match the row question and column question for each user. The MIN on the option text is because we grouped and ordered by OptionID to match your sequencing shown.
EDIT: Here's a SQLFiddle
For what it's worth, your query is complicated because you are using the Entity-Attribute-Value design pattern. Quite a few SQL Server experts consider that pattern to be problematic and to be avoided if possible. For instance see https://www.simple-talk.com/sql/t-sql-programming/avoiding-the-eav-of-destruction/.
EDIT 2: Since you accepted my answer, here's the dynamic SQL pivot solution :) SQLFiddle
DECLARE #SqlCmd NVARCHAR(MAX)
SELECT #SqlCmd = N'SELECT RowOptionText, ' + STUFF(
(SELECT ', ' + QUOTENAME(o.OptionID) + ' AS ' + QUOTENAME(o.OptionText)
FROM tblOptions o
WHERE o.QuestionID = cq.QuestionID
FOR XML PATH ('')), 1, 2, '') + ', RowTotal AS [Row Total]
FROM (
SELECT ro.OptionID RowOptionID, ro.OptionText RowOptionText, co.OptionID ColOptionID,
ca.UserID, COUNT(ca.UserID) OVER (PARTITION BY ra.OptionID) AS RowTotal
FROM tblOptions ro
JOIN tblOptions co ON ro.QuestionID = ' + CAST(rq.QuestionID AS VARCHAR(10)) +
' AND co.QuestionID = ' + CAST(cq.QuestionID AS VARCHAR(10)) + '
LEFT JOIN tblAnswers ra ON ra.OptionID = ro.OptionID
LEFT JOIN tblAnswers ca ON ca.OptionID = co.OptionID AND ca.UserID = ra.UserID
UNION ALL
SELECT 999999, ''Column Total'' RowOptionText, co.OptionID ColOptionID,
ca.UserID, COUNT(ca.UserID) OVER () AS RowTotal
FROM tblOptions ro
JOIN tblOptions co ON ro.QuestionID = ' + CAST(rq.QuestionID AS VARCHAR(10)) +
' AND co.QuestionID = ' + CAST(cq.QuestionID AS VARCHAR(10)) + '
LEFT JOIN tblAnswers ra ON ra.OptionID = ro.OptionID
LEFT JOIN tblAnswers ca ON ca.OptionID = co.OptionID AND ca.UserID = ra.UserID
) t
PIVOT (COUNT(UserID) FOR ColOptionID IN (' + STUFF(
(SELECT ', ' + QUOTENAME(o.OptionID)
FROM tblOptions o
WHERE o.QuestionID = cq.QuestionID
FOR XML PATH ('')), 1, 2, '') + ')) p
ORDER BY RowOptionID'
FROM tblQuestions rq
CROSS JOIN tblQuestions cq
WHERE rq.questionText = 'Gender'
AND cq.questionText = 'How happy are you?'
EXEC sp_executesql #SqlCmd
I think I see the problem. I know you can't modify the schema, but you need a conceptual table for the crosstab information such as which questionID is the rowHeader and which is the colHeader. You can create it in an external data source and join with the existing source or simply hard-code the table values in your sql.
you need to have 2 instances of the question/option/answer relations, one for each rowHeader and colHeader for each crosstab. Those 2 relations are joined by the userID.
this version has your outer joins:
sqlFiddle
this version doesn't have the crossTab table, just the row and col questionIDs hard-coded:
sqlFiddleNoTbl
The following piece of mess works with no hard-coded values but fails to show the rows where the count is 0.
This might however still work for your report.
;with stepone as(
SELECT
RANK() OVER(PARTITION BY a.UserId ORDER BY o.QuestionID) AS [temprank]
, o.QuestionID AS [QID1]
, o.OptionID AS [OID1]
, same.QuestionID
, same.OptionID
, a.UserId AS [IDUser]
, same.UserId
FROM
tblAnswers a
INNER JOIN
tblOptions o
ON a.OptionID = o.OptionID
INNER JOIN
tblQuestions q
ON o.QuestionID = q.QuestionID
INNER JOIN
(
SELECT
a.AnswerID
, a.OptionID
, a.UserId
, o.QuestionID
FROM
tblAnswers a
INNER JOIN
tblOptions o
ON a.OptionID = o.OptionID
) same
ON a.UserId = same.UserId AND a.AnswerID <> same.AnswerID
)
, stepthree AS(
SELECT
t.QID1, t.OID1, t.QuestionID, t.OptionID
, COUNT(UserId) AS myCount
FROM
stepone t
WHERE t.temprank = 1
GROUP BY
t.QID1, t.OID1, t.QuestionID, t.OptionID
)
SELECT
o1.OptionText AS [RowTest]
, o2.OptionText AS [ColumnText]
, t.myCount AS [Count]
FROM
stepthree t
INNER JOIN tblOptions o1
ON t.OID1 = o1.OptionID
INNER JOIN tblOptions o2
ON t.OptionID = o2.OptionID
ORDER BY t.OID1
Hope it helps, I enjoyed trying to do it.

SQL joining on names

I am querying two tables and joining them together on people's names. I am trying to produce all employees who have not filled out a form within the past month. The problem I am encountering is I am receiving an overlap of names when people list their short name (Joe rather than Joseph, or Mike rather than Michael). How can I still produce the list of people without overlap, even when they use their short names?
This is the query I have as of now:
SELECT DISTINCT ge.employeeNo,
(ge.firstName + ' ' + ge.lastName) AS empName,
ge.email
FROM dbo.hist_Employees ge
INNER JOIN dbo.ctrl_Sites cs ON ge.locationID = cs.ID
WHERE (ge.firstName + ' ' + ge.lastName) NOT IN
(SELECT sc.recordedBy
FROM GRSTOPS.dbo.hist_StopCard sc
INNER JOIN dbo.ctrl_Area a ON sc.area = a.ID
INNER JOIN dbo.ctrl_Site s ON a.site = s.ID
WHERE sc.recorded BETWEEN '10/01/2013' AND '10/30/2013'
AND s.code = 'gre')
AND cs.Abbreviation = 'gre'
AND ge.employmentStatus = 1
AND ge.primaryDept <> 3
It would be better not to join on peoples names as they are not unique.
You should join using a primary key like ID/employeeNo etc.

Query Performance too Slow

Im having performance issues with this query. If I remove the status column it runs very fast but adding the subquery in the column section delays way too much the query 1.02 min. How can I modify this query so it runs fast getting the desired data.
The reason I put that subquery there its because I needed the status for the latest activity, some activities have null status so I have to ignore them.
Establishments: 6.5k rows -
EstablishmentActivities: 70k rows -
Status: 2 (Active, Inactive)
SELECT DISTINCT
est.id,
est.trackingNumber,
est.NAME AS 'establishment',
actTypes.NAME AS 'activity',
(
SELECT stat3.NAME
FROM SACPAN_EstablishmentActivities eact3
INNER JOIN SACPAN_ActivityTypes at3
ON eact3.activityType_FK = at3.code
INNER JOIN SACPAN_Status stat3
ON stat3.id = at3.status_FK
WHERE eact3.establishment_FK = est.id
AND eact3.rowCreatedDT = (
SELECT MAX(est4.rowCreatedDT)
FROM SACPAN_EstablishmentActivities est4
INNER JOIN SACPAN_ActivityTypes at4
ON est4.establishment_fk = est.id
AND est4.activityType_FK = at4.code
WHERE est4.establishment_fk = est.id
AND at4.status_FK IS NOT NULL
)
AND at3.status_FK IS NOT NULL
) AS 'status',
est.authorizationNumber,
reg.NAME AS 'region',
mun.NAME AS 'municipality',
ISNULL(usr.NAME, '') + ISNULL(+ ' ' + usr.lastName, '')
AS 'created',
ISNULL(usr2.NAME, '') + ISNULL(+ ' ' + usr2.lastName, '')
AS 'updated',
est.rowCreatedDT,
est.rowUpdatedDT,
CASE WHEN est.rowUpdatedDT >= est.rowCreatedDT
THEN est.rowUpdatedDT
ELSE est.rowCreatedDT
END AS 'LatestCreatedOrModified'
FROM SACPAN_Establishments est
INNER JOIN SACPAN_EstablishmentActivities eact
ON est.id = eact.establishment_FK
INNER JOIN SACPAN_ActivityTypes actTypes
ON eact.activityType_FK = actTypes.code
INNER JOIN SACPAN_Regions reg
ON est.region_FK = reg.code --
INNER JOIN SACPAN_Municipalities mun
ON est.municipality_FK = mun.code
INNER JOIN SACPAN_ContactEstablishments ce
ON ce.establishment_FK = est.id
INNER JOIN SACPAN_Contacts con
ON ce.contact_FK = con.id
--JOIN SACPAN_Status stat ON stat.id = actTypes.status_FK
INNER JOIN SACPAN_Users usr
ON usr.id = est.rowCreatedBy_FK
LEFT JOIN SACPAN_Users usr2
ON usr2.id = est.rowUpdatedBy_FK
WHERE (con.ssn = #ssn OR #ssn = '*')
AND eact.rowCreatedDT = (
SELECT MAX(eact2.rowCreatedDT)
FROM SACPAN_EstablishmentActivities eact2
WHERE eact2.establishment_FK = est.id
)
--AND est.id = 6266
ORDER BY 'LatestCreatedOrModified' DESC
Try moving that 'activiy' query to a Left Join and see if it speeds it up.
I solved the problem by creating a temporary table and creating an index to it, this removed the need of the slow subquery in the select statement. Then I join the temp table as I do with normal tables.
Thanks to all.