Should I Write Lengthy SQL Query or Breakdown into Few Iterations - mysql

I have a very lengthy SQL query to fetch the expected output but on the other side, I also can generate the expected output by using multiple iterations.
Which one should I use?
I care about performance and writing better code.
By using length SQL query it takes around 3000ms to generate the output
Need about 4 ~ 5 iterations to generate the output
What is the query/code is doing
This code is generating the total number of the forecast record based on the financial year regardless the total number is 0 or not.
Using Length SQL Query
SELECT
CONCAT('FY\'', SUBSTR(`quarters`.fy, 3), ' Q', `quarters`.fy_quarter) AS name,
(
SELECT
COUNT(*)
FROM
member_project_stages
WHERE
YEAR ( member_project_stages.start_at ) = `quarters`.fy
AND QUARTER ( member_project_stages.start_at ) = `quarters`.fy_quarter
AND member_project_stages.stage_id = 9
) AS actual,
(
SELECT
COUNT(*)
FROM
projects AS a
WHERE
( a.forecast IS NOT NULL AND a.forecast > '' )
AND a.forecast LIKE CONCAT( '%FY\'', SUBSTR( `quarters`.fy, 3 ), '%' )
AND a.forecast LIKE CONCAT( '% Q', `quarters`.fy_quarter, '%' )
AND a.deleted_at IS NULL
GROUP BY
a.forecast
) AS forecast
FROM
`member_project_stages`,
(
SELECT YEAR
(
DATE_ADD( CURDATE(), INTERVAL - 9 MONTH )) AS fy,
QUARTER (
DATE_ADD( CURDATE(), INTERVAL - 9 MONTH )) AS fy_quarter UNION
SELECT YEAR
(
DATE_ADD( CURDATE(), INTERVAL - 6 MONTH )) AS fy,
QUARTER (
DATE_ADD( CURDATE(), INTERVAL - 6 MONTH )) AS fy_quarter UNION
SELECT YEAR
(
DATE_ADD( CURDATE(), INTERVAL - 3 MONTH )) AS fy,
QUARTER (
DATE_ADD( CURDATE(), INTERVAL - 3 MONTH )) AS fy_quarter UNION
SELECT YEAR
(
CURDATE()) AS fy,
QUARTER (
CURDATE()) AS fy_quarter UNION
SELECT YEAR
(
DATE_ADD( CURDATE(), INTERVAL 3 MONTH )) AS fy,
QUARTER (
DATE_ADD( CURDATE(), INTERVAL 3 MONTH )) AS fy_quarter UNION
SELECT YEAR
(
DATE_ADD( CURDATE(), INTERVAL 6 MONTH )) AS fy,
QUARTER (
DATE_ADD( CURDATE(), INTERVAL 6 MONTH )) AS fy_quarter UNION
SELECT YEAR
(
DATE_ADD( CURDATE(), INTERVAL 9 MONTH )) AS fy,
QUARTER (
DATE_ADD( CURDATE(), INTERVAL 9 MONTH )) AS fy_quarter
) AS `quarters`
GROUP BY
`quarters`.fy,
`quarters`.fy_quarter"
Using Iteration
for(...) {
run SQL query
}
for(...) {
using the previous output and run SQL query again
}
for(...) {
using the previous output and run SQL query again
}
for(...) {
using the previous output and run SQL query again
}
Finally I have my output

Its true that interacting with DB is costlier. Hence people prefer combining several queries into 1 query to optimize the performance.
And for better code, why not explain the logic in comments.

Related

How to get weekly dates in MYSQL

I am trying to write a query which outputs weekly dates from a given date to the end of the current year.
I am stuck here:
Select DATE_ADD(input_date, INTERVAL 7 DAY)
as alarm_date from userInput;
How do I get all the dates?
WITH RECURSIVE
cte AS ( SELECT id, input_date + INTERVAL 7 DAY alarm_date FROM userInput
UNION ALL
SELECT id, alarm_date + INTERVAL 7 DAY FROM cte WHERE YEAR(alarm_date) = YEAR(NOW()) )
SELECT * FROM cte;

