SQL increment date using recursion - mysql

I have a table which contains the following structure:
|-----------|------------|-----------|
| Number | Date | Subject |
|-----------|------------|-----------|
| 1 | 2015-01-01 | ABC |
| 2 | 2015-01-01 | ABC |
| 3 | 2015-01-01 | ABC |
| 4 | 2015-01-01 | ABC |
|-----------|------------|-----------|
I need to loop through the table and increment of n days each date.
So, assuming n = 10 I should get this result:
|-----------|------------|-----------|
| Number | Date | Subject |
|-----------|------------|-----------|
| 1 | 2015-01-01 | ABC |
| 2 | 2015-01-11 | ABC |
| 3 | 2015-01-21 | ABC |
| 4 | 2015-01-31 | ABC |
|-----------|------------|-----------|
The problem is a bit more complicated because n is generated by using a function which needs the previous generated date
I am trying to use CTE to accomplish this with the following CTE, but I get too many rows than expected.
WITH myCte(Number, Date, Subject)
AS
(
SELECT * FROM MyTable
UNION ALL
SELECT
Number, dbo.get_next_date(Date)
FROM MyCte
)
SELECT * FROM MyCte

That is because you have no WHERE clause in the recursive CTE. This would cause the query to stop when MAXRECURSION value is reached (default 100).
Here is an example to set a limit with a WHERE clause
DECLARE #MyTable TABLE
(
Number int,
Dt Date,
Sub CHAR(3)
)
INSERT INTO #MyTable
VALUES
(1,'2015-01-01','ABC'),
(2,'2015-01-11','ABC'),
(3,'2015-01-21','ABC'),
(4,'2015-01-31','ABC')
;WITH myCte(Number, Date, Subject)
AS
(
SELECT * FROM #MyTable
UNION ALL
SELECT
Number, DATEADD(day, 10, Date),Subject
FROM MyCte
WHERE Date < GETDATE()
)
SELECT * FROM MyCte
EDIT - If you know the number of rows, then you could just use TOP and get those rows.
SELECT TOP 4 * FROM MyCte

Related

Hash the result from the database , Is it possible?

I don't know if the question wording is correct or not, but I want to do the following:
I have a table named sales It contain following columns:
------------------------------------------------
| PRODUCT_NAME | PRODUCT_QUANTITY | ExpierDate |
------------------------------------------------
if I SELECT * FROM sales then the result will be :
------------------------------------------------
| PRODUCT_NAME | PRODUCT_QUANTITY | ExpierDate |
------------------------------------------------
| TestName | 5 | 2021-6-12 |
| TestName | 2 | 2024-10-18 |
------------------------------------------------
What I need to do is to select query and get the result look like this :
------------------------------------------------
| PRODUCT_NAME | PRODUCT_QUANTITY | ExpierDate |
------------------------------------------------
| TestName | 1 | 2021-6-12 |
| TestName | 2 | 2021-6-12 |
| TestName | 3 | 2021-6-12 |
| TestName | 4 | 2021-6-12 |
| TestName | 5 | 2021-6-12 |
| TestName | 1 | 2024-10-18 |
| TestName | 2 | 2024-10-18 |
------------------------------------------------
Is this even possible ?
How can I do this..!?
I would recommend directly using a recursive CTE:
with recursive cte as (
select product_name, product_quantity, expire_date, 1 as n
from sales s
union all
select product_name, product_quantity, expire_date, n + 1
from cte
where n < product_quantity
)
select *
from cte
order by product_name, product_quantity, expire_date, n;
Here is a db<>fiddle.
As I mention in comment, with cte create pseudo data to fill up the row is really easy:
with RECURSIVE quan(quantity) AS (
SELECT 1
UNION ALL
SELECT quantity+1 FROM quan WHERE quantity < 10 --you may have to increase this
)
SELECT tb.PRODUCT_NAME ,quan.quantity as PRODUCT_QUANTITY,tb.ExpierDate
FROM [tb] -- your result table
JOIN quan on tb.PRODUCT_QUANTITY >= quan.quantity
ORDER BY tb.time,quan.quantity
here is db<>fiddle with pseudo data.
using int column as time column in pseudo data but I think query itself will still work.
In recursive cte you may have to increase the where part to create more pseudo quantity data depend on your max quantity.

Update first occurrence of value in a time interval

