Selecting max Date from multiple tables - c#

Is it possible to turn this into an entity framework query that will only query the database once?
select max(TableADate) latestTableADate, max(TableBDate) latestTableBDate
from
(
select max(a.ModifiedDate) TableADate, null TableBDate
from TableA a
union all
select null, max(b.ModifiedDate)
from TableB b
) data
The intent is to get the latest ModifiedDate from multiple tables with one sql statement so Entity Framework is not doing multiple trips to the database to get the data.
I need to know when the last time that one of the tables was updated.
Update:
I ended using DbContext.Database.SqlQuery doing the following:
var output = db.Database.SqlQuery<DateTime>
("select data from (" +
"select 1 orderBy, max(ModifiedDate) data from TableA" +
"union all " +
"select 2, max(ModifiedDate) from TableB " +
") temp order by orderby").ToList();
data.TableADate = output[0];
data.TableBDate = output[1];

You can execute a query similar to the one you've listed using the DbContext.Database.SqlQuery method. You'd need to change it to return the dates in some sort of order or use out parameters.

Related

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();

Update Table From Select Statement Sql Query

I have three tables, one holds a list of available products with relevant details such as product price, the other holds orders and the last holds specific order details including the items purchased in an order.
I am trying to create a query to calculate the total price of an order by linking the two of the tables with an inner join and then updating the order table with the value. The code I have written contains syntax errors, being a novice I have probably made an obvious mistake but any help would be appreciated.
"SELECT Sum(ProductTable.prodPrice) AS Total, OrderDetailTable.orderID " +
"FROM ProductTable INNER JOIN OrderDetailTable " +
"ON ProductTable.prodID = OrderDetailTable.prodID " +
"GROUP BY OrderDetailTable.orderID " +
"HAVING OrderDetailTable.orderID = ? " +
"UPDATE OrderTable " +
"SET " +
"totalPrice = Total " +
"WHERE OrderTable.orderID = ? ";
I am using Access.
I also forgot to mention that the ? is represented using a Dataview parameter
pc.Add(new Parameter("?", TypeCode.Int32, basketId.ToString()));
EDIT: Using PaqoGomez's suggestion to declare Total as a value I now get the error : Syntax error (missing operator) in query expression '0WHERE OrderTable.orderID = ?'.
Comment made before OP described this as MS Access:
Haven't tried this but you had the Select and update the wrong way round. This should be quite close. Its been a while for me but you may need to change the FROM clause to reference the table you are updating and INNER JOIN the sub-select instead of the WHERE clause.
"UPDATE OrderTable " +
"SET totalPrice = Sum(ProductTable.prodPrice)" +
"FROM (" +
"SELECT OrderDetailTable.orderID, Sum(ProductTable.prodPrice)" +
"FROM ProductTable INNER JOIN OrderDetailTable " +
"ON ProductTable.prodID = OrderDetailTable.prodID " +
"GROUP BY OrderDetailTable.orderID " +
") x" +
"WHERE x.orderID = OrderTable.OrderId"
UPDATE :
Based on OPs update regarding MS Access you can do an update with a sub-query, but I think it depends on the version of MS Access you are using and how you do it. Posts below seem to indicate it's quite problematic:
SQL Subqueries (Microsoft Access SQL)
How do I perform update query with subquery in Access?
Can It Be Done - Access 2007 SQL Update Query Using a Subquery?
By declaring a variable you can save the total value so that you can use it in the later update.
declare #Total int;
SELECT #Total = Sum(ProductTable.prodPrice)
//...
"SET " +
"totalPrice = #Total "
You dont mention which database platform you are using, I'm assuming MSSQL, other platforms would require a slightly different syntax.
You might also be interested to know that if you use the # symbol you can have a multiline string. This would allow you to avoid the concatenation that you are doing. eg.
var sql = #"some
sql
string"

Retrieving distinct record using join in SQL