sql take all querys from last month

I followed the first answer on this question but i have some problems with my sql query. I don´t get any result.
What error i made ?
How can i set CURDATE always on the first day of the month ?
SELECT DATE_FORMAT(`date`, '%Y-%m-%d') , `price`
FROM `sales`
WHERE `id` = :id
AND (`date` BETWEEN CURDATE() - INTERVAL 30 DAY AND CURDATE())
Edit Whole code:
$verkaufmonat3 = 0;
$verkaufmonatanzahl2 = 0;
$verkaufmonat4 = array();
$sqluser5 = $X['dbh']->prepare("SELECT DATE_FORMAT(`date`, '%Y-%m-%d') , `preis` FROM `verkauf` WHERE `vertreterid` = :id AND (`date` BETWEEN CURDATE(), '%Y-%m-01' - INTERVAL 60 DAY AND CURDATE(), '%Y-%m-01')");
$sqluser5->execute(array(
':id'=>$_SESSION['id']
));
$verkaufmonatanzahl2 = $sqluser5->rowCount();
$verkaufmonat4 = $sqluser5->fetchAll();
for ($a = 0; $a <= $verkaufmonatanzahl2; $a++) {
$verkaufmonat3 += $verkaufmonat2[$a]['preis'];
}
In my database i have a sale on the 28.10.2016 wich has for ex. a value of 50 eur. So my $verkaufmonat3 should be 50 but it isn´t, it´s 0.
Your question isnt clear but to get the first day of current month then you use
SELECT DATE_FORMAT(CURDATE() , '%Y-%m-1')
So if you want the previous month data try
SELECT
DATE_FORMAT(CURDATE() , '%Y-%m-1') - INTERVAL 1 MONTH as first_day,
DATE_FORMAT(CURDATE() , '%Y-%m-1') - INTERVAL 1 DAY as last_day
MEANING
WHERE `date` BETWEEN DATE_FORMAT(CURDATE() , '%Y-%m-1') - INTERVAL 1 MONTH
AND DATE_FORMAT(CURDATE() , '%Y-%m-1') - INTERVAL 1 DAY

How to select 6 months of data in MySQL, grouped between the 22nd and 21st of the next month