I'm trying to set the value of another column on the first occurrence of any value in a username column in monthly intervals, if there's another column with an specific value.
create table table1
(
username varchar(30) not null,
`date` date not null,
eventid int not null,
firstFlag int null
);
insert table1 (username,`date`, eventid) values
('john','2015-01-01', 1)
, ('kim','2015-01-01', 1)
, ('john','2015-01-01', 1)
, ('john','2015-01-01', 1)
, ('john','2015-03-01', 2)
, ('john','2015-03-01', 1)
, ('kim','2015-01-01', 1)
, ('kim','2015-02-01', 1);
This should result in:
| username | date | eventid | firstFlag |
|----------|------------|---------|-----------|
| john | 2015-01-01 | 1 | 1 |
| kim | 2015-01-01 | 1 | 1 |
| john | 2015-01-01 | 1 | (null) |
| john | 2015-01-01 | 1 | (null) |
| john | 2015-03-01 | 2 | 1 |
| john | 2015-03-01 | 1 | (null) |
| kim | 2015-01-01 | 1 | (null) |
| kim | 2015-02-01 | 1 | 1 |
I've tried using joins as described here, but it updates all rows:
update table1 t1
inner join
( select username,min(`date`) as minForGroup
from table1
group by username,`date`
) inr
on inr.username=t1.username and inr.minForGroup=t1.`date`
set firstFlag=1;
As a1ex07 points out, it would need another per row unique constrain to update the rows I need to:
update table1 t1
inner join
( select id, username,min(`date`) as minForGroup
from table1
where eventid = 1
group by username,month(`date`)
) inr
on inr.id=t1.id and inr.username=t1.username and inr.minForGroup=t1.`date`
set firstFlag=1;
Add an Id column, and use it on the join on constrains.
To allow only those that satisfies a specific condition on another column you need the where clause inside the subquery, otherwise it would try to match different rows as the subquery would return rows with eventid=2 while the update query would return only those with eventid=1.
To use yearly intervals instead of monthly, change the group by statement to use years.

nested select - SQL-query

In my MySQL DB I have a table like this
| - value - | - date - |
|------------------------|
| 1 | 01.01.2016 |
| 2 | 02.01.2016 |
| 3 | 04.02.2016 |
| 5 | 01.01.2017 |
| 1 | 02.01.2017 |
| 5 | 04.02.2017 |
As result, I would like to have a value differences of rows with equal dates (regarding only the day and the month).
So basically I want this as result:
| - value - | - date - |
|------------------------|
| 1 - 4 | 01.01 |
| 2 - 1 | 02.01 |
| 3 - 5 | 04.02 |
How can I do it in SQL? I can use a nested SELECT to get data of one year like this:
SELECT col FROM (
SELECT value,date FROM Table
WHERE date BETWEEN '2016-01-01' AND '2016-12-31'
) tab1
Now I would have to add the 2017 year data - but if I just write FROM (SELECT ...) tab1, (SELECT ...) tab2 It will be a "cross prodcut" - And the numbers won't add up.
I'd create two subqueries, one for 2016 and one for 2017, and then join them:
SELECT CONCAT(v16, ' - ', v17)
FROM (SELECT value AS v16, date AS d16
FROM mytable
WHERE YEAR(date) = 2016) a
JOIN (SELECT value AS v17, date AS d17
FROM mytable
WHERE YEAR(date) = 2017) b ON MONTH(d16) = MONTH(d17) AND
DAY(d16) = DAY(d17)
try this
SELECT GROUP_CONCAT(value SEPARATOR '-'),DATE_FORMAT(date,'%m-%d')as date
FROM table GROUP BY DATE_FORMAT(date,'%m-%d');

Mysql query data transformation

