Formatting SQL result as a sum - mysql

Here is the query I'm using to gather daily values from three different "addresses", which reference different sensors in our system.
SELECT DISTINCT
LEFT(changes.date_time, 6) AS 'date',
changes.address AS 'address',
(CASE WHEN changes.address IN (18) THEN - MAX(changes.value * types.multiplier) ELSE MAX(changes.value * types.multiplier) END) AS 'value'
FROM sensor.changes
INNER JOIN sensor.types ON changes.type = types.idx
WHERE
changes.address IN (1, 4, 8)
AND changes.date_time >= '12110100000000'
AND changes.date_time <= '13050100000000'
GROUP BY LEFT(changes.date_time, 6),
changes.address
ORDER BY changes.idx;
The first date contains the following values, with subsequent dates carrying the same addresses:
date address value
130203 1 0.0160
130203 4 0.1220
130203 8 -0.0070
I want to combine these three values, address 1+4+8, into a single row for that date. I've attempted a SUM on the "value" column, but that results in an SQL error: #1111 - Invalid use of group function. Taking out the second GROUP BY column results in one value per date, but the value is not a sum of those values. Using a second query to format the result is OK, but I would prefer if the math was done in the first query because the database server is not local.
EDIT: to clarify what I need as the result:
date address value
130203 1 0.131
The integer under the address column in this case doesn't matter.

You should add a grouping clause.
SELECT Resdate,SUM(Res.address),SUM(Res.value)
FROM
(
SELECT DISTINCT
LEFT(changes.date_time, 6) AS 'date',
changes.address AS 'address',
(CASE WHEN changes.address IN (18) THEN - MAX(changes.value * types.multiplier) ELSE MAX(changes.value * types.multiplier) END) AS 'value'
FROM sensor.changes
INNER JOIN sensor.types ON changes.type = types.idx
WHERE
changes.address IN (1, 4, 8)
AND changes.date_time >= '12110100000000'
AND changes.date_time <= '13050100000000'
GROUP BY LEFT(changes.date_time, 6),
changes.address
ORDER BY changes.idx
) AS Res
GROUP BY Res.Date

Related

Generating complex sql tables

