MySQL SUM on a query with mutiple values - php

I have the following query:
$strQuery = "SELECT siteid, SUM(watts) AS wattage, unit, device, time FROM inverter WHERE siteid = '528' AND time Between '$time1' AND '$time2' Order By device Asc";
I'm making a graph in fusion charts and need the total watts for each device but when i do the query above it takes all values and places them for just the first device. I have 40 devices and need each one to have its total watts produced.
On the chart i am displaying device as the x axis name label and the wattage as the value.
Thanks

SUM() is an aggregate function which should be used together with a GROUP statement. If GROUP statement is omitted, all selected rows are aggregated as a group.
see docs: http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html
Look into GROUP BY statement:
SELECT siteid, device,
SUM(watts) AS wattage,
MIN(time) as start_time,
MAX(time) as end_time
unit
FROM inverter
WHERE siteid = '528'
AND time BETWEEN '$time1' AND '$time2'
GROUP BY device, unit
ORDER BY device ASC
Also, when you use aggregation and select fields that are not contained in the GROUP statement (here: time) you must make sure you really get the value you want.
The values of the aggregated row you get for each device are calculated using many rows with possibly individual values in those fields. When you don't use an aggregate function (MIN, MAX, AVG, ..) to determine which of the multiple values your aggregated row should contain, you will receive a value selected more or less selected randomly by MySQL.
You should either
do not select such field, when not necessary. (e.g. time)
use an appropriate aggregate function (MIN, MAX, AVG, ..) for each field that is not contained in the GROUP BY clause to ensure you get the right value. (here i inserted both start and end time for the measure timespan using MIN and MAX)
or include it in the GROUP BY clause
(i did that for "unit" in the example, because you get a wrong result when using SUM on both megawatts and kilowatts .. i assume that is what you use "unit" for?)
Again: Otherwise the resulting value in such fields is random and not deterministic.

You can just use the naive query as proposed by #Kaii and #cairnz.
However, if there are multiple times and units for the devices selected, MySQL will just select a unit and time from one device more or less at random.
Why your query failed
Use are using a aggregate function.
Unless you specify a group by clause the aggregate function will simple add all rows into 1 aggregate.
All the rows not covered in an aggregate function will be condensed into one item (one will simple be chosen at leasure).
In order to see what data lies underneigh, I suggest doing:
SELECT
siteid
, SUM(watts) AS wattage
, GROUP_CONCAT(DISTINCT unit) as units
, device
, GROUP_CONCAT(time) as times
FROM inverter
WHERE siteid = '528'
AND time BETWEEN '$time1' AND '$time2'
GROUP BY device
ORDER BY device ASC
The aggregate function GROUP_CONCAT will show a comma separated list of all (DISTINCT) values lumped together by the aggregation.
Because you are only grouping by device, you will different kinds of units and times will be lumped together.
The more columns you add to the group by clause the more you will "zoom in" on the data and the more rows will be shown.
Why you should be careful in listing 'naked' columns in the select that are not in the group by
If you list 'naked' columns (i.e. not as part of an aggregate expression) that are not listed in the group by clause and these columns are not functionally dependent on the group by clause.
There will be multiple different values, of which only one will be shown.
This behavior is unique to MySQL, most other SQL flavors will issue an error and refuse.
The reason MySQL allows this dangerous behavior is that
A: it allows for faster queries,
B: if you specify a unique key in the group by clause, the other fields will be functionally dependent on that key (cf ANSI SQL 2003) and specifying more fields makes no sense,
C: sometimes you don't care about the values of those other fields and displaying a random representative is what you want.
Why can't I just list all non-aggregate columns in the group by
If you alter the group by statement, this has a profound effect on the output.
The more columns (up until the point where you hit a unique key for the selected columns) you group by, the more details you get, which negates the whole point of using aggregate functions.
The solution
If you just want to show a random unit and time, that's cool of course, but perhaps it makes more sense to use the following query:
SELECT
siteid
, SUM(watts) AS wattage
, COUNT(*) as NumberOfUnits
, device
, MIN(time) as Mintime
, MAX(time) as Maxtime
FROM inverter
WHERE siteid = '528'
AND time BETWEEN '$time1' AND '$time2'
GROUP BY device
ORDER BY device ASC
Creative use of the aggregate functions at your disposal is the answer, see:
http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html
Because you're using MySQL, I esp. recommend studying GROUP_CONCAT a very versatile and useful function.