I am running a sql query using two tables namely QuestionInsert and Question_Papers.
The columns in th erespective table are as follows:-
Table:-QuestionInsert
Columns:-QuestionNum,Question,Answer,CatId,SubCatId
Table:-Question_Papers
Columns:-QuestionNum
I want an sql query which will retrieve all QuestionNum,Question,Answer from table QuestionInsert which QuestionNum is present in table Question_Papers.
Also, I want to retrieve all QuestionNum,Question,Answer from table QuestionInsert which QuestionNum is not present in table Question_Papers.
This data is displayed on a Grid View.The queries I am using are as follows:-
The Query for first condition is:
SELECT F.QuestionNum,
F.Question,
F.Answer
FROM QuestionInsert F
INNER JOIN Question_Papers FS ON F.[QuestionNum]=FS.QuestionNum
WHERE ((F.QuestionNum=FS.QuestionNum) AND (F.CatId='" +
DropDownList1.SelectedValue + "' And F.SubCatId='" + DropDownList3.SelectedValue + "'))
ORDER BY F.QuestionNum DESC;
The other query for 2nd condition. is:-
SELECT F.QuestionNum,
F.Question,
F.Answer
FROM QuestionInsert F INNER JOIN Question_Papers FS ON F.[QuestionNum]!=FS.QuestionNum
WHERE ((F.QuestionNum!=FS.QuestionNum) AND (F.CatId='" + DropDownList1.SelectedValue + "'
And F.SubCatId='" + DropDownList3.SelectedValue + "'))
ORDER BY F.QuestionNum DESC
My code is retrieving correct information but if more than one row of same QuestionNum is present in Question_Papers table, it is displaying all the rows repeatedly. I want to display the unique rows which are present and not present in table Question_Papers separately.
Kindly help me.
You could try the following for the second condition:
SELECT F.QuestionNum,F.Question,F.Answer
FROM QuestionInsert F
WHERE (F.CatId='" + DropDownList1.SelectedValue + "' And F.SubCatId='" + DropDownList3.SelectedValue + "')
AND F.QuestionNum NOT IN (SELECT QuestionNum FROM Question_Papers)
ORDER BY F.QuestionNum DESC
And this for the first condition:
SELECT F.QuestionNum,F.Question,F.Answer
FROM QuestionInsert F
WHERE (F.CatId='" + DropDownList1.SelectedValue + "'
AND F.SubCatId='" + DropDownList3.SelectedValue + "')
AND F.QuestionNum IN (SELECT QuestionNum FROM Question_Papers)
ORDER BY F.QuestionNum DESC";
However, there are serious problems with your code - have you looked into SQL injection? There are many data access frameworks, like Entity Framework, that would push you down a better route.
Your first query can be rewritten using EXISTS
SELECT F.QuestionNum,F.Question,F.Answer FROM QuestionInsert F
WHERE EXISTS (SELECT * FROM Question_Papers P WHERE P.QuestionNum = F.QuestionNum)
AND F.CatId='" + DropDownList1.SelectedValue + "'
AND F.SubCatId='" + DropDownList3.SelectedValue + "'
Second query using NOT EXISTS
SELECT F.QuestionNum,F.Question,F.Answer FROM QuestionInsert F
WHERE NOT EXISTS (SELECT * FROM Question_Papers P WHERE P.QuestionNum = F.QuestionNum)
AND F.CatId='" + DropDownList1.SelectedValue + "'
AND F.SubCatId='" + DropDownList3.SelectedValue + "'
Please note that with the way those queries are written (which was taken from your question), you are vulnerable to SQL Injection. You should use parameters instead.
There doesn't appear to be a need to use a join nor a reason for repeating the join clause within where. Form what I can gather all you need to do is check for existence, which recent versions (2005+) of sql server supports with EXISTS. Doing this as a single query than a correlated subquery can be used to check and flag existence
DECLARE #question_insert TABLE ( id INT, question VARCHAR(50), answer VARCHAR(50), catid INT, subcatid INT )
DECLARE #question_paper TABLE ( id INT, question_insert_id INT )
INSERT INTO #question_insert ( id, question, answer, catid, subcatid )
VALUES
(1, 'How old are you?', '20', 1, 1),
(2, 'Who was the first president?', '?', 2, 1)
INSERT INTO #question_paper ( id, question_insert_id )
VALUES (1, 1),(2, 1)
SELECT
qi.id,
qi.question,
qi.answer,
CASE WHEN EXISTS(SELECT 1 FROM #question_paper qp
WHERE qp.question_insert_id = qi.id)
THEN 'Yes' ELSE 'No' END AS in_question_paper
FROM #question_insert qi
--WHERE qi.catid=#catid AND qi.subcatid=#subcatid
demo
Alternatively AS individual queries
SELECT
qi.id,
qi.question,
qi.answer,
'Yes' AS in_question_paper
FROM #question_insert qi
WHERE EXISTS(SELECT 1 FROM #question_paper qp
WHERE qp.question_insert_id = qi.id)
And
SELECT
qi.id,
qi.question,
qi.answer,
'No' AS in_question_paper
FROM #question_insert qi
WHERE NOT EXISTS(SELECT 1 FROM #question_paper qp
WHERE qp.question_insert_id = qi.id)
I will reiterate that you should read up on SQL Injection and not concatenate user input into queries.
Also re. DISTINCT not being "acceptable in joins" that is not the case. What is not acceptable is to use DISTINCT and refer to a column that is not part of select list in another part of the query (in this case it would've been the WHERE clause), a way round this is to use GROUP BY instead.

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

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.

Updating only the rows matched by field value from CROSS JOIN of 2 tables (SQL)

Here's what needs to be done:
Match 2 fields from different tables in SQL Server( done ), update fields in table1 where table1.field1 = table2.field2 (problem, it just updates all the records in the table)
Here's what I have , where tempTableName is a table imported to SQL Server for the purpose of the query:
"UPDATE Table1 SET Table1.fieldN ='" + DateTime.Now.DayOfYear + "' FROM " + tempTableName + " CROSS JOIN Table1 WHERE (" + tempTableName + ".fieldX = Table1.fieldY)"
Here's what I've figured out:
everything after the FROM is useless as far as actual functionality is concerned, it runs the query but the results are not "linked" in any way to the actual UPDATE statement
To sum up :
The query I've figured out updates all records in table1, I need the query to update only the rows matched by the query after FROM
PS. Forgive me if this seems trivial and that I haven't done research but the fact of the matter is that 2 weeks ago I'd never even heard of SQL and have relied heavily on SO for direction and advice.
Your CROSS JOIN should be an INNER JOIN instead.
"UPDATE t1
SET fieldN ='" + DateTime.Now.DayOfYear + "'
FROM " + tempTableName + " t2 INNER JOIN Table1 t1 ON t1.fieldy = t2.fieldx"

Categories