The performance impact from multiple logical reads could be a result of your UDF.
Provided is a great article which describes using inline table-valued UDFs to reduce the row-by-row calls to the scalar UDF. UPDATE: I noticed you were already using table-valued UDF.
http://dataeducation.com/scalar-functions-inlining-and-performance-an-entertaining-title-for-a-boring-post/
To replicate your scenario, I executed the following query:
DECLARE
@Measurements TABLE(Precidence int, -- the relative scale of a measurement
Measurement varchar(max), -- Grams, Kilograms, Liters, etc
MeasurementType varchar(max)); -- Weight, Volume, etc`
DECLARE
@Items TABLE(ItemType varchar(max),
Quantity float,
Measurement varchar(max),
ToMeasurement varchar(max));
insert into @items (ItemType, Quantity, Measurement) values ('Widget','1','G');
insert into @items (ItemType, Quantity, Measurement) values ('Widget','1','KG');
insert into @items (ItemType, Quantity, Measurement) values ('Widget','1','ML');
insert into @items (ItemType, Quantity, Measurement) values ('Widget','1','L');
insert into @Measurements (Precidence, Measurement, MeasurementType) values ('2','G','Weight');
insert into @Measurements (Precidence, Measurement, MeasurementType) values ('1','KG','Weight');
insert into @Measurements (Precidence, Measurement, MeasurementType) values ('1','GAL','Volume');
insert into @Measurements (Precidence, Measurement, MeasurementType) values ('2','L','Volume');
insert into @Measurements (Precidence, Measurement, MeasurementType) values ('3','ML','Volume');
Afterwards, I complied and executed the following update statement:
WITH Items_CTE AS
(
SELECT A.ItemType,
A.Quantity,
A.Measurement,
(SELECT TOP 1 M.Measurement FROM @Measurements M
JOIN @Items C ON C.Measurement = M.Measurement
WHERE M.MeasurementType = B.MeasurementType
AND C.ItemType = A.ItemType
ORDER BY Precidence)ToMeasurement
FROM @Items A
JOIN @Measurements B
ON A.Measurement = B.Measurement
)
UPDATE @Items
SET ToMeasurement = cte.ToMeasurement
FROM ITEMS_CTE as cte
JOIN @Items as i
ON cte.measurement = i.measurement;
The UPDATE
statement uses a CTE and the results were as follows:
ItemType | Quantity | Measurement | ToMeasurement
Widget | 1 | G | KG
Widget | 1 | KG | KG
Widget | 1 | ML | L
Widget | 1 | L | L
This can be done using the PIVOT function, but since it sounds like you want to change the query based on the schemaId, then you will want to use dynamic SQL.
If you had a known number of values or knew the columns for a specific schemaID, then you could hard-coded the query. A static query would be:
select loannumber,
[First Name],
[Middle Name],
[Last Name]
from
(
select
l.loannumber,
sf.fieldname,
lf.fieldvalue
from loans l
left join loanfields lf
on l.id = lf.loanid
left join schemafields sf
on lf.schemafieldid = sf.id
and l.schemaid = sf.schemaid
) src
pivot
(
max(fieldvalue)
for fieldname in ([First Name], [Middle Name], [Last Name])
)piv;
See SQL Fiddle with Demo.
If you had an unknown number or you want the columns to change based on a SchemaId
that you are passing into a procedure, then you will use dynamic SQL to generate the SQL string:
DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX),
@schemaId int = 1
select @cols = STUFF((SELECT distinct ',' + QUOTENAME(FieldName)
from SchemaFields
where schemaid = @schemaid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set @query = 'SELECT loannumber,' + @cols + '
from
(
select
l.loannumber,
sf.fieldname,
lf.fieldvalue
from loans l
left join loanfields lf
on l.id = lf.loanid
left join schemafields sf
on lf.schemafieldid = sf.id
and l.schemaid = sf.schemaid
where sf.schemaid = '+cast(@schemaid as varchar(10))+'
) x
pivot
(
max(fieldvalue)
for fieldname in (' + @cols + ')
) p '
execute(@query);
See SQL Fiddle with Demo. Both of these queries will generate the result:
| LOANNUMBER | FIRST NAME | LAST NAME | MIDDLE NAME |
-----------------------------------------------------
| ABC123 | John | Doe | (null) |
| XYZ789 | Charles | Smith | Lee |
Best Answer
As far as you're using a hierarchical structure you can apply a recursive solution. But for a large number of rows you must deal with MAXRECURSION, have a look at MS Docs about it.
Quoted from docs:
db<>fiddle here
If you don't like or don't want or simply you cannot use a recursive solution, you can use a series of nested CTE's (one for each level), and finally combine the results using a UNION operation:
db<>fiddle here