How to perform COUNT with HABTM and left join? - sql

Considering the following associations:
class Pool < ActiveRecord::Base
has_and_belongs_to_many :participations
end
class Participation < ActiveRecord::Base
has_and_belongs_to_many :pools
end
I want to get the number of participations in each pools (even if there is no participation).
This is what I am expecting (id is pool id):
+----+----------------------------+
| id | count('participations.id') |
+----+----------------------------+
| 1 | 1 |
| 2 | 0 |
| 3 | 0 |
| 4 | 0 |
| 5 | 0 |
| 6 | 0 |
| 7 | 0 |
| 8 | 0 |
+----+----------------------------+
This is what I get:
+----+----------------------------+
| id | count('participations.id') |
+----+----------------------------+
| 1 | 3 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 1 |
| 6 | 1 |
| 7 | 1 |
| 8 | 1 |
+----+----------------------------+
To obtain this result, I do a left join with a group by and a count:
Pool.joins('LEFT JOIN participations_pools ON
participations_pools.pool_id = pools.id LEFT JOIN participations ON
participations.id =
participations_pools.participation_id').select("pools.id,
count('participations.id')").group('pools.id')
I don't know how to get the good result and why I get that?
EDIT:
My answer at my question:
Pool.joins('LEFT JOIN participations_pools ON
participations_pools.pool_id = pools.id LEFT JOIN participations ON
participations.id =
participations_pools.participation_id').select("pools.id,
count(participations.id)").group('pools.id')
The quotes around count are the cause of my troubles

If you don't want to worry about that, write your query using only ActiveRecord methods:
Pool.joins('LEFT JOIN participations_pools ON participations_pools.pool_id = pools.id')
.joins('LEFT JOIN participations ON participations.id = participations_pools.participation_id')
.group('pools.id').count('participations.id')
The result will be a hash having pools.id as a key and count('participations.id') as a value for each row extracted from your database.
More info for count method: http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-count
If count is used with group, it returns a Hash whose keys represent the aggregated column, and the values are the respective amounts:

Related

Select from a concatenation of two columns after a left join

Problem description
Let the tables C and V have those values
>> Table V <<
| UnID | BillID | ProductDesc | Value | ... |
| 1 | 1 | 'Orange Juice' | 3.05 | ... |
| 1 | 1 | 'Apple Juice' | 3.05 | ... |
| 1 | 2 | 'Pizza' | 12.05 | ... |
| 1 | 2 | 'Chocolates' | 9.98 | ... |
| 1 | 2 | 'Honey' | 15.98 | ... |
| 1 | 3 | 'Bread' | 3.98 | ... |
| 2 | 1 | 'Yogurt' | 8.55 | ... |
| 2 | 1 | 'Ice Cream' | 7.05 | ... |
| 2 | 1 | 'Beer' | 9.98 | ... |
| 2 | 2 | 'League of Legends RP' | 40.00 | ... |
>> Table C <<
| UnID | BillID | ClientName | ... |
| 1 | 1 | 'Alexander' | ... |
| 1 | 2 | 'Tom' | ... |
| 1 | 3 | 'Julia' | ... |
| 2 | 1 | 'Tom' | ... |
| 2 | 2 | 'Alexander' | ... |
Table C have the values of each product, which is associated with a bill number. Table V has the relationship between the client name and the bill number. However, the bill number has a counter that is dependent on the UnId, which is the store unity ID. That being said, each store has it`s own Bill number 1, number 2, etc. Also, the number of bills from each store are not equal.
Solution description
I'm trying to make select between the C left join V without sucess. Because each BillID is dependent on the UnID, I have to make the join considering the concatenation between those two columns.
I've used this script, but it gives me an error.
SELECT
SUM(C.Value),
V.ClientName
FROM
C
LEFT JOIN
V
ON
CONCAT(C.UnID, C.BillID) = CONCAT(V.UnID, V.BillID)
GROUP BY
V.ClientName
and SQL server returns me this 'CONCAT' is not a recognized built-in function name.
I'm using Microsoft SQL Server 2008 R2
Is the use of CONCAT wrong? Or is it the way I tried to SELECT? Could you give me a hand?
[OBS: The tables I've present you are just for the purpose of explaining my difficulties. That being said, if you find any errors in the explanation, please let me know to correct them.]
You should be joining on the equality of the UnID and BillID columns in the two tables:
SELECT
c.ClientName,
COALESCE(SUM(v.Value), 0) AS total
FROM C c
LEFT JOIN V v
ON c.UnID = v.UnID AND
c.BillID = v.BillID
GROUP BY
c.ClientName;
In theory you could try joining on CONCAT(UnID, BillID). However, you could run into problems. For example, UnID = 1 with BillID = 23 would, concatenated together, be the same as UnID = 12 and BillID = 3.
Note: We wrap the sum with COALESCE, because should a given client have no entries in the V table, the sum would return NULL, which we then replace with zero.
concat is only available in sql server 2012.
Here's one option.
SELECT
SUM(C.Value),
V.ClientName
FROM
C
LEFT JOIN
V
ON
cast(C.UnID as varchar(100)) + cast(C.BillID as varchar(100)) = cast(V.UnID as varchar(100)) + cast(V.BillID as varchar(100))
GROUP BY
V.ClientName

Select all rows where rows in another joined table match condition

So I want to select all rows where a subset of rows in another table match the given values.
I have following tables:
Main Profile:
+----+--------+---------------+---------+
| id | name | subprofile_id | version |
+----+--------+---------------+---------+
| 1 | Main 1 | 4 | 1 |
| 2 | Main 1 | 5 | 2 |
| 3 | Main 2 | ... | 1 |
+----+--------+---------------+---------+
Sub Profile:
+---------------+----------+
| subprofile_id | block_id |
+---------------+----------+
| 4 | 6 |
| 4 | 7 |
| 5 | 8 |
| 5 | 9 |
+---------------+----------+
Block:
+----------+-------------+
| block_id | property_id |
+----------+-------------+
| 7 | 10 |
| 7 | 11 |
| 7 | 12 |
| 7 | 13 |
| 8 | 14 |
| 8 | 15 |
| 8 | 16 |
| 8 | 17 |
| ... | ... |
+----------+-------------+
Property:
+----+--------------------+--------------------------+
| id | name | value |
+----+--------------------+--------------------------+
| 10 | Description | XY |
| 11 | Responsible person | Mr. Smith |
| 12 | ... | ... |
| 13 | ... | ... |
| 14 | Description | XY |
| 15 | Responsible person | Mrs. Brown |
| 16 | ... | ... |
| 17 | ... | ... |
+----+--------------------+--------------------------+
The user can define multiple conditions on the property table. For example:
Description = 'XY'
Responsible person = 'Mr. Smith'
I need all 'Main Profiles' with the highest version which have ALL matching properties and can have more of course which do not match.
It should be doable in JPA because i would translate it into QueryDSL to build typesafe, dynamic queries with the users input.
I already searched trough all questions regarding similar problems but couldn't project the answer onto my problem.
Also, I've already tried to write a query which worked quite good but retrieved all rows with at least one matching condition. Therefore i need all properties in my set but it only fetched (fetch join, which is missing in my code examplte) the matching ones.
from MainProfile as mainProfile
left join mainProfile.subProfile as subProfile
left join subProfile.blocks as block
left join block.properties as property
where mainProfile.version = (select max(mainProfile2.version)from MainProfile as mainProfile2 where mainProfile2.name = mainProfile.name) and ((property.name = 'Description' and property.value = 'XY') or (property.name = 'Responsible person' and property.value = 'Mr. Smith'))
Running my query i got two rows:
Main 1 with version 2
Main 2 with version 1
I would have expected to get only one row due to mismatch of 'responsible person' in 'Main 2'
EDIT 1:
So I found a solution which works but could be improved:
select distinct mainProfile
from MainProfile as mainProfile
left join mainProfile.subProfile as subProfile
left join subProfile.blocks as block
left join block.properties as property
where mainProfile.version = (select max(mainProfile2.version)from MainProfile mainProfile2 where mainProfile2.name = mainProfile.name)
and ((property.name = 'Description' and property.content = 'XY') or (property.name = 'Responsible person' and property.content = 'Mr. Smith'))
group by mainProfile.id
having count (distinct property) = 2
It actually retrieves the right 'Main Profiles'. But the problem is, that only the two found properties are getting fetched. I need all properties though because of further processing.

Check if relation exists and return true or false

I have 3 tables, Category Step and CategoryStep, where CategoryStep relates the two other tables together. I want to return all categories with a true/false column whether or not the relation exists in CategoryStep based on a StepID.
The schema for the tables is simple,
Category:
CategoryID | CategoryName
Step:
StepID | StepName
CategoryStep:
CategoryStepID | CategoryID | StepID
When trying to get results based on StepID, I only get the relations that exist, and not ones that don't.
SELECT [CategoryID], [Category], CAST(CASE WHEN [CategoryStep].[CategoryStep] IS NULL THEN 0 ELSE 1 END AS BIT) AS related
FROM Category
LEFT JOIN CategoryStep ON Category.CategoryID = CategoryStep.CategoryID
INNER JOIN Step ON CategoryStep.StepID = Step.StepID
WHERE Step.StepID = 2
Step Table:
|StepID | StepName
|-------|---------
| 1 | StepOne
| 2 | StepTwo
| 3 | StepThree
Category Table:
| CategoryID | CategoryName
|------------|-------------
| 1 | Holidays
| 2 | States
| 3 | Cities
| 4 | Animals
| 5 | Food
CategoryStep Table
| CategoryStepID | CategoryID | StepID
|----------------|------------|-------
| 1 | 1 | 1
| 2 | 1 | 2 <--
| 3 | 2 | 1
| 4 | 2 | 3
| 5 | 3 | 2 <--
| 6 | 4 | 1
| 7 | 4 | 2 <--
| 8 | 4 | 3
| 9 | 5 | 1
| 10 | 5 | 3
So, if I was looking for StepID = 2 the result table I am looking for is:
| CategoryID | Category | Related
|------------|----------|--------
| 1 | Holidays | 1
| 2 | States | 0
| 3 | Cities | 1
| 4 | Animals | 1
| 5 | Food | 0
Try replacing the INNER JOIN with a LEFT JOIN.
Update:
The fatal flaw with your original attempt was the WHERE clause. You were performing the correct LEFT JOIN, but the WHERE clause was filtering off category records which did not match. In the query below, I moved the check for step ID into the join condition, where it belongs.
SELECT [CategoryID], [Category],
CAST(CASE WHEN [CategoryStep].[CategoryStep] IS NULL THEN 0 ELSE 1 END AS BIT) AS related
FROM Category
LEFT JOIN CategoryStep
ON Category.CategoryID = CategoryStep.CategoryID AND
CategoryStep.StepCodeID = 2
LEFT JOIN Step
ON CategoryStep.StepID = Step.StepID

CREATE VIEW with multiple tables - must show 0 values

I have three tables:
// priorities // statuses // projects
+----+--------+ +----+-------------+ +----+------+--------+----------+
| ID | NAME | | ID | STATUS NAME | | ID | NAME | STATUS | PRIORITY |
+----+--------+ +----+-------------+ +----+------+--------+----------+
| 1 | Normal | | 1 | Pending | | 1 | a | 1 | 3 |
+----+--------+ +----+-------------+ +----+------+--------+----------+
| 2 | High | | 2 | In Progress | | 2 | b | 1 | 1 |
+----+--------+ +----+-------------+ +----+------+--------+----------+
| 3 | Urgent | | 3 | c | 2 | 1 |
+----+--------+ +----+------+--------+----------+
| 4 | d | 1 | 2 |
+----+------+--------+----------+
I need to create a view that shows how many projects hold a status of 1 and a priority of 1, how many hold a status of 1 and a priority of 2, how many hold a status of 1 and a priority of 3, and so on.
This should go through each status, then each priority, then count the projects that apply to the criteria.
The view should hold values something like this:
// VIEW (stats)
+--------+----------+-------+
| STATUS | PRIORITY | COUNT |
+--------+----------+-------+
| 1 | 1 | 1 |
+--------+----------+-------+
| 1 | 2 | 1 |
+--------+----------+-------+
| 1 | 3 | 1 |
+--------+----------+-------+
| 2 | 1 | 1 |
+--------+----------+-------+
| 2 | 2 | 0 |
+--------+----------+-------+
| 2 | 3 | 0 |
+--------+----------+-------+
This view is so that I can call, for example, how many projects have a status of 1 and a priority of 3, the answer given the data above should be 1.
Using the below select statement I've been able to produce a similar result but it does not explicitly show that 0 projects have a status of 2 and a priority of 3. I need this 0 value to be accessible the same way as any of the others with a COUNT >= 1.
// my current select statement
CREATE VIEW stats
AS
SELECT P.STATUS, P.PRIORITY, COUNT(*) AS hits
FROM projects P
GROUP BY P.STATUS, P.PRIORITY
// does not show rows where COUNT = 0
How could I create a VIEW that holds all of the priorities' ids, all of the statuses' ids, and 0 values for COUNT?
You need to generate all the rows and then get the count for each one. Here is a query that should work:
SELECT s.status, p.priority, COUNT(pr.status) AS hits
FROM (SELECT DISTINCT status FROM projects) s CROSS JOIN
(SELECT DISTINCT priority FROM projects) p LEFT JOIN
project pr
ON pr.status = s.status and pr.priority = p.priority
GROUP BY s.status, p.priority;

How to query following scenario to count number of users in Django?

I have table in database called fileupload_share.
+----+----------+----------+----------------+----------------------------------+
| id | users_id | files_id | shared_user_id | shared_date |
+----+----------+----------+----------------+----------------------------------+
| 3 | 1 | 1 | 2 | 2013-01-31 14:27:06.523908+00:00 |
| 2 | 1 | 1 | 2 | 2013-01-31 14:25:37.760192+00:00 |
| 4 | 1 | 3 | 2 | 2013-01-31 14:46:01.089560+00:00 |
| 5 | 1 | 1 | 3 | 2013-01-31 14:50:54.917337+00:00 |
I want to count the number of shared_user_id according to the file_id.
For example I want to find with how many users the file with id 1 is shared. The answer is with 2 users(shared_user_id). How can I find that in Django?
file_id = 2 #Here is your file_id variable
fileupload_share.objects.filter(file_id = file_id)
.order_by('shared_user_id').distinct('shared_user_id').count()
As comments below say this example doesn't work on MySQL, because of distinct method on field.
However you can try danihp's method:
file_id = 2 #Here is your file_id variable
fileupload_share.objects.filter(file_id = file_id)
.values_list('shared_user_id', flat=True).distinct().count()