mysql - performing addition, subtraction etc on two rows - mysql

I have a following Mysql table storing meter readings of different power stations.
Date, station_name, reading
2013-05-06, ABC, 102
2013-05-06, PQR, 122
I want a SQL query with following result for a particular date.
Date, ABC, PQR, ABC-PQR
2013-05-06,102,122,-20

You could use CASE statements:
SELECT Date
, SUM(CASE WHEN station_name = 'ABC' THEN reading ELSE 0 END) as ABC
, SUM(CASE WHEN station_name = 'PQR' THEN reading ELSE 0 END) as PQR
, SUM(CASE WHEN station_name = 'ABC' THEN reading ELSE 0 END) - SUM(CASE WHEN station_name = 'PQR' THEN reading ELSE 0 END) as 'ABC-PQR'
FROM table
WHERE Date = '20130506'
GROUP BY Date
You can search for MySQL PIVOT to find out other methods people use.

I believe that it is not possible to do dynamic column based on value of row. I believe you should do it in application-layer rather than database-layer.
See this post: mysql select dynamic row values as column names, another column as value.

Related

MySQL count different value in same table

I am working on a database right now, and I am trying to select some special data.
so the table looks like this.
name title type
Type is including two different value, "book" and "paper".
And this is the result I would like to get
name book paper
person A 0 1
person B 1 2
person C 0 5
What is the best way to write the query it in MySQL.
You may use conditional aggregation:
SELECT
name,
SUM(CASE WHEN type = 'book' THEN 1 ELSE 0 END) AS book,
SUM(CASE WHEN type = 'paper' THEN 1 ELSE 0 END) AS paper
FROM yourTable
GROUP BY
name;

Searching large (6 million) rows MySQL with stored queries?

I have a database with roughly 6 million entries - and will grow - where I'm running queries to return for a HighCharts charting functionality. I need to read longitudinally over years, so I'm running queries like this:
foreach($states as $state_id) { //php code
SELECT //mysql psuedocode
sum(case when mydatabase.Year = '2003' then 1 else 0 end) Year_2003,
sum(case when mydatabase.Year = '2004' then 1 else 0 end) Year_2004,
sum(case when mydatabase.Year = '2005' then 1 else 0 end) Year_2005,
sum(case when mydatabase.Year = '2006' then 1 else 0 end) Year_2006,
sum(case when mydatabase.Year = '2007' then 1 else 0 end) Year_2007,
sum(case when mydatabase.Year = '$more_years' then 1 else 0 end) Year_$whatever_year,
FROM mytable
WHERE State='$state_id'
AND Sex IN (0,1)
AND Age_segment IN (5,4,3,2,1)
AND "other_filters IN (etc, etc, etc)
} //end php code
But for various state at once... So returning lets say 5 states, each with the above statement but a state ID is substituted. Meanwhile the years can be any number of years, the Sex (male/female/other) and Age segment and other modifiers keep changing based on filters. The queries are long (at minimum 30-40seconds) a piece. So a thought I had - unless I'm totally doing it wrong - is to actually store the above query in a second table with the results, and first check that "meta query" and see if it was "cached" and then return the results without reading the db (which won't be updated very often).
Is this a good method or are there potential problems I'm not seeing?
EDIT: changed to table, not db (duh).
Table structure is:
id | Year | Sex | Age_segment | Another_filter | Etc
Nothing more complicated than that and no joining anything else. There are keys on id, Year, Sex, and Age_segment right now.
Proper indexing is what is needed to speed up the query. Start by doing an "EXPLAIN" on the query and post the results here.
I would suggest the following to start off. This way avoids the for loop and returns the data in 1 query. Not knowing the number of rows and cardinality of each column I suggest a composite index on State and Year.
SELECT mytable.State,mytable.Year,count(*)
FROM mytable
AND Sex IN (0,1)
AND Age_segment IN (5,4,3,2,1)
AND "other_filters IN (etc, etc, etc)
GROUP BY mytable.State,mytable.Year
The above query can be further optimised by checking the cardinality of some of the columns. Run the following to get the cardinality:
SELECT Age_segment FROM mytable GROUP BY Age_segment;
Pseudo code...
SELECT Year
, COUNT(*) total
FROM my_its_not_a_database_its_a_table
WHERE State = $state_id
AND Sex IN (0,1)
AND Age_segment IN (5,4,3,2,1)
GROUP
BY Year;

