I have a simple table of timestamps , types and counts like
timestamp |t|c
==============
1415024797|1|1
1415025774|1|1
1415202785|1|1
1415204559|1|1
1415204593|1|2
1415629057|1|1
1415791322|2|1
1415797887|1|1
now i get a result which counts the c column group by t and for a certain date YYYY-MM-DD
I have to add 3600 to the timestamp to respect timezone offset!
SELECT From_unixtime(a.timestamp + 3600, '%Y-%m-%d') AS date,
Count(From_unixtime(a.timestamp + 3600, '%Y-%m-%d')) AS count,
a.t AS type
FROM table AS a
WHERE a.timestamp >= 1415322000 AND a.timestamp < 1415926800
GROUP BY From_unixtime(a.timestamp + 3600, '%Y-%m-%d'),
a.t
ORDER BY a.timestamp DESC
with this query I get something like
date |count | type
==========================
2014-12-03 | 3 | 1
2014-12-03 | 1 | 2
2014-12-04 | 3 | 1
2014-12-05 | 3 | 3
2014-12-07 | 4 | 2
2014-12-07 | 7 | 3
....
But I would like to get
date | t_1 | t_2 | t_3
=============================
2014-12-03 | 3 | 1 | 0
2014-12-04 | 3 | 0 | 0
2014-12-05 | 0 | 0 | 3
2014-12-06 | 0 | 0 | 0
2014-12-07 | 0 | 4 | 7
....
So on each line a date with all counts of a certain type.
There are only 3 types possible (1 => t_1, 2 => t_2, 3 => t_3)
Also dates with 0 values (2014-12-06) should be included
to build the query I'll use PHP (foreach)
SELECT From_unixtime(a.timestamp + 3600, '%Y-%m-%d') AS date,
CASE
WHEN type = 1
THEN count
ELSE
NULL
END AS t_1,
CASE
WHEN type = 2
THEN count ELSE NULL END AS t_2,
CASE
WHEN type = 3
THEN count ELSE NULL END AS t_3
FROM...
(?)
There is a relatively simple way of doing this, but the amount of repetitious/ugly code increases along with the number of values of t:
what you basically do is:
select date,
count(case when type=1 then 1 else null end) as t1_count,
count(case when type=2 then 1 else null end) as t2_count...
et cetera
If you are expecting a lot of different values of t, this becomes quite impractical, and you'd have to look at more complex techniques such as this one: http://www.artfulsoftware.com/infotree/qrytip.php?id=523
edit:
If you want to include dates which have no data at all, then you should consider creating a date table, and then 'left joining' it to your results set. you could use this code (https://gist.github.com/johngrimes/408559) - just ignore the first two statements that create numbers tables.
Related
I count my data from database, but I have a problem with the result. the result only displays data that is not empty, while the empty data is not displayed. how do I display data rows that are empty and not empty?
the result of my query like this
pendidikan| Male | Famale | Total
----------+------+--------+------
SD | 3 | 4 | 7
SMP | 2 | 1 | 3
SMA | 1 | 3 | 4
S1 | 10 | 1 | 11
BUT i want the result like this :
pendidikan| Male | Famale | Total
----------+------+--------+------
SD | 3 | 4 | 7
SMP | 2 | 1 | 3
SMA | 1 | 3 | 4
S1 | 10 | 1 | 11
S2 | 0 | 0 | 0
S3 | 0 | 0 | 0
i want to show empty data from my database. this is my query
SELECT a.NamaStatusPendidikan, COUNT(c.IDPencaker) as total,
count(case when c.JenisKelamin='0' then 1 end) as laki,
count(case when c.JenisKelamin='1' then 1 end) as cewe
FROM msstatuspendidikan as a JOIN mspencaker as c ON
a.IDStatusPendidikan = c.IDStatusPendidikan JOIN
mspengalaman as d ON c.IDPencaker = d.IDPencaker
WHERE d.StatusPekerjaan = '0' AND c.RegisterDate
BETWEEN '2019-01-01' AND '2019-03-01' GROUP BY a.IDStatusPendidikan
Try running this query:
SELECT sp.NamaStatusPendidikan,
COUNT(*) as total,
SUM( p.JenisKelamin = 0 ) as laki,
SUM( p.JenisKelamin = 1 ) as cewe
FROM msstatuspendidikan sp LEFT JOIN
mspencaker p
ON sp.IDStatusPendidikan = p.IDStatusPendidikan AND
p.RegisterDate BETWEEN '2019-01-01' AND '2019-03-01' LEFT JOIN
mspengalaman g
ON g.IDPencaker = c.IDPencaker AND
g.StatusPekerjaan = 0
GROUP BY sp.IDStatusPendidikan;
Notes:
The JOINs have been replaced with LEFT JOINs.
Filtering conditions on all but the first table have been moved to the ON clauses.
This replaces the meaningless table aliases with table abbreviations, so the table is easier to read.
Things that looks like numbers probably are numbers, so I removed the single quotes.
This simplifies the counts, using the fact that MySQL treats booleans as numbers in a numeric context.
For this question
(MYSQL select query return list of months as string from between start/end date) I have found solution of query, It gives correct result , BUT I need Month list in Ascending order.
Table : Contracts
------------------------------
ID | START | END |
------------------------------
1 | 2016-05-01 | 2016-07-31 |
2 | 2016-04-01 | 2016-08-31 |
3 | 2016-01-22 | 2016-02-25 |
4 | 2016-06-15 | 2017-11-30 |
------------------------------
Here I need result as per bellow formate, one extra field which represent range/list of months between startdate and enddate of contract using SELECT query.
Result (as per give format)
----------------------------------------------------------------------------------------
ID | START | END | Description
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 | 2016-05-01 | 2016-07-31 | May-2016, Jun-2016, July-2016
2 | 2016-04-01 | 2016-07-31 | April-2016, May-2016, Jun-2016, July-2016
3 | 2016-01-22 | 2016-02-25 | January-2016, February-2016
3 | 2016-06-15 | 2017-11-30 | May-2017 ,November-2016 ,June-2016 ,August-2017 ,March-2017 ,July-2016 ,October-2016 ,November-2017 ,June-2017 ,February-2017 ,September-2016 ,September-2017 ,August-2016,April-2017 ,January-2017 ,July-2017 ,December-2016 ,October-2017
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL Query is:
Select id, DATE_FORMAT(start_Date, '%Y-%c-%d') as Start_Date,
DATE_FORMAT(end_date,'%Y-%c-%d') as END_Date,
group_concat( distinct(DATE_FORMAT(aDate, '%M %Y'))) as Descp
from (
select ss.end_date - interval (a.a ) month as aDate from
(select 0 as a union all select 1 union all select 2 union all select 3
union all select 4 union all select 5 union all select 6 union all
select 7 union all select 8 union all select 9) a, Contracts ss
) mon, Contracts sa
where aDate between sa.start_date and sa.end_date
group by id;
It give result randomly like result ie, "May-2017 ,November-2016 ,June-2016 ,August-2017 ,March-2017 ,July-2016 ,October-2016 ,November-2017 ,June-2017 ,February-2017 ,September-2016 ,September-2017 ,August-2016,April-2017 ,January-2017 ,July-2017 ,December-2016 ,October-2017"
BUT I need
"June-2016 ,July-2016 ,August-2016,September-2016,October-2016, November-2016 ,December-2016 ,January-2017 ,February-2017 ,March-2017 ,April-2017 ,May-2017 ,June-2017 ,July-2017 ,August-2017 ,September-2017 ,October-2017, November-2017
"
Please help me to find solution about above result,
Here is my problem, I have a MYSQL table with the following columns and data examples :
id | user | starting date | ending date | activity code
1 | Andy | 2010-04-01 | 2010-05-01 | 3
2 | Andy | 1988-11-01 | 1991-03-01 | 3
3 | Andy | 2005-06-01 | 2008-08-01 | 3
4 | Andy | 2005-08-01 | 2008-11-01 | 3
5 | Andy | 2005-06-01 | 2010-05-01 | 4
6 | Ben | 2010-03-01 | 2011-06-01 | 3
7 | Ben | 2010-03-01 | 2010-05-01 | 4
8 | Ben | 2005-04-01 | 2011-05-01 | 3
As you can see in this table users can have same activity code and similar dates or periods. And For a same user, periods can overlap others or not. It is also possible to have several overlap periods in the table.
What I want is a MYSQL QUERY to get the following result :
new id | user | starting date | ending date | activity code
1 | Andy | 2010-04-01 | 2010-05-01 | 3 => ok, no overlap period
2 | Andy | 1988-11-01 | 1991-03-01 | 3 => ok, no overlap period
3 | Andy | 2005-06-01 | 2008-11-01 | 3 => same user, same activity but ending date coming from row 4 as extended period
4 | Andy | 2005-06-01 | 2010-05-01 | 4 => ok other activity code
5 | Ben | 2005-04-01 | 2011-06-01 | 3 => ok other user, but as overlap period rows 6 and 8 for the same user and activity, I take the widest range
6 | Ben | 2010-03-01 | 2010-05-01 | 4 => ok other activity for second user
In other words, for a same user and activity code, if there is no overlap, I need the starting and ending dates as they are. If there is an overlap for a same user and activity code, I need the lower starting date and the higher ending date coming from the different related rows. I need this for all the users and activity code of the table and in SQL for MYSQL.
I hope it is clear enough and someone can help me because I try different codes from solutions supplied on this site and others without success.
I have somewhat convoluted (strictly MySQL-specific) solution:
SET #user = NULL;
SET #activity = NULL;
SET #interval_id = 0;
SELECT
MIN(inn.`starting date`) AS start,
MAX(inn.`ending date`) AS end,
inn.user,
inn.`activity code`
FROM
(SELECT
IF(user <> #user OR `activity code` <> #activity,
#interval_id := #interval_id + 1, NULL),
IF(user <> #user OR `activity code` <> #activity,
#interval_end := STR_TO_DATE('',''), NULL),
#user := user,
#activity := `activity code`,
#interval_id := IF(`starting date` > #interval_end,
#interval_id + 1,
#interval_id) AS interval_id,
#interval_end := IF(`starting date` < #interval_end,
GREATEST(#interval_end, `ending date`),
`ending date`) AS interval_end,
t.*
FROM Table1 t
ORDER BY t.user, t.`activity code`, t.`starting date`, t.`ending date`) inn
GROUP BY inn.user, inn.`activity code`, inn.interval_id;
The underlying idea was shamelessly borrowed from the 1st answer to this question.
You can use this SQL Fiddle to review the results and try different source data.
Here is a solution - (see http://sqlfiddle.com/#!2/fda3d/15)
SELECT DISTINCT summarized.`user`
, summarized.activity_code
, summarized.true_begin
, summarized.true_end
FROM (
SELECT t1.id,t1.`user`,t1.activity_code
, MIN(LEAST(t1.`starting`, COALESCE(overlap.`starting` ,t1.`starting`))) as true_begin
, MAX(GREATEST(t1.`ending`, COALESCE(overlap.`ending` ,t1.`ending`))) as true_end
FROM t1
LEFT JOIN t1 AS overlap
ON t1.`user` = overlap.`user`
AND t1.activity_code = overlap.activity_code
AND overlap.`ending` >= t1.`starting`
AND overlap.`starting` <= t1.`ending`
AND overlap.id <> t1.id
GROUP BY t1.id, t1.`user`, t1.activity_code) AS summarized;
I am not sure how it will perform with a large data set with many overlaps. You will definitely need an index on the user and activity_code fields - probably the starting and ending date fields also as part of that index.
I'm in trouble with a mysql statement counting appointments for one day within a given time period. I've got a calendar table including starting and finishing column (type = DateTime). The following statement should count all appointments for November including overall appointments:
SELECT
COUNT('APPOINTMENTS') AS Count,
DATE(c.StartingDate) AS Datum
FROM t_calendar c
WHERE
c.GUID = 'blalblabla' AND
((DATE(c.StartingDate) <= DATE('2012-11-01 00:00:00')) AND (DATE(c.EndingDate) >= DATE('2012-11-30 23:59:59'))) OR
((DATE(c.StartingDate) >= DATE('2012-11-01 00:00:00')) AND (DATE(c.EndingDate) <= DATE('2012-11-30 23:59:59')))
GROUP BY DATE(c.StartingDate)
HAVING Count > 1
But how to include appointments that starts before a StartingDate and ends on the StartingDate?
e.g.
StartingDate = 2012-11-14 17:00:00, EndingDate = 2012-11-15 08:00:00
StartingDate = 2012-11-15 09:00:00, EndingDate = 2012-11-15 10:00:00
StartingDate = 2012-11-15 11:00:00, EndingDate = 2012-11-15 12:00:00
My statement returns a count of 2 for 15th of November. But that's wrong because the first appointment is missing. How to include these appointments? What I am missing, UNION SELECT, JOIN, sub selection?
A possible solution?
SELECT
c1.GUID, COUNT('APPOINTMENTS') + COUNT(DISTINCT c2.ANYFIELD) AS Count,
DATE(c1.StartingDate) AS Datum,
COUNT(DISTINCT c2.ANYFIELD)
FROM
t_calendar c1
LEFT JOIN
t_calendar c2
ON
c2.ResourceGUID = c1.ResourceGUID AND
(DATE(c2.EndingDate) = DATE(c1.StartingDate)) AND
(DATE(c2.StartingDate) < DATE(c1.StartingDate))
WHERE
((DATE(c1.StartingDate) <= DATE('2012-11-01 00:00:00')) AND (DATE(c1.EndingDate) >= DATE('2012-11-30 23:59:59'))) OR
((DATE(c1.StartingDate) >= DATE('2012-11-01 00:00:00')) AND (DATE(c1.EndingDate) <= DATE('2012-11-30 23:59:59')))
GROUP BY
c1.ResourceGUID,
DATE(c1.StartingDate)
First: Consolidate range checking
First of all your two range where conditions can be replaced by a single one. And it also seems that you're only counting appointments that either completely overlap target date range or are completely contained within. Partially overlapping ones aren't included. Hence your question about appointments that end right on the range starting date.
To make where clause easily understandable I'll simplify it by using:
two variables to define target range:
rangeStart (in your case 1st Nov 2012)
rangeEnd (I'll rather assume to 1st Dec 2012 00:00:00.00000)
won't be converting datetime to dates only (using date function) the way that you did, but you can easily do that.
With these in mind your where clause can be greatly simplified and covers all appointments for given range:
...
where (c.StartingDate < rangeEnd) and (c.EndingDate >= rangeStart)
...
This will search for all appointments that fall in target range and will cover all these appointment cases:
start end
target range |==============|
partial front |---------|
partial back |---------|
total overlap |---------------------|
total containment |-----|
Partial front/back may also barely touch your target range (what you've been after).
Second: Resolving the problem
Why you're missing the first record? Simply because of your having clause that only collects those groups that have more than 1 appointment starting on a given day: 15th Nov has two, but 14th has only one and is therefore excluded because Count = 1 and is not > 1.
To answer your second question what am I missing is: you're not missing anything, actually you have too much in your statement and needs to simplified.
Try this statement instead that should return exactly what you're after:
select count(c.GUID) as Count,
date(c.StartingDate) as Datum
from t_calendar c
where (c.GUID = 'blabla') and
(c.StartingDate < str_to_date('2012-12-01', '%Y-%m-%d') and
(c.EndingDate >= str_to_date('2012-11-01', '%Y-%m-%d'))
group by date(c.StartingDate)
I used str_to_date function to make string to date conversion more safe.
I'm not really sure why you included having in your statement, because it's not really needed. Unless your actual statement is more complex and you only included part that's most relevant. In that case you'll likely have to change it to:
having Count > 0
Getting appointment count per day in any given date range
There are likely other ways as well but the most common way would be using a numbers or ?calendar* table that gives you the ability to break a range into individual points - days. They you have to join your appointments to this numbers table and provide results.
I've created a SQLFiddle that does the trick. Here's what it does...
Suppose you have numbers table Num with numbers from 0 to x. And appointments table Cal with your records. Following script created these two tables and populates some data. Numbers are only up to 100 which is enough for 3 months worth of data.
-- appointments
create table Cal (
Id int not null auto_increment primary key,
StartDate datetime not null,
EndDate datetime not null
);
-- create appointments
insert Cal (StartDate, EndDate)
values
('2012-10-15 08:00:00', '2012-10-20 16:00:00'),
('2012-10-25 08:00:00', '2012-11-01 03:00:00'),
('2012-11-01 12:00:00', '2012-11-01 15:00:00'),
('2012-11-15 10:00:00', '2012-11-16 10:00:00'),
('2012-11-20 08:00:00', '2012-11-30 08:00:00'),
('2012-11-30 22:00:00', '2012-12-05 00:00:00'),
('2012-12-01 05:00:00', '2012-12-10 12:00:00');
-- numbers table
create table Nums (
Id int not null primary key
);
-- add 100 numbers
insert into Nums
select a.a + (10 * b.a)
from (select 0 as a union all
select 1 union all
select 2 union all
select 3 union all
select 4 union all
select 5 union all
select 6 union all
select 7 union all
select 8 union all
select 9) as a,
(select 0 as a union all
select 1 union all
select 2 union all
select 3 union all
select 4 union all
select 5 union all
select 6 union all
select 7 union all
select 8 union all
select 9) as b
Now what you have to do now is
Select a range of days which you do by selecting numbers from Num table and convert them to dates.
Then join your appointments to those dates so that those appointments that fall on particular day are joined to that particular day
Then just group all these appointments per each day and get results
Here's the code that does this:
-- just in case so comparisons don't trip over
set names 'latin1' collate latin1_general_ci;
-- start and end target date range
set #s := str_to_date('2012-11-01', '%Y-%m-%d');
set #e := str_to_date('2012-12-01', '%Y-%m-%d');
-- get appointment count per day within target range of days
select adddate(#s, n.Id) as Day, count(c.Id) as Appointments
from Nums n
left join Cal c
on ((date(c.StartDate) <= adddate(#s, n.Id)) and (date(c.EndDate) >= adddate(#s, n.Id)))
where adddate(#s, n.Id) < #e
group by Day;
And this is the result of this rather simple select statement:
| DAY | APPOINTMENTS |
-----------------------------
| 2012-11-01 | 2 |
| 2012-11-02 | 0 |
| 2012-11-03 | 0 |
| 2012-11-04 | 0 |
| 2012-11-05 | 0 |
| 2012-11-06 | 0 |
| 2012-11-07 | 0 |
| 2012-11-08 | 0 |
| 2012-11-09 | 0 |
| 2012-11-10 | 0 |
| 2012-11-11 | 0 |
| 2012-11-12 | 0 |
| 2012-11-13 | 0 |
| 2012-11-14 | 0 |
| 2012-11-15 | 1 |
| 2012-11-16 | 1 |
| 2012-11-17 | 0 |
| 2012-11-18 | 0 |
| 2012-11-19 | 0 |
| 2012-11-20 | 1 |
| 2012-11-21 | 1 |
| 2012-11-22 | 1 |
| 2012-11-23 | 1 |
| 2012-11-24 | 1 |
| 2012-11-25 | 1 |
| 2012-11-26 | 1 |
| 2012-11-27 | 1 |
| 2012-11-28 | 1 |
| 2012-11-29 | 1 |
| 2012-11-30 | 2 |
I want to convert multiple rows to a single row, based on week. It should look like the following. Can any one help me?
id | Weight | Created |
1 | 120 | 02-04-2012 |
2 | 110 | 09-04-2012 |
1 | 100 | 16-04-2012 |
1 | 130 | 23-04-2012 |
2 | 140 | 30-04-2012 |
3 | 150 | 07-05-2012 |
Result should look like this:
id | Weight_week1 | Weight_week2 | weight_week3 | weight_week4 |
1 | 120 | 100 | 130 | |
2 | 110 | 140 | | |
3 | 150 | | | |
Thanks in advance.
if this a single table then
SELECT GROUP_CONCAT(weight) as Weight,
WEEK(Created) as Week
Group by Week(Created)
This will give you a row each having week id and comma seperated whights
You could do it like this:
SELECT
t.id,
SUM(CASE WHEN WeekNbr=1 THEN Table1.Weight ELSE 0 END) AS Weight_week1,
SUM(CASE WHEN WeekNbr=2 THEN Table1.Weight ELSE 0 END) AS Weight_week2,
SUM(CASE WHEN WeekNbr=3 THEN Table1.Weight ELSE 0 END) AS Weight_week3,
SUM(CASE WHEN WeekNbr=4 THEN Table1.Weight ELSE 0 END) AS Weight_week4
FROM
(
SELECT
(
WEEK(Created, 5) -
WEEK(DATE_SUB(Created, INTERVAL DAYOFMONTH(Created) - 1 DAY), 5) + 1
)as WeekNbr,
Table1.id,
Table1.Weight,
Table1.Created
FROM
Table1
) AS t
GROUP BY
t.id
I don't know if you want a AVG,SUM,MAX or MIN but you can change the aggregate to what you want.
Useful references:
Function for week of the month in mysql
you cannot create fields on the fly like that but you can group them.
use GROUP_CONCAT to deliver results with a delimiter that you can separate on later.
You could also do this:
SELECT id, created, weight, (
SELECT MIN( created ) FROM weights WHERE w.id = weights.id
) AS `min` , round( DATEDIFF( created, (
SELECT MIN( created )
FROM weights
WHERE w.id = weights.id ) ) /7) AS diff
FROM weights AS w
ORDER BY id, diff
This code does not do pivot table. You should add some additional code to convert the data to your needs. You may run into trouble if you use WEEK() because of the years.