Duplicate field name problem in a nested multi-mapping Dapper pagination query - c#

I've ran into an issue when trying to do multi-mapping using Dapper, for pagination queries.
Because I am using a nested query in this pagination scenario, there are multiple tables within the nested query that I must join to get my multi-mapped data, but some of these tables will share some fields of the same name which you can see in my example query below (e.g. id, displayname and email):
q = #"select * from (select p.id, p.title, p.etc...,
u1.id, u1.displayname, u1.email,
u2.id, u2.displayname, u2.email,
t.id, t.name,
row_number() over (order by " + sort.ToPostSortSqlClause() + ") as rownum" +
" from posts p" +
" join users u1 on p.owneruserid = u1.id" +
" join users u2 on p.lastediteduserid = u2.id" +
" join topics t on p.topicid = t.id" +
") seq where seq.rownum between #pLower and #pUpper";
In the example above you can see that within the nested query, there are going to be problems with the fields id (appears in the posts table, both users table joins and the topics table join), and also displayname and email (appear in both users table joins).
The only workaround I have thought of so far involves casting each of these 'problem' fields as a different name, but this then involves the very messy process of creating dummy properties in the affected models, so multimapping can map into these, and editing the 'real' properties in my models to also check the dummy property for a value if the real value has not been set.
Also, in the above scenario I would have to create x dummy properties where x is the number of joins I may have on the same table within a query (in this example, 2 joins on the same Users table, therefore requiring 2 uniquely named dummy properties just for Dapper mapping purposes).
This is obviously not ideal and I'm sure would have knock on problems and more untidyness as I created more of these multi-mapping pagination queries.
I'm hoping there is nice, clean solution to this problem?

There are 2 options I can think of:
option 1: join back to your extended properties outside of your nested query:
select s.*, t1.*, t2.* from
(
select s.*, ROW_NUMBER() OVER (order by somecol) AS RowNumber from Something s
) as X
left join Table t1 on Id = x.SomeId
left join Table t2 on Id = x.SomeOtherId
option 2: Extend SqlBuilder to handle column aliasing:
select s.*, /**unalias(Table,t1)**/, /**unalias(Table,t2)**/ from
(
select s.*, /**alias(Table,t1)**/, /**alias(Table,t2)**/ ROW_NUMBER() OVER (order by somecol) AS RowNumber from Something s
left join Table t1 on Id = x.SomeId
left join Table t2 on Id = x.SomeOtherId
) as X
Then define the alias macro to query and cache a list of columns from the db using INFORMATION_SCHEMA.COLUMNS and simply add a 'column as column_t1` string for each column.
Unalias can do the reverse quite simply.

Related

EF6 Linq to Sql column alias exceeds 30 characters