I currently have an employee logging sql table that has 3 columns
fromState: String,
toState: String,
timestamp: DateTime
fromState is either In or Out. In means employee came in and Out means employee went out. Each row can only transition from In to Out or Out to In.
I'd like to generate a temporary table in sql to keep track during a given hour (hour by hour), how many employees are there in the company. Aka, resulting table has columns HourBucket, NumEmployees.
In non-SQL code I can do this by initializing the numEmployees as 0 and go through the table row by row (sorted by timestamp) and add (employee came in) or subtract (went out) to numEmployees (bucketed by timestamp hour).
I'm clueless as how to do this in SQL. Any clues?
Use a COUNT ... GROUP BY query. Can't see what you're using toState from your description though! Also, assuming you have an employeeID field.
E.g.
SELECT fromState AS 'Status', COUNT(*) AS 'Number'
FROM StaffinBuildingTable
INNER JOIN (SELECT employeeID AS 'empID', MAX(timestamp) AS 'latest' FROM StaffinBuildingTable GROUP BY employeeID) AS LastEntry ON StaffinBuildingTable.employeeID = LastEntry.empID
GROUP BY fromState
The LastEntry subquery will produce a list of employeeIDs limited to the last timestamp for each employee.
The INNER JOIN will limit the main table to just the employeeIDs that match both sides.
The outer GROUP BY produces the count.
SELECT HOUR(SBT.timestamp) AS 'Hour', SBT.fromState AS 'Status', COUNT(*) AS 'Number'
FROM StaffinBuildingTable AS SBT
INNER JOIN (
SELECT SBIJ.employeeID AS 'empID', MAX(timestamp) AS 'latest'
FROM StaffinBuildingTable AS SBIJ
WHERE DATE(SBIJ.timestamp) = CURDATE()
GROUP BY SBIJ.employeeID) AS LastEntry ON SBT.employeeID = LastEntry.empID
GROUP BY SBT.fromState, HOUR(SBT.timestamp)
Replace CURDATE() with whatever date you are interested in.
Note this is non-optimal as it calculates the HOUR twice - once for the data and once for the group.
Again you are using the INNER JOIN to limit the number of returned row, this time to the last timestamp on a given day.
To me your description of the FromState and ToState seem the wrong way round, I'd expect to doing this based on the ToState. But assuming I'm wrong on that the following should point you in the right direction:
First, I create a "Numbers" table containing 24 rows one for each hour of the day:
create table tblHours
(Number int);
insert into tblHours values
(0),(1),(2),(3),(4),(5),(6),(7),
(8),(9),(10),(11),(12),(13),(14),(15),
(16),(17),(18),(19),(20),(21),(22),(23);
Then for each date in your employee logging table, I create a row in another new table to contain your counts:
create table tblDailyHours
(
HourBucket datetime,
NumEmployees int
);
insert into tblDailyHours (HourBucket, NumEmployees)
select distinct
date_add(date(t.timeStamp), interval h.Number HOUR) as HourBucket,
0 as NumEmployees
from
tblEmployeeLogging t
CROSS JOIN tblHours h;
Then I update this table to contain all the relevant counts:
update tblDailyHours h
join
(select
h2.HourBucket,
sum(case when el.fromState = 'In' then 1 else -1 end) as cnt
from
tblDailyHours h2
join tblEmployeeLogging el on
h2.HourBucket >= el.timeStamp
group by h2.HourBucket
) cnt ON
h.HourBucket = cnt.HourBucket
set NumEmployees = cnt.cnt;
You can now retrieve the counts with
select *
from tblDailyHours
order by HourBucket;
The counts give the number on site at each of the times displayed, if you want during the hour in question, we'd need to tweak this a little.
There is a working version of this code (using not very realistic data in the logging table) here: rextester.com/DYOR23344
Original Answer (Based on a single over all count)
If you're happy to search over all rows, and want the current "head count" you can use this:
select
sum(case when t.FromState = 'In' then 1 else -1) as Heads
from
MyTable t
But if you know that there will always be no-one there at midnight, you can add a where clause to prevent it looking at more rows than it needs to:
where
date(t.timestamp) = curdate()
Again, on the assumption that the head count reaches zero at midnight, you can generalise that method to get a headcount at any time as follows:
where
date(t.timestamp) = "CENSUS DATE" AND
t.timestamp <= "CENSUS DATETIME"
Obviously you'd need to replace my quoted strings with code which returned the date and datetime of interest. If the headcount doesn't return to zero at midnight, you can achieve the same by removing the first line of the where clause.

Sql Server 2008 coalesce

The following sql returns correct data formatted just as needed, except when no data is returned.
Desired result -- using the coalesce on ename--
is to return 'none' when no data is returned.
What is it I am doing wrong on the coalesce? Why does it not return 'none' when no data is returned?
When no data is returned, we get all the column names and all are null.
all pointers and suggestions appreciated.
select
ltrim(right(convert(varchar(20), tstart, 100), 7))
as 'START TIME',
ltrim(right(convert(varchar(20), tend, 100), 7))
as 'END TIME',
coalesce(vb.tname, 'none') as TITLE,
tr.description as LOCATION
from vwbooks vb
join troom tr
on vb.room = tr.id
where vb.room in(select id
from tblroom
where building = 4971
and vb.tstart >= floor(cast(getdate() as float))
and vb.tstart < ceiling(cast(getdate() as float))
and datepart(hour, vb.tstart) between 6 and 18
You are returning 0 rows. You are interpreting these as null. You need to understand why nothing is returned. The coalesce will work when it gets a row!

sum if condition true, subtract if false mysqlite

I'm using mysqlite in my app and I'm having the following problem:
I have a table with the columns 'name', 'value' and 'condition' and I'd like to query the distinct names and the sum of the values of this name. But there's a condition: I want to sum only the names with 'condition' = 1 and I want to subtract the value if the 'condition' = 0.
Try this:
SELECT name, SUM(((condition * 2) - 1) * value)
FROM tablename
GROUP BY name
Notice how (condition * 2) - 1) evaluates to 1, if condition is 1 and -1 if condition is 0, thereby producing the output you need.
You could construct a case expression to do that:
SELECT name, SUM(CASE condition WHEN 1 THEN value
WHEN 0 THEN value * (-1))
FROM mytable
GROUP BY name

