Sub Query in select clause with Squeryl - postgresql

I'm trying to replicate the following query usine Squeryl.
SELECT c.order_number,p.customer,p.base,(
SELECT sum(quantity) FROM "Stock" s where s.base = p.base
) as stock
FROM "Card" c, "Part" p WHERE c."partId" = p."idField";
I have the following code for selecting the Cards and Parts but I cannot see a way to add a sumation into the select clause.
from(cards, parts)((c,p) =>
where(c.partId === p.id)
select(c,p)
Any help is much appreciated!

In Squeryl, you can use any Queryable object in the from clause of your query. So, to create a subquery, something like the following should work for you:
def subQuery = from(stock)(s => groupBy(s.base) compute(sum(s.quantity)))
from(cards, parts, subquery)((c, p, sq) =>
where(c.partId === p.idField and sq.key === p.base)
select(c.orderNumber, p.customer, sq.measures))
Of course the field names may vary slightly, just guessing at the class definitions. If you want the whole object for cards and parts instead of the single fields from the original query - just change the select clause to: select(c, p, sq.measures)

Related

EF- two WHERE IN clauses in an INCLUDE table produce two EXISTS in SQL sent to server

I have an INCLUDE table that I want to check a couple of values, in the same row, using an IN clause. The below doesn't return the correct result set because it produces two EXISTS clauses with subqueries. This results in the 2 values being checked independently and not strictly in the same child row. (forgive any typos as I'm typing this in from printed code)
var db = new dbEntities();
IQueryable<dr> query = db.drs;
// filter the parent table
query = query.Where(p => DropDown1.KeyValue.ToString().Contains(p.system_id.ToString()));
// include the child table
query = query.Include(p => p.drs_versions);
// filter the child table using the other two dropdowns
query = query.Where(p => p.drs_versions.Any(c => DropDown2.KeyValue.ToString().Contains(c.version_id.ToString())) && c => DropDown3.KeyValue.ToString().Contains(c.status_id.ToString()));
// I tried removing the second c=> but received an error "'c' is inaccessible due to its protection level" error and couldn't find an clear answer to how this related to Entity Framework
// query = query.Where(p => p.drs_versions.Any(c => DropDown2.KeyValue.ToString().Contains(c.version_id.ToString())) && DropDown3.KeyValue.ToString().Contains(c.status_id.ToString()));
This is an example of the query the code above produces...
SELECT *
FROM drs d
LEFT OUTER JOIN drs_versions v ON d.dr_id = v.dr_id
WHERE d.system_id IN (9,8,3)
AND EXISTS (SELECT 1 AS C1
FROM drs_versions sub1
WHERE d.tr_id = sub1.tr_id
AND sub1.version_id IN (9, 4, 1))
AND EXISTS (SELECT 1 AS C1
FROM drs_versions sub2
WHERE d.tr_id = sub2.tr_id
AND sub2.status_id IN (12, 7))
This is the query I actually want:
SELECT *
FROM drs d
LEFT OUTER JOIN drs_versions v ON d.dr_id = v.dr_id
WHERE d.system_id IN (9, 8, 3)
AND v.version_id IN (9, 4, 1)
AND v.status_id IN (12, 7)
How do I get Entity Framework to create a query that will give me the desired result set?
Thank you for your help
I'd drop all of the .ToString() everywhere and format your values ahead of the query to make it a lot easier to follow.. If EF is generating SQL anything like what you transcribed, you are casting to String just to have EF revert it back to the appropriate type.
From that it just looks like your parenthesis are a bit out of place:
I'm also not sure how something like DropDown2.KeyBalue.ToString() resolves back to what I'd expect to be a collection of numbers based on your SQL examples... I've just substituted this with a method called getSelectedIds().
IEnumerable<int> versions = getSelectedIds(DropDown2);
IEnumerable<int> statuses = getSelectedIds(DropDown3);
query = query
.Where(p => p.drs_versions
.Any(c => versions.Contains(c.version_id)
&& statuses.Contains(c.status_id));
As a general bit of advice I suggest always looking to simplify the variables you want to use in a linq expression as much as possible ahead of time to keep the text inside the expression as simple to read as possible. (avoiding parenthesis as much as possible) Make liberal use of line breaks and indentation to organize what falls under what, and use the code highlighting to double-check your closing parenthesis that they are closing the opening you expect.
I don't think your first example actually was input correctly as it would result in a compile error as you cannot && c => ... within an Any() block. My guess would be that you have:
query = query.Where(p => p.drs_versions.Any(c => DropDown2.KeyValue.ToString().Contains(c.version_id.ToString())) && p.drs_versions.Any(c => DropDown3.KeyValue.ToString().Contains(c.status_id.ToString()));
Your issue is closing off the inner .Any()
query.Where(p => p.drs_versions.Any(c => DropDown2.KeyValue.Contains(c.version_id))
&& DropDown3.KeyValue.Contains(c.status_id)); //<-- "c" is still outside the single .Any() condition so invalid.
Even then I'm not sure this will fully explain the difference in queries or results. It sounds like you've tried typing across code rather than pasting the actual statements and captured EF queries. It may help to copy the exact statements from the code because it's pretty easy to mistype something when trying to simplify an example only to find out you've accidentally excluded the smoking gun for your issue.

Select most reviewed courses starting from courses having at least 2 reviews

I'm using Flask-SQLAlchemy with PostgreSQL. I have the following two models:
class Course(db.Model):
id = db.Column(db.Integer, primary_key = True )
course_name =db.Column(db.String(120))
course_description = db.Column(db.Text)
course_reviews = db.relationship('Review', backref ='course', lazy ='dynamic')
class Review(db.Model):
__table_args__ = ( db.UniqueConstraint('course_id', 'user_id'), { } )
id = db.Column(db.Integer, primary_key = True )
review_date = db.Column(db.DateTime)#default=db.func.now()
review_comment = db.Column(db.Text)
rating = db.Column(db.SmallInteger)
course_id = db.Column(db.Integer, db.ForeignKey('course.id') )
user_id = db.Column(db.Integer, db.ForeignKey('user.id') )
I want to select the courses that are most reviewed starting with at least two reviews. The following SQLAlchemy query worked fine with SQlite:
most_rated_courses = db.session.query(models.Review, func.count(models.Review.course_id)).group_by(models.Review.course_id).\
having(func.count(models.Review.course_id) >1) \ .order_by(func.count(models.Review.course_id).desc()).all()
But when I switched to PostgreSQL in production it gives me the following error:
ProgrammingError: (ProgrammingError) column "review.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT review.id AS review_id, review.review_date AS review_...
^
'SELECT review.id AS review_id, review.review_date AS review_review_date, review.review_comment AS review_review_comment, review.rating AS review_rating, review.course_id AS review_course_id, review.user_id AS review_user_id, count(review.course_id) AS count_1 \nFROM review GROUP BY review.course_id \nHAVING count(review.course_id) > %(count_2)s ORDER BY count(review.course_id) DESC' {'count_2': 1}
I tried to fix the query by adding models.Review in the GROUP BY clause but it did not work:
most_rated_courses = db.session.query(models.Review, func.count(models.Review.course_id)).group_by(models.Review.course_id).\
having(func.count(models.Review.course_id) >1) \.order_by(func.count(models.Review.course_id).desc()).all()
Can anyone please help me with this issue. Thanks a lot
SQLite and MySQL both have the behavior that they allow a query that has aggregates (like count()) without applying GROUP BY to all other columns - which in terms of standard SQL is invalid, because if more than one row is present in that aggregated group, it has to pick the first one it sees for return, which is essentially random.
So your query for Review basically returns to you the first "Review" row for each distinct course id - like for course id 3, if you had seven "Review" rows, it's just choosing an essentially random "Review" row within the group of "course_id=3". I gather the answer you really want, "Course", is available here because you can take that semi-randomly selected Review object and just call ".course" on it, giving you the correct Course, but this is a backwards way to go.
But once you get on a proper database like Postgresql you need to use correct SQL. The data you need from the "review" table is just the course_id and the count, nothing else, so query just for that (first assume we don't actually need to display the counts, that's in a minute):
most_rated_course_ids = session.query(
Review.course_id,
).\
group_by(Review.course_id).\
having(func.count(Review.course_id) > 1).\
order_by(func.count(Review.course_id).desc()).\
all()
but that's not your Course object - you want to take that list of ids and apply it to the course table. We first need to keep our list of course ids as a SQL construct, instead of loading the data - that is, turn it into a derived table by converting the query into a subquery (change the word .all() to .subquery()):
most_rated_course_id_subquery = session.query(
Review.course_id,
).\
group_by(Review.course_id).\
having(func.count(Review.course_id) > 1).\
order_by(func.count(Review.course_id).desc()).\
subquery()
one simple way to link that to Course is to use an IN:
courses = session.query(Course).filter(
Course.id.in_(most_rated_course_id_subquery)).all()
but that's essentially going to throw away the "ORDER BY" you're looking for and also doesn't give us any nice way of actually reporting on those counts along with the course results. We need to have that count along with our Course so that we can report it and also order by it. For this we use a JOIN from the "course" table to our derived table. SQLAlchemy is smart enough to know to join on the "course_id" foreign key if we just call join():
courses = session.query(Course).join(most_rated_course_id_subquery).all()
then to get at the count, we need to add that to the columns returned by our subquery along with a label so we can refer to it:
most_rated_course_id_subquery = session.query(
Review.course_id,
func.count(Review.course_id).label("count")
).\
group_by(Review.course_id).\
having(func.count(Review.course_id) > 1).\
subquery()
courses = session.query(
Course, most_rated_course_id_subquery.c.count
).join(
most_rated_course_id_subquery
).order_by(
most_rated_course_id_subquery.c.count.desc()
).all()
A great article I like to point out to people about GROUP BY and this kind of query is SQL GROUP BY techniques which points out the common need for the "select from A join to (subquery of B with aggregate/GROUP BY)" pattern.

How to ORDER BY non-column field?

I am trying to create an Entity SQL that is a union of two sub-queries.
(SELECT VALUE DISTINCT ROW(e.ColumnA, e.ColumnB, 1 AS Rank) FROM Context.Entity AS E WHERE ...)
UNION ALL
(SELECT VALUE DISTINCT ROW(e.ColumnA, e.ColumnB, 2 AS Rank) FROM Context.Entity AS E WHERE ...)
ORDER BY *??* LIMIT 50
I have tried:
ORDER BY Rank
and
ORDER BY e.Rank
but I keep getting:
System.Data.EntitySqlException: The query syntax is not valid. Near keyword 'ORDER'
Edit:
This is Entity Framework. In C#, the query is executed using:
var esql = "...";
ObjectParameter parameter0 = new ObjectParameter("p0", value1);
ObjectParameter parameter1 = new ObjectParameter("p1", value2);
ObjectQuery<DbDataRecord> query = context.CreateQuery<DbDataRecord>(esql, parameter0, parameter1);
var queryResults = query.Execute(MergeOption.NoTracking);
There is only a small portion of my application where I have to resort to using Entity SQL. Generally, the main use case is when I need to do: "WHERE Column LIKE 'A % value % with % multiple % wildcards'".
I do not think it is a problem with the Rank column. I do think it is how I am trying to apply an order by to two different esql statements joined by union all. Could someone suggest:
How to apply a ORDER BY to this kind of UNION/UNION ALL statment
How to order by the non-entity column expression.
Thanks.

What does Query Projection mean in Entity Framework?

I am looking at some EF examples and trying to decipher what 'Query Projection' exactly equates to when doing LINQ to Entities or EntitySQL. I believe it is when the query results are filtered and projected into an anonymous type but not 100% sure.
Can someone please define this and maybe provide a small L2E query that uses an example of it?
Projection is when the result of a query is output to a different type than the one queried. Another article defined it as : the process of transforming the results of a query
Projection can be to an anonymous type, but could also be to a concrete type. If you come from a SQL world, it is akin to the columns listed in your SELECT clause.
Example selecting a sub-set of an object into an concrete type:
ParentObj.Select(x=> new ParentSlim { ParentID = x.ParentID, Name = x.Name } );
.
Example merging to object into a 3rd anonymous type:
Note: the select new portion is the projection.
from P in ParentObj.AsQueryable()
join C in ChildObj.AsQueryable() on P.ParentID == C.ParentID
select new { // <-- look ma, i'm projecting!
ParentID = P.ParentID,
Name = P.Name,
SubName = C.Name
RandomDate = DateTime.UtcNow()
}

How do I do this search and order_by on a DBIx::Class::ResultSet

Problem definition.
I have multiple clients with multiple users. Each client needs to be able to associate custom data with a user, search, and order by.
Database Solution:
A table Customfields which defines the customfields table. It has an id and name.
It has a has_many relationship with a Userfields table (aka "attributes").
The Userfields table has a userid, customfieldid, content and id.
It belongs_to a Useraccounts table (aka "useraccount") and Customfields (aka "customfield")
Proposed select statement that I want:
This is a select statement that achieves and produces what I need.
SELECT ua.*, (
SELECT content FROM Userfields uf
INNER JOIN Customfields cf
ON cf.id = uf.customfieldid
WHERE cf.name = 'Mothers birthdate'
AND uf.uid=ua.uid
) AS 'Mothers birthdate',
(
SELECT content FROM Userfields uf
INNER JOIN Customfields cf
ON cf.id = uf.customfieldid
WHERE cf.name = 'Join Date' AND
uf.uid=ua.uid
) AS 'Join Date'
FROM UserAccounts ua
ORDER BY 'Mothers birthdate';
In this case their could be anything from 0 ... x sub select statements in the select statement and any one of them or none of them could be wanting to be ordered by.
Question
How do I achieve this with a ->search on my dbix class resultset or how do I achieve the same result with a search on my dbix class resultset?
Here is how I usually select from my Useraccounts table, although I am unsure how to do the complex statement that I want to from here.
my #users = $db->resultset('Useraccounts')->search(
undef,
{
page => $page,
join => 'attributes',
...
});
Thanks for your time.
-pdh
This is really pretty hairy, and any solution isn't going to be pretty, but it does look to be possible if you bend the rules a little bit. Forgive any mistakes I make, as I didn't go and create a schema to test this on, but it's based on the best info I have (and much help from ribasushi).
First, (assuming that your userfields table has a belongs_to relation with the customfields table, called customfield)
my $mbd = $userfields_rs->search(
{
'customfield.name' => 'Mothers birthdate',
'uf.uid' => \'me.uid' # reference to outer query
},
{
join => 'customfield',
alias => 'uf', # don't shadow the 'me' alias here.
}
)->get_column('content')->as_query;
# Subqueries and -as don't currently mix, so hack the SQL ourselves
$mbd->[0] .= q{ AS 'Mothers Birthdate'};
The literal me.uid that uf.uid is being matched against is an unbound variable -- it's the uid field from the query that we're eventually going to put this query into as a subselect. By default DBIC aliases the table that the query is addressing to me; if you gave it a different alias then you would use something diferent here.
Anyway, You could repeat this as_query business with as many different fields as you like, just varying the field-name (if you're smart, you'll write a method to generate them), and put them in an array, so now let's suppose that #field_queries is an array, containing $mbd above as well as another one based on Join Date, and anything you like.
Once you have that, it's as "simple" as...
my $users = $useraccounts_rs->search(
{ }, # any search criteria can go in here,
{
'+select' => [ #field_queries ],
'+as' => [qw/mothers_birthdate join_date/], # this is not SQL AS
order_by => {-asc => 'Mothers birthdate'},
}
);
which will include each of the subqueries into the select.
Now for the sad part: as of right now, this whole thing actually won't work, because subqueries with placeholders don't work properly. So for now you need an additional workaround: instead of 'customfield.name' => 'Mothers birthdate' in the subselect search, do 'customfield.name' => \q{'Mothers birthdate'} -- this is using literal SQL for the field name (BE CAREFUL of SQL injection here!), which will sidestep the placeholder bug. But in the not-too-distant future, that bug will be resolved and the code above will work okay, and we'll update the answer to let you know that's the case.
See DBIx::Class::ResultSource order_by documentation