Fetching data from many to many relationship - mysql

I am having 3 tables namely
category
case
cost
office_id
category_id
linked to >
category_id
case_number
linked to >
case_number
total_cost
Problem:
I need to fetch the total number of case and their respective cost for every office id which is there in category table
Query I have written:
select cm_c_d.case_number,cm_c.office_id,count(*) as case_count from categories as cm_c
join case as cm_c_d on cm_c.category_id = cm_c_d.category_id
join cost on cm_c_d.case_number = cost.case_number group by office_id;
but I don't think this will provide the desired result as joining all the three tables will increase the row count.
Updated SQL query:
select cm_c.office_id
, count(DISTINCT cm_costs.case_number) as case_count
, SUM(total_charge) AS overall_cost
from cm_categories as cm_c
JOIN cm_case_details as cm_c_d
on cm_c.category_id = cm_c_d.category_id
join cm_costs
on cm_c_d.case_number = cm_costs.case_number
group by cm_c.office_id
;

If you want distinct case count (adjusted with the new table names):
SELECT cm_c.office_id
, COUNT(DISTINCT cm_costs.case_number) AS case_count
, SUM(total_charge) AS overall_cost
FROM cm_categories AS cm_c
JOIN cm_case_details AS cm_c_d ON cm_c.category_id = cm_c_d.category_id
JOIN cm_costs ON cm_c_d.case_number = cm_costs.case_number
GROUP BY cm_c.office_id
;
COUNT(DISTINCT x) means count the distinct number of x values for each group.
Also notice we needed to quote the case table name. As mentioned in prior comments, that's a reserved word. I suggest avoiding use of reserved words as identifiers (table, column, etc names).
I've removed case_number from your SELECT list because it's not functionally dependent on the GROUP BY terms. That means the design/query does not guarantee that there is at most one case_number for each office_id.
If you want a case_number in the select list, you would need to use a form of aggregation (like COUNT), as follows:
SELECT cm_c.office_id
, COUNT(DISTINCT cm_costs.case_number) AS case_count
, SUM(total_charge) AS overall_cost
, MIN(cost.case_number) AS some_case_number
, GROUP_CONCAT(cost.case_number) AS all_cases
FROM cm_categories AS cm_c
JOIN cm_case_details AS cm_c_d ON cm_c.category_id = cm_c_d.category_id
JOIN cm_costs ON cm_c_d.case_number = cm_costs.case_number
GROUP BY cm_c.office_id
;
Be careful of GROUP_CONCAT for very large sets, since that result column width could get rather wide.
Here's the test case, with necessary adjustments:
Test case
For more detail on GROUP BY and functional dependence, see the following links:
group-by-handling
group-by-functional-dependence

Related

MySQL - Split SELECT query (WHERE IN) into two rows

So, I am using MySQL to do a query and have a database like this:
I wanted to do a select query to show every transaction of Bank A and C based on their prefix. This is the expected result:
I have done the query as followed:
SELECT
M.merk AS 'Merk',
COUNT( T.amount ) AS 'Jumlah Transaksi',
SUM( T.amount ) AS 'Total Amount'
FROM
tb_transaksiatm T
INNER JOIN tb_issuer I ON
T.nomor_kartu LIKE CONCAT(I.prefix, '%')
INNER JOIN tb_terminalatm M ON
T.terminal = M.nomor_terminal
WHERE
I.bank IN ('A', 'C') # Declare the Bank;
But my result is not the same as expected. It combined and summed both rows from Bank A and Bank C. This is my result:
The question is, how do I split the WHERE IN condition into two rows? Any help would be appreciated. Thank you.
Note: The language is Indonesian.
SELECT
M.merk AS 'Merk',
COUNT( T.amount ) AS 'Jumlah Transaksi',
SUM( T.amount ) AS 'Total Amount'
FROM
tb_transaksiatm T
INNER JOIN tb_issuer I ON
T.nomor_kartu LIKE CONCAT(I.prefix, '%')
INNER JOIN tb_terminalatm M ON
T.terminal = M.nomor_terminal
WHERE
I.bank IN ('A', 'C') # Declare the Bank
group by M.merk;
When you use an aggregation function such as SUM or COUNT and you do not specify a GROUP BY, it will aggregate all rows together. Fields such as M.Merk that could vary between the rows being aggregated will have a value taken from an arbitrary one of the rows being aggregated, though modern versions of mysql default to an ONLY_FULL_GROUP_BY mode where selecting such a field will result in an error instead of an arbitrary value.
It sounds like you intend to have a GROUP BY I.Bank, M.Merk, though it is confusing that you don't include the bank in your selected fields.

Where clause on count column

