Doctrine2 - using group by method to add totals for each month - mysql

I have the following table:
I require the SUM(total), SUM(cost), SUM(net) grouped as a total for each month.
So far I have the following queries:
Returns a single global total:
$qb ->add('select','SUM(u.total) as Total, Sum(u.cost) as Cost, Sum(u.net) as Net')
->where('u.status = :status')
->setParameter('status' , 1);
Returns totals grouped by the created date:
$qb ->add('select','SUM(u.total) as Total, Sum(u.cost) as Cost, Sum(u.net) as Net')
->groupBy('u.created')
->where('u.status = :status')
->setParameter('status' , 1);
How do I return by days or months or years?

Okay looks like I solved this.
Doctrine query builder supports sub-string, so if a date looks like this:
2015-08-13 14:02:15
We can use substring to pull out the parts we want and then use the orderBy method to list by them.
Making listing by, day, hour, minute and second, simple.
This being the template:
$qb ->add('select','SUM(u.total) as Total, Sum(u.cost) as Cost, Sum(u.net) as Net, SUBSTRING(u.created, 6, 2) as Month')
->groupBy('Month')
->where('u.status = :status')
->setParameter('status' , 1);
Simply replace the SUBSTRING value with:
SUBSTRING(u.created, 1, 4) for Year
SUBSTRING(u.created, 6, 2) for Month
SUBSTRING(u.created, 9, 2) for Day
Day and Month will clearly run across multiple years and months respectively, so further checks would be required.
My code is like this if anyone is interested, I use multiple day, month and year attributes to ensure the data does not aggregate.
public function getSalesBy($date)
{
if (!in_array($date,['day','month','year']))
{
return [];
}
$qb = $this->ordersRepository->createQueryBuilder('u');
if ($date == 'day')
{
$qb->add('select','SUM(u.total) as total, Sum(u.cost) as cost, Sum(u.net) as net, SUBSTRING(u.created, 9, 2) as day, SUBSTRING(u.created, 6, 2) as month, SUBSTRING(u.created, 1, 4) as year');
$qb->groupBy('year,month,day');
}
if ($date == 'month')
{
$qb->add('select','SUM(u.total) as total, Sum(u.cost) as cost, Sum(u.net) as net, SUBSTRING(u.created, 9, 2) as day, SUBSTRING(u.created, 6, 2) as month, SUBSTRING(u.created, 1, 4) as year');
$qb->groupBy('year,month');
}
if ($date == 'year')
{
$qb->add('select','SUM(u.total) as total, Sum(u.cost) as cost, Sum(u.net) as net, SUBSTRING(u.created, 9, 2) as day, SUBSTRING(u.created, 6, 2) as month, SUBSTRING(u.created, 1, 4) as year');
$qb->groupBy('year');
}
$qb->where('u.status = :status')->setParameter('status' , 1);
return $qb->getQuery()->getResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);
}

Related

Mysql month number to month name conversion

I have month value like "22018" in my column I need it like Feb-2018 in mysql workbench
You need to first extract the month from the date (considering it will have one or two digits), e.g.:
SELECT LPAD(SUBSTRING('22018', 1, LENGTH('22018') - 4), 2, '0');
This will give you 02. Now, you can extract the year with similar logic, e.g.:
SELECT SUBSTRING('22018', LENGTH('22018') - 4 + 1, LENGTH('22018'));
Finally, you can concatenate all these to get a string like 2018-02-01:
SELECT CONCAT(SUBSTRING('22018', LENGTH('22018') - 4 + 1, LENGTH('22018')),
'-',
LPAD(SUBSTRING('22018', 1, LENGTH('22018') - 4), 2, '0'), '-01');
Once this is done, you can use DATE_FORMAT function to get the required output:
SELECT DATE_FORMAT(CONCAT(SUBSTRING('22018', LENGTH('22018') - 4 + 1,
LENGTH('22018')),
'-',
LPAD(SUBSTRING('22018', 1, LENGTH('22018') - 4), 2, '0'), '-01'), '%M-%Y');

What kind of structure would you recommend for querying Max/Min over range efficiently as shown?

