How can I combine the values of a single column in SQL - sql-server-2008

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.

Related

MySQL Finding the next row value by ID

id month status
1 1997-11-01 A
1 2015-08-01 B
2 2010-01-01 A
2 2010-02-01 B
2 2012-10-01 C
That I would like to format to be:
id month lead_month status
1 1997-11-01 2015-08-01 A
1 2015-08-01 NOW() B
2 2010-01-01 2010-02-01 A
2 2010-02-01 2012-10-01 B
2 2012-10-01 NOW() C
MySQL is new to me, and I have trouble wrapping my head around variables. I would prefer to use a simple LEAD() with a PARTITION but unfortunately, I can't.
Here's my attempt, that doesn't work:
SET #lead = '1995-01-01'; --abitrary floor
select id, month, status, #lead, #lead:=month from table
The output looks like this, which also doesn't check if the id's from row to row are the same:
id month lead_month status
1 1997-11-01 1995-01-01 A
1 2015-08-01 1997-11-01 B
2 2010-01-01 2015-08-01 A
2 2010-02-01 2010-01-01 B
2 2012-10-01 2010-02-01 C
Don't muck around with variables in MySQL. That sort of logic would better reside in whatever language you are using for your application. This, however can be done in SQL.
My first instinct is simply to save that data in an extra column. Don't worry about the size of the db–there aren't enough months in the universe to become a problem.
There is also something wrong with your ids: these should almost always be primary keys, i. e. unique.
If you insist on your scheme, you can use a join. Assuming consecutive unique ids:
SELECT a.id, a.month, b.month AS lead_month, status FROM table AS a LEFT JOIN table AS b WHERE a.id - 1 = b.id;
You can use a correlated subquery:
select t.*,
(select t2.month
from t t2
where t.id = t2.id
order by t2.month desc
limit 1
) as next_month
from t;
If you want to replace the value for the last month for each id, then you can use coalesce():
select t.*,
coalesce((select t2.month
from t t2
where t.id = t2.id
order by t2.month desc
limit 1
), now()) as next_month
from t;

SQL showing all weeks(52) in current year

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.

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;

MySQL - Full outer join on same table using COUNT

I am trying to generate a table in the following format.
Proday | 2014-04-01 | 2014-03-01
--------------------------------
1 | 12 | 17
2 | 6 | 0
7 | 0 | 24
13 | 3 | 7
Prodays (duration between two timestamps) is a calculated value and the data for months is a COUNT. I can output the data for a single month, but am having troubles joining queries to additional months. The index (prodays) may not match for each month. e.g.. 2014-04-01 may not have any data for Prodays 7, whereas 2014-03-01 may not have Proday 2. Should indicate with 0 or null.
I suspect FULL OUTER JOIN is what should do the trick. But have read that's not possible in Mysql?
This is the query to get data for a single month:
SELECT round((protime - createtime) / 86400) AS prodays, COUNT(id) AS '2014-04-01'
FROM `tbl_users` as t1
WHERE status = 1 AND DATE_FORMAT(FROM_UNIXTIME(createtime),'%Y-%m-%d') >= '2014-04-01'
AND DATE_FORMAT(FROM_UNIXTIME(createtime),'%Y-%m-%d') <= LAST_DAY('2014-04-01')
GROUP BY prodays
ORDER BY `prodays` ASC
How can I join/union an additional query to create a column for 2014-03-01?
You want to use conditional aggregation -- that is, move the filtering logic from the where clause to the select clause:
SELECT round((protime - createtime) / 86400) AS prodays,
sum(DATE_FORMAT(FROM_UNIXTIME(createtime),'%Y-%m-%d') >= '2014-04-01' AND
DATE_FORMAT(FROM_UNIXTIME(createtime),'%Y-%m-%d') <= LAST_DAY('2014-04-01')
) as `2014-04-01`,
sum(DATE_FORMAT(FROM_UNIXTIME(createtime),'%Y-%m-%d') >= '2014-03-01' AND
DATE_FORMAT(FROM_UNIXTIME(createtime),'%Y-%m-%d') <= LAST_DAY('2014-03-01')
) as `2014-03-01`
FROM `tbl_users` as t1
WHERE status = 1
GROUP BY prodays
ORDER BY `prodays` ASC;

How to display dates between to given values along with another column in MySQL

I have a MySQL table (A) and I would like the end result to look like (B). Does anyone know how I can accomplish this task?? I'm guessing a loop/stored procedure, but I'm not sure and I'm fairly new to loops and stored procedures.
(A)
Name str_date end_date
Lorenzo 2010-10-09 2010-10-11
Karen 2010-09-10 2010-09-10
Mike 2010-06-01 2010-06-03
(B)
Name Date
Lorenzo 2010-10-09
Lorenzo 2010-10-10
Lorenzo 2010-10-11
Karen 2010-09-10
Mike 2010-06-01
Mike 2010-06-02
Mike 2010-06-03
dont even try to do this in mysql, use your server side programming language to format data.
and if you really, really, really want to:
create a table with all dates needed (i.e. since 1970-01-01 to 2030-01-01) and do something like (untested):
-- ddl
CREATE TABLE all_dates ( date DATE, PRIMARY KEY (date) );
-- query
SELECT ad.date, A.Name
FROM all_dates ad
JOIN A ON A.str_date <= ad.date AND A.end_date >= ad.date
but still its server side task, not db.
Try something like this:
/* create view that will help us to to create calendar table */
create or replace view n as select 0 as n union all select 1 union
select 2 union select 3 union
select 4 union select 5;
drop temporary table if exists cal;
/* create calendar table */
create temporary table cal (d datetime primary key);
set #i = 0;
/* select min date value */
select #d := min(str_date) from tab;
/* create table with dates based on min date*/
insert into cal
select date_add(#d, interval #i := #i + 1 day) from n a, n b, n c, n d, n e;
/* join date to calendar table */
select Name, c.d as Date
from tab t
join cal c on t.str_date <= c.d and t.end_date >= c.d
order by c.d desc;
Don't forget to clean up:
drop view if exists n;
drop table if exists cal;
Result:
Name Date
Lorenzo 2010-10-11 00:00:00
Lorenzo 2010-10-10 00:00:00
Lorenzo 2010-10-09 00:00:00
Karen 2010-09-10 00:00:00
Mike 2010-06-03 00:00:00
Mike 2010-06-02 00:00:00
Added simpler version:
/* create view that allows generate integers */
create or replace view n as select 0 as n union all select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9;
/* use generated integers to generate dates and join them */
select Name, c.d as Date
from tab t
join (
select date_add(t.d, interval i day) as d
from (select min(str_date) as d from tab) t
join (select a.n + b.n * 10 + c.n * 100 + d.n * 1000 as i from n a, n b, n c, n d) n
) c on t.str_date <= c.d and t.end_date >= c.d
order by c.d desc;