Complicated SQLite or SQL SUM() between several rows - mysql

I need to extend this question: SQLite SUM() between several rows
Before table was
and query was :
SELECT Sum(SERVICE)
FROM (
SELECT ft.*, (
SELECT count(*)
FROM fuel_table ft2
WHERE ft2.note='Your tank was full up.' and ft2.id>=ft.id)
AS numNotesAhead
FROM fuel_table ft)
AS ft
WHERE numNotesAhead=1
but now my fuel table is split and looks like:
I've tried:
SELECT Sum(fuel_table.SERVICE)
FROM (
SELECT ft.*, (
SELECT count(*)
FROM fuel_table ft2
LEFT JOIN note_table ON fuel_table.EnterId = note_table.EnterId
WHERE ft2.note='Your tank was full up.' and ft2.id>=ft.id)
AS numNotesAhead
FROM fuel_table ft)
AS ft
WHERE numNotesAhead=1
but it doesn't works. My app just stops.
*Note there is "_" in name of fuel table.

So there are two issues here - one is the joining of the tables, which is different from your previous problem. The second issue is doing the appropriate summation of the right values, which is already beautifully answered here: SQLite SUM() between several rows.
So, let's look at the JOIN.
Since the fuel_table has the greater number of records, we're going to expect some 'NULL' values from the desired JOIN with the note_table. (And we're not going to lose any records from fuel_table since we're joining on enterId, so we don't need a FULL OUTER JOIN). This means (for a LEFT JOIN), we want the fuel_table to be on the left:
SELECT f.id, f.service, f.enterid, n.note
FROM fuel_table f
LEFT JOIN note_table n
ON f.enterid = n.enterid
This will give us output:
id service enterid note
----------------------------------
2 50 25 Yes
3 20 26 NULL
4 20 35 Yes
8 30 36 NULL
9 15 37 NULL
10 20 42 Yes
So far, so good.
Now - rather than thinking about it - I just looked at the answer to the previous question, and substituted this subquery from the fuel_table from this answer: SQLite SUM() between several rows
which is a very good answer, and I can take no credit for any part of it in the following bit ...
Putting them together you get the ugly but functional query:
SELECT SUM(fq.service) AS total
FROM
(SELECT fq2.*,
(SELECT COUNT(*)
FROM (SELECT f.id, f.service, f.enterid, n.note
FROM fuel_table f
LEFT JOIN note_table n
ON f.enterid = n.enterid) fq1
WHERE fq1.note = 'Yes' AND fq1.id >= fq2.id) AS numNotesAhead
FROM
(SELECT f.id, f.service, f.enterid, n.note
FROM fuel_table f
LEFT JOIN note_table n
ON f.enterid = n.enterid) fq2) fq
WHERE numNotesAhead = 1
which returns output:
total
------
65

Related

Count of joined items per group in MySql

I need to get a set of results showing the number of items accumulated for each 'esta' group.
I'm grouping the results by establishment.
Establishment is inner joined to base.
Left joined items are joined against the base.
So in Esta group 2, let's say there are 3 base ids. Each written and verbal record attached to the base ID would count towards that esta in the results set. There can be multiple 'written' or 'verbal' attached to each base record.
I have 6 verbals and 4 writtens in the database, they are spread around the different 'esta' records. In my query, they are all counting towards the first row of the result I get.
I have tried the same with much more data, and regardless of the 'esta', the first row contains every left joined element counted together.
sql:
SELECT
esta.enf_esta_id
,SUM(IF(verbal.enf_verbal_id is not null,1,0)) as verbals
,SUM(IF(written.enf_written_id is not null,1,0)) as writtens
FROM
enf_base base
INNER JOIN enf_esta esta ON esta.enf_esta_id = base.enf_esta_id
LEFT JOIN enf_verbal verbal ON verbal.enf_base_id = base.enf_base_id
LEFT JOIN enf_written written ON written.enf_base_id = base.enf_base_id
WHERE
1=1
GROUP BY
esta.enf_esta_id
result:
enf_esta_id verbals writtens
2 10 10
3 1 0
4 1 1
6 0 0
To prove that the top row is incorrect, here are the results of just getting the verbals and writtens from enf_esta_id 2.
SELECT
COUNT( * ) AS total
FROM
enf_written
INNER JOIN enf_base ON enf_base.enf_base_id = enf_written.enf_base_id
INNER JOIN enf_esta ON enf_base.enf_esta_id = enf_esta.enf_esta_id
WHERE
enf_esta.enf_esta_id =2
yields:
5
And the same with enf_verbal yields 2. adding up the totals of each gives us the correct 10 if we discount the top row of the problem query result.
Can anyone help me get the result I need?
You are multiplying. Say there are 2 verbals and 5 writtens then your joins make these 10 records (i.e. all combinations). Rather then joining tables and aggregating then, you should first aggregate and then join your aggregates. In your case this is aggregates per base ID, which you will finally further aggregate to get estas.
select
base.enf_esta_id,
coalesce(sum(verbal.cnt), 0) as verbals,
coalesce(sum(written.cnt), 0) as writtens
from enf_base base
left join
(
select enf_base_id, count(*) as cnt
from enf_verbal
group by enf_base_id
) verbal on verbal.enf_base_id = base.enf_base_id
left join
(
select enf_base_id, count(*) as cnt
from enf_written
group by enf_base_id
) written on written.enf_base_id = base.enf_base_id
group by base.enf_esta_id;