FW: just a heads up that I may have combined mysql syntax and SQL syntax. I work with SQL at work (aka, right now) and then use mysql for side projects so I may not have used proper syntax on everything (query should be correct) so please bear with me lol
I have a database consisting of prices for various parts aggregated by day (PartPrices). I want to run a query to see which parts grew in price the most and which declined in price the most over a date range. This date range could be from a start date to now or a start date to any period in time.
The schema looks as follows:
PartID | DateTime (Time stamp from start of day) | Price
Essentially what I have is as follows:
DECLARE #StartDate AS DATETIMEOFFSET
DECLARE #EndDate AS DATETIMEOFFSET
WITH LastPartPrices AS (
SELECT *
FROM
(SELECT
PartID
,DateTime
,Price
FROM PartPrices
WHERE PartPrices.DateTime <= #EndDate
GROUP BY `PartID`, DateTime DESC, Price) t
GROUP BY `PartID`),
HighestPartPrices AS (
SELECT *
FROM
(SELECT
PartID
,DateTime
,Price
FROM PartPrices
WHERE PartPrices.DateTime BETWEEN #StartDate AND #EndDate
GROUP BY `PartID`, Price DESC, DateTime) t
GROUP BY `PartID`),
LowestPartPrices AS (
SELECT *
FROM
(SELECT
PartID
,DateTime
,Price
FROM PartPrices
WHERE PartPrices.DateTime BETWEEN #StartDate AND #EndDate
GROUP BY `PartID`, Price ASC, DateTime) t
GROUP BY `PartID`)
SELECT
Parts.ID
,Parts.Name
,Parts.Description
,LastPartPrices.Price AS LastPrice
,( (LastPartPrices.Price - HighestPartPrices.Price) / HighestPartPrices.Price ) AS HighCurrentDifference
,( (LastPartPrices.Price - LowestPartPrices.Price) / LowestPartPrices.Price ) AS LowCurrentDifference
FROM Parts
INNER JOIN LastPartPrices ON Parts.ID = LastPartPrices.PartID
INNER JOIN HighestPartPrices ON Parts.ID = HighestPartPrices.PartID
INNER JOIN LowestPartPrices ON Parts.ID = LowestPartPrices.PartID
I don't feel this query is optimized which is why I'm reaching out to the SO community for input. If you think I should handle this data differently, I'm open to suggestions as well. Thanks in advance. Here is some raw sample data from PartPrices (keep in mind, these are exaggerated to avoid having to add a ton of data):
{1, '2016-03-01T00:00:00+00:00', 150.40 },
{1, '2016-03-02T00:00:00+00:00', 170.50 },
{1, '2016-03-03T00:00:00+00:00', 160.00 },
{2, '2016-03-01T00:00:00+00:00', 80.30 },
{2, '2016-03-02T00:00:00+00:00', 100.00 },
{2, '2016-03-03T00:00:00+00:00', 120.00 },
{3, '2016-03-01T00:00:00+00:00', 10.50 },
{3, '2016-03-02T00:00:00+00:00', 20.10 },
{3, '2016-03-03T00:00:00+00:00', 30.00 }
What I would expect is to get the following:
{ 'ID', 'Name', 'Description', 'LastPrice', 'HighCurrentDifference', 'LowCurrentDifference' }
{ 1, 'Advil', 'Pain Killer', 160.00, -0.06, 0.06 },
{ 2, 'Bud Light', 'Beer', 120.00, 0.2, 0.49 },
{ 3, 'XBox One', 'Game Console', 30.00, 0.49, 1.85 }

SSRS: How to return last day of a Custom/Financial Month (Not Calendar) through expression

This is a real hair puller so any help, much appreciated!
I want to be able to determine the:
First day of the current custom/financial month
Last day of the current custom/financial month
And use these new columns Start_Date and end_Date as between Filters in the Matrix.
Note: I understand that if this was calendar Month, then that will be "quite" straightforward.
But in this case its quite different.
Please see image which might help with the context i am trying to work with:
I'm sure this is possible using expressions in SSRS but I don;t have time to investigate. In case it's useful, here's how I would do it in SQL.
Again there is probably a more elegant solution but this was what came to me.
I'll reproduced your data plus a few more dates either end for testing which I guessed based on your sample.
DECLARE #t TABLE(Custom_Date date, Custom_Day int)
INSERT INTO #t VALUES
('2017-10-26', 28),
('2017-10-27', 29),
('2017-10-28', 30),
('2017-10-29', 1),
('2017-10-30', 2),
('2017-10-31', 3),
('2017-11-01', 4),
('2017-11-02', 5),
('2017-11-03', 6),
('2017-11-04', 7),
('2017-11-05', 8),
('2017-11-06', 9),
('2017-11-07', 10),
('2017-11-08', 11),
('2017-11-09', 12),
('2017-11-10', 13),
('2017-11-11', 14),
('2017-11-12', 15),
('2017-11-13', 16),
('2017-11-14', 17),
('2017-11-15', 18),
('2017-11-16', 19),
('2017-11-17', 20),
('2017-11-18', 21),
('2017-11-19', 22),
('2017-11-20', 23),
('2017-11-21', 24),
('2017-11-22', 25),
('2017-11-23', 26),
('2017-11-24', 27),
('2017-11-25', 28),
('2017-11-26', 1),
('2017-11-27', 2),
('2017-11-28', 3)
Then two queries to pull out the correct dates which you could combine if required.
SELECT MAX(Custom_Date) FROM #t WHERE Custom_Date < getdate() AND custom_day = 1
SELECT MAX(Custom_Date)
FROM #t
WHERE
Custom_Date > getdate()
AND DATEDIFF(d, getdate(), Custom_Date)<=31
AND Custom_Day = (
SELECT MAX(custom_day)
FROM #t
WHERE
Custom_Date > getdate()
AND datediff(d, getdate(), Custom_Date)<=31
)
FYI: This would be a lot easier if you had a custom month/period and year in your dates table as then you could just look custom_day 1 and max(custom_day) where the custom month and year are the same as the current date.

