If anyone could help me join these two selects together to create one mega statement that would be awesome!!
--running against quotingSystem database
SELECT
CONVERT (varchar, Quote.QuoteID) AS QuoteID, Quote.FirstName,
Quote.LastName, Quote.ProductSKU, Quote.ProductID, Quote.Quantity,
Quote.CreationDate,
CONVERT (CHAR(8), Quote.CreationTime, 8) AS CreationTime,
Quote.CompanyName,
IncommingQuoteStatus.StatusDesc, Quote.LockedDateTime,
Users.FirstName + ' ' + Users.LastName AS UserName
FROM
Quote
INNER JOIN
IncommingQuoteStatus ON IncommingQuoteStatus.StatusID = Quote.Status
INNER JOIN
Users ON Quote.LockedUserID = Users.UserID
WHERE
(Quote.Status > 2 AND Quote.Status < 6)
ORDER BY
QuoteID DESC
-- running on quoting system database but also pulling in values from another database CBretailDB
select
D2.stock AS CurrentStock
from
dbo.Quote as D1
inner join
CBretailDB.dbo.products AS D2 ON D1.ProductID = D2.idProduct
the Quote.ProductID on the first statement needs to join on the D2.idProduct from second statement to pull the CurrentStock value
I think that makes sense!
Many thanks
Try this:
SELECT CONVERT (varchar, Quote.QuoteID) AS QuoteID, Quote.FirstName, Quote.LastName,
Quote.ProductSKU, Quote.Quantity, Quote.CreationDate, CONVERT (CHAR(8),
Quote.CreationTime, 8) AS CreationTime, Quote.CompanyName,
IncommingQuoteStatus.StatusDesc, Quote.LockedDateTime, Users.FirstName + ' ' +
Users.LastName AS UserName, D2.stock FROM Quote INNER JOIN IncommingQuoteStatus ON
IncommingQuoteStatus.StatusID = Quote.Status INNER JOIN Users ON Quote.LockedUserID =
Users.UserID
INNER JOIN CBretailDB.dbo.products AS D2 ON Quote.ProductID = D2.idProduct
WHERE (Quote.Status > 2 AND Quote.Status < 6) ORDER BY QuoteID DESC
Related
I'm trying to scan my codebase to find all select queries without a where clause using regex. The results will be fed into an IDE or a grep file output, but must contain the full matching queries only.
My biggest challenge is getting the entire statement without the WHERE. The caveats are:
some selects don't have a where but also don't have a FROM
some selects target a database view (always starts with a vw) which don't need a where clause
Here's a sample list of all queries fetched from one file:
'
DECLARE #RowsAffected INT = 0;
INSERT INTO tblInvoice (InvID, OcID, InvTimeStamp)
SELECT DISTINCT OcInvID, OcID, GETDATE() AS InvTimeStamp
FROM tblOrderCost OC WITH(NOLOCK)
INNER JOIN tblVendor WITH(NOLOCK) ON InvVendorID = VendorID AND VendorType = 1 -- 1 for supplier.
INNER JOIN #tmpOpID tmp WITH(NOLOCK) ON tmp.OpID = OcOpID
WHERE id = ' . quote($order_id, NUMERIC);
$sql = ' SELECT rphrpid,rphwho,rphdate,rphnotes,opid
FROM tblReplacementPartHistory (nolock)
INNER JOIN tblReplacementPart (nolock)
ON rphrpid = rpid
INNER JOIN tblOrderProduct (nolock)
ON rpopid = opid
WHERE oporid =' . quote($order_id, NUMERIC)
. 'ORDER BY rphrpid';
select * from table where id = 1;
'
select count()
';
DECLARE #RowsAffected INT = 0;
INSERT INTO tblInvoice (InvID, OcID, InvTimeStamp)
SELECT DISTINCT OcInvID, OcID, GETDATE() AS InvTimeStamp
FROM tblOrderCost OC WITH(NOLOCK)
INNER JOIN tblVendor WITH(NOLOCK) ON InvVendorID = VendorID AND VendorType = 1
INNER JOIN #tmpOpID tmp WITH(NOLOCK) ON tmp.OpID = OcOpID';
$sql = ' SELECT rphrpid,rphwho,rphdate,rphnotes,opid
FROM tblReplacementPartHistory (nolock)
INNER JOIN tblReplacementPart (nolock)
ON rphrpid = rpid
INNER JOIN tblOrderProduct (nolock)
ON rpopid = opid
ORDER BY rphrpid';
SELECT rphrpid,rphwho,rphdate,rphnotes,opid
FROM vwOrder';
select * from tbl;
I tried several variations of regex patterns and the closest I got was finding matches with the WHERE line stripped out. I would like to have the entire match made only if the query does not have a WHERE clause. I tried the following
SELECT(.*)(\s)*FROM(\s|.)+?((?!.*where))(?=(';|";|;))
SELECT\s*(?!.*\s*where|vw(\w)*).*\s*(';|";|;)
SELECT[^;\n]*(?:\n(?![^\n;]*where)[^;\n]*)*\n[\n]*
The work can also be tested in the regex101 sandbox: https://regex101.com/r/jvbLOE/1
What I expect to see, given the sample data, is only three matches
1. SELECT DISTINCT OcInvID, OcID, GETDATE() AS InvTimeStamp
FROM tblOrderCost OC WITH(NOLOCK)
INNER JOIN tblVendor WITH(NOLOCK) ON InvVendorID = VendorID AND VendorType = 1
INNER JOIN #tmpOpID tmp WITH(NOLOCK) ON tmp.OpID = OcOpID';
2. SELECT rphrpid,rphwho,rphdate,rphnotes,opid
FROM tblReplacementPartHistory (nolock)
INNER JOIN tblReplacementPart (nolock)
ON rphrpid = rpid
INNER JOIN tblOrderProduct (nolock)
ON rpopid = opid
ORDER BY rphrpid';
3. select * from tbl;
You can use following regex to match select queries not having a where clause in it based on your example.
/(?!.*where)select.*?;/gis
Regex 101 example:
https://regex101.com/r/XaGXp6/1
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
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.
I have a stored procedure that uses this select statement:
SELECT dbo.VendorProgram.id,
dbo.VendorProgram.CODE,
dbo.Programs.ProgramName
+ '-' + dbo.Divisions.Division
+ '-' + dbo.Vendors.Source
+ '-' + dbo.Mediums.Medium
+ '-' + dbo.VendorProgram.content
AS SourceDetail,
dbo.Vendors.Source,
dbo.Programs.ProgramName,
dbo.Divisions.Division,
dbo.Mediums.Medium,
dbo.VendorProgram.content,
dbo.VendorProgram.url,
dbo.VendorProgram.cost,
dbo.VendorProgram.Notes,
dbo.VendorProgram.StartDate,
dbo.VendorProgram.EndDate
FROM dbo.Programs
RIGHT OUTER JOIN dbo.VendorProgram
ON dbo.Programs.id = dbo.VendorProgram.programID
LEFT OUTER JOIN dbo.Vendors
ON dbo.VendorProgram.vendorID = dbo.Vendors.mappingID
LEFT OUTER JOIN dbo.Divisions
ON dbo.VendorProgram.divisionID = dbo.Divisions.id
LEFT OUTER JOIN dbo.Mediums
ON dbo.VendorProgram.mediumID = dbo.Mediums.id
Basically we have a system that put together a code for a vendor. It is made up of 5 IDs pulled from 5 separate tables. Those tables have actual text, for a vendor's name, a type of medium, etc... that match to each ID. So far nothing truly ground breaking in use here I think.
What I need to do is be able to write a query that uses paramaters, in text, to search those 5 separate tables, and find all the "vendor mappings" that could match. I am still new in the SQL world so I am not quite sure what that query would look like.
As an example of how I want to search. I enter into my search form the text "Face" for the vendor field. I would then expect the query runs a select against the vendor table itself to first find all possible vendor IDs with "face" in the name. Then it would need to select all rows from the combined table that have any of those IDs in them.
Hopefully this makes sense, and is possible. As always thanks for any help.
SELECT vp.id, vp.CODE,
p.ProgramName + '-' + d.Division + '-' + v.Source + '-' + m.Medium + '-' + vp.content AS SourceDetail,
v.Source,
p.ProgramName,
d.Division,
m.Medium,
vp.content,
vp.url,
vp.cost,
vp.Notes,
vp.StartDate,
vp.EndDate
FROM dbo.Vendors v
LEFT OUTER JOIN dbo.VendorProgram vp vp.vendorID = v.mappingID
LEFT OUTER JOIN dbo.Programs p ON p.id = vp.programID
LEFT OUTER JOIN dbo.Divisions d ON vp.divisionID = d.id
LEFT OUTER JOIN dbo.Mediums m ON vp.mediumID = m.id
WHERE v.Name LIKE '%Face%'
add
WHERE dbo.Vendors.ID in
(select ID From dbo.Vendors where dbo.Vendors.Field LIKE '%'+ #QueryText + '%'
If you want only the rows related to your search, switch to inner joins (or include some where not nulls) and in your join to Vendors, include:
AND Vendor.VendorName LIKE #PassedInParam
Where #PassedInParam would be passed into your proc and would be your search string with a '%' on either side, ie '%face%'
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