MySQL SELECT with LEFT JOIN & SUM GROUP BY

Need some help with mysql select with left join, i tried alot of options, looked up for the answers on stack, but can't solve this problem. I'll be grateful for any help. Thanks.
SELECT profile_main.id,
profile_main.add_date,
profile_main.expire_date,
profile_main.status_begin,
profile_main.status_expire,
profile_main.views,
profile_main.last,
profile_main.confirm,
profile_info.name,
profile_contacts.remains,
profile_rating.rating
FROM profile_main
LEFT JOIN profile_info
ON profile_main.id=profile_info.profile_id
LEFT JOIN profile_contacts
ON profile_main.id=profile_contacts.profile_id
LEFT JOIN (
SELECT profile_rating.profile_id, SUM(profile_rating.rating)
FROM profile_rating
GROUP BY profile_rating.profile_id
) profile_rating
ON profile_main.id=profile_rating.profile_id
WHERE profile_main.user_id=$user_id
AND profile_main.deleted=0
The problem is LEFT JOIN with profile_rating. I need SUM(profile_rating.rating)
here is the profile_main table. table what saving all profiles
enter image description here
here is the profile_rating table. [edit] This table may not contain all profiles rating, just those wich were rated earlyer
id profile_id user_id rating ip_addr add_date deleted
--------------------------------------------------------------
1 16 5 15 111.111.111.111 123432433 0
2 16 5 17 111.111.111.111 123432433 0
3 16 5 25 111.111.111.111 123432433 0
4 16 5 34 111.111.111.111 123432433 0
5 16 5 12 111.111.111.111 123432433 0
If something else is needed, i will add it
Your query looks fine and it is a good idea to join pre-aggregated data, just as you are doing here. (A good habit in order not to mistakenly multiply the aggregates from one table with matches in another.)
But you access the field profile_rating.rating, which is not available, because you no longer deal with single record values, but sums. Make an alias name for the sum: SUM(rating) AS sum_rating and use that in your select clause.
SELECT
m.id,
m.add_date,
m.expire_date,
m.status_begin,
m.status_expire,
m.views,
m.last,
m.confirm,
i.name,
c.remains,
r.sum_rating
FROM profile_main m
LEFT JOIN profile_info i ON m.id = i.profile_id
LEFT JOIN profile_contacts c ON m.id = c.profile_id
LEFT JOIN
(
SELECT
profile_id,
SUM(rating) AS sum_rating
FROM profile_rating
GROUP BY profile_id
) r ON m.id = r.profile_id
WHERE m.user_id = $user_id
AND m.deleted = 0;
I also use table aliases to enhance readaility.
Why do you SELECT in the JOIN?
Just select the fields in your main select:
SELECT profile_main.id,
profile_main.add_date,
profile_main.expire_date,
profile_main.status_begin,
profile_main.status_expire,
profile_main.views,
profile_main.last,
profile_main.confirm,
profile_info.name,
profile_contacts.remains,
profile_rating.rating,
profile_rating.profile_id,
SUM(profile_rating.rating) AS rating_sum
FROM profile_main
LEFT JOIN profile_info
ON profile_main.id=profile_info.profile_id
LEFT JOIN profile_contacts
ON profile_main.id=profile_contacts.profile_id
LEFT JOIN profile_rating
ON profile_main.id=profile_rating.profile_id
WHERE profile_main.user_id=$user_id
AND profile_main.deleted=0
GROUP BY profile_rating.profile_id
So you get your summed rating back as rating_sum
you can try below query and let me know if need some thing else so that I can modify-
SELECT profile_main.id,
profile_main.add_date,
profile_main.expire_date,
profile_main.status_begin,
profile_main.status_expire,
profile_main.views,
profile_main.last,
profile_main.confirm,
profile_info.name,
profile_contacts.remains,
SUM(profile_rating.rating)
FROM profile_main
LEFT JOIN profile_info
ON profile_main.id=profile_info.profile_id
LEFT JOIN profile_contacts
ON profile_main.id=profile_contacts.profile_id
LEFT JOIN profile_rating
ON profile_main.id=profile_rating.profile_id
WHERE profile_main.user_id=$user_id
AND profile_main.deleted=0
GROUP BY profile_main.id
Similar questions has already been answered on SO. You are doing the groupby on your wrong column which is not available, use table alias as shown above answer. Please refer these links :
SQL Server Query LEFT JOIN, SUM and GROUP BY and I'm stumped!
And
Left join, sum and count group by

