I have a table where each data has a date when it was inserted in form of two columns, year and week:
+------+------+-------+
| Week | Year | Value |
+------+------+-------+
| 1 | 2014 | 5 |
| 5 | 2014 | 23 |
| 6 | 2014 | 12 |
| 7 | 2014 | 43 |
| 8 | 2014 | 4 |
| 9 | 2014 | 2 |
| 26 | 2013 | 21 |
| 27 | 2013 | 17 |
| 28 | 2013 | 42 |
| 31 | 2013 | 5 |
| ... | ... | .. |
+------+------+-------+
I need a query to get data that was inserted between two dates (year, week). I guess that it should be alternative to intersection of two queries, one with a starting date and the second with ending data, but I can't get it to work. Any help or sugestion?
Here is my shot but INTERSECT is not supported in MySql:
(SELECT SUM(Duration), Week, Type, Year
FROM UP26rotordowntime
WHERE (Year>=2013)
AND (Week >=01)
GROUP BY Week)
INTERSECT
(SELECT SUM(Duration), Week, Type, Year
FROM UP26rotordowntime
WHERE (Year<=2014)
AND (Week <=14)
GROUP BY Week)
You can put simple logic in WHERE conditions and use (year,week) pairs for GROUP BY:
SELECT SUM(Duration), Week, Type, Year
FROM UP26rotordowntime
WHERE Year = 2005 AND Week >= 10
OR Year BETWEEN 2006 AND 2013
OR Year = 2014 AND Week <= 14
GROUP BY Year,Week
If you have id the query is very simple:
SELECT SUM(Duration), Week, Type, Year FROM UP26rotordowntime
WHERE ID IN (
SELECT ID FROM UP26rotordowntime WHERE (Year>=2013) AND (Week >=01) )
OR ID IN (
SELECT ID FROM UP26rotordowntime WHERE (Year<=2014) AND (Week <=14))
GROUP BY Week
This should return you the intersect you need
Related
I have a table from where I am getting month names and some quantity measures.
Table Name = Month_Name
SELECT month_name,q1,q2 FROM month_name;
mysql> SELECT * FROM MONTH;
+------------+------+------+
| month_name | q1 | q2 |
+------------+------+------+
| January | 10 | 20 |
| March | 30 | 40 |
| March | 10 | 5 |
+------------+------+------+
Expected Output:
mysql> SELECT month_name ,SUM(q1),SUM(q2) FROM MONTH GROUP BY month_name;
+------------+---------+---------+
| month_name | sum(q1) | sum(q2) |
+------------+---------+---------+
| January | 10 | 20 |
| Febuary | 0 | 0 |
| March | 40 | 45 |
| April | 0 | 0 |
+------------+---------+---------+
Group by month will not print February and April since these 2 months are not present in base table. I do not want to use Union All since there will be performance issues with union All, Is there any other optimised approach to this.
You can use a calendar table which keeps track of all the month names which you want to appear in your report.
SELECT
m1.month_name,
SUM(q1) AS q1_sum,
SUM(q2) AS q2_sum
FROM
(
SELECT 'January' AS month_name UNION ALL
SELECT 'February' UNION ALL
SELECT 'March' UNION ALL
...
SELECT 'December'
) m1
LEFT JOIN month m2
ON m1.month_name = m2.month_name
GROUP BY
m1.month_name;
Note that while this solve your immediate problem, it is still not ideal, because we don't have any easy way to sort the months. A much better table design would be to maintain a date column. The month name is easily derived from the date.
I have a table full of monthly contracts. There is a monthly price, a start date, and an end date for each. I am trying to graph each month's total revenue and am wondering if it's possible to do this in one query (vs. a query for each month).
I know how to group by month and year in mysql, but this requires a more complex solution that "understands" whether to include in the sum for a given month/year based on the start and end date of the contract.
Shorthand example
| contract_id | price | start_date | end_date |
| 1 | 299 | 1546318800 (1/1/19) | 1554004800 (3/31/19) |
| 2 | 799 | 1551416400 (3/1/19) | 1559275200 (5/31/19) |
With this example, there's an overlap in March. Both contracts are running in March, so the sum returned for that month should be 1098.
I'd like to be able to produce a report that includes every month between two dates, so in this case I'd send 1/1/19 - 12/31/19, the full year of 2019 and would hope to see 0 results as well.
| month | year | price_sum |
| 1 | 2019 | 299 |
| 2 | 2019 | 299 |
| 3 | 2019 | 1098 |
| 4 | 2019 | 799 |
| 5 | 2019 | 799 |
| 6 | 2019 | 0 |
| 7 | 2019 | 0 |
| 8 | 2019 | 0 |
| 9 | 2019 | 0 |
| 10 | 2019 | 0 |
| 11 | 2019 | 0 |
| 12 | 2019 | 0 |
Here is a full working script for your problem, which uses a calendar table approach to represent every month in 2019. Specifically, we represent each month using the first of that month. Then, a given price from your table is applicable to that month if there is overlap with the start and end range.
WITH yourTable AS (
SELECT 1 AS contract_id, 299 AS price, '2019-01-01' AS start_date, '2019-03-31' AS end_date UNION ALL
SELECT 2, 799, '2019-03-01', '2019-05-31'
),
dates AS (
SELECT '2019-01-01' AS dt UNION ALL
SELECT '2019-02-01' UNION ALL
SELECT '2019-03-01' UNION ALL
SELECT '2019-04-01' UNION ALL
SELECT '2019-05-01' UNION ALL
SELECT '2019-06-01' UNION ALL
SELECT '2019-07-01' UNION ALL
SELECT '2019-08-01' UNION ALL
SELECT '2019-09-01' UNION ALL
SELECT '2019-10-01' UNION ALL
SELECT '2019-11-01' UNION ALL
SELECT '2019-12-01'
)
SELECT
d.dt,
SUM(t.price) AS price_sum
FROM dates d
LEFT JOIN yourTable t
ON d.dt < t.end_date
AND DATE_ADD(d.dt, INTERVAL 1 MONTH) > t.start_date
GROUP BY
d.dt;
Demo
Notes:
If your dates are actually stored as UNIX timestamps, then just call FROM_UNIXTIME(your_date) to convert them to dates, and use the same approach I gave above.
I had to use the overlapping date range formula here, because the criteria for overlap in a given month is that the range of that month intersects the range given by a start and end date. Have a look at this SO question for more information on that.
My code is for MySQL 8+, though in practice you may wish to create a bona fide calendar table (the CTE version of which I called dates above), which contains the range of months/years which you want to cover your data set.
I understand that you will be given a range of dates for which you will need to report. My solution requires you to initialize a temporary table, such as date_table with the first day of each month for which you want to report on:
create temporary table date_table (
d date,
primary key(d)
);
set #start_date = '2019-01-01';
set #end_date = '2019-12-01';
set #months = -1;
insert into date_table(d)
select DATE_FORMAT(date_range,'%Y-%c-%d') AS result_date from (
select (date_add(#start_date, INTERVAL (#months := #months +1 ) month)) as date_range
from mysql.help_topic a limit 0,1000) a
where a.date_range between #start_date and last_day(#end_date);
Then this should do it:
select month(dt.d) as month, year(dt.d) as year, ifnull(sum(c.price), 0) as price_sum
from date_table dt left join contract c on
dt.d >= date(from_unixtime(c.start_date)) and dt.d < date(from_unixtime(c.end_date))
group by dt.d
order by dt.d
;
Resulting in:
+-------+------+-----------+
| month | year | price_sum |
+-------+------+-----------+
| 1 | 2019 | 299 |
| 2 | 2019 | 299 |
| 3 | 2019 | 1098 |
| 4 | 2019 | 799 |
| 5 | 2019 | 799 |
| 6 | 2019 | 0 |
| 7 | 2019 | 0 |
| 8 | 2019 | 0 |
| 9 | 2019 | 0 |
| 10 | 2019 | 0 |
| 11 | 2019 | 0 |
| 12 | 2019 | 0 |
+-------+------+-----------+
See demo
I am not sure about the semantics of the column end_date. Right now I am comparing the first a follows: start_date <= first_of_month < end_date. Perhaps the test should be start_date <= first_of_month <= end_date, in which case:
dt.d >= date(from_unixtime(c.start_date)) and dt.d < date(from_unixtime(c.end_date))
becomes:
dt.d between date(from_unixtime(c.start_date)) and date(from_unixtime(c.end_date))
With end_date being the last day of the month, it would not matter either way.
Here is a part of table from which I am retrieving data for the last 3 months including current month
+-------------+-----------------------+
| Wo_id | updated_at |
+-------------+-----------------------+
| 1 | 2018-12-05 10:38:06 |
| 2 | 2018-12-02 15:21:17 |
| 3 | 2018-12-01 22:18:53 |
| 4 | 2018-10-25 10:38:06 |
| 5 | 2018-10-18 15:21:17 |
| 6 | 2018-10-16 22:18:53 |
| 7 | 2018-10-19 10:26:19 |
| 8 | 2018-10-27 07:06:52 |
| 9 | 2018-09-25 11:35:09 |
| 10 | 2018-09-18 12:54:27 |
The query I tried is
SELECT MONTHNAME(updated_at) month,YEAR(updated_at) year_name,
MONTH(updated_at) month_no, COUNT(*) work_orders
FROM work_orders where updated_at >= last_day(now()) + interval 1 day - interval 3 month
GROUP by MONTH(updated_at),YEAR(updated_at)
ORDER BY MONTH(updated_at) DESC
The Output I am getting is
+-------------+-------------+----------+-------------+
| month | year_name | month_no | work_orders |
+-------------+-------------+----------+-------------+
| December | 2018 | 12 | 3 |
| October | 2018 | 10 | 5 |
| September | 2018 | 9 | 2 |
As you can see the query is neglecting November as its data is not in the table. It is Including September in order to complete the cycle of 3 months which is wrong. I want the output like this
+-------------+-------------+----------+-------------+
| month | year_name | month_no | work_orders |
+-------------+-------------+----------+-------------+
| December | 2018 | 12 | 3 |
| November | 2018 | 9 | 0 |
| October | 2018 | 10 | 5 |
Can someone guide me in modifying the above mentioned query. Thanks
You need to create a table of the last three months and then LEFT JOIN that to your work orders table (using the month of the work order) to get the results you want. The table of the last 3 months can be generated using a UNION:
SELECT NOW() AS month
UNION
SELECT NOW() - INTERVAL 1 MONTH
UNION
SELECT NOW() - INTERVAL 2 MONTH
Output (as of 2018-12-07):
month
2018-12-07 11:06:15
2018-11-07 11:06:15
2018-10-07 11:06:15
Note that it is OK to subtract 1 month from the date as if the day number is larger than the number of days in the previous month it will be adjusted downward to make the date valid (see the manual).
The final query then becomes:
SELECT MONTHNAME(m.month) AS month_name, YEAR(m.month) AS year_name,
MONTH(m.month) AS month_no, COUNT(wo.Wo_id) work_orders
FROM (SELECT NOW() AS month
UNION
SELECT NOW() - INTERVAL 1 MONTH
UNION
SELECT NOW() - INTERVAL 2 MONTH) m
LEFT JOIN work_orders wo ON MONTH(wo.updated_at) = MONTH(m.month) AND
YEAR(wo.updated_at) = YEAR(m.month)
GROUP by m.month, year_name
ORDER BY m.month DESC
Note that we don't need a WHERE clause as the values in the month table restrict the data to the last 3 months that we are interested in. Also we use a LEFT JOIN so that we get a result for each month even if there were no work orders that month.
Output:
month_name year_name month_no work_orders
December 2018 12 3
November 2018 11 0
October 2018 10 5
Demo on dbfiddle
I need to populate my fact table with data from lds_placement table. I have selected the records and here is what it looks like:
fk1_account_id | fk3_job_role_id | salary | no_of_placements | YEAR
---------------------------------------------------------------------
10 | 3 | 165000 | 5 | 2010
10 | 3 | 132000 | 4 | 2011
10 | 3 | 132000 | 4 | 2012
20 | 2 | 990000 | 3 | 2010
20 | 2 | 132000 | 2 | 2011
20 | 2 | 132000 | 2 | 2012
I want to insert time_id from a different table called time_dim into the column year and not the actual year itself.
The time_dim table looks like this:
time_id | year
---------------
5 | 2015
1 | 2013
2 | 2010
3 | 2014
4 | 2012
6 | 2011
I need to insert into "year" column is actually:
year
2
6
4
2
6
4
Please give me the way to insert time_id instead of year in the table.
Here is the code I used to select the top-most table.
SELECT
fk1_account_id,
fk3_job_role_id,
Sum(actual_salary) AS salary,
Count(1) AS no_of_placements,
MAX(EXTRACT(YEAR FROM plt_estimated_end_date)) AS year
FROM lds_placement
GROUP BY fk1_account_id, fk3_job_role_id, EXTRACT(YEAR FROM plt_estimated_end_date)
ORDER BY fk1_account_id;
Use a left join if you want to capture records where year doesn't exist in time_dim. Else use inner_join.
select t.fk1_account_id,t.fk3_job_role_id,t.salary,t.no_of_placements
,d.time_id
from
(SELECT fk1_account_id, fk3_job_role_id, Sum(actual_salary) as salary, Count(1) as no_of_placements, MAX(EXTRACT(YEAR FROM plt_estimated_end_date)) AS YEAR
FROM lds_placement
GROUP BY fk1_account_id, fk3_job_role_id, EXTRACT(YEAR FROM plt_estimated_end_date)
)t
left join time_dim d
on t.year=d.year
order by t.fk1_account_id
I have table that contains data of query, sample data looks like this:
| YEAR | JAN | FEB | ... | DEC |
|------|-----|-----|-----|-----|
| 2013 | 90 | 40 | ... | 50 |
| 2014 | 30 | 20 | ... | 40 |
I'm trying to unpivot this table to have data like this:
| MONTH | 2013 | 2014 |
|-------|------|------|
| JAN | 90 | 30 |
| FEB | 40 | 20 |
| ... | ... | ... |
| DEC | 50 | 40 |
I've tried this:
select Month, 2013, 2014
from Data
unpivot
(
marks
for Month in (Jan, Feb, Mar, Apr)
) u
but all I get are months and years. Here is my sqlfiddle
I will always have 12 months, but I can have multiple data rows.
Can this be done without dynamic sql?
You're not going to get what you want with a single unpivot statement. Start with this unpivot:
with cte(Year, Month, Orders)
as
(
select Year, Month, Orders
from Data d
unpivot
(
orders
for Month in (Jan, Feb, Mar, Apr)
) u
)
I'm going to use those results in the next part, so I store it as a CTE. This query gives you results like this:
| YEAR | MONTH | ORDERS |
|------|-------|--------|
| 2013 | JAN | 90 |
| 2013 | FEB | 40 |
| 2013 | MAR | 30 |
etc...
I don't know what the numbers in your table represent, but I just called them orders. You can rename that column to whatever is appropriate. The next step is to pivot those results so that we can get the year displayed as columns:
select Month, [2013], [2014]
from cte
pivot
(
sum(orders)
for year in ([2013], [2014])
) p
order by datepart(mm, Month+'1900')
If you need to add more years, it should be obvious where to do that. Note the clever order by that sorts the months chronologically instead of alphabetically.
Here's a SQL Fiddle.