MySQL Sorting when using FORMAT function

I have a query that SUMS different values and then orders the results using ORDER BY.
Whenever I format the result using FORMAT I get a different ordering than without format.
For example:
Ordering without format: 2827.0000, 1668.0000, 663.1000
Ordering with format: 663.10, 2,827.00, 1,668.00
What could be causing this behaviour?
This is the full query:
SELECT
FORMAT( ( (Sum(CASE WHEN YEAR(order_date) = 2015 THEN total END) / 100) - (SELECT COALESCE( ( SUM(total) / 100), 0)
FROM returns WHERE customer = orders.customer AND YEAR(return_dat) = 2015) ), 2) AS anual
FROM orders
WHERE 1 GROUP BY customer ORDER BY anual DESC
Ordering formatted strings is going to result in ASCII-abetical sorting. If you want them sorted numerically, you'll need to have two columns, formatted and unformatted. Keep in mind this is usually best done in your application layer.

group by based on part of a column value in mySql

I have a mySql table call details. details table is having a column call date which is having date like 2015-02-01. There can be multiple rows from a same date.
I want to find how many rows COUNT() it contains for each month of the year.
for example,
'568', '192.168.1.100', '790', '1.00', '2014-11-14'
'569', '192.168.1.100', '780', '1.00', '2014-11-14'
'699', '192.168.1.100', '780', '1.00', '2014-11-16'
'767', '192.168.1.102', '780', '1.00', '2014-12-15'
'768', '192.168.1.102', '780', '1.00', '2014-12-15'
this should give COUNT like:
'2014-11-' as 3
'2014-12-' as 2
How can i do it using a sql select query.
Select DATE_FORMAT(My_Call_Date_Field, '%Y-%m') as MyYearAndMonth, Count(*) as MyCount
From My_Call_Details_Table Group By DATE_FORMAT(My_Call_Date_Field, '%Y-%m')
You can also use the CAST function of MySql.
Select substr(convert(My_Call_Date_Field, CHAR), 1, 7) as MyYearAndMonth, Count(*) From My_Call_Details_Table Group By substr(convert(My_Call_Date_Field, CHAR), 1, 7) Order By substr(convert(My_Call_Date_Field, CHAR), 1, 7)
In response to additional info specific to your case, as seen in your comment to the previous answer by Ed King: here it is :
SELECT DATE_FORMAT(detailtable.date, '%Y-%m') , AVG(detailtable.loadstate) as "load",count(detailtable.loadstate) as "count" from ruby_snmp.detailtable where ipaddtress='192.168.1.102' and date LIKE '2014-12-%' GROUP BY DATE_FORMAT(detailtable.date, '%Y-%m')
Convert to month and year using mysql functions, group and count:
SELECT month(calldate) as m, year(calldate) as y, count(*)
FROM calldetails
GROUP by m, y;
Given the additional information, I would do the following:
SELECT YEAR(detailtable.date) as y,
MONTH(detailtable.date) as m,
AVG(detailtable.loadstate) as "load",
count(detailtable.loadstate) as "count"
FROM ruby_snmp.detailtable
WHERE ipaddtress='192.168.1.102' and date LIKE '2014-12-%'
GROUP BY y, m;
The format variables y and m would be disregarded, but need to be there for the group to work. You can add the FORMAT function to give the specific YYYY-MM as was mentioned in the second post, or you can handle data presentation in the program / report writer.