How to efficiently query for a stacked column chart?

My use-case is creating a stacked column graph using a logs table stored in MySQL.
Currently I have a regular column chart, but I would like to aggregate data by site_id so that I can see which parts of the bar are attributed to which site.
My current technique for the existing column chart is to get a list of dates, and count the records whilst grouping by the date. Then I use a for loop to create the 14 day period I need, and then loop my data to populate matching counts into the correct day.
SELECT DATE(`created`) AS `day`,
COUNT(`id`) AS `count`
FROM `api_logs` `ApiLogs`
WHERE DATE(created) BETWEEN DATE_SUB(CURDATE(), INTERVAL 14 day) AND CURDATE()
GROUP BY DATE(`created`)
ORDER BY DATE(`created`)
For the stacked chart though, I can't think of a way to collate the data in MySQL without performing a number of queries or building a subquery to collate the count per site.
Is there an established pattern for querying for a result which is easily compatible with the stacked column chart?
My front-end is built in PHP, if there are any post-query processing solutions.
the problem is you need a column for each site
if you have a set number of sites, then you can build the columns manually in the sql
SELECT DATE(`created`) AS `day`,
SUM(CASE WHEN `site_id` = 'A' THEN 1 ELSE 0 END) AS `site A`,
SUM(CASE WHEN `site_id` = 'B' THEN 1 ELSE 0 END) AS `site B`
FROM `api_logs` `ApiLogs`
WHERE DATE(created) BETWEEN DATE_SUB(CURDATE(), INTERVAL 14 day) AND CURDATE()
GROUP BY DATE(`created`)
ORDER BY DATE(`created`)
otherwise, you can build the columns dynamically and aggregate
using google's DataView and data.group
first, add site_id to the sql
SELECT DATE(`created`) AS `day`,
`site_id` AS `site_id`,
COUNT(`id`) AS `count`
FROM `api_logs` `ApiLogs`
WHERE DATE(created) BETWEEN DATE_SUB(CURDATE(), INTERVAL 14 day) AND CURDATE()
GROUP BY DATE(`created`), `site_id`
ORDER BY DATE(`created`), `site_id`
which should result, similar to the following...
['Date', 'Site', 'Count'],
[new Date('11/17/2016'), 'A', 10],
[new Date('11/17/2016'), 'B', 15],
[new Date('11/17/2016'), 'C', 22],
see following working snippet for building columns dynamically...
google.charts.load('current', {
callback: function () {
// raw table data
var data = google.visualization.arrayToDataTable([
['Date', 'Site', 'Count'],
[new Date('11/17/2016'), 'A', 10],
[new Date('11/17/2016'), 'B', 15],
[new Date('11/17/2016'), 'C', 22],
[new Date('11/17/2016'), 'D', 8],
[new Date('11/16/2016'), 'A', 12],
[new Date('11/16/2016'), 'B', 6],
[new Date('11/16/2016'), 'C', 13],
[new Date('11/16/2016'), 'E', 14],
[new Date('11/15/2016'), 'A', 9],
[new Date('11/15/2016'), 'B', 16],
[new Date('11/15/2016'), 'D', 11]
]);
// create view with columns for each site, then agg view
var view = new google.visualization.DataView(data);
var aggColumns = [];
var viewColumns = [0];
data.getDistinctValues(1).forEach(function (site, index) {
viewColumns.push({
calc: function (dt, row) {
if (dt.getValue(row, 1) === site) {
return dt.getValue(row, 2);
}
return null;
},
label: site,
type: 'number'
});
aggColumns.push({
aggregation: google.visualization.data.sum,
column: index + 1,
label: site,
type: 'number'
});
});
view.setColumns(viewColumns);
var group = google.visualization.data.group(
view,
[0],
aggColumns
);
var chart = new google.visualization.ColumnChart(document.getElementById('chart_div'));
chart.draw(group, {
isStacked: true
});
},
packages: ['corechart']
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>

inserting between condition in sum condition

HAVING SUM (DECODE (account_number, '1', 1, 0)) > :p_num_to
I want to integrate between condition into sum condition, user will enter two variables :p_num_from and p:_num_to.
Simply add another condition with AND
HAVING (SUM (DECODE (account_number, '1', 1, 0)) <p_num_to)
AND (SUM (DECODE (account_number, '1', 1, 0))>p_num_from)