SQL showing all weeks(52) in current year - mysql

how can I show all weeks(52) in current year
I have made this query:
SELECT
COALESCE(IF(DATE_FORMAT(q.date_add, '%Y-%u') IS NULL,
(DATE_FORMAT(q.date_add, '%Y-%u')),
DATE_FORMAT(q.date_add, '%Y WEEK %u'))) AS CurrentDate,
COALESCE(IF(SUM(q.totalExcl) IS NULL,
0,
SUM(q.totalExcl))) AS total
FROM
expoled.ps_oxoquotation_quotationstate_history h
RIGHT JOIN
expoled.ps_oxoquotation_quotation q ON h.idQuotation = q.idQuotation
LEFT JOIN
expoled.ps_employee e ON h.idEmployee = e.id_employee
INNER JOIN
expoled.ps_sv_employee_meta m ON h.idEmployee = m.id_employee
WHERE
h.idEmployee = 39
AND YEAR(q.date_add) = YEAR(UTC_TIMESTAMP())
AND h.idQuotationState = 3
GROUP BY IFNULL(CurrentDate, '')
I think I need to do something in here:
SELECT
IFNULL(DATE_FORMAT(q.date_add, '%Y WEEK %u'), 0) AS CurrentDate,
IFNULL(SUM(q.totalExcl),0) AS total
FROM
I have tryed to put IFNULL but that gave me the same result
this is what I'm getting right now:
It show the current weeks from 5 to week 16.
There is also nothing in Week 1 till Week 4 because there is no data in. And there it needs to show just a simple zero(0)
So what I want is it needs to show standard from Week 1 till week 52 and if there is no data just 0
The query is working right now without any errors.