Why duplicate query results, without "GROUP BY"?

I have this query:
SELECT C.ID_PASS, C.ID_MERCE, CTRL.ESITO, M.ID_CAT, M.QTA, M.DESCRIZ,
CTRL.ID_PUNTO, CTRL.ID_ADDETTO, C.DATE_OPEN, C.DATE_CLOSE, C.NOTE
FROM CONTESTAZIONI C, CONTROLLI CTRL, FUNZIONARI F, ADDETTI A, MERCI M
WHERE A.ID=CTRL.ID_ADDETTO
AND A.ID_FUNZ=501
AND M.ID=C.ID_MERCE
AND M.ID_PASS=C.ID_PASS
AND CTRL.ESITO > 1
GROUP BY C.ID_PASS;
Why, if I don't add GROUP BY C.ID_PASS, do I get 20 rows (instead of 2).
You get a cross-product when doing joins with "," operator. You should include the IDs from all tables into your where clause!
Basically, you need to link all 5 tables together by ensuring equalities of their rows' IDs, because as noted in the comment, you have two groups of tables, but they are not linked and the resulting set thus has lots of duplicates.
GROUP BY is used to group rows together when using aggregate functions, such as SUM or COUNT.
If you had, for example, 4 records, with say 2 for each cust id, each with a value:-
CustId Spend
1 10
1 20
2 30
2 40
If you wanted to know the total of each value for each customer you would use something like:-
SELECT CustId, SUM(Spend) FROM SomeTable GROUP BY CustId
This would give you
CustId Sum(Spend)
1 30
2 70
Part of what it does is remove the duplicated rows and sum up all the values into one row.
It can be misused without an aggregate function to remove duplicates and this is what you have done. Hence 2 records instead of 20.
Note that if you have fields in the SELECT that are not in the GROUP BY variable, and which are not 100% dependent on the group by fields then the value of that field is indeterminate.
For example
CustId Spend ShopId
1 10 1
1 20 2
2 30 3
2 40 4
If you wanted to know the total of each value for each customer you would use something like:-
SELECT CustId, ShopId, SUM(Spend) FROM SomeTable GROUP BY CustId
This would give you
CustId Sum(Spend) ShopId
1 30 Could be 1 or could be 2
2 70 Could be 3 or could be 4
In your query this probably applies to the fields CTRL.ESITO, M.ID_CAT, M.QTA, M.DESCRIZ,
CTRL.ID_PUNTO, CTRL.ID_ADDETTO.
You are doing an implicit cross join on the tables. Try using different type of Join such as Inner, Right or Left.
example:
SELECT *
FROM CONTESTAZIONI c
INNER JOIN CONTROLLI ctrl ON c.ID = ctrl.ContId
One advantage of the modern (meaning post-1992) explicit JOIN notation is that you are less likely to forget joining conditions. You have 5 tables; you need (at least) 4 join conditions. Your SQL has only 2 join conditions (one of them a compound join). You've not given us enough schema to be sure of coming up with the right columns for joining, but your query should probably be something like:
SELECT C.ID_PASS, C.ID_MERCE, CTRL.ESITO, M.ID_CAT, M.QTA, M.DESCRIZ,
L.ID_PUNTO, L.ID_ADDETTO, C.DATE_OPEN, C.DATE_CLOSE, C.NOTE
FROM ADDETTI A
JOIN CONTROLLI L ON A.ID = L.ID_Addetto
JOIN CONTESTAZIONI C ON A.xxx1 = C.xxx2
JOIN FUNZIONARI F ON C.yyy1 = F.yyy2
JOIN MERCI M ON M.ID = C.ID_Merce AND M.ID_Pass = C.ID_Pass
WHERE A.ID_FUNZ=501
AND L.ESITO > 1;
Note that you showed the join of A and L (renamed from CTRL), and M and C. The joins of C to A and of F to C are semi-arbitrary guesses (and the column names xxx1 etc are placeholders for your real column names); you will need to understand your schema and make the appropriate joins.
Thanks to everbody. Now I understood: I have to join together all the tables that I put in select (of course only that are in relationship); without this join I get a cross product. I can do join either "JOIN...ON..." or "WHERE ...". I made join with WHERE. Now works ok:
SELECT C.ID_PASS, C.ID_PUNTO, C.ID_ADDETTO, C.TIME_START,
C.TIME_END, C.ESITO, P.ID_NAZ, C.ID_MERCE, M.QTA, M.DESCRIZ, M.ID_CAT
FROM CONTROLLI C, PASSEGGERI P, MERCI M, FUNZIONARI F, CATEGORIE, ADDETTI, NAZIONI
WHERE
ADDETTI.ID=C.ID_ADDETTO
AND P.ID=M.ID_PASS
AND P.ID_NAZ=NAZIONI.ID
AND M.ID_PASS=C.ID_PASS AND M.ID=C.ID_MERCE -- composite PK (so another AND reuqired)
AND M.ID_CAT=CATEGORIE.ID
AND F.ID=ADDETTI.ID_FUNZ
AND ESITO > 1
AND F.ID = 501