Column has Multiple Values Used as Identifiers for Next Column

MySQL column has multiple values used as identifiers for the next column. Table Structure:
id (key), occurrence_id, name, value
The name column values then correspond to the values column. How can I display this information in one view?
Currently, it looks like this:
occurrence_id name value
1576 Attempts 1
1576 ClientIP "94.xxx.xxx.xxx"
1576 UserAgent ""
1576 CurrentUserID 0<
I want to make a view that will look like this:
occurrence_id Attempts Client IP CurrentUserID
1576 1 "94.xxx.xxx.xxx" 0
2009 30 "68.111.xxx.xxx" 0
One method is using conditional aggregation:
select occurrence_id,
max(case when name = 'Attempts' then value end) as Attempts,
max(case when name = 'ClientIP' then value end) as ClientIP,
max(case when name = 'UserAgent' then value end) as UserAgent,
max(case when name = 'CurrentUserID' then value end) as CurrentUserID
from table t
group by occurrence_id;

SQL - Query same column twice with different dates in where clause

I have tried searching all over for answers but none have answered my exact issue. I have what should be a relatively simple query. However, I am very new and still learning SQL.
I need to query two columns with different dates. I want to return rows with the current number of accounts and current outstanding balance and in the same query, return rows for the same columns with data 90 days prior. This way, we can see how much the number of accounts and balance increased over the past 90 days. Optimally, I am looking for results like this:
PropCode|PropCat|Accts|AcctBal|PriorAccts|PriorBal|
----------------------------------------------------
77 |Comm | 350 | 1,000| 275 | 750
Below is my starting query. I realize it's completely wrong but I have tried numerous different solution attempts but none seem to work for my specific problem. I included it to give an idea of my needs. The accts & AcctBal columns would contain the 1/31/14 data. The PriorAcct & PriorBal columns would contain the 10/31/13 data.
select
prop_code AS PropCode,
prop_cat,
COUNT(act_num) Accts,
SUM(act_bal) AcctBal,
(SELECT
COUNT(act_num)
FROM table1
where date = '10/31/13'
and Pro_Group in ('BB','FF')
and prop_cat not in ('retail', 'personal')
and Not (Acct_Code = 53 and ACTType in (1,2,3,4,5,6,7))
)
AS PriorAccts,
(SELECT
SUM(act_bal)
FROM table1
where date = '10/31/13'
and Pro_Group in ('BB','FF')
and prop_cat not in ('retail', 'personal')
and Not (Acct_Code = 53 and ACTType in (1,2,3,4,5,6,7))
)
AS PriorBal
from table1
where date = '01/31/14'
and Pro_Group in ('BB','FF')
and prop_cat not in ('retail', 'personal')
and Not (Acct_Code = 53 and ACTType in (1,2,3,4,5,6,7))
group by prop_code, prop_cat
order by prop_cat
You can use a CASE with aggregates for this (at least in SQL Server, not sure about MySQL):
...
COUNT(CASE WHEN date='1/31/14' THEN act_num ELSE NULL END) as 'Accts'
,SUM(CASE WHEN date='1/31/14' THEN act_bal ELSE NULL END) as 'AcctBal'
,COUNT(CASE WHEN date='10/31/13' THEN act_num ELSE NULL END) as 'PriorAccts'
,SUM(CASE WHEN date='10/31/13' THEN act_bal ELSE NULL END) as 'PriorAcctBal'
....
WHERE Date IN ('1/31/14', '10/31/13')

Separating/Sorting single column values into several columns using case function