To obtain a fixed number of weeks, without a particular table, I tried this.
Table XX1 is just to make the test, you can substitute with one of your table. If it has a number of records >=366 you can eliminate the CROSS JOIN.
CREATE TABLE XX1 (id INT);
INSERT INTO XX1 VALUES (1);
INSERT INTO XX1 VALUES (2);
INSERT INTO XX1 VALUES (3);
INSERT INTO XX1 VALUES (4);
INSERT INTO XX1 VALUES (5);
SELECT DISTINCT DATE_FORMAT(RN, '%Y w%u') AS CURR_WEEK
FROM
(SELECT #RN:=DATE_ADD(str_to_date( CONCAT(YEAR(UTC_TIMESTAMP()) ,'0101'), '%Y%m%d'), INTERVAL -1 DAY) AS RN
UNION ALL
SELECT #RN:=DATE_ADD(#RN, INTERVAL 1 DAY) AS RN
FROM (SELECT 1 AS DUM FROM XX1
CROSS JOIN XX1 X2
CROSS JOIN XX1 X3
CROSS JOIN XX1 X4
) Y LIMIT 366
) X
WHERE YEAR(RN)=YEAR(UTC_TIMESTAMP())
ORDER BY 1;
DROP TABLE XX1 ;
Output:
CURR_WEEK
1 2017 w00
2 2017 w01
3 2017 w02
4 2017 w03
5 2017 w04
...
51 2017 w50
52 2017 w51
53 2017 w52
I think you could use this in place of your query (I couldn't do any test on your query of course)
Other (and hope last) version:
SELECT Y.CURR_WEEK,
COALESCE(SUM(qh.totalExcl), 0) AS total
FROM
(SELECT DISTINCT DATE_FORMAT(RN, '%Y-%u') AS CURR_WEEK
FROM
(SELECT #RN:=DATE_ADD(str_to_date( CONCAT(YEAR(UTC_TIMESTAMP()) ,'0101'), '%Y%m%d'), INTERVAL -1 DAY) AS RN
UNION ALL
SELECT #RN:=DATE_ADD(#RN, INTERVAL 1 DAY) AS RN
FROM XX1
CROSS JOIN XX1 X2
CROSS JOIN XX1 X3
CROSS JOIN XX1 X4
) X
WHERE YEAR(RN)=YEAR(UTC_TIMESTAMP())
) Y
LEFT JOIN (SELECT q.date_add, q.totalExcl, h.idEmployee
FROM expoled.ps_oxoquotation_quotation q
INNER JOIN expoled.ps_oxoquotation_quotationstate_history h ON h.idQuotation = q.idQuotation
WHERE h.idEmployee = 39 AND h.idQuotationState = 3) qh ON DATE_FORMAT(qh.date_add, '%Y-%u')=Y.CURR_WEEK
/* are these useless? */
LEFT JOIN expoled.ps_employee e ON qh.idEmployee = e.id_employee
LEFT JOIN expoled.ps_sv_employee_meta m ON qh.idEmployee = m.id_employee
GROUP BY Y.CURR_WEEK

In MariaDB, it is really easy to build a table of weeks:
mysql> SELECT ('2017-01-02' + INTERVAL seq WEEK) AS wk FROM seq_0_to_53;
+------------+
| wk |
+------------+
| 2017-01-02 |
| 2017-01-09 |
| 2017-01-16 |
| 2017-01-23 |
| 2017-01-30 |
| 2017-02-06 |
| 2017-02-13 |
| 2017-02-20 |
...
Based on that, you can:
JOIN to your table and use wk as needed.
Change the starting date to align with Sunday instead of Monday (or whatever)
Go for as many weeks as you like
Restrict the range after the fact with WHERE wk BETWEEN ...
By fetching something like CONCAT('2017 WEEK ', seq) AS iso you can get the syntax you requested.

Related

Count the number of records per month

I have records with a start and end date, like so:
id start_date end_date
1 2016-01-01 2016-10-31
2 2016-06-01 2016-12-31
3 2016-06-01 2016-07-31
I have to know the number of records that were active per month (or better put: on the first day of all months in a given period). The counts would look like this when calculated for 2016:
jan: 1
feb: 1
mar: 1
apr: 1
may: 1
jun: 3
jul: 3
aug: 2
sep: 2
oct: 2
nov: 1
dec: 1
The solution I came up with, is to create a TEMP TABLE with all applicable dates for the given period:
date
2016-01-01
2016-02-01
...
Which makes the query very easy:
SELECT
COUNT(*),
m.date
FROM
months m
INNER JOIN table t
ON m.date BETWEEN t.start_date AND t.end_date
GROUP BY
m.date
This produces exactly the results I'm looking for. However; I do feel as if this could be done easier. I just don't know how.
Any suggestions?
You can do it the following way, even if it looks ugly:
Assuming you want to run a report and you are only interested in "months of a certain year", the following query might work:
select m,Count(id) FROM (
SELECT 1 as m UNION
SELECT 2 as m UNION
SELECT 3 as m UNION
SELECT 4 as m UNION
SELECT 5 as m UNION
SELECT 6 as m UNION
SELECT 7 as m UNION
SELECT 8 as m UNION
SELECT 9 as m UNION
SELECT 10 as m UNION
SELECT 11 as m UNION
SELECT 12 as m) AS tabseq
CROSS JOIN x WHERE
(year (start_date) = 2016 AND year (end_date) = 2016 AND m >= month(start_date) AND m <= month(end_date)) -- starts abd ends this year
or
(year (start_date) < 2016 AND year (end_date) = 2016 AND m <= month(end_date)) -- ends this year, consider months until end of contract
or
(year (start_date) < 2016 AND year (end_date) > 2016) -- spans the year, ignore month,
or
(year (start_date) = 2016 AND year (end_date) > 2016 AND m >= month(start_date)) -- starts this year, consider months until end of year
GROUP BY m;
result:
m count(id)
1 1
2 1
3 1
4 1
5 1
6 3
7 3
8 2
9 2
10 2
11 1
12 1
As suggested in the comments, I replaced the temp table with a permanent table called 'calendar'.
CREATE TABLE `calendar` (
`date` date NOT NULL,
PRIMARY KEY (`date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
I filled this table with all dates from 2000-01-01 until 2100-12-31. I rewrote my query to this:
SELECT
COUNT(*),
c.date
FROM
calendar c
INNER JOIN table t
ON c.date BETWEEN t.start_date AND t.end_date
WHERE
DAYOFMONTH(c.date) = 1
AND
c.date BETWEEN '2016-01-01' AND '2016-12-31'
GROUP BY
c.date

Selected Intervals of dates in MySQL

I am working with MySQL. I am trying to get the nights of a booking that belong to each interval in a group of intervals of dates. But there are some intervals that are preferred over others and therefore I will take as many nights for the preferred intervals as possible and fill the gaps with the **not preferred interval **. To illustrate this I will show it here:
Given the dates:
check in => 2016-01-16
check out => 2016-02-08
total nights => 24
Preferred | date_from | date_to | Nights
----------------------------------------------------
1 | 2016-01-15 | 2016-01-17 | 2
1 | 2016-02-03 | 2016-02-10 | 6
1 | 2016-01-20 | 2016-01-25 | 6
0 | 2016-01-20 | 2016-01-31 | 2 (2016-01-26 and 2016-01-31 because the other nights are covered by a preferred period)
1 | 2016-01-27 | 2016-01-30 | 4
0 | 2016-01-15 | 2016-01-17 | 0 (these dates are covered by a the first interval which is a preferred interval )
0 | 2016-02-01 | 2016-02-10 | 2 (just 2016-02-01 and 2016-02-02 because 03 - 08 are covered by the second interval which is a preferred interval)
0 | 2016-01-18 | 2016-01-19 | 2
How can I achieve this in MySQL?
assuming you have a table with columns Preferred,date_from,date_to and you're just trying to calculate # of nights.
You can try this query.
SET #checkin = '2016-01-16';
SET #checkout = '2016-02-08';
SELECT T0.preferred,T0.date_from,T0.date_to,IFNULL(NIGHTS.nights,0) as Nights
FROM YourTable T0
LEFT JOIN
(SELECT T1.preferred,T1.date_from,T1.date_to,COUNT(*) AS Nights
FROM YourTable AS T1
INNER JOIN
(SELECT (#checkin + INTERVAL n DAY) as singleday
FROM numbers
WHERE (#checkin + INTERVAL n DAY) <= #checkout)DAYS1
ON DAYS1.singleday BETWEEN T1.date_from AND T1.date_to
WHERE T1.preferred = 1
OR NOT EXISTS
(SELECT 1
FROM YourTable AS T
WHERE T.preferred = 1
AND DAYS1.singleday BETWEEN T.date_from AND T.date_to
)
GROUP BY T1.preferred,T1.date_from,T1.date_to
)NIGHTS
ON T0.preferred = NIGHTS.preferred
AND T0.date_from = NIGHTS.date_from
AND T0.date_to = NIGHTS.date_to
WHERE
T0.date_from <= #checkout
AND T0.date_to >= #checkin
;
http://sqlfiddle.com/#!9/d64344/10
you can replace #checkout and #checkin occurrences with your actual checkin and check out times.
and you can replace YourTable occurrences with your actual table name
Oh yeah in the sqlfiddle i have included a table called Numbers with column n that contains numbers from 0 counting upward to whatever maximum number of possible days of stay. You need to create this table as well.
to create table numbers use the below
CREATE TABLE numbers AS
SELECT a.n+b.n+c.n+d.n+e.n+f.n+g.n+h.n+i.n as n
FROM
(SELECT 0 as n UNION SELECT 1)a,
(SELECT 0 as n UNION SELECT 2)b,
(SELECT 0 as n UNION SELECT 4)c,
(SELECT 0 as n UNION SELECT 8)d,
(SELECT 0 as n UNION SELECT 16)e,
(SELECT 0 as n UNION SELECT 32)f,
(SELECT 0 as n UNION SELECT 64)g,
(SELECT 0 as n UNION SELECT 128)h,
(SELECT 0 as n UNION SELECT 256)i;
explaination of the query
1) subquery DAYS1 returns all single dates
from #checkin to #checkout range
2) T1 is Joined with DAYS1 WHERE
preferred is 1 OR that there doesnt exist a preferred row that covers
the DAYS1's dates
3) then we do a COUNT(*) GROUP BY
preferred,date_from,date_to to get count of single days
4) Then we call our result NIGHTS
5) Then T0 is LEFT JOINED with NIGHTS to get even rows that have 0 nights
6) And only return T0 rows that intercept out #checkin/#checkout range.
UPDATE If you table is too large you can try and narrow down your subqueries with only rows you're interested in like this
SET #checkin = '2016-01-16';
SET #checkout = '2016-02-08';
SELECT T0.preferred,T0.date_from,T0.date_to,IFNULL(NIGHTS.nights,0) as Nights
FROM (SELECT * FROM YourTable WHERE date_from <= #checkout AND date_to >= #checkin) T0
LEFT JOIN
(SELECT T1.preferred,T1.date_from,T1.date_to,COUNT(*) AS Nights
FROM (SELECT * FROM YourTable WHERE date_from <= #checkout AND date_to >= #checkin) AS T1
INNER JOIN
(SELECT (#checkin + INTERVAL n DAY) as singleday
FROM numbers
WHERE (#checkin + INTERVAL n DAY) <= #checkout)DAYS1
ON DAYS1.singleday BETWEEN T1.date_from AND T1.date_to
WHERE T1.preferred = 1
OR NOT EXISTS
(SELECT 1
FROM (SELECT * FROM YourTable WHERE date_from <= #checkout AND date_to >= #checkin) AS T
WHERE T.preferred = 1
AND DAYS1.singleday BETWEEN T.date_from AND T.date_to
)
GROUP BY T1.preferred,T1.date_from,T1.date_to
)NIGHTS
ON T0.preferred = NIGHTS.preferred
AND T0.date_from = NIGHTS.date_from
AND T0.date_to = NIGHTS.date_to
;

MySQL get count of periods where date in row

I have an MySQL table, similar to this example:
c_id date value
66 2015-07-01 1
66 2015-07-02 777
66 2015-08-01 33
66 2015-08-20 200
66 2015-08-21 11
66 2015-09-14 202
66 2015-09-15 204
66 2015-09-16 23
66 2015-09-17 0
66 2015-09-18 231
What I need to get is count of periods where dates are in row. I don't have fixed start or end date, there can be any.
For example: 2015-07-01 - 2015-07-02 is one priod, 2015-08-01 is second period, 2015-08-20 - 2015-08-21 is third period and 2015-09-14 - 2015-09-18 as fourth period. So in this example there is four periods.
SELECT
SUM(value) as value_sum,
... as period_count
FROM my_table
WHERE cid = 66
Cant figure this out all day long.. Thx.
I don't have enough reputation to comment to the above answer.
If all you need is the NUMBER of splits, then you can simply reword your question: "How many entries have a date D, such that the date D - 1 DAY does not have an entry?"
In which case, this is all you need:
SELECT
COUNT(*) as PeriodCount
FROM
`periods`
WHERE
DATE_ADD(`date`, INTERVAL - 1 DAY) NOT IN (SELECT `date` from `periods`);
In your PHP, just select the "PeriodCount" column from the first row.
You had me working on some crazy stored procedure approach until that clarification :P
I should get deservedly flamed for this, but anyway, consider the following...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(date DATE NOT NULL PRIMARY KEY
,value INT NOT NULL
);
INSERT INTO my_table VALUES
('2015-07-01',1),
('2015-07-02',777),
('2015-08-01',33),
('2015-08-20',200),
('2015-08-21',11),
('2015-09-14',202),
('2015-09-15',204),
('2015-09-16',23),
('2015-09-17',0),
('2015-09-18',231);
SELECT x.*
, SUM(y.value) total
FROM
( SELECT a.date start
, MIN(c.date) end
FROM my_table a
LEFT
JOIN my_table b
ON b.date = a.date - INTERVAL 1 DAY
LEFT
JOIN my_table c
ON c.date >= a.date
LEFT
JOIN my_table d
ON d.date = c.date + INTERVAL 1 DAY
WHERE b.date IS NULL
AND c.date IS NOT NULL
AND d.date IS NULL
GROUP
BY a.date
) x
JOIN my_table y
ON y.date BETWEEN x.start AND x.end
GROUP
BY x.start;
+------------+------------+-------+
| start | end | total |
+------------+------------+-------+
| 2015-07-01 | 2015-07-02 | 778 |
| 2015-08-01 | 2015-08-01 | 33 |
| 2015-08-20 | 2015-08-21 | 211 |
| 2015-09-14 | 2015-09-18 | 660 |
+------------+------------+-------+
4 rows in set (0.00 sec) -- <-- This is the number of periods
there is a simpler way of doing this, see here SQLfiddle:
SELECT min(date) start,max(date) end,sum(value) total FROM
(SELECT #i:=#i+1 i,
ROUND(Unix_timestamp(date)/(24*60*60))-#i diff,
date,value
FROM tbl, (SELECT #i:=0)n WHERE c_id=66 ORDER BY date) t
GROUP BY diff
This select groups over the same difference between sequential number and date value.
Edit
As Strawberry remarked quite rightly, there was a flaw in my apporach, when a period spans a month change or indeed a change into the next year. The unix_timestamp() function can cure this though: It returns the seconds since 1970-1-1, so by dividing this number by 24*60*60 you get the days since that particular date. The rest is simple ...
If you only need the count, as your last comment stated, you can do it even simpler:
SELECT count(distinct diff) period_count FROM
(SELECT #i:=#i+1 i,
ROUND(Unix_timestamp(date)/(24*60*60))-#i diff,
date,value
FROM tbl,(SELECT #i:=0)n WHERE c_id=66 ORDER BY date) t
Tnx. #cars10 solution worked in MySQL, but could not manage to get period count to echo in PHP. It returned 0. Got it working tnx to #jarkinstall. So my final select looks something like this:
SELECT
sum(coalesce(count_tmp,coalesce(count_reserved,0))) as sum
,(SELECT COUNT(*) FROM my_table WHERE cid='.$cid.' AND DATE_ADD(date, INTERVAL - 1 DAY) NOT IN (SELECT date from my_table WHERE cid='.$cid.' AND coalesce(count_tmp,coalesce(count_reserved,0))>0)) as periods
,count(*) as count
,(min(date)) as min_date
,(max(date)) as max_date
FROM my_table WHERE cid=66
AND coalesce(count_tmp,coalesce(count_reserved,0))>0
ORDER BY date;

How can I combine the values of a single column in SQL

I have the table in the following format.
BatchID BatchTime
1 10:00:00
2 13:00:00
3 16:00:00
4 19:00:00
And I what I actually need is:
BatchID BatchTime
1 10:00:00 - 13:00:00
2 13:00:00 - 16:00:00
3 16:00:00 - 19:00:00
4 19:00:00 - 10:00:00
Assuming you have consecutive ids you can do the following:
SELECT a.id,a.dt date_a, b.dt date_b FROM tbl a
INNER JOIN tbl b ON b.id=a.id % (SELECT MAX(id) FROM tbl) + 1
ORDER BY a.id
See here: http://sqlfiddle.com/#!3/24791/6
Should, however, the times dt not be ascending with id (which incidentally could also have gaps) then the following will still work:
WITH t AS (SELECT id,dt,ROW_NUMBER() OVER (ORDER BY dt) n FROM tbl)
SELECT a.id,a.dt date_a, b.dt date_b FROM t a
INNER JOIN t b ON b.n=a.n % (SELECT MAX(n) FROM t) + 1
ORDER BY a.n -- order by ascending times in table a
See here: http://sqlfiddle.com/#!3/74ed6/1
The window function ROW_NUMBER() puts the times in an ascending order in the common table expression t. After that two ts are joined in a cyclic manner (using modulus % on the newly generated row number n).
a.n % (SELECT MAX(n) FROM t) + 1 will always calculate the "next" line in the cyclic order with which to join table t with alias b.

Use a sub query result

I have a table with numbers and dates (1 number each date and dates aren't necessarily at regular intervals).
I would like to get the count of dates when a number isn't in the table.
Where I am :
select *
from
(
select
date from nums
where chiffre=1
order by date desc
limit 2
) as f
I get this :
date
--------------
2014-09-07
--------------
2014-07-26
Basically, I have this query dynamically:
select * from nums where date between "2014-07-26" and "2014-09-07"
And in a second time, browse the whole table (because there I limited to the first 2 rows but I would compare the 2 and 3 and 3 and 4 etc...)
The goal is to get this:
date | actual_number_of_real_dates_between_two_given_dates
2014-09-07 - 2014-07-26 | 20
2014-04-02 - 2014-02-12 | 13
etc...
How can I do this? Thanks.
Edit:
What I have (just an example, dates and "chiffre" are more complex) :
date | chiffre
2014-09-30 | 2
2014-09-29 | 1
2014-09-28 | 2
2014-09-27 | 2
2014-09-26 | 1
2014-09-25 | 2
2014-09-24 | 2
etc...
What I need for the number "1":
actual_number_of_real_dates_between_two_given_dates
1
3
etc...
Edit 2:
My updated query thanks to Gordon Linoff
select count(n.id) as difference
from nums n inner join
(select min(date) as d1, max(date) as d2
from (select date from nums where chiffre=1 order by date desc limit 2) d
) dd
where n.date between dd.d1 and dd.d2
How can I test row 2 with 3? 3 with 4 etc... Not only last 2?
Should I use a loop? Or I can do it without?
Does this do what you want?
select count(distinct n.date) as numDates,
(datediff(dd.d2, dd.d1) + 1) as datesInPeriod,
(datediff(dd.d2, dd.d1) + 1 - count(distinct n.date)) as missingDates
from nums n cross join
(select date('2014-07-26') as d1, date('2014-09-07') as d2) d
where n.date between dd.d1 and dd.d2;
EDIT:
If you just want the last two dates:
select count(distinct n.date) as numDates,
(datediff(dd.d2, dd.d1) + 1) as datesInPeriod,
(datediff(dd.d2, dd.d1) + 1 - count(distinct n.date)) as missingDates
from nums n cross join
(select min(date) as d1, max(date) as d2
from (select date from nums order by date desc limit 2) d
) dd
where n.date between dd.d1 and dd.d2;