I have a table that contains the amount of data used each day, it looks something like this:
date | bytes
------------------
2014-01-1 | 12345
2014-01-2 | 56789
2014-01-3 | 78901
...
2014-02-1 | 12345
2014-02-2 | 56789
2014-02-3 | 78901
...
What I need to do is get the last 6 monthly totals, however the month must start on the 22nd day of the month and finish on the 21st day of the following month. For the current month it should start on the 22nd and finish today.
The best I can come up with is the following, the problem is - it is very messy and doesn't seem to give the correct result.
SELECT monthname(`date`),sum(`bytes`)
FROM `trafficDaily`
WHERE `date` between STR_TO_DATE( CONCAT( "22,", MONTH( NOW( ) )-6 , ",", YEAR( NOW( ) ) ) , "%d,%m,%Y" )
and STR_TO_DATE( CONCAT( "21,", MONTH( NOW( ) ) , ",", YEAR( NOW( ) ) ) , "%d,%m,%Y" )
group by month(DATE_SUB(`date`, INTERVAL 21 DAY))
order by `date`
Thank you in advance for your help.
You can do so by using user-defined variables to track the the change of month i.e in your case month starts from 21st
SELECT
MONTHNAME(STR_TO_DATE(group_day, '%m')) month_name ,
SUM(`bytes`) `sum`
FROM (
SELECT *,
#changemonth:= CASE
WHEN DAY(`date`) > 21
THEN #month
WHEN MONTH(`date`) <> #month
THEN #month
ELSE #month - 1
END group_day,
#month:= MONTH(`date`)
FROM
t ,(SELECT #changemonth:=0,
#month:= (SELECT MONTH(`date`) FROM t
WHERE `date` > NOW() - INTERVAL 6 MONTH ORDER BY `date` LIMIT 1) aa
) tt
WHERE `date` > NOW() - INTERVAL 6 MONTH
ORDER BY `date`
) a
GROUP BY group_day
Demo for last 3 months
Edit from comments for the case when January lies in last 6 month period
SELECT
MONTHNAME(
STR_TO_DATE(
CASE WHEN group_day < 1
THEN 12 ELSE group_day
END, '%m'
)
) month_name ,
SUM(`bytes`) `sum`
FROM (
SELECT *,
#changemonth:= CASE
WHEN DAY(`date`) > 21
THEN #month
WHEN MONTH(`date`) <> #month
THEN #month
ELSE #month - 1
END group_day,
#month:= MONTH(`date`)
FROM
t ,(SELECT #changemonth:=0,
#month:= (SELECT MONTH(`date`) FROM t
WHERE `date` > NOW() - INTERVAL 6 MONTH ORDER BY `date` LIMIT 1) aa
) tt
WHERE `date` > NOW() - INTERVAL 6 MONTH
ORDER BY `date`
) a
GROUP BY group_day
Demo with January
You have at least two options:
1st option
Create a calendar table and assign the 'business month' to each days. You can prepare your table for a long time period, then you can join to that table by date and you can do the grouping. If you have to do this calculation regularry, this is a good solution. (You can upgrade and use the calendar table to do several tasks)
2nd option
You can calculate the 'business month' by the date using the following query. (Please note, that I did not tested this query, so there could be typos).
SELECT
CASE
WHEN DAY(date) >= 22 THEN CONCAT(YEAR(date), '-', MONTH(date))
ELSE CONCAT(YEAR(date - INTERVAL 1 MONTH), '-', MONTH(date - INTERVAL 1 MONTH))
END AS m,
SUM(bytes)
FROM
log -- Use your table name instead :)
GROUP BY
CASE
WHEN DAY(date) >= 22 THEN CONCAT(YEAR(date), '-', MONTH(date))
ELSE CONCAT(YEAR(date - INTERVAL 1 MONTH), '-', MONTH(date - INTERVAL 1 MONTH))
END
You can adjust the calculation to your needs.

Changing start-date in MySQL for week

I found the following code to help in creating a weekly report based on a start date of Friday. The instructions say to replace ".$startWeekDay." with a 4. When I put '".$startDay."' as '2013-01-30', I get errors.
Also I get a report by day rather than week as I desire.
SELECT SUM(cost) AS total,
CONCAT(IF(date - INTERVAL 6 day < '".$startDay."',
'".$startDay."',
IF(WEEKDAY(date - INTERVAL 6 DAY) = ".$startWeekDay.",
date - INTERVAL 6 DAY,
date - INTERVAL ((WEEKDAY(date) - ".$startWeekDay.")) DAY)),
' - ', date) AS week,
IF((WEEKDAY(date) - ".$startWeekDay.") >= 0,
TO_DAYS(date) - (WEEKDAY(date) - ".$startWeekDay."),
TO_DAYS(date) - (7 - (".$startWeekDay." - WEEKDAY(date)))) AS sortDay
FROM daily_expense
WHERE date BETWEEN '".$startDay."' AND '".$endDay."'
GROUP BY sortDay;
The following code is what I am using
SELECT count(DISTINCT (
UserID)
) AS total, CONCAT(IF(date(LastModified) - INTERVAL 6 day < date(LastModified),
date(LastModified),
IF(WEEKDAY(date(LastModified) - INTERVAL 6 DAY) = 4,
date(LastModified) - INTERVAL 6 DAY,
date(LastModified) - INTERVAL ((WEEKDAY(date(LastModified)) - 4)) DAY)),
' - ', date(LastModified)) AS week
FROM `Purchase`
WHERE `OfferingID` =87
AND `Status`
IN ( 1, 4 )
GROUP BY week
The output I get is
total week
3 2013-01-30 - 2013-01-30
1 2013-01-31 - 2013-01-31
I'm not sure exactly how you want to display your week, the sql above is attempting to display date ranges. If this isn't a requirement, your query could be very simple, you can just offset your time by two days (since friday is two days away from the natural star of the week) and use the week function to get the week number.
The query would look like this:
select count(distinct (UserID)) as total
, year( LastModified + interval 2 day ) as year
, week( LastModified + interval 2 day ) as week_number
FROM `Purchase`
WHERE `OfferingID` =87
AND `Status`
IN ( 1, 4 )
group by year, week_number;