I have two tables that I want to join and split with a case function depending on the values in one of the columns. (I know, sounds weird so let me explain)
It's a process where I run separate batches. Every batch has several samples that are measured in instances of voltage readings in several locations. My two tables looks like this:
Sample Readings
id id
BatchesID SampleID
... voltage
... location
When a batch is run, it takes one sample at a time and for every location (25 locations) it takes about 20 readings of the voltage before moving on to the next one.
I want to look at one batch at a time, and for every Sample.id, I want to gather the AVG(voltage) for all the locations. My table for Readings turns out like:
SampleID location voltage
1 1 5.23
1 1 4.53
... ... ...
1 25 7.89
2 1 4.96
2 1 5.04
... ... ...
2 25 6.09
...
But I want it to look like:
SampleID avg_v_for_1 avg_v_for_2 ... avg_v_for_25
1 4.73 5.24 ... 6.35
2 3.87 4.76 ... 9.32
... ... ... ... ...
200 6.73 3.87 ... 8.23
Basically, what I want to do is for every separate sample, I want to take the average voltage for all the measurements in every location and put in on a single row. What my current syntax looks like is this:
SELECT Readings.SampleID, Sample.BatchesID
(case when location = '1' then AVG(voltage) else 0 end) avg_v_for_1,
(case when location = '2' then AVG(voltage) else 0 end) avg_v_for_2,
...
(case when location = '25' then AVG(voltage) else 0 end) avg_v_for_25
FROM DB.Readings
INNER JOIN Sample
ON Readings.SampleID = Sample.id
WHERE Sample.BatchesID = 'specific_batch_id'
GROUP BY Readings.location, Sample.id;
The problem is that this generates the following table:
SampleID avg_v_for_1 avg_v_for_2 ... avg_v_for_25
1 4.73 0 ... 0
1 0 4.76 ... 0
1 0 0 ... 6.73
2 3.87 0 ... 0
2 0 4.83 ... 0
...
How can I get MySQL to gather ALL the average values for EVERY location on a SINGLE row? I have tried removing the group by location and only group by sampleID but then I only get the values for the first location and everything else becomes 0.
Any help is appreciated, thank you!
I add another answer with explanation how the the query with AVG(case ..when ... then..end) works, and why the version with case ... when ... then AVG(..) end doesn't give expected results.
The first remark: the ANSI SQL standard for group by queries is the following:
SELECT column1, column2, ... column_n, aggregate_function (expression)
FROM tables
WHERE predicates
GROUP BY column1, column2, ... column_n;
where aggregated_function can be a function such a: SUM, MAX, MIN, COUNT, AVG
There are several rules (restrictions) for the GROUP BY CLASUE, see this link for details: http://etutorials.org/SQL/Mastering+Oracle+SQL/Chapter+4.+Group+Operations/4.2+The+GROUP+BY+Clause/
one of them says that:
GROUP BY clause must include all nonaggregate expressions
It means, that all columns in SELECT clause must be listed in the GROUP BY clause,
for example this query:
SELECT col1, col2, AVG( expression )
FROM table
GROUP BY col2
is wrong, because col1 is not listed in the GROUP BY clause, and this query won't work on all databases (Oracle, Postgresql, MS-SQL etc.) - except MySql (why - I'll tell about it later).
The expression within the aggregated function can refer to all columns of the table, regardless of the column is listed in the GROUP BY clause or not.
Because of the above the query:
SELECT Readings.SampleID,
(case when location = '1' then AVG(voltage) else 0 end) avg_v_for_1
....
GROUP BY sampleId
simply won't work on all databases that are compliant with ANSI SQL, this query will give a syntax error because location is out of AVG function, but is not listed in the GROUP BY clause.
The question - why this query works on MySql ?
Because MySql implemented it's own extension to the GROUP BY query, see this link --> http://dev.mysql.com/doc/refman/5.6/en/group-by-extensions.html
In MySql the select list can refer to nonaggregated columns not listed in the GROUP BY clause. Becaue of this extension our query is syntactically correct and runs on MySql, but gives unexpected (unwanted) results, since an order of expression's evaluation is different:
1. it first runs an aggregated (group by) query and evaluates AVG( price ),
2. then evaluates CASE WHEN ... THEN, but for resultset returned by the aggregated query from point 1
The query with the clause AVG( case when ... then ):
1. first calucates the expression CASE-WHEN-THEN for all table rows
2. then runs an aggregated query for resultset returned by #1 and calculates the AVG.
Try:
SELECT Readings.SampleID, Sample.BatchesID
AVG(case when location = '1' then voltage else null end) avg_v_for_1,
AVG(case when location = '2' then voltage else null end) avg_v_for_2,
...
AVG(case when location = '25' then voltage else null end) avg_v_for_25
FROM DB.Readings
........
GROUP BY sample_id
--- EDIT --> use ifnull function to change nulls into 0
SELECT Readings.SampleID, Sample.BatchesID
ifnull( AVG(case when location = '1' then voltage else null end), 0 ) avg_v_for_1,
ifnull( AVG(case when location = '2' then voltage else null end), 0 ) avg_v_for_2,
...
ifnull( AVG(case when location = '25' then voltage else null end), 0 ) avg_v_for_25
FROM DB.Readings
........
GROUP BY sample_id