I am trying to do transformation on a table in Mysql. I can't figure out how to do it. Could anyone tell me how to do it? The input and output is given. I would like to know how it is done?
Input table
+-------------+------------+------------------+-------------------+
| Employee_ID | Start_Date | Termination_Date | Performance_Level |
+-------------+------------+------------------+-------------------+
| 1 | 1/1/2007 | 3/1/2007 | Low |
| 2 | 6/5/2004 | Null | Medium |
| 3 | 4/3/2003 | Null | High |
| 4 | 9/1/2002 | 4/15/2007 | Medium |
| 5 | 4/6/2007 | 11/1/2007 | Low |
| 6 | 7/1/2007 | Null | High |
| 7 | 3/2/2005 | 8/1/2007 | Low |
+-------------+------------+------------------+-------------------+
Ouput Table
+---------+-----------------------------------+-----------------+-------------------+----------------+
| Period | Total_Employees_at_end_of_quarter | High_Performers | Medium_Performers | Low_Performers |
+---------+-----------------------------------+-----------------+-------------------+----------------+
| Q1-2007 | 4 | 1 | 2 | 1 |
| Q2-2007 | 4 | 1 | 1 | 2 |
| Q3-2007 | 4 | 2 | 1 | 1 |
| Q4-2007 | 3 | 2 | 1 | 0 |
+---------+-----------------------------------+-----------------+-------------------+----------------+
This is what I tried
select * from emp
where date(sdate)< date'2007-04-01' and (date(tdate)> date'2007-03-31' or tdate is null);
select * from emp
where date(sdate)< date'2007-07-01' and (date(tdate)> date'2007-06-30' or tdate is null);
select * from emp
where date(sdate)< date'2007-010-01' and (date(tdate)> date'2007-09-30' or tdate is null);
select * from emp
where date(sdate)< date'2008-01-01' and (date(tdate)> date'2007-12-31' or tdate is null);
I have the individual queries but I want a single query which will give the outputs.
The approach taken below is to create a driver table for each quarter, with information about the year and quarter. This is then joined to the employee table, using a non-equijoin. Employees who start in or before the quarter and end after the quarter are active at the end of quarter.
It uses one trick for the date comparisons, which is to convert the year-quarter combination into a quarter count, by multiplying the year by 4 and adding the quarter. This is a convenience for simplifying the date comparisons.
select driver.qtryr, count(*) as TotalPerformers,
sum(Performance_level = 'High') as HighPerformers,
sum(Performance_level = 'Medium') as MediumPerformers,
sum(Performance_level = 'Low') as LowPerformers
from (select 2007 as yr, 1 as qtr, 'Q1-2007' as qtryr union all
select 2007 as yr, 2 as qtr, 'Q2-2007' as qtryr union all
select 2007 as yr, 3 as qtr, 'Q3-2007' as qtryr union all
select 2007 as yr, 4 as qtr, 'Q4-2007' as qtryr
) driver left outer join
Table1 emp
on year(emp.start_date)*4+quarter(emp.start_date) <= driver.yr*4+qtr and
(emp.termination_date is null or
year(emp.termination_date)*4+quarter(emp.termination_date) > driver.yr*4+qtr
)
group by driver.qtryr
sqlfiddle
try this
SELECT QUARTER('2008-04-01');
http://dev.mysql.com/doc/refman/5.6/en/date-and-time-functions.html#function_quarter
and CONCAT()

Expanding a mySQL table using only mySQL

Let's say I have a mySQL table whose structure is like this:
mysql> select * from things_with_stuff;
+----+---------+----------+
| id | counter | quantity |
+----+---------+----------+
| 1 | 101 | 1 |
| 2 | 102 | 2 |
| 3 | 103 | 3 |
+----+---------+----------+
My goal is to "expand" the table so I end up with something like:
mysql> select * from stuff;
+----+---------+
| id | counter |
+----+---------+
| 1 | 101 |
| 2 | 102 |
| 3 | 102 |
| 4 | 103 |
| 5 | 103 |
| 6 | 103 |
+----+---------+
And I want to do this "expansion" using only mysql. Note that I end up with a row per quantity and per counter. Any suggestions? A stored procedure is not an option here (I know they offer while loops).
Thanks!
The following will do the trick as long as some_large_table has a length greater than or equal to the largest quantity in things_with_stuff. For example, let some_large_table be a big fact table in a data warehouse.
SELECT #kn:=#kn+1 AS id, counter
FROM (SELECT #kn:=0) k, things_with_stuff ts
INNER JOIN (
SELECT #rn:=#rn+1 AS num
FROM (SELECT #rn:=0) t, some_large_table
) nums ON num <= ts.quantity;
Assuming there is a maximum value for quantity, you could do:
INSERT INTO things SELECT counter FROM things_with_stuff WHERE quantity > 0;
INSERT INTO things SELECT counter FROM things_with_stuff WHERE quantity > 1;
INSERT INTO things SELECT counter FROM things_with_stuff WHERE quantity > 2;
--... and so on until the max.
It's a bit of a hack but it should do the job.
If the ordering is important you could do a clean up afterwards.
I have sometimes in databases a table named num (number) with a single column i, filled with all integers from 1 to 1000000. It's not hard to make such a table and populate it.
Then you could use this if stuff.id is auto incremented:
INSERT INTO stuff
( counter )
SELECT ts.counter
FROM things_with_stuff AS ts
JOIN num
ON num.i <= ts.quantity