SQL View to show interval of 12 months and by year

Right now one of the other programmers wrote this view to show interval of 6months. How do i write this so that it shows interval of 12 months grouped by month but only for year 2011
I'd like to copy it for a separate view of 12 months grouped by month but only for year 2012
CREATE ALGORITHM=UNDEFINED DEFINER=`root`#`%`
SQL SECURITY DEFINER VIEW `vw_dash_bymonth`AS
select
month(from_unixtime(`tbl_services`.`datetime`)) AS` month1`,
date_format(from_unixtime(`tbl_services`.`datetime`),'%Y') AS` year1`,
date_format(from_unixtime(`tbl_services`.`datetime`),'%d') AS `day1`,
`tbl_services`.`datetime` AS `realdate`,sum(`tbl_services`.`gallons`) AS `gallons`,
count(0) AS `service`,
round(avg(`tbl_services`.`gallons`),1) AS `average`
from `tbl_services`
where (from_unixtime(`tbl_services`.`datetime`) > (now() - interval 6 month))
group by month(from_unixtime(`tbl_services`.`datetime`))
If you look at the where clause
where (from_unixtime(`tbl_services`.`datetime`) > (now() - interval 6 month))
I believe this is getting the dates from everything from 6 months ago until today. If you want 12 months in 2011 I think you could replace that line with something like:
where (from_unixtime(`tbl_services`.`datetime`) >= DATE('2011-01-01 00:00:00'))
AND (from_unixtime(`tbl_services`.`datetime`) < DATE('2012-01-01 00:00:00'))
Although I don't know MySQL (just SQLServer) so if this doesn't work, hopefully someone else can tell me where I went wrong.
It can be simplified to:
where (from_unixtime(`tbl_services`.`datetime`) >= '2011-01-01')
AND (from_unixtime(`tbl_services`.`datetime`) < '2012-01-01')
SELECT DISTINCT FROM_UNIXTIME('date','%m-%Y') AS month,
COUNT(`id`) AS count
FROM 'blog'
GROUP BY month
ORDER BY month DESC LIMIT 0,12
SELECT
MONTH( UNIX_TIMESTAMP( t.`datetime`) ) AS month1,
DATE_FORMAT( UNIX_TIMESTAMP( t.`datetime`), '%Y' ) AS year1,
DATE_FORMAT( UNIX_TIMESTAMP( t.`datetime`), '%d') AS day1,
t.`datetime` AS realdate,
SUM(t.gallons) AS gallons,
COUNT(*) AS `service`,
ROUND( AVG( t.gallons ), 1 ) AS `average`
FROM
tbl_services AS t
CROSS JOIN
( SELECT 2011 AS YearToCheck
) AS c
WHERE t.`datetime` >= UNIX_TIMESTAMP( MAKEDATE( YearToCheck, 1 ) )
AND t.`datetime` < UNIX_TIMESTAMP( MAKEDATE( YearToCheck+1, 1 ) )
GROUP BY MONTH( FROM_UNIXTIME( t.`datetime` ) )