I am converting our Data Access Layer from Telerik ORM (deprecated) to EF 6. It connects to an Oracle 12c datasource with a 30 character limit. Some table names and column names are exactly 30 characters (as in this example below), some are shorter.
The Linq to Sql queries already exist and worked find with Telerik because it used alias for column names as t1, t2, etc.. whereas EF apparently copies the original column name as the alias. In most cases this works except for code table joins where both the PK of the code table and the FK of the parent table are exactly the same, then it appends a 1 to the end of the column alais thus making it 31 characters. As you can guess Oracle complains.
I need to find a way to force EF to use a shorter alias, any way to do this?
Here is the Linq query:
var dbElevationsHistory = (from elevationHist in dbContext.ElevationDataReadingJournals
join it in dbContext.MeasurementIssueTypes on elevationHist.MeasurementIssueTypeId equals it.MeasurementIssueTypeId into iType
from issueType in iType.DefaultIfEmpty()
join mt in dbContext.ElevationMeasureMethodTypes on elevationHist.ElevationMeasureMethodTypeId equals mt.ElevationMeasureMethodTypeId into mType
from methodType in mType.DefaultIfEmpty()
join at in dbContext.ElevationAccuracyTypes on elevationHist.ElevationAccuracyTypeId equals at.ElevationAccuracyTypeId into aType
from accuracyType in aType.DefaultIfEmpty()
join cAgency in dbContext.Organizations on elevationHist.CoopAgencyOrganizationId equals cAgency.OrganizationId into foundAgencies
from coopAgency in foundAgencies.DefaultIfEmpty()
where elevationHist.StationId == wellKey
select new { elevationHist, issueType, accuracyType, methodType, coopAgency })
.OrderByDescending(eh => eh.elevationHist.ModifiedDate)
.OrderByDescending(eh => eh.elevationHist.ElevationDataReadingJournalId);
And the sql query it produces:
SELECT
"Project1"."C1" AS "C1",
"Project1"."EWM_ELEVATION_DATA_READ_JRL_ID" AS "EWM_ELEVATION_DATA_READ_JRL_ID",
"Project1"."EWM_ELEVATION_DATA_READING_ID" AS "EWM_ELEVATION_DATA_READING_ID",
"Project1"."CRUD_TYPE" AS "CRUD_TYPE",
"Project1"."CRUD_DT" AS "CRUD_DT",
"Project1"."MEASUREMENT_DT" AS "MEASUREMENT_DT",
"Project1"."ORG_ID" AS "ORG_ID",
"Project1"."EWM_MEASUREMENT_ISSUE_TYPE_ID" AS "EWM_MEASUREMENT_ISSUE_TYPE_ID",
"Project1"."EWM_STATION_ID" AS "EWM_STATION_ID",
"Project1"."EWM_ELEV_MEASURE_METHOD_TYP_ID" AS "EWM_ELEV_MEASURE_METHOD_TYP_ID",
"Project1"."EWM_ELEVATION_ACCURACY_TYPE_ID" AS "EWM_ELEVATION_ACCURACY_TYPE_ID",
"Project1"."REFERENCE_POINT_ELEVATION" AS "REFERENCE_POINT_ELEVATION",
"Project1"."GROUND_SURFACE_ELEVATION" AS "GROUND_SURFACE_ELEVATION",
"Project1"."WATER_SURFACE_READING" AS "WATER_SURFACE_READING",
"Project1"."REFERENCE_POINT_READING" AS "REFERENCE_POINT_READING",
"Project1"."MANDATORY_READING" AS "MANDATORY_READING",
"Project1"."COMMENTS" AS "COMMENTS",
"Project1"."MODIFIED_DATE" AS "MODIFIED_DATE",
"Project1"."MODIFIED_USER" AS "MODIFIED_USER",
"Project1"."MODIFIED_PROC" AS "MODIFIED_PROC",
"Project1"."COOPERATING_AGENCY_ORG_ID" AS "COOPERATING_AGENCY_ORG_ID",
"Project1"."APPL_ID" AS "APPL_ID",
"Project1"."SUBMISSION_DATE" AS "SUBMISSION_DATE",
"Project1"."EWM_MEASUREMENT_ISSUE_TYPE_ID1" AS "EWM_MEASUREMENT_ISSUE_TYPE_ID1",
"Project1"."EWM_MEASURE_ISSUE_TYPE_CODE" AS "EWM_MEASURE_ISSUE_TYPE_CODE",
"Project1"."EWM_MEASURE_ISSUE_TYPE_DESC" AS "EWM_MEASURE_ISSUE_TYPE_DESC",
"Project1"."EWM_MEASURE_ISSUE_TYPE_ACTV" AS "EWM_MEASURE_ISSUE_TYPE_ACTV",
"Project1"."EWM_MEASURE_ISSUE_TYPE_ORDER" AS "EWM_MEASURE_ISSUE_TYPE_ORDER",
"Project1"."EWM_MEASURE_ISSUE_TYPE_CLASS" AS "EWM_MEASURE_ISSUE_TYPE_CLASS",
"Project1"."MODIFIED_DATE1" AS "MODIFIED_DATE1",
"Project1"."MODIFIED_USER1" AS "MODIFIED_USER1",
"Project1"."MODIFIED_PROC1" AS "MODIFIED_PROC1",
"Project1"."APPL_ID1" AS "APPL_ID1",
"Project1"."EWM_ELEVATION_ACCURACY_TYPE_ID1" AS "EWM_ELEVATION_ACCURACY_TYPE_ID1",
"Project1"."EWM_ELEVATION_ACCURACY_DESC" AS "EWM_ELEVATION_ACCURACY_DESC",
"Project1"."EWM_ELEVATION_ACCURACY_ACTV" AS "EWM_ELEVATION_ACCURACY_ACTV",
"Project1"."EWM_ELEVATION_ACCURACY_ORDER" AS "EWM_ELEVATION_ACCURACY_ORDER",
"Project1"."MODIFIED_DATE3" AS "MODIFIED_DATE2",
"Project1"."MODIFIED_USER3" AS "MODIFIED_USER2",
"Project1"."MODIFIED_PROC3" AS "MODIFIED_PROC2",
"Project1"."EWM_ELEVATION_ACCURACY_CD" AS "EWM_ELEVATION_ACCURACY_CD",
"Project1"."APPL_ID3" AS "APPL_ID2",
"Project1"."EWM_ELEV_MEASURE_METHOD_TYP_ID1" AS "EWM_ELEV_MEASURE_METHOD_TYP_ID1",
"Project1"."EWM_ELEV_MEASURE_METHOD_DESC" AS "EWM_ELEV_MEASURE_METHOD_DESC",
"Project1"."EWM_ELEV_MEASURE_METHOD_ACTV" AS "EWM_ELEV_MEASURE_METHOD_ACTV",
"Project1"."EWM_ELEV_MEASURE_METHOD_ORDER" AS "EWM_ELEV_MEASURE_METHOD_ORDER",
"Project1"."MODIFIED_DATE2" AS "MODIFIED_DATE3",
"Project1"."MODIFIED_USER2" AS "MODIFIED_USER3",
"Project1"."MODIFIED_PROC2" AS "MODIFIED_PROC3",
"Project1"."EWM_ELEV_MEASURE_METHOD_CD" AS "EWM_ELEV_MEASURE_METHOD_CD",
"Project1"."APPL_ID2" AS "APPL_ID3",
"Project1"."ORG_ID1" AS "ORG_ID1",
"Project1"."ORG_NAME" AS "ORG_NAME",
"Project1"."ORG_ABBR" AS "ORG_ABBR",
"Project1"."ORG_TYPE_ID" AS "ORG_TYPE_ID",
"Project1"."MODIFIED_DATE4" AS "MODIFIED_DATE4",
"Project1"."MODIFIED_USER4" AS "MODIFIED_USER4",
"Project1"."MODIFIED_PROC4" AS "MODIFIED_PROC4",
"Project1"."ORG_TIN" AS "ORG_TIN"
FROM ( SELECT
"Extent1"."EWM_ELEVATION_DATA_READ_JRL_ID" AS "EWM_ELEVATION_DATA_READ_JRL_ID",
"Extent1"."EWM_ELEVATION_DATA_READING_ID" AS "EWM_ELEVATION_DATA_READING_ID",
"Extent1"."CRUD_TYPE" AS "CRUD_TYPE",
"Extent1"."CRUD_DT" AS "CRUD_DT",
"Extent1"."MEASUREMENT_DT" AS "MEASUREMENT_DT",
"Extent1"."ORG_ID" AS "ORG_ID",
"Extent1"."EWM_MEASUREMENT_ISSUE_TYPE_ID" AS "EWM_MEASUREMENT_ISSUE_TYPE_ID",
"Extent1"."EWM_STATION_ID" AS "EWM_STATION_ID",
"Extent1"."EWM_ELEV_MEASURE_METHOD_TYP_ID" AS "EWM_ELEV_MEASURE_METHOD_TYP_ID",
"Extent1"."EWM_ELEVATION_ACCURACY_TYPE_ID" AS "EWM_ELEVATION_ACCURACY_TYPE_ID",
"Extent1"."REFERENCE_POINT_ELEVATION" AS "REFERENCE_POINT_ELEVATION",
"Extent1"."GROUND_SURFACE_ELEVATION" AS "GROUND_SURFACE_ELEVATION",
"Extent1"."WATER_SURFACE_READING" AS "WATER_SURFACE_READING",
"Extent1"."REFERENCE_POINT_READING" AS "REFERENCE_POINT_READING",
"Extent1"."MANDATORY_READING" AS "MANDATORY_READING",
"Extent1"."COMMENTS" AS "COMMENTS",
"Extent1"."MODIFIED_DATE" AS "MODIFIED_DATE",
"Extent1"."MODIFIED_USER" AS "MODIFIED_USER",
"Extent1"."MODIFIED_PROC" AS "MODIFIED_PROC",
"Extent1"."COOPERATING_AGENCY_ORG_ID" AS "COOPERATING_AGENCY_ORG_ID",
"Extent1"."APPL_ID" AS "APPL_ID",
"Extent1"."SUBMISSION_DATE" AS "SUBMISSION_DATE",
1 AS "C1",
"Extent2"."EWM_MEASUREMENT_ISSUE_TYPE_ID" AS "EWM_MEASUREMENT_ISSUE_TYPE_ID1",
"Extent2"."EWM_MEASURE_ISSUE_TYPE_CODE" AS "EWM_MEASURE_ISSUE_TYPE_CODE",
"Extent2"."EWM_MEASURE_ISSUE_TYPE_DESC" AS "EWM_MEASURE_ISSUE_TYPE_DESC",
"Extent2"."EWM_MEASURE_ISSUE_TYPE_ACTV" AS "EWM_MEASURE_ISSUE_TYPE_ACTV",
"Extent2"."EWM_MEASURE_ISSUE_TYPE_ORDER" AS "EWM_MEASURE_ISSUE_TYPE_ORDER",
"Extent2"."EWM_MEASURE_ISSUE_TYPE_CLASS" AS "EWM_MEASURE_ISSUE_TYPE_CLASS",
"Extent2"."MODIFIED_DATE" AS "MODIFIED_DATE1",
"Extent2"."MODIFIED_USER" AS "MODIFIED_USER1",
"Extent2"."MODIFIED_PROC" AS "MODIFIED_PROC1",
"Extent2"."APPL_ID" AS "APPL_ID1",
"Extent3"."EWM_ELEV_MEASURE_METHOD_TYP_ID" AS "EWM_ELEV_MEASURE_METHOD_TYP_ID1",
"Extent3"."EWM_ELEV_MEASURE_METHOD_DESC" AS "EWM_ELEV_MEASURE_METHOD_DESC",
"Extent3"."EWM_ELEV_MEASURE_METHOD_ACTV" AS "EWM_ELEV_MEASURE_METHOD_ACTV",
"Extent3"."EWM_ELEV_MEASURE_METHOD_ORDER" AS "EWM_ELEV_MEASURE_METHOD_ORDER",
"Extent3"."MODIFIED_DATE" AS "MODIFIED_DATE2",
"Extent3"."MODIFIED_USER" AS "MODIFIED_USER2",
"Extent3"."MODIFIED_PROC" AS "MODIFIED_PROC2",
"Extent3"."EWM_ELEV_MEASURE_METHOD_CD" AS "EWM_ELEV_MEASURE_METHOD_CD",
"Extent3"."APPL_ID" AS "APPL_ID2",
**"Extent4"."EWM_ELEVATION_ACCURACY_TYPE_ID" AS "EWM_ELEVATION_ACCURACY_TYPE_ID1"**,
"Extent4"."EWM_ELEVATION_ACCURACY_DESC" AS "EWM_ELEVATION_ACCURACY_DESC",
"Extent4"."EWM_ELEVATION_ACCURACY_ACTV" AS "EWM_ELEVATION_ACCURACY_ACTV",
"Extent4"."EWM_ELEVATION_ACCURACY_ORDER" AS "EWM_ELEVATION_ACCURACY_ORDER",
"Extent4"."MODIFIED_DATE" AS "MODIFIED_DATE3",
"Extent4"."MODIFIED_USER" AS "MODIFIED_USER3",
"Extent4"."MODIFIED_PROC" AS "MODIFIED_PROC3",
"Extent4"."EWM_ELEVATION_ACCURACY_CD" AS "EWM_ELEVATION_ACCURACY_CD",
"Extent4"."APPL_ID" AS "APPL_ID3",
"Extent5"."ORG_ID" AS "ORG_ID1",
"Extent5"."ORG_NAME" AS "ORG_NAME",
"Extent5"."ORG_ABBR" AS "ORG_ABBR",
"Extent5"."ORG_TYPE_ID" AS "ORG_TYPE_ID",
"Extent5"."MODIFIED_DATE" AS "MODIFIED_DATE4",
"Extent5"."MODIFIED_USER" AS "MODIFIED_USER4",
"Extent5"."MODIFIED_PROC" AS "MODIFIED_PROC4",
"Extent5"."ORG_TIN" AS "ORG_TIN"
FROM "EWM_ADM"."EWM_ELEVATION_DATA_READING_JRL" "Extent1"
LEFT OUTER JOIN "EWM_ADM"."EWM_MEASUREMENT_ISSUE_TYPE" "Extent2" ON "Extent1"."EWM_MEASUREMENT_ISSUE_TYPE_ID" = "Extent2"."EWM_MEASUREMENT_ISSUE_TYPE_ID"
LEFT OUTER JOIN "EWM_ADM"."EWM_ELEV_MEASURE_METHOD_TYP" "Extent3" ON "Extent1"."EWM_ELEV_MEASURE_METHOD_TYP_ID" = "Extent3"."EWM_ELEV_MEASURE_METHOD_TYP_ID"
LEFT OUTER JOIN "EWM_ADM"."EWM_ELEVATION_ACCURACY_TYPE" "Extent4" ON "Extent1"."EWM_ELEVATION_ACCURACY_TYPE_ID" = "Extent4"."EWM_ELEVATION_ACCURACY_TYPE_ID"
LEFT OUTER JOIN "BUS_ADM"."ORGANIZATION" "Extent5" ON "Extent1"."COOPERATING_AGENCY_ORG_ID" = "Extent5"."ORG_ID"
WHERE (("Extent1"."EWM_STATION_ID" = 52370) OR (("Extent1"."EWM_STATION_ID" IS NULL) AND (52370 IS NULL)))
) "Project1"
ORDER BY "Project1"."EWM_ELEVATION_DATA_READ_JRL_ID" DESC
The line in question is this one (marked with asterisks in the query since I cannot bold it).
"Extent4"."EWM_ELEVATION_ACCURACY_TYPE_ID" AS "EWM_ELEVATION_ACCURACY_TYPE_ID1"
As you can see it appended a 1 to the alias. I need to prevent that or force a custom alias name.

