Expand rows to contain monthly rows between 2 dates - mysql

I have a table 'positions' with the following variable:
- id
- role
- startdate
- enddate
I need to generate monthly series using data of anothr table. Is there a way to expand each of the rows of the table 'positions' so that each role is expanded into n-rows of months between start date and end date.
For example, Row 1 containing:
(0001, 'Salesperson', '2020-01', '2020-05')
I need to expand into something like:
(0001, 'Salesperson', '2020-01')
(0001, 'Salesperson', '2020-02')
(0001, 'Salesperson', '2020-03')
(0001, 'Salesperson', '2020-04')
(0001, 'Salesperson', '2020-05')
Thanks!
I tried iterating through the table but haven't been able to get the reuslt

Your question is tagged with 2 Databases. I am using SQL Server to answer. You can try something like this:
SQL Fiddle DEMO
DECLARE #StartDate DATETIME = '2018-01-01 00:00:00.000'; -- this can be any date below the minimum StartDate
WITH tt AS (
SELECT '0001' AS EmployeeID, '2020-01-01 00:00:00.000' AS StartDate, '2020-05-31 00:00:00.000' AS EndDate
),
MyTable AS
(
SELECT #StartDate AS myDate
UNION ALL
SELECT DATEADD(Month,1,myDate)
FROM MyTable
WHERE DATEADD(Month,1,myDate) <= '2020-05-31 00:00:00.000'
)
SELECT
EmpId.EmployeeID,
CONVERT(VARCHAR(7), a.myDate, 126)
FROM
MyTable a
INNER JOIN
(
SELECT EmployeeID, MIN(StartDate) MinStartDate
FROM tt
GROUP BY EmployeeID
) EmpId ON
a.MyDate >= EmpId.MinStartDate
LEFT JOIN
tt ON
EmpId.EmployeeID = tt.EmployeeID AND
a.myDate >= tt.StartDate AND
a.myDate <= ISNULL(tt.EndDate, GETDATE())
ORDER BY EmpId.EmployeeID DESC, a.MyDate
OPTION (MAXRECURSION 0)
Also I have assumed that your startDate and EndDate are in Date format inside your table.

Related

one to many relations data fetch from database mysql

This is my clockin table:
This is my user table:
I want to accepted output in this type:
Try this:
set #start_date='2022-12-30';
set #end_date='2022-12-31';
WITH recursive Date_Ranges AS (
select #start_date as date
union all
select date + interval 1 day from Date_Ranges
where date < #end_date
)
SELECT
new_table.*,
(
SELECT
group_concat(`time`)
FROM
`clockin`
WHERE
`emid`=new_table.`id` AND
`date`=new_table.`date`
) as `time`
FROM
(select * from `user` full join Date_Ranges) as new_table
order by date desc

MySQL Select where column greater than or equal to closest past date from given date

TABLE
Table:
Id Date
1 01-10-15
2 01-01-16
3 01-03-16
4 01-06-16
5 01-08-16
Given two dates startdate 01-02-16 and enddate 01-05-16. I need to get the data from the table such that it returns all data between the closest past date from startdate and closest future date from enddate including the two dates. So the result will look like this.
Result:
Id Date
2 01-01-16
3 01-03-16
4 01-06-16
What I am doing
What I am doing now is fetching the whole data and removing from the array results less than closest fromdate and greater than closest enddate
What I want
What I want is to do this in query itself so that I don't have to fetch the whole data from table each time.
If you column's type is date, use union can do it:
(select * from yourtable where `date` <= '2016-01-02' order by `date` desc limit 1)
-- This query will get record which is closest past date from startdate
union
(select * from yourtable where `date` => '2016-01-05' order by `date` asc limit 1)
-- This query will get record which is closest future date from enddate
union
(select * from yourtable where `date` between '2016-01-02' and '2016-01-05')
Demo Here
Imaging your date is in YYYY-mm-dd
## get rows within the dates
SELECT * FROM tab WHERE ymd BETWEEN :start_date AND :end_date
## get one row closest to start date
UNION
SELECT * FROM tab WHERE ymd < :start_date ORDER BY ymd DESC LIMIT 1
## get one row closest to end date
UNION
SELECT * FROM tab WHERE ymd > :end_date ORDER BY ymd LIMIT 1
Try this
Select *
From
dTable
Where
[Date]
Between
(Select
Max(t1.Date)
From
dTable t1
Where
t1.date <startdate) And
(Select
Min(t2.Date)
From
dTable t2
Where
t2.date >enddate)
If Date is String, STR_TO_DATE and DATEDIFF can be used here.
SELECT id, Date
FROM tab
where
STR_TO_DATE(Date, '%d-%m-%y') BETWEEN('2016-02-01')AND('2016-05-01')
or
id = (SELECT id FROM tab
where STR_TO_DATE(Date, '%d-%m-%y') > '2016-05-01'
ORDER BY DATEDIFF(STR_TO_DATE(Date, '%d-%m-%y'), '2016-05-01') Limit 1)
or
id = (SELECT id FROM tab
where STR_TO_DATE(Date, '%d-%m-%y') < '2016-02-01'
ORDER BY DATEDIFF('2016-02-01', STR_TO_DATE(Date, '%d-%m-%y')) Limit 1)