Comparing two values from the same select query

I have a select query which selects all products from my inventory table and joins them with two other tables (tables l_products and a_products)
SELECT
i.*,
b.title,
ROUND((i.price/100*80) - l.price,2) AS margin,
l.price AS l_price,
a.price AS a_price,
ROUND((a.price/100*80) - l.price, 2) AS l_margin
FROM inventory i
LEFT JOIN products b ON i.id = b.id
LEFT JOIN a_products a ON i.id = a.id
LEFT JOIN l_products l ON i.id = l.id
WHERE
a.condition LIKE IF(i.condition = 'New', 'New%', 'Used%')
AND l.condition LIKE IF(i.condition = 'New', 'New%', 'Used%')
This select query will normally give me a table such as...
id, title, condition, margin, l_price, a_price ...
001-new ... new 10 20 10
001-used ... used 10 25 20
002....
Now I need a condition in the query which will ignore all used products that are more expensive (have a higher a_price) than their 'new' counterparts, such as in the example above you can see that 001-used has a higher a_price than 001-new.
How can I achieve this with out having to resolve to using php
FULL JOIN this query with it self on a column which has a uniquely same value for each id prefix.
You may achieve this effect by adding another field to your SELECT call which produces same unique value for 001-new and 001-used, 002-new and 002-used...
Such value generation can be done by defining your own SQL Routine to extract first 3 characters from a column.

Is the Query returning in consistent results?

Changed the question after peeling the outer layers
I am using MySQL 5.1
select s.status, street, m.meterpointid
from meterpoint m inner join account a
on ( m.accountid = a.accountid) inner join
meterservice s
on ( m.meterpointid = s.meterpointid )
where a.citystateid=1 and m.meterpointid=3008 and m.lastupdate is not null
group by status, street ;
The above query returns
1 210 S HWY 3 3008
select s.status, street, m.meterpointid
from meterpoint m inner join account a
on ( m.accountid = a.accountid) inner join
meterservice s
on ( m.meterpointid = s.meterpointid )
where a.citystateid=1 and m.lastupdate is not null
group by status, street ;
but the output from above query which is exactly the same as the previous query without the m.meterpointid=3008 does not contain the 1 210 S HWY 3 3008
Any ideas?
Thanks
Naren
The change on the inner query is changing the count in the outer query, so having count(*) = 1 is no longer true. I find the best way to diagnose these sorts of things is to peel away outer layers and look at the inner queries until I figure out what is going on.
This is not at all surprising. You are using a (mis)feature of MySQL called Hidden Columns. That is, you have columns in the select clause that are not in the group by clause and are not in aggregation functions.
In this case, an arbitrary value is chosen for the value.
To fix this problem, either do:
group by status, street, m.meterpointid
or
select s.status, street, min(m.meterpointid)
These will fix the query so the results are determinate.
Not including the test m.meterpointid=3008 can change the count. If, according to count(*) = 1, the count is not 1 the result is discarded. If the subselect of the second query returns more than one record, then this result will be discarded.