SQL "IN" statement in linq query mistake, how resolve?

I have this query in SQL:
SELECT *
FROM TableName
WHERE myData IN (SELECT MAX(myData) AS DATA_MAX
FROM TableName
GROUP BY id1, id2)
I want replicate it in Linq (c#) - how can I do that?
This isn't really a direct answer because it doesn't implement it via LINQ; but it does solve the problem, with the minimum amount of fuss:
You can use tools like "Dapper" to execute raw queries without involving any LINQ. If you're using something like LINQ-to-SQL or Entity Framework, the data-context there also usually has a raw query API that you can use, but I'm going to show a "Dapper" implementation:
class SomeType
{
// not shown: properties that look like the columns
// of [TableName] in the database - correct names/types
}
...
var data = connection.Query<SomeType>(#"
SELECT * FROM TableName
WHERE myData IN (Select max(myData) as DATA_MAX from TableName group
by id1, id2)").AsList();
This approach makes it very easy to migrate existing SQL queries without having to rewrite everything as LINQ.
If you are using LINQ-to-SQL, DataContext has a similiar ExecuteQuery<TResult> method. Entity Framework has a SqlQuery method
Long story short - don't use LINQ, optimize the query and use a microORM like Dapper to map results to classes :
var query = "Select * "
"from ( select *, " +
" ROW_NUMBER() OVER (partition by id1,id2 order by mydata desc) AS RN " +
" From TableName ) T " +
"where RN=1";
var data = connection.Query<SomeType>(query);
LINQ isn't a replacement for SQL. ORMs in general aren't meant to write reporting queries like this one.
Reporting queries need a lot of optimization and usually have to change in production. You don't want to have to redeploy your application each time a query changes. In this case it's far better to create a view and map to it using a microOMR like Dapper.
This specific query could require two table scans, one to calculate the maximum per id1,id2 and one to find the rows with matching mydata. The intermediate data would have to be spooled into tempdb too. If mydata is covered by an index, it may not be such an expensive query. If it isn't, all the data will be scanned twice.
An alternative is to calculate the ranking of each row by mydata based on id1, id2. You can do this with one of the ranking functions like ROW_NUMBER, RANK, NTILE.
Select *
from ( select *,
ROW_NUMBER() OVER (partition by id1,id2 order by mydata desc) AS RN
From TableName) T
where RN=1
You can use that query directly with Dapper or create a view and map your entities to the view, not the table itself.
One option would be to crate a MyTableRanked view :
CREATE VIEW MyTableRanked AS
select *,
ROW_NUMBER() OVER (partition by id1,id2 order by mydata desc) AS RN
From TableName
This would allow you to write :
var query="Select * from MyTableRanked where RN=#rank";
var data = connection.Query<SomeType>(query,new {rank=2});
Allowing you to return the top N records per ID1,ID2 combination
You can try this. May be it will work.
var myData = (from c in _context.TableName
group c by new
{
c.id1,
c.id2
} into gcs
select new
{
gcs.Max(p=>p.myData)
}).AsQueryable();
var result = (from t in _context.TableName
where myData.Contains(t.myData)
select t).ToList();

Select latest records and add results as new columns

I have the following database
my StudentRecords for specific sutdent is
How can i get the latest 3 studentRecords based on the latest Records.RecordDate
so for each sutdent, row will have ReportItems.ReportItemName ReportItems.TotalPSR,, Students.FullName, + add the following columns:
The latest studentRecords based on RecordDate and display the psr + reordsNames + recordDate so that one row look like this
Is this even possible?
And how can i do this using EF6 also what will be the SQL select statment
Providing your EF entities are set up with their mappings correctly to associate the Students, StudentRecords, and Records then you should be able to accomplish this in EF using something like the following:
var results = testContext.Students
.SelectMany(s => s.StudentRecords.OrderByDescending(sr => sr.Record.RecordDate).Take(3))
.GroupBy(x => x.Student)
.ToList();
What this will give you is a result per-student in a grouped structure where the grouping is on the student, with the grouped values are StudentResults. From there you can extract the student details from the "Key", while each result is a collection of (up to) 3 latest StudentRecords in descending order. You will need to handle the scenario where the # of results is < 3.
As a simple example extraction...
foreach (var studentGrouping in results)
{
studentName = studentGrouping.Key.FullName;
psr1 = studentGrouping[0].PSR;
recordName1 = studentGrouping[0].Record.RecordName;
recordDate1 = studentGrouping[0].Record.RecordDate;
// ... etc.
}
Now a caveat to the above solution is that it will invoke lazy-loading on the Records, so up to 3 hits per student. Curiously I tried to mitigate this with .Include(s=>s.StudentRecords.Select(r=>r.Record)) or trying the .Include(r=> r.Record) after the SelectMany() but while the initial query looked to include the columns for the Records, it still triggered the lazy loads.
If anyone can offer a correction to address the lazy loads, I'd love to hear it. I was a bit surprised to see them after adding the Include statements.
If your object model is more complex, such as lots of fields in Student/Record, etc. then I would recommend altering the above to return an anonymous type structure to retrieve just the values you're going to need to populate your results. The above is Ok for simple structures.
For example if Student had 30 columns, but we only care about the ID and Name:
var results = testContext.Students
.SelectMany(s => s.StudentRecords.OrderByDescending(sr => sr.Record.RecordDate).Take(3))
.Select( sr=> new {sr.Student.StudentId, sr.Student.FullName, sr.PSR, sr.Record.RecordId, RecordName = sr.Record.Name})
.GroupBy(x => new{StudentId, FullName})
.ToList();
This would return a structure where the Key was an anonymous type containing just the StudentId and Name, with the grouped values being the type containing the record details for that student. This has the benefit of only returning the data you will need in a single query to the database.
You can use Row_Number and Pivot as below:
;With Cte as (
Select RI.ReportItemName, RI.TotalPSR, S.FullName, SR.PSR, R.[Name], R.[Date],
RowNPSR = Row_Number() over(Partition by SR.StudentRecord order by R.RecordDate DESC),
RowName = Row_Number() over(Partition by SR.StudentRecord order by R.RecordDate DESC) + 500,
RowDate = Row_Number() over(Partition by SR.StudentRecord order by R.RecordDate DESC) + 1000
from
StudentRecords SR
Join Students s
on SR.StudentId = S.StudentId
Join Records R
On SR.RecordId = R.recordId
Left Join ReportItems RI
On S.ReportItemId = RI.ReportItemId
)
Select a.*, p1.[1] as PSR1, p1.[2] as PSR2, p1.[3] as PSR3
, p2.[501] as Name1, p2.[502] as Name2, p2.[503] as Name3 from (
Select * from CTE RowNPSR <= 3 ) a
Pivot (max(PSR) for RowNPSR in ([1],[2],[3]) ) p1
Pivot (max([Name]) for RowName in ([501],[502],[503]) ) p2
Pivot (max([Date]) for RowDate in ([1001],[1002],[1003]) ) p3

How do I use multiple IDs from a table with an INNER JOIN using SQL?

I have a list of SiteUsers in one table and another table has columns with different types of owners (ID) for the record. For example, the SiteUserID in the SiteUsers table will be used for the SalesRepID, the StaffingManagerID, and RecruiterID in the Fill table. Of course, the SiteUserID is different for each of the values in the Fill table.
I'd like to return the name of the SiteUser for each ID column in the Fill Table.
How do I properly construct a JOIN statement to do this?
I'm guessing this is done through INNER JOIN, but I'm not sure.
My current select statement already has an INNER JOIN as I'm pulling the name of the FillType from another table. I'm using this in an asp.net application.
I'm not sure if this is even possible. Any help is appreciated.
Since each of the IDs in the Fills table allows null, you probably want to LEFT JOIN to the SiteUsers table like so:
SELECT f.FillID, s1.SiteUserLastName 'SalesRep', s2.SiteUserLastName 'StaffingManager', s3.SiteUserLastName 'Recruiter'
FROM Fills f
LEFT JOIN SiteUsers s1 on f.SalesRepID = s1.SiteUserID
LEFT JOIN SiteUsers s2 on f.StaffingManagerID = s2.SiteUserID
LEFT JOIN SiteUsers s3 on f.RecruiterID = s3.SiteUserID
You can always UNPIVOT the results like so:
SELECT
DISTINCT
unpvt.FillID
,unpvt.RepID
,unpvt.RepType
,s.SiteUserFirstName
,s.SiteUserLastName
FROM
(SELECT
FillID
,SalesRepID
,StaffingManagerID
,RecruiterID
FROM Fills
) f
UNPIVOT
(RepID FOR RepType IN
(SalesRepID, StaffingManagerID,RecruiterID)
) AS unpvt
JOIN SiteUsers AS s on unpvt.RepID = s.SiteUserID`
Obviously you can play with exact output (such as substituting the RepType for a different value with a CASE statement or whatnot.
My question is: why the piss-poor design? Instead of having three IDs in the Fills table, you should have a junction table between SiteUsers and Fills to allow many-to-many relationships. IF it were designed with a junction table, you'd never have had to ask this question.
You will have to join the Fill table with the SiteUsers table multiple times, one for each xxxID column in the Fills for which you want the SiteUser name and combine the results using an union as below:
select a.SiteUserId, a.SiteUserFirstName, a.SiteUserLastName
from dbo.SiteUsers a
inner join dbo.Fills b on b.SalesRepId = a.SiteUserId
UNION
select a.SiteUserId, a.SiteUserFirstName, a.SiteUserLastName
from dbo.SiteUsers a
inner join dbo.Fills b on b.StaffingManagerId = a.SiteUserId
UNION
select a.SiteUserId, a.SiteUserFirstName, a.SiteUserLastName
from dbo.SiteUsers a
inner join dbo.Fills b on b.RecruiterId = a.SiteUserId

Advice on removing multiple sub-queries (which contains a join) by optimizing Linq To SQL query

I'm working on adding globalization to my product cataloge and I have made it work. However, I feel that the underlaying SQL query isn't performing as well as it could and I could need some advice on how to change my Linq To SQL query to make it more efficient.
The tables that are used
Product contains a unique id for each product. This column is called EntityID
TextTranslation contains the globalized text. The columns in this table are CultureID (a string), TextID (a reference to the text), Value (the actuall globalized text).
Text contains the mapping between a globalized text and a product. There is also a column which indicates which type of text it is (like name, description and so on)
TextType contains the definition (id, name and description) for a text type.
var culturedTexts =
from translation in ctx.TextTranslations
join text in ctx.Texts on translation.TextId equals text.TextId
where translation.CultureId == "en-EN"
select new
{
text.EntityId,
text.TextTypeId,
translation.Value,
};
var products =
from p in ctx.Products
let texts = culturedTexts.Where(i => i.EntityId == p.EntityId)
select new Model.Product
{
Description = texts.Where(c => c.TextTypeId == (int)TextType.Description).SingleOrDefault().Value,
Name = texts.Where(c => c.TextTypeId == (int)TextType.Name).SingleOrDefault().Value
};
When this is executed I get a query which looks like
SELECT (
SELECT [t1].[Value]
FROM [Common].[TextTranslation] AS [t1]
INNER JOIN [Common].[Text] AS [t2] ON [t1].[TextId] = [t2].[TextId]
WHERE ([t2].[TextTypeId] = 2) AND ([t2].[EntityId] = [t0].[EntityId]) AND ([t1].[CultureId] = 'sv-SE')
) AS [Description], (
SELECT [t3].[Value]
FROM [Common].[TextTranslation] AS [t3]
INNER JOIN [Common].[Text] AS [t4] ON [t3].[TextId] = [t4].[TextId]
WHERE ([t4].[TextTypeId] = 1) AND ([t4].[EntityId] = [t0].[EntityId]) AND ([t3].[CultureId] = 'sv-SE')
) AS [Name]
FROM [Catalog].[Product] AS [t0]
So each globalized text (Name, Description) in the LINQ query gets its own sub query and associated join. Is it possible to streamline this a bit and remove each text type getting its own join and subquery?
Well, given that they are getting different TextTypeId values, how would you prefer the TSQL to look? If you do a single JOIN, you'll have to put in a messy SELECT CASE or similar to discriminate between type "1" and type "2".
One option would be to to simply bring back all the suitable rows and do the final projection in-memory at the client, but to be honest I expect that the SQL optimizer will make light work of that TSQL anyway... especially if that query hits a good spanning index.

Categories