SQL Query issue with joining data don't exist in table [duplicate]

I've got a SQL Server CE 3.5 table (Transactions) with the following Schema:
ID
Transaction_Date
Category
Description
Amount
Query:
SELECT Transaction_Date, SUM(Amount)
FROM Transactions
GROUP BY Transaction_Date;
I'm trying to do a SUM(Amount) and group by transaction_date just so I can get the total amount for each day but I want to get back values even for days there were no transactions so basically the record for a day with no transactions would just have $0.00 for amount.
Thanks for the help!
You need a Calendar table to select over the dates. Alternatively, if you have a Numbers table, you could turn that effectively into a Calendar table. Basically, it's just a table with every date in it. It's easy enough to build and generate the data for it and it comes in very handy for these situations. Then you would simply use:
SELECT
C.calendar_date,
SUM(T.amount)
FROM
Calendar C
LEFT OUTER JOIN Transactions T ON
T.transaction_date = C.calendar_date
GROUP BY
C.calendar_date
ORDER BY
C.calendar_date
A few things to keep in mind:
If you're sending this to a front-end or reporting engine then you should just send the dates that you have (your original query) and have the front end fill in the $0.00 days itself if that's possible.
Also, I've assumed here that the date is an exact date value with no time component (hence the "=" in the join). Your calendar table could include a "start_time" and "end_time" so that you can use BETWEEN for working with dates that include a time portion. That saves you from having to strip off time portions and potentially ruining index usage. You could also just calculate the start and end points of the day when you use it, but since it's a prefilled work table it's easier IMO to include a start_time and end_time.
You'll need to upper and lower bound your statement somehow, but perhaps this will help.
DECLARE #Start smalldatetime, #End smalldatetime
SELECT #Start = 'Jan 1 2010', #End = 'Jan 18 2010';
--- make a CTE of range of dates we're interested in
WITH Cal AS (
SELECT CalDate = convert(datetime, #Start)
UNION ALL
SELECT CalDate = dateadd(d,1,convert(datetime, CalDate)) FROM Cal WHERE CalDate < #End
)
SELECT CalDate AS TransactionDate, ISNULL(SUM(Amount),0) AS TransactionAmount
FROM Cal AS C
LEFT JOIN Transactions AS T On C.CalDate = T.Transaction_Date
GROUP BY CalDate ;
Once you have a Calendar table (more on that later) you can then do an inner join on the range of your data to fill in missing dates:
SELECT CalendarDate, NULLIF(SUM(t.Amount),0)
FROM (SELECT CalendardDate FROM Calendar
WHERE CalendarDate>= (SELECT MIN(TransactionDate) FROM Transactions) AND
CalendarDate<= (SELECT MAX(TransactionDate) FROM Transactions)) c
LEFT JOIN
Transactions t ON t.TransactionDate=c.CalendarDate
GROUP BY CalendarDate
To create a calendar table, you can use a CTE:
WITH CalendarTable
AS
(
SELECT CAST('20090601' as datetime) AS [date]
UNION ALL
SELECT DATEADD(dd, 1, [date])
FROM CTE_DatesTable
WHERE DATEADD(dd, 1, [date]) <= '20090630' /* last date */
)
SELECT [date] FROM CTE_DatesTable
OPTION (MAXRECURSION 0);
Combining the two, we have
WITH CalendarTable
AS
(
SELECT MIN(TransactionDate) FROM Transactions AS [date]
UNION ALL
SELECT DATEADD(dd, 1, [date])
FROM CTE_DatesTable
WHERE DATEADD(dd, 1, [date]) <= (SELECT MAX(TransactionDate) FROM Transactions)
)
SELECT c.[date], NULLIF(SUM(t.Amount),0)
FROM Calendar c
LEFT JOIN
Transactions t ON t.TransactionDate=c.[date]
GROUP BY c.[date]
Not sure if any this works with CE
With common table expressions
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SET #StartDate = '2010-07-10'
SET #EndDate = '2010-07-20'
;WITH Dates AS (
SELECT #StartDate AS DateValue
UNION ALL
SELECT DateValue + 1
FROM Dates
WHERE DateValue + 1 <= #EndDate
)
SELECT Dates.DateValue, ISNULL(SUM(Transactions.Amount), 0)
FROM Dates
LEFT JOIN Transactions ON
Dates.DateValue = Transactions.Transaction_Date
GROUP BY Dates.DateValue;
With loop + temporary table
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SET #StartDate = '2010-07-10'
SET #EndDate = '2010-07-20'
SELECT #StartDate AS DateValue INTO #Dates
WHILE #StartDate <= #EndDate
BEGIN
SET #StartDate = #StartDate + 1
INSERT INTO #Dates VALUES (#StartDate)
END
SELECT Dates.DateValue, ISNULL(SUM(Transactions.Amount), 0)
FROM #Dates AS Dates
LEFT JOIN Transactions ON
Dates.DateValue = Transactions.Transaction_Date
GROUP BY Dates.DateValue;
DROP TABLE #Dates
If you want dates that don't have transactions to appear
you can add a DUMMY transaction for each day with the amount of zero
it won't interfere with SUM and would so what you want

Select the first element in each day of the month

How to select the first element of each day in a month with mysql query ?
I have table with offers - startdate, so i can check for each day,month,year i'm getting the element but, i'm wondering how to get only the first element in each day of some month ?
Assume the following
Table is called mytable
Table has id as primary key
Table has dt as datatime
You want the first id of everyday in February 2012
Try this:
SELECT B.id FROM
(
SELECT DATE(dt) date_dt,MIN(dt) dt
FROM mytable
WHERE dt >= '2012-02-01 00:00:00'
AND dt < '2012-03-01 00:00:00'
GROUP BY DATE(dt)
) A
LEFT JOIN mytable B USING (dt);
If any dt has multiple B.id values try this:
SELECT dt,MIN(id) id
(
SELECT B.id,B.dt FROM
(
SELECT DATE(dt) date_dt,MIN(dt) dt
FROM mytable
WHERE dt >= '2012-02-01 00:00:00'
AND dt < '2012-03-01 00:00:00'
GROUP BY DATE(dt)
) A
LEFT JOIN mytable B USING (dt)
) AA GROUP BY dt;
Assuming startdate is a DATETIME type, and the earliest entry is the one with the earliest DATETIME value, for March, 2012:
SELECT DISTINCT *
FROM tbl t1
LEFT JOIN tbl t2
ON (t2.startdate BETWEEN '2012-02-01 00:00:00' AND '2012-02-29 23:59:59')
AND t2.startdate < t1.startdate
WHERE (t1.startdate BETWEEN '2012-02-01 00:00:00' AND '2012-02-29 23:59:59')
AND t2.startdate IS NULL
If there are no duplicate dates, then you don't need the DISTINCT.
This query works by joining with any earlier record for the same month, so if nothing was joined, it's the earliest, through process of elimination.
This technique is explained in detail in the book SQL Antipatterns.
This could also be solved with subqueries, but this type of JOIN is supposed to be easier to optimize by MySQL than subqueries, which often negate the use of indexes.
without knowing the exact structure of your table something like this should work:
SELECT MIN(offerId) FROM offers WHERE startdate <= '2012-03-06' AND startdate >= '2012-02-06' GROUP BY date(startdate)
It sounds like you are trying to do something like the following:
SELECT col_1, date_col, col_3 FROM tbl
WHERE
date_col = ( SELECT min(date_col) FROM tbl
WHERE
year(date_col) = 2006 AND
month(date_col) = 02
);
This can also be used to find the max( date_col ) . Hope this helps.
Just to offer a different way to skin this cat (much easier in SQL Server for once actually)
SELECT
t0.offerId
FROM
offers AS t0 LEFT JOIN
offers AS t1 ON t0.offerId = t1.offerId AND t1.startDate > t0.startDate AND
(t0.startDate BETWEEN '2012-02-01' AND '2012-03-01') AND
(t1.startDate BETWEEN '2012-02-01' AND '2012-03-01')
WHERE
t1.col1 IS NULL;
If you have multiple rows with the same exact time you will get multiple values returned, which you can weed out in your application logic or with a sub-query. BTW this is called a groupwise minimum/maximum.

Group by month including empty months

I want to select all my order values per month. I know this works fine with GROUP BY month but only with months with orders in it. Now I want also the months with no orders so I get all months.
This is my query:
SELECT SUM(VerkoopfactBedrag) AS bedrag, DATE_FORMAT(VerkoopfactDatum,'%M') AS date
FROM verkoopfacturen
WHERE Verkoopfact_UserId = 12
AND VerkoopfactDatum BETWEEN '2011-01-01' AND '2011-12-30'
GROUP BY MONTH(VerkoopfactDatum)
So when the result of a month is 0 I want to see the month with value 0 but now the month don't show up.
Is this possible?
One way to do this is to create and populate a table full of consecutive months.
You can then OUTER JOIN using that table.
So something like:
drop table if exists all_months;
create table all_months (a_month_id int unsigned PRIMARY KEY,a_month varchar(20) NOT NULL, UNIQUE KEY `all_months_uidx1` (a_month));
insert into all_months values (1,'January');
insert into all_months values (2,'February');
insert into all_months values (3,'March');
insert into all_months values (4,'April');
insert into all_months values (5,'May');
insert into all_months values (6,'June');
insert into all_months values (7,'July');
insert into all_months values (8,'August');
insert into all_months values (9,'September');
insert into all_months values (10,'October');
insert into all_months values (11,'November');
insert into all_months values (12,'December');
SELECT SUM(IFNULL(t1.VerkoopfactBedrag,0)) AS bedrag,
am.a_month AS date
from
(
select
ifnull(vn.VerkoopfactBedrag,0) as VerkoopfactBedrag,
cast(DATE_FORMAT(VerkoopfactDatum, '%M') as char) as mdate
FROM verkoopfacturen vn
WHERE Verkoopfact_UserId = 12
AND VerkoopfactDatum BETWEEN '2011-01-01' AND '2011-12-31'
GROUP BY DATE_FORMAT(VerkoopfactDatum, '%M')
) t1 RIGHT OUTER JOIN all_months am on t1.mdate = am.a_month
group by am.a_month
order by a_month_id asc;
PS Not sure if you have anything against Oudejaarsavond but there are 31 days in December ;-)
After searching for a simple solution i finally found this which I think is SIMPLE. This will show last year and this years sales side by side.
select
date_format(current_date - INTERVAL 1 YEAR,'%Y') as LAST_YR,
date_format(NOW(),'%Y') as THIS_YR,
monthname(date) as month,
sum(case when year(date) = date_format(current_date - INTERVAL 1 YEAR,'%Y') then amount else 0 end) as sales_ly,
sum(case when year(date) = DATE_FORMAT(NOW(),'%Y') then amount else 0 end) as sales_ty
from tablename
where date between date_format(current_date - INTERVAL 1 YEAR,'%Y-%01-%01')
and date_format(current_date, '%Y-%12-%31')
group by monthname(date)
order by max(month(date));