You need to tell what to group by:
SELECT
siteid,
SUM(watts) AS wattage,
unit,
device,
MIN(time) AS StartTime,
MAX(time) AS EndTime
FROM inverter
WHERE siteid = '528' AND time Between '$time1' AND '$time2'
GROUP BY siteid, unit, device
Order By device Asc
EDIT: Using MIN() and MAX() for time allows you to see which timespans the SUM() covers. You should not use time by itself in the query without specifying what time for the sum of wattage for the unit/device combination is showing.

Related

SQL PHP get full row with MAX i a single table

i have one table collecting scores and other informations like the date and the user id. I would like to get the MAX of the current month and the other fields of the row. I'm having a problem to get the other informations since with functions we cannot get other fields.
I think i should do an inner join but i don't how to make it.
Thank you.
Your question is rather vague. But if you want one row, then the idea for the solution is order by and fetch first row only.
In standard SQL, the query would look like:
select t.*
from t
where extract(year from datecol) = extract(year from current_date) and
extract(month from datecol) = extract(month from current_date)
order by t.score desc
fetch first 1 row only;
Databases often differ on database functions. For instance, many use year() and month() functions, rather than extract(). Similarly, many databases do not support fetch first 1 row only, using limit or select top instead.
Thank you, yes it does work doing this way but the idea was to use MAX (for learning purpose).
I have scores table, with the following fields: id, score, date, user_id
I would like, using MAX, to get the best and latest score of the current month along with the other fields (ie the id, date and user_id).

How to order the ORDER BY using the IN() mysql? [duplicate]

