Order by not-selected column - c#

I'm trying to use the SQL operator CONTAINSTABLE to get a list of search results, like this:
SELECT c.*, ccontains.[RANK]
FROM Customers c
INNER JOIN CONTAINSTABLE(Customers, LastName, #searchTerm) ccontains ON c.Id = ccontains.[KEY]
And calling this function from EF Core 2.1:
var query = DbContext.Customers.FromSql("SELECT * FROM udfSearchCustomers(#searchTerm)",
new SqlParameter(#searchTerm, mySearchTerm));
query = query.Include(c => c.Addresses).Take(maxResults);
I want to order my search results descending by RANK, to get the most relevant results at the top. Adding an ORDER BY ccontains.[RANK] to my function is not allowed, as my SELECT * FROM udfSearchCustomers(...) will be wrapped by EF Core: ORDER BY is not allowed on an inner query. Adding query.OrderBy(c => c.Rank) is not possible, as RANK is not on the Customer entity.
I've tried using System.Linq.Dynamic, as well as other reflection solutions, to do this:
query = query.OrderBy("Rank");
But I got an exception:
"Rank" is not a member of type "Customer"
which is true. Is there any way to order on a column not on an entity, or will I need to create a MyCustomerSearchQuery query object and use AutoMapper to convert those to Customer? I'd rather not, as Customer has many properties and keeping those in sync will be a hassle.
Thanks in advance!

you can try with
query = query.OrderBy(x => x.Rank);
OR
query = query.OrderBy(x => x["Rank"]);

You can create the stored procedure of the query which takes two parameter : #searchKey, #orderByColumn.
CREATE PROCEDURE [dbo].[UdfSearchCustomers]
#searchTerm varchar(50),
#orderByColumn varchar(50)
AS
BEGIN
DECLARE #sql NVARCHAR(MAX);
SET #sql =' SELECT c.*, ccontains.[RANK]
FROM Customers c
INNER JOIN CONTAINSTABLE(Customers, LastName, ''#searchTerm'') ccontains
ON c.Id = ccontains.[KEY]
ORDER BY #orderByColumn'
SET #sql = REPLACE(#sql, '#orderByColumn', #orderByColumn)
SET #sql = REPLACE(#sql, '#searchTerm', #searchTerm)
exec sp_executesql #sql
END
GO
Then you can query the same stored procedure as:
var query = DbContext.Customers.FromSql("exec UdfSearchCustomers #p0, #p1", mySearchTerm, "Rank");
If you want to add join to the address table then you can add the join to the stored procedure. This may give you your desired result.

Related

SQL query error: "Only one expression can be specified in the select list when the subquery is not introduced with EXISTS." Output parameter

I have a SQL query that is being used in a C# controller class to return search query results:
SELECT #Output = (SELECT Name, Id
FROM [dbo].[Users]
WHERE Name = 'Robert');
However, when I run this query, I get the following error:
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
I know that simply removing one of the column names will fix the issue, but I want to be able to return the Name and ID for each searched user.
Is there a way to change my query so that I can return the desired columns and still keep my #Output parameter in the query? Any recommendations would be much appreciated
One method is to concatenate them:
SELECT #Output = (SELECT Name + '|' + CONVERT(VARCHAR(255), Id)
FROM [dbo].[Users]
WHERE Name = 'Robert'
);
Another alternative is to use two variables, because you want two items:
SELECT #Name = Name, #Id = Id
FROM [dbo].[Users]
WHERE Name = 'Robert';
Given that you know that the name is 'Robert', I don't see why that is useful. You can do:
DECLARE #Name VARCHAR(255);
SET #Name = 'Robert';
SELECT #Id = Id
FROM [dbo].[Users]
WHERE Name = #Name;
Not sure if this is what you were looking for, you can declare #output as table, and insert the values when condition is true.
Declare #output table ([name] varchar(50), ID int)
insert into #output([name], id) select 'abc', 1
select * from #output
Output: This is just sample data
name ID
abc 1

EF Core 2.1 Making multiple DB calls

Is there a way to prevent EF Core from doing multiple DB round trips on single enumeration function call?
Take into consideration this relatively simple LINQ expression:
var query2 = context.CheckinTablets.Select(ct => new
{
Id = ct.Id,
DeviceName = ct.Name,
Status = ct.CheckinTabletStatuses
.OrderByDescending(cts => cts.TimestampUtc).FirstOrDefault()
}).ToList();
In the past expactation was that "One enumeration call translates to one DB call" (if you disable lazy loading). In EF Core this is no longer the case!
In EF 6.2.0 this LINQ is translated to
SELECT [Extent1].[CheckinTabletID] AS [CheckinTabletID],
[Limit1].[TimestampUtc] AS [TimestampUtc]
--...
FROM [dbo].[CheckinTablet] AS [Extent1] OUTER APPLY (
SELECT TOP (1) [Project1].[CheckinTabletStatusID] AS [CheckinTabletStatusID],
[Project1].[CheckinTabletID] AS [CheckinTabletID],
[Project1].[TimestampUtc] AS [TimestampUtc]
FROM (
SELECT [Extent2].[CheckinTabletStatusID] AS [CheckinTabletStatusID],
[Extent2].[CheckinTabletID] AS [CheckinTabletID],
[Extent2].[TimestampUtc] AS [TimestampUtc]
--...
FROM [dbo].[CheckinTabletStatus] AS [Extent2]
WHERE [Extent1].[CheckinTabletID] = [Extent2].[CheckinTabletID]
) AS [Project1] ORDER BY [Project1].[TimestampUtc] DESC
) AS [Limit1];
While quite ugly, it was something that followed POLA quite nicely. Even more it was something we could work with to optimize DB side (indexes).
With EF Core 2.1.0 we get something like this:
SELECT [ct].[CheckinTabletID] AS [Id], [ct].[strName] AS [DeviceName] FROM [CheckinTablet] AS [ct]
exec sp_executesql N'SELECT TOP(1) [cts].[CheckinTabletStatusID], [cts].[CheckinTabletID], [cts].[TimestampUtc] FROM [CheckinTabletStatus] AS [cts] WHERE #_outer_Id = [cts].[CheckinTabletID] ORDER BY [cts].[TimestampUtc] DESC',N'#_outer_Id int',#_outer_Id=1
exec sp_executesql N'SELECT TOP(1) [cts].[CheckinTabletStatusID], [cts].[CheckinTabletID], [cts].[TimestampUtc] FROM [CheckinTabletStatus] AS [cts] WHERE #_outer_Id = [cts].[CheckinTabletID] ORDER BY [cts].[TimestampUtc] DESC',N'#_outer_Id int',#_outer_Id=2
exec sp_executesql N'SELECT TOP(1) [cts].[CheckinTabletStatusID], [cts].[CheckinTabletID], [cts].[TimestampUtc] FROM [CheckinTabletStatus] AS [cts] WHERE #_outer_Id = [cts].[CheckinTabletID] ORDER BY [cts].[TimestampUtc] DESC',N'#_outer_Id int',#_outer_Id=3
exec sp_executesql N'SELECT TOP(1) [cts].[CheckinTabletStatusID], [cts].[CheckinTabletID], [cts].[TimestampUtc] FROM [CheckinTabletStatus] AS [cts] WHERE #_outer_Id = [cts].[CheckinTabletID] ORDER BY [cts].[TimestampUtc] DESC',N'#_outer_Id int',#_outer_Id=4
exec sp_executesql N'SELECT TOP(1) [cts].[CheckinTabletStatusID], [cts].[CheckinTabletID], [cts].[TimestampUtc] FROM [CheckinTabletStatus] AS [cts] WHERE #_outer_Id = [cts].[CheckinTabletID] ORDER BY [cts].[TimestampUtc] DESC',N'#_outer_Id int',#_outer_Id=5
Yes, that is one call to first get all entities (CheckinTablets) and then call per row to get status for each entity...
So in one call ToList() Entity Framework is making n+1 calls to database. This is extremely undesirable, is there a way to disable this behaviour or workaround?
Edit 1:
.Include() is not helping the issue... It still makes n+1 DB requests.
Edit 2 (credit #jmdon):
Not returning object but simple value make only one call! Of course this doesn’t really help if you don't want to flatten your entity, or if you want multiple values from second table. Never the less good to know!
var query2 = _context.CheckinTablets.Select(ct => new
{
Id = ct.Id,
DeviceName = ct.Name,
Status = new CheckinTabletStatus
{
Id = ct.CheckinTabletStatuses.OrderByDescending(cts => cts.TimestampUtc).FirstOrDefault().Id,
CheckinTabletId = ct.CheckinTabletStatuses.OrderByDescending(cts => cts.TimestampUtc).FirstOrDefault().CheckinTabletId,
}
}).ToList();
Produces one call to DB:
SELECT [ct].[intCheckinTabletID] AS [Id0],
[ct].[strName] AS [DeviceName],
(
SELECT TOP (1) [cts].[intCheckinTabletStatusID]
FROM [tCheckinTabletStatus] AS [cts]
WHERE [ct].[intCheckinTabletID] = [cts].[intCheckinTabletID]
ORDER BY [cts].[dtmTimestampUtc] DESC
) AS [Id],
(
SELECT TOP (1) [cts0].[intCheckinTabletID]
FROM [tCheckinTabletStatus] AS [cts0]
WHERE [ct].[intCheckinTabletID] = [cts0].[intCheckinTabletID]
ORDER BY [cts0].[dtmTimestampUtc] DESC
) AS [CheckinTabletId]
FROM [tCheckinTablet] AS [ct];
I asked this questions during .Net Conf 2018 to Diego Vega and Smit Patel... This was their answer (paraphrased).
EF Core is not only for relational DB... Customers did not want to see Exception if something cannot be translated to SQL... "If it needs more then one query, that is fine"... By default multiple queries per enumeration are enabled. There is a warning system that will output a warning if this happens. They are thinking about adding a method that will upgrade warning to exception if multiple round-trips are executed. They are working on optimizing (n+1) queries to a few (fixed size) queries based on data structure.
It is possible to force EF Core to throw exception when it evaluates part of the query client side by adding this to OnConfiguring method.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;")
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}
More info: https://learn.microsoft.com/en-us/ef/core/querying/client-eval
I've noticed it does that when you try to return nested objects.
You can try flattening the Status object in your projection, eg. something like:
var query2 = context.CheckinTablets.Select(ct => new
{
Id = ct.Id,
DeviceName = ct.Name,
StatusName = ct.CheckinTabletStatuses
.OrderByDescending(cts => cts.TimestampUtc).FirstOrDefault().Name
}).ToList();

how to select values from 3 tables + count()

I have three Tables :
tbl_Publisher [Publisher_ID, addr,account-num,...,city];
tbl_Title [Title_ID, frequency, publisher,.., Publisher_ID];
tbl_Invoice [Invoice_ID, ordered_Date,...,Title_ID];
I would like to return a list of Titles by Publisher and each Title has the count number of Invoices it contains. in one result set.
I'm using a stored procedure as following :
PROCEDURE [dbo].[usp_GetTitlesbyPublisher]
-- Add the parameters for the stored procedure here
#PublisherID INT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
-- Insert statements for procedure here
SELECT Title_ID,TitleName,pub_type,Frequency , Holdings ,tbl_Title.publisher ,section ,tbl_Title.Publisher_ID from tbl_Title, tbl_Publisher
where tbl_Title.Publisher_ID = tbl_Publisher.Publisher_ID
and #PublisherID = tbl_Publisher.Publisher_ID
END
How can I return the number of Invoice by each Title ?
You can probably accomplish this with a GROUP BY:
SELECT t.Title_ID, t.TitleName, p.pub_type,
t.Frequency, Holdings, t.publisher, section,
t.Publisher_ID, count(i.Invoice_ID) as NoOfInvoices
from tbl_Title t
inner join tbl_Publisher p on t.Publisher_ID = p.Publisher_ID
left join tbl_Invoice i on i.Title_ID = t.Title_ID
where #PublisherID = p.Publisher_ID
group by t.Title_ID, t.TitleName, p.pub_type, t.Frequency,
Holdings, t.publisher, section, t.Publisher_ID
Not checked the syntax on this.
SELECT COUNT(tbl_Invoice.Invoice_ID) 'InvoiceCount',Title_ID,TitleName,pub_type,Frequency, Holdings ,tbl_Title.publisher ,section ,tbl_Title.Publisher_ID
FROM tbl_Title
INNER JOIN tbl_Publisher ON tbl_Publisher.Publisher_ID = tbl_Title.Publisher_ID
INNER JOIN tbl_Invoice ON tbl_Invoice.Invoice_ID = tbl_Title.Invoice_ID
WHERE tbl_Publisher.Publisher_ID = #PublisherID
GROUP BY
Title_ID,TitleName,pub_type,Frequency, Holdings ,tbl_Title.publisher ,section ,tbl_Title.Publisher_ID

Cannot pass variables to a dynamic pivot query from Razor Web Pages

My Dynamic Pivot Query is as follows
var sql_ovrd = #"DECLARE #out VARCHAR(MAX)
SELECT #out = COALESCE(#out+'],[' ,'') + Hospital_Name
FROM Hospitals
SELECT #out = '['+#out+ ']'
DECLARE #sql varchar(1000)
SET #sql = '
SELECT * FROM
(
SELECT OVRCATEGORY.OVRCATEGORY AS OVRCAT, OVRCATEGORY.OVRCATEGORY AS OVRCATEGORY, Hospitals.Hospital_Name AS Hospital, OVRREPORTED.OVRCatID,
MONTH(OVRREPORTED.DATERECEIVED) AS [Month], YEAR(OVRREPORTED.DATERECEIVED) AS [YEAR]
FROM OVRREPORTED
INNER JOIN OVRCategory ON OVRReported.OVRCatID = OVRCategory.OVRCatID
INNER JOIN Hospitals ON OVRReported.Hospital_ID = Hospitals.Hospital_ID
WHERE ovrreported.cancel_id=0 AND MONTH(OVRREPORTED.DATERECEIVED) =#0 AND YEAR(OVRREPORTED.DATERECEIVED)=#1
) AS T
PIVOT (COUNT(OVRCAT) FOR Hospital IN ('+#out+')) AS P'
EXEC (#sql)"; //
var ovrd_data = db.Query(sql_ovrd,3,2015);
This keeps giving me error message that "Must declare the scalar variable "#0". Am I doing something silly?
The web grid I am trying to create works fine when I remove "AND MONTH(OVRREPORTED.DATERECEIVED) =#0 AND YEAR(OVRREPORTED.DATERECEIVED)=#1" from the Where Clause. Also when I try to execute directly on the sql studio as "AND MONTH(OVRREPORTED.DATERECEIVED) =3 AND YEAR(OVRREPORTED.DATERECEIVED)=2015" it is working perfectly.
Is it not possible to pass variables from Web Pages Razor syntax to a dynamic query?
To pass values into variable in dynamic SQL you need to use sp_executesql sys stored procedure see MSDN explication : enter link description here

How to do Searching and Filtering Data based on Checkboxes in Many to Many Relationship in SQL?

Basically i have 3 tables like this (with many to many relationship);
And i am querying searching like this;
ALTER PROC [dbo].[usp_ContactSearch]
(
#PersonName varchar(60)= '',
#MobileNo varchar(20)= '',
#Nationlity varchar(50)='' ,
#ContactTypes varchar(max) = ''
)
AS
BEGIN
SELECT DISTINCT c.ContactId, c.PersonName, ct.ContactType, ct.ContactTypeId
FROM Contact c
LEFT OUTER JOIN ContactWithContactType cct
ON c.ContactId = cct.ContactId
LEFT OUTER JOIN ContactType ct
ON cct.CountactTypeId = ct.ContactTypeId
WHERE
c.PersonName LIKE CASE WHEN #PersonName='' THEN c.PersonName ELSE '%'+#PersonName+'%' END
AND
c.MobileNo1 LIKE CASE WHEN #MobileNo='' THEN c.MobileNo1 ELSE '%'+#MobileNo+'%' END
AND
c.Nationality LIKE CASE WHEN #Nationlity='' THEN c.Nationality ELSE '%'+#Nationlity+'%' END
END
So, the result data by default is;
So, from the Front End, i have ContactTypes (which are dynamic i.e comming from contact types table), and the interface looks like this
Now, whenever user check the PropertyOwner(ContactTypeId=1), The data should be filtered and only those contacts should be shown which are belong to ContactTypeId=1
Silarly, when i check the second checkbox i.e Tenant(ContactTypeId=2). The data should be more filtered and only those contacts will be displayed which belongs to ContactTypeId= 1 and 2. And similarly for 3rd ContactType, the data should be more filtered and so on and so forth.
So, the problem is ContactTypes are dynamic and i don't know how to handle this situation.
Any help regards to the query and performance is much apreciated.
Try this. This will work...
-- This is User Defined Table Type Variable
Declare #MyTypeDataType ContType
-- You will pass value to this variable from Front End
Insert into #MyTypeDataType values(1),(2),(3);
-- From Front end you will pass the
-- selected values to "Table Type Variable" and
-- also to a "Varchar" Variable
Declare #Type as Varchar(20);
SET #Type = '1,2,3';
SELECT X.* FROM
(
-- This query will get all persons,
-- who have any one Type u want to Search...
SELECT C.*,CTT.ContactType, CTT.ContactTypeId FROM Contact C
INNER JOIN ContactWithType CT
ON C.ContactId = CT.ContactId
INNER JOIN ContactType CTT
ON CTT.ContactTypeId = CT.ContactTypeId
WHERE #Type LIKE '%' + CAST( CT.ContactTypeId AS VARCHAR(MAX)) + '%'
) X
INNER JOIN
(
-- This will count the Record of each Person,
-- how many time a persons record exists..
SELECT C.ContactId, COUNT(C.ContactId) AS Total
FROM Contact C
INNER JOIN ContactWithType CT
ON C.ContactId = CT.ContactId
INNER JOIN ContactType CTT
ON CTT.ContactTypeId = CT.ContactTypeId
WHERE #Type LIKE '%' + CAST( CT.ContactTypeId AS VARCHAR(MAX)) + '%'
GROUP BY C.ContactId
)Y
ON X.ContactId = Y.ContactId
-- Filter persons
AND Y.Total = (SELECT COUNT(*) FROM #MyTypeDataType)
I would like to recommend to use split function for your query to filter ContactTypes. For example, when in your form someone check Property Owner and Tenant (contactType = 1, 2) or so on, then you can pass it to your stored proc as such
#ContactTypes varchar(max) = '1,2'
Then you can use one of the split string function that you need to create. You can refer to this excellent article (http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings).
I use the article function SplitStrings_Moden for simple string split.
Then you can use it like in your stored proc like this.
And ContactType in (select [item] from SplitStrings_Moden(#ContactTypes , ','))
As for performance wise, the example given return a table of string column which in your case, you could cast it to int for better for performance. But i guess you might to test it on your dataset if the performance is reasonable without the casting.
I would recommend using xml datatype for this. You can find more information here.
Use XML as input variable to an SP

Categories