I have spent too much time on this, and still cannot get the syntax to work.
Is this select statement possible in DBIx::Class?
"SELECT A.id, A.name, count(C.a_id) AS count1,
(SELECT count(B.id FROM A, B WHERE A.id=B.a_id GROUP BY B.a_id, A.id) AS count2
FROM A LEFT OUTER JOIN C on A.id=C.a_id GROUP BY C.a_id, A.id"
This code below works in DBIx::Class to pull the count for table 'C', but multiple efforts of mine to add in the count for table 'B' have repeatedly failed:
my $data= $c->model('DB::Model')
->search({},
{
join => 'C',
join_type => 'LEFT_OUTER',
distinct => 1,
'select' => [ 'me.id','name',{ count => 'C.id', -as => 'count1'} ],
'as' => [qw/id name count1 /],
group_by => ['C.a_id','me.id'],
}
)
->all();
I am trying to get two counts in one query so that the results are saved in one data structure. On another forum it was suggested that I make two separate search calls and then union the results. When I looked at the DBIx::Class documentation though, it mentioned that 'union' is being deprecated. Using the 'literal' DBIx::Class doesn't work because it's only meant to be used as a where clause. I do not want to use a view (another's suggestion) because the SQL will eventually be expanded to match upon one of the id's. How do I format this query to work in DBIx::Class? Thank you.
DBIx::Class supports subqueries pretty conveniently by using the as_query method on your subquery resultset. There are some examples in the cookbook. For your use case, it'd look something like:
# your subquery goes in its own resultset object
my $subq_rs = $schema->resultset('A')->search(undef, {
join => 'B',
group_by => [qw/A.id B.id/],
})->count_rs;
# the subquery is attached to the parent query with ->as_query
my $combo_rs = $schema->resultset('A')->search(undef, {
select => [qw/ me.id me.name /,{ count => 'C.id' }, $subq_rs->as_query],
as => [qw/ id name c_count b_count/],
});
Related
Basically i want this as query:
SELECT DISTINCT c.description FROM students s
join courses c on s.courseId = c.Id
WHERE c.Id = 100
How do i do this in EF Core?
When i do :
db.Students
.Include(s => s.courseId)
.Select( -- how can i select for course description? --)
.Distinct()
Bear with me. I am new to Entity Framework.
First of all, your Include expression handles the joining for you, so you don't have to specify how your going to join the table. So your include will look like this:
_db.Students.Include(s => s.Course)
Then you'd accomplish your select through the use of a lamba expression like this:
_db.Students .Include(s => s.Course).Select(s => s.Course.Description).Distinct()
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)
I have a few Tables I want to join together:
Users
UserRoles
WorkflowRoles
Role
Workflow
The equivalent sql I want to generate is something like
select * from Users u
inner join UserRoles ur on u.UserId = ur.UserId
inner join WorkflowRoles wr on wr.RoleId = ur.RoleId
inner join Workflow w on wr.WorkflowId = w.Id
where u.Id = x
I want to get all the workflows a user is part of based on their roles in one query. I've found that you can get the results like this:
user.Roles.SelectMany(r => r.Workflows)
but this generates a query for each role which is obviously less than ideal.
Is there a proper way to do this without having to resort to hacks like generating a view or writing straight sql?
You could try the following two queries:
This one is better readable, I think:
var workflows = context.Users
.Where(u => u.UserId == givenUserId)
.SelectMany(u => u.Roles.SelectMany(r => r.Workflows))
.Distinct()
.ToList();
(Distinct because a user could have two roles and these roles may contain the same workflow. Without Distinct duplicate workflows would be returned.)
But this one performs better, I believe:
var workflows = context.WorkFlows
.Where(w => w.Roles.Any(r => r.Users.Any(u => u.UserId == givenUserId)))
.ToList();
So it turns out the order which you select makes the difference:
user.Select(x => x.Roles.SelectMany(y => y.Workflows)).FirstOrDefault()
Have not had a chance to test this, but it should work:
Users.Include(user => user.UserRoles).Include(user => user.UserRole.WorkflowRoles.Workflow)
If the above is not correct then is it possible that you post your class structure?
Given the following LINQ to Entities (EF4) query...
var documents =
from doc in context.Documents
.Include(d => d.Batch.FinancialPeriod)
.Include(d => d.Batch.Contractor)
.Include(d => d.GroupTypeHistory.Select(gth => gth.GroupType))
.Include(d => d.Items.Select(i => i.Versions))
.Include(d => d.Items.Select(i => i.Versions.Select(v => v.ProductPackPeriodic.ProductPack.Product.HomeDelivery)))
.Include(d => d.Items.Select(i => i.Versions.Select(v => v.ProductPackPeriodic.ProductPack.Product.Manufacturer)))
.Include(d => d.Items.Select(i => i.Versions.Select(v => v.ProductPackPeriodic.ProductPeriodic)))
.Include(d => d.Items.Select(i => i.Versions.Select(v => v.ProductPackPeriodic.SpecialContainerIndicator)))
.Include(d => d.Items.Select(i => i.Versions.Select(v => v.Endorsements.Select(e => e.Periodic))))
where doc.ID == this.documentID
select doc;
Document document = documents.FirstOrDefault();
Where ...
a Document will always have a Batch which in turn will always have a
financial period and a contractor
a Document will always have one or
more GroupTypeHistory, each of which will always have a GroupType
a Document will have zero or more Item's, which in turn will have one
or more Version's
a Version will have zero or one ProductPackPeriodic
a ProductPackPeriodic will always have one ProductPack and one
ProductPeriodic, along with zero or one SpecialContainerIndicator
a ProductPack will always have one Product
a Product will have zero or one Manufacturer, and zero or one HomeDelivery
a Version will have zero or more Endorsements, each of which will have one Periodic
The above LINQ query generates some of the worst TSQL that I have ever seen, with some of the related tables included multiple times (probably because they are referenced within the query multiple times) and takes significantly longer than I would like to run (the tables concerned can contain millions of rows, but this is not the cause).
I know that there has to be a better way to write it (taking into account all of the different reference types that I describe above) which will result in better TSQL, but every version that I try fails to return the data correctly.
Can anybody assist in pointing me to a better solution?
If it makes it any easier to understand, were I writing TSQL directly I would be looking at something like the following...
select *
from Document d
inner join Batch b
inner join FinancialPeriod fp on b.FinancialPeriodID = fp.FinancialPeriodID
inner join Contractor c on b.ContractorID = c.ContractorID
on d.BatchID = b.BatchID
inner join DocumentGroupType dgt on d.DocumentID = dgt.DocumentID
left join Item i
left join ItemVersion iv
left join ProductPackPeriodic ppp
inner join ProductPack pack
inner join Product p
left join Manufacturer m on p.ManufacturerID = m.ManufacturerID
left join HomeDeliveryProduct hdp on p.ProductID = hdp.ProductID
on pack.ProductID = p.ProductID
on ppp.ProductPackID = pack.ProductPackID
inner join ProductPeriodic pp on ppp.ProductPeriodicID = pp.ProductPeriodicID
left join SpecialContainerIndicator sci on ppp.SpecialContainerIndicatorCode = sci.SpecialContainerIndicatorCode
on iv.ProductPackPeriodicID = ppp.ProductPackPeriodicID
left join ItemVersionEndorsement ive
inner join EndorsementPeriodic ep on ive.EndorsementPeriodicID = ep.EndorsementPeriodicID
on iv.ItemVersionID = ive.ItemVersionID
on i.ItemID = iv.ItemID
on d.DocumentID = i.DocumentID
where d.DocumentID = 33 -- example value
This may also make the relationship requirements clearer.
Thanks in advance.
For scenarios like this i write specific stored procedures and then use EFExtensions with a custom materializer to not only get excellent performance but also correctly materialized entities.
I don't have any good answer for EF, but it might suit you to use a micro ORM for certain complex queries. Micro ORMs are essentially low-level wrappers over SQL, that allow you to get strongly typed objects, along with other convenience features. You can take a look at Dapper, for example, which is used by this very site for some of the bottleneck queries. It should perform very close to native SQL performance.
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