I have two tables pdc and class
select roll_no as roll,sum(pdc.amount) as amount,count(amount) as given,
stu_profile.name,f_name,scholarship,class_id,batch_id,statuss
from stu_profile left join
pdc
on pdc.roll=stu_profile.roll_no
where 1 and class_id!='' and given=0
group by roll
I want a condition on count (amount) column
Use having clause instead
. . .
having count(amount) = 0;
Then answer to your question is the having clause. But, your query is malformed and would not work in most databases, including the more recent versions of MySQL.
The solution is simple: In an aggregation query, the only columns in the SELECT should be the GROUP BY keys or arguments to aggregation functions.
Let me assume that you want:
select p.roll_no as roll, sum(pdc.amount) as amount,
count(pdc.amount) as given,
p.name, p.f_name, p.scholarship, p.class_id, p.batch_id, p.statuss
from stu_profile p left join
pdc
on pdc.roll = p.roll_no
where 1 and class_id <> ''
group by roll, p.name, p.f_name, p.scholarship, p.class_id, p.batch_id, p.statuss
You can then just add something like:
having given = 0

MySQL Inner join naming error?

http://sqlfiddle.com/#!9/e6effb/1
I'm trying to get a top 10 by revenue per brand for France on december.
There are 2 tables (first table has date, second table has brand and I'm trying to join them)
I get this error "FUNCTION db_9_d870e5.SUM does not exist. Check the 'Function Name Parsing and Resolution' section in the Reference Manual"
Is my use of Inner join there correct?
It's because you had an extra space after SUM. Please change it from
SUM (o1.total_net_revenue)to SUM(o1.total_net_revenue).
See more about it here.
Also after correcting it, your query still had more error as you were not selecting order_id on your intermediate table i2 so edited here as :
SELECT o1.order_id, o1.country, i2.brand,
SUM(o1.total_net_revenue)
FROM orders o1
INNER JOIN (
SELECT i1.brand, SUM(i1.net_revenue) AS total_net_revenue,order_id
FROM ordered_items i1
WHERE i1.country = 'France'
GROUP BY i1.brand
) i2
ON o1.order_id = i2.order_id AND o1.total_net_revenue = i2.total_net_revenue
AND o1.total_net_revenue = i2.total_net_revenue
WHERE o1.country = 'France' AND o1.created_at BETWEEN '2016-12-01' AND '2016-12-31'
GROUP BY 1,2,3
ORDER BY 4
LIMIT 10`
--EDIT stack Fan is correct that the o2.total_net_revenue exists. My confusion was because the data structure duplicated three columns between the tables, including one that was being looked for.
There were a couple errors with your SQL statement:
1. You were referencing an invalid column in your outer-select-SUM function. I believe you're actually after i2.total_net_revenue.
The table structure is terrible, the "important" columns (country, revenue, order_id) are duplicated between the two tables. I would also expect the revenue columns to share the same name, if they always have the same values in them. In the example, there's no difference between i1.net_revenue and o1.total_net_revenue.
In your inner join, you didn't reference i1.order_id, which meant that your "on" clause couldn't execute correctly.
PROTIP:
When you run into an issue like this, take all the complicated bits out of your query and get the base query working correctly first. THEN add your functions.
PROTIP:
In your GROUP BY clause, reference the actual columns, NOT the column numbers. It makes your query more robust.
This is the query I ended up with:
SELECT o1.order_id, o1.country, i2.brand,
SUM(i2.total_net_revenue) AS total_rev
FROM orders o1
INNER JOIN (
SELECT i1.order_id, i1.brand, SUM(i1.net_revenue) AS total_net_revenue
FROM ordered_items i1
WHERE i1.country = 'France'
GROUP BY i1.brand
) i2
ON o1.order_id = i2.order_id AND o1.total_net_revenue = i2.total_net_revenue
AND o1.total_net_revenue = i2.total_net_revenue
WHERE o1.country = 'France' AND o1.created_at BETWEEN '2016-12-01' AND '2016-12-31'
GROUP BY o1.order_id, o1.country, i2.brand
ORDER BY total_rev
LIMIT 10

Incorrect group by and order by merge

I have couple tables joined in MySQL - one has many others.
And try to select items from one, ordered by min values from another table.
Without grouping in seems to be like this:
Code:
select `catalog_products`.id
, `catalog_products`.alias
, `tmpKits`.`minPrice`
from `catalog_products`
left join `product_kits` on `product_kits`.`product_id` = `catalog_products`.`id`
left join (
SELECT MIN(new_price) AS minPrice, id FROM product_kits GROUP BY id
) AS tmpKits on `tmpKits`.`id` = `product_kits`.`id`
where `category_id` in ('62')
order by product_kits.new_price ASC
Result:
But when I add group by, I get this:
Code:
select `catalog_products`.id
, `catalog_products`.alias
, `tmpKits`.`minPrice`
from `catalog_products`
left join `product_kits` on `product_kits`.`product_id` = `catalog_products`.`id`
left join (
SELECT MIN(new_price) AS minPrice, id FROM product_kits GROUP BY id
) AS tmpKits on `tmpKits`.`id` = `product_kits`.`id`
where `category_id` in ('62')
group by `catalog_products`.`id`
order by product_kits.new_price ASC
Result:
And this is incorrect sorting!
Somehow when I group this results, I get id 280 before 281!
But I need to get:
281|1600.00
280|2340.00
So, grouping breaks existing ordering!
For one, when you apply the GROUP BY to only one column, there is no guarantee that the values in the other columns will be consistently correct. Unfortunately, MySQL allows this type of SELECT/GROUPing to happen other products don't. Two, the syntax of using an ORDER BY in a subquery while allowed in MySQL is not allowed in other database products including SQL Server. You should use a solution that will return the proper result each time it is executed.
So the query will be:
For one, when you apply the GROUP BY to only one column, there is no guarantee that the values in the other columns will be consistently correct. Unfortunately, MySQL allows this type of SELECT/GROUPing to happen other products don't. Two, the syntax of using an ORDER BY in a subquery while allowed in MySQL is not allowed in other database products including SQL Server. You should use a solution that will return the proper result each time it is executed.
So the query will be:
select CP.`id`, CP.`alias`, TK.`minPrice`
from catalog_products CP
left join `product_kits` PK on PK.`product_id` = CP.`id`
left join (
SELECT MIN(`new_price`) AS "minPrice", `id` FROM product_kits GROUP BY `id`
) AS TK on TK.`id` = PK.`id`
where CP.`category_id` IN ('62')
order by PK.`new_price` ASC
group by CP.`id`
The thing is that group by does not recognize order by in MySQL.
Actually, what I was doing is really bad practice.
In this case you should use distinct and by catalog_products.*
In my opinion, group by is really useful when you need group result of agregated functions.
Otherwise you should not use it to get unique values.

COUNT evaluate to zero if no matching records

Take the following:
SELECT
Count(a.record_id) AS newrecruits
,a.studyrecord_id
FROM
visits AS a
INNER JOIN
(
SELECT
record_id
, MAX(modtime) AS latest
FROM
visits
GROUP BY
record_id
) AS b
ON (a.record_id = b.record_id) AND (a.modtime = b.latest)
WHERE (((a.visit_type_id)=1))
GROUP BY a.studyrecord_id;
I want to amend the COUNT part to display a zero if there are no records since I assume COUNT will evaluate to Null.
I have tried the following but still get no results:
IIF(ISNULL(COUNT(a.record_id)),0,COUNT(a.record_id)) AS newrecruits
Is this an issue because the join is on record_id? I tried changing the INNER to LEFT but also received no results.
Q
How do I get the above to evaluate to zero if there are no records matching the criteria?
Edit:
To give a little detail to the reasoning.
The studies table contains a field called 'original_recruits' based on activity before use of the database.
The visits tables tracks new_recruits (Count of records for each study).
I combine these in another query (original_recruits + new_recruits)- If there have been no new recruits I still need to display the original_recruits so if there are no records I need it to evalulate to zero instead of null so the final sum still works.
It seems like you want to count records by StudyRecords.
If you need a count of zero when you have no records, you need to join to a table named StudyRecords.
Did you have one? Else this is a nonsense to ask for rows when you don't have rows!
Let's suppose the StudyRecords exists, then the query should look like something like this :
SELECT
Count(a.record_id) AS newrecruits -- a.record_id will be null if there is zero count for a studyrecord, else will contain the id
sr.Id
FROM
visits AS a
INNER JOIN
(
SELECT
record_id
, MAX(modtime) AS latest
FROM
visits
GROUP BY
record_id
) AS b
ON (a.record_id = b.record_id) AND (a.modtime = b.latest)
LEFT OUTER JOIN studyrecord sr
ON sr.Id = a.studyrecord_id
WHERE a.visit_type_id = 1
GROUP BY sr.Id
I solved the problem by amending the final query where I display the result of combining the original and new recruits to include the IIF there.
SELECT
a.*
, IIF(IsNull([totalrecruits]),consents,totalrecruits)/a.target AS prog
, IIf(IsNull([totalrecruits]),consents,totalrecruits) AS trecruits
FROM
q_latest_studies AS a
LEFT JOIN q_totalrecruitment AS b
ON a.studyrecord_id=b.studyrecord_id
;