I am wondering if there is away (possibly a better way) to order by the order of the values in an IN() clause.
The problem is that I have 2 queries, one that gets all of the IDs and the second that retrieves all the information. The first creates the order of the IDs which I want the second to order by. The IDs are put in an IN() clause in the correct order.
So it'd be something like (extremely simplified):
SELECT id FROM table1 WHERE ... ORDER BY display_order, name
SELECT name, description, ... WHERE id IN ([id's from first])
The issue is that the second query does not return the results in the same order that the IDs are put into the IN() clause.
One solution I have found is to put all of the IDs into a temp table with an auto incrementing field which is then joined into the second query.
Is there a better option?
Note: As the first query is run "by the user" and the second is run in a background process, there is no way to combine the 2 into 1 query using sub queries.
I am using MySQL, but I'm thinking it might be useful to have it noted what options there are for other DBs as well.
Use MySQL's FIELD() function:
SELECT name, description, ...
FROM ...
WHERE id IN([ids, any order])
ORDER BY FIELD(id, [ids in order])
FIELD() will return the index of the first parameter that is equal to the first parameter (other than the first parameter itself).
FIELD('a', 'a', 'b', 'c')
will return 1
FIELD('a', 'c', 'b', 'a')
will return 3
This will do exactly what you want if you paste the ids into the IN() clause and the FIELD() function in the same order.
See following how to get sorted data.
SELECT ...
FROM ...
WHERE zip IN (91709,92886,92807,...,91356)
AND user.status=1
ORDER
BY provider.package_id DESC
, FIELD(zip,91709,92886,92807,...,91356)
LIMIT 10
Two solutions that spring to mind:
order by case id when 123 then 1 when 456 then 2 else null end asc
order by instr(','||id||',',',123,456,') asc
(instr() is from Oracle; maybe you have locate() or charindex() or something like that)
If you want to do arbitrary sorting on a query using values inputted by the query in MS SQL Server 2008+, it can be done by creating a table on the fly and doing a join like so (using nomenclature from OP).
SELECT table1.name, table1.description ...
FROM (VALUES (id1,1), (id2,2), (id3,3) ...) AS orderTbl(orderKey, orderIdx)
LEFT JOIN table1 ON orderTbl.orderKey=table1.id
ORDER BY orderTbl.orderIdx
If you replace the VALUES statement with something else that does the same thing, but in ANSI SQL, then this should work on any SQL database.
Note:
The second column in the created table (orderTbl.orderIdx) is necessary when querying record sets larger than 100 or so. I originally didn't have an orderIdx column, but found that with result sets larger than 100 I had to explicitly sort by that column; in SQL Server Express 2014 anyways.
SELECT ORDER_NO, DELIVERY_ADDRESS
from IFSAPP.PURCHASE_ORDER_TAB
where ORDER_NO in ('52000077','52000079','52000167','52000297','52000204','52000409','52000126')
ORDER BY instr('52000077,52000079,52000167,52000297,52000204,52000409,52000126',ORDER_NO)
worked really great
Ans to get sorted data.
SELECT ...
FROM ...
ORDER BY FIELD(user_id,5,3,2,...,50) LIMIT 10
The IN clause describes a set of values, and sets do not have order.
Your solution with a join and then ordering on the display_order column is the most nearly correct solution; anything else is probably a DBMS-specific hack (or is doing some stuff with the OLAP functions in standard SQL). Certainly, the join is the most nearly portable solution (though generating the data with the display_order values may be problematic). Note that you may need to select the ordering columns; that used to be a requirement in standard SQL, though I believe it was relaxed as a rule a while ago (maybe as long ago as SQL-92).
Use MySQL FIND_IN_SET function:
SELECT *
FROM table_name
WHERE id IN (..,..,..,..)
ORDER BY FIND_IN_SET (coloumn_name, .., .., ..);
For Oracle, John's solution using instr() function works. Here's slightly different solution that worked -
SELECT id
FROM table1
WHERE id IN (1, 20, 45, 60)
ORDER BY instr('1, 20, 45, 60', id)
I just tried to do this is MS SQL Server where we do not have FIELD():
SELECT table1.id
...
INNER JOIN
(VALUES (10,1),(3,2),(4,3),(5,4),(7,5),(8,6),(9,7),(2,8),(6,9),(5,10)
) AS X(id,sortorder)
ON X.id = table1.id
ORDER BY X.sortorder
Note that I am allowing duplication too.
Give this a shot:
SELECT name, description, ...
WHERE id IN
(SELECT id FROM table1 WHERE...)
ORDER BY
(SELECT display_order FROM table1 WHERE...),
(SELECT name FROM table1 WHERE...)
The WHEREs will probably take a little tweaking to get the correlated subqueries working properly, but the basic principle should be sound.
My first thought was to write a single query, but you said that was not possible because one is run by the user and the other is run in the background. How are you storing the list of ids to pass from the user to the background process? Why not put them in a temporary table with a column to signify the order.
So how about this:
The user interface bit runs and inserts values into a new table you create. It would insert the id, position and some sort of job number identifier)
The job number is passed to the background process (instead of all the ids)
The background process does a select from the table in step 1 and you join in to get the other information that you require. It uses the job number in the WHERE clause and orders by the position column.
The background process, when finished, deletes from the table based on the job identifier.
I think you should manage to store your data in a way that you will simply do a join and it will be perfect, so no hacks and complicated things going on.
I have for instance a "Recently played" list of track ids, on SQLite i simply do:
SELECT * FROM recently NATURAL JOIN tracks;

Get previous 10 row from specific WHERE condition

Im currently working on a project that requires MySql database and im having a hard time constructing the query that i want get.
i want to get the previous 10 rows from the specific WHERE condition on my mysql query.
for example
My where is date='December';
i want the last 10 months to as a result.
Feb,march,april,may,june,july,aug,sept,oct,nov like that.
Another example is.
if i have a 17 strings stored in my database. and in my where clause i specify that WHERE strings='eyt' limit 3
Test
one
twi
thre
for
payb
six
seven
eyt
nayn
ten
eleven
twelve
tertin
fortin
fiftin
sixtin
the result must be
payb
six
seven
Thanks in advance for your suggestions or answers
If you are using PDO this is the right syntax:
$objStmt = $objDatabase->prepare('SELECT * FROM calendar ORDER BY id DESC LIMIT 10');
You can change ASC to DESC in order to get either the first or the last 10.
Here's a solution:
select t.*
from mytable t
inner join (select id from mytable where strings = 'eyt' order by id limit 1) x
on t.id < x.id
order by t.id desc
limit 3
Demo: http://sqlfiddle.com/#!9/7ffc4/2
It outputs the rows in descending order, but you can either live with that, or else put that query in a subquery and reverse the order.
Re your comment:
x in the above query is called a "correlation name" so we can refer to columns of the subquery as if they were columns of a table. It's required when you use a subquery as a table.
I chose the letter x arbitrarily. You can use anything you like as a correlation name, following the same rules you would use for any identifier.
You can also optionally define a correlation name for any simple table in the query (like mytable t above), so you can refer to columns of that table using a convenient abbreviated name. For example in t.id < x.id
Some people use the term "table alias" but the technical term is "correlation name".

PHP/MySQL Debug + Optimization

I have about 100,000 merchants stored in my database, the table contains their Name, Lat, Lon
Right now my search function takes a lat/lon input and finds the stores within 10 miles radius. However, a lot of the 100,000 merchants are franchises (ie. groups of associated stores). For the franchises, I'd like to show only the Closest store, with the rest stores of the same franchise hidden (if there are multiple locations within 10 miles radius).
I've tried doing:
SELECT SQL_CALC_FOUND_ROWS *, xxxxx as Distance
FROM table
WHERE isActive = 1
GROUP BY Name
HAVING Distance < 10
ORDER BY Distance ASC
xxxxx is the function that calculates distance based on input lat/lon:
((ACOS(SIN('$lat'*PI()/180)*SIN(`Latitude`*PI()/180) + COS('$lat'*PI()/180)*COS(`Latitude`*PI()/180)*COS(('$long'-`Longitude`)*PI()/180))*180/PI())*60*1.1515)
However it's not returning the correct results. I'm getting significantly less results regardless of franchised or unfranchised stores when comparing with the same query without the "GROUP BY" clause. I wonder what's the problem?
Also, the speed is really slow. I have Name column indexed. But I think the "GROUP BY Name" is the bottleneck since MySQL must be doing a lot of string comparison? Assuming GROUP BY bug can be fixed, I'm wondering what are my options to make this faster. Is it worth while to setup a "Business_Group" column and pre-process the stores so the franchised stores would be assigned a Business_Group ID, this way GROUP BY would be faster since it's comparing int?
Make a virtual table using views of calculated column xxxxx
and use join of table and view.
That will be faster and optimized.
Try to group derived table
SELECT * FROM (
SELECT *, xxxxx as Distance FROM table WHERE isActive = 1
HAVING Distance < 10 ORDER BY Distance ASC
) AS all
GROUP BY Name
In not grouped query HAVING applies to every record as instant filter, and with GROUP BY it applies to whole groups, so groups are first filled with all data and then filtered with HAVING.

trick to get the sum of MySQL rows outside of MySQL

so I have the following query:
SELECT DISTINCT d.iID1 as 'id',
SUM(d.sum + d.count*r.lp)/sum(d.count) AS avgrat
FROM abcd i, abce r, abcf d
WHERE r.aID = 1 AND
d.iID1 <> r.rID AND d.iID2 = r.rID GROUP BY d.iID1
ORDER BY avgrat LIMIT 50;
the problem is....with millions of entries in the table, SUM() and GROUP BY would freeze up the query....is there a way to do exactly this that would execute instantaneously using MySQL and/or PHP hacks (perhaps do the summing with PHP....but how would I go about doing that...)
To answer the direct question: no, there is no way to do anything instantaneously.
If you have control over the table updates, or the application which adds the relevant records, then you could add logic which updates another table with the sum, count, and id with each update. Then a revised query targets the "sum table" and trivially calculates the averages.
One solution is to create a rollup table that holds your aggregate values
using a triggers on your source tables to keep it up to date.
You will need to decide if the overhead of the triggers is less then that of the query.
some important factors are:
The frequency of the source table updates
The run frequency of the aggregate query.

Categories