I have created a query that gives me a sum of sales for each department for the day.
What I want is to generate this data for the whole month using one master query.
This is the result I expect to see:
Department No Department Name 1-April 2-April 3-April
1 Infants 100 112 96
2 Kids 120 132 123
Total Total 220 144 219
Currently, I can generate this for one day only, and I have to keep changing the date value to get the data for the next day.
As stated earlier, I want to run a this for the whole month in one master query.
Please try the following...
SELECT CONCAT( 'SELECT department.id AS `Department No`,
department.departmentName AS `Department Name`',
GROUP_CONCAT( ', SUM( date_',
DATE_FORMAT( salesDate, '%d_%M' ),
'.sales ) AS `',
DATE_FORMAT( salesDate, '%e-%M' ),
'`',
SEPARATOR '' ),
' FROM department',
GROUP_CONCAT( ' LEFT JOIN departmentSales AS date_',
DATE_FORMAT( salesDate, '%d_%M' ),
' ON department.id = date_',
DATE_FORMAT( salesDate, '%d_%M' ),
'.department_id AND date_',
DATE_FORMAT( salesDate, '%d_%M' ),
'.salesDate = `',
DATE_FORMAT( salesDate, '%Y-%M-%d' ),
'`',
SEPARATOR '' ),
' GROUP BY department.id
ORDER BY department.id
UNION
SELECT `Totals`,
` `,
GROUP_CONCAT( ', SUM( date_',
DATE_FORMAT( salesDate, '%d_%M' ),
'.sales )',
SEPARATOR '' ),
' FROM department',
GROUP_CONCAT( ' LEFT JOIN departmentSales AS date_',
DATE_FORMAT( salesDate, '%d_%M' ),
' ON department.id = date_',
DATE_FORMAT( salesDate, '%d_%M' ),
'.department_id AND date_',
DATE_FORMAT( salesDate, '%d_%M' ),
'.salesDate = `',
DATE_FORMAT( salesDate, '%Y-%M-%d' ),
'`',
SEPARATOR '' ),
' ) INTO #salesQuery
FROM ( SELECT DISTINCT salesDate
FROM departmentSales
WHERE salesDate BETWEEN FROM_DAYS( TO_DAYS( targetDate ) -
DAYOFMONTH( targetDate ) +
1
)
AND LAST_DAY( targetDate )
) AS salesDateFinder;
PREPARE salesTotals FROM #salesQuery;
EXECUTE salesTotals;
This attempt is based on code found at mysql select dynamic row values as column names, another column as value. I am concerned about the group_concat_max_len variable being a problem. If it is then I'll step up my research into the CASE method eggyal mentioned. If it is not a problem, then I'll take things a bit easier.
Most of the lines here actually form just one SQL statement. It takes the list of salesDate values constructed by the subquery near the end, which are then used to help construct a rather long string containing the SQL statement to be run against the database.
The string is formed from a list of substrings specified as the arguments to the function CONCAT(), which you can read about at https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_concat.
The string starts by specifying that the fields id and departmentName from the table department should be the first selected for each record, followed by the SUM() of each date's sales for that department. Each date field is given a name like date_09_March and in the output a heading like 9-March.
The repeated segments of the string are achieved by using the GROUP_CONCAT function, which you can read about at https://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html#function_group-concat.
After the fields are specified the string specifies that the values should come from a temporary table formed from department LEFT JOINed with an instance of the departmentSales table for each date, with each instance being given an alias like date_09_March and joined on its department_id field to the id field of department.
Once it is formed, the temporary table's records are grouped by department.id so that the totals for each department may be calculated. The output is then sorted by department.id.
The statement used to produce this sorted output and its column headings is then joined to another (somewhat simpler) statement used to generate the totals row. The string containing the SQL statement formed by the concatenation of each substring is then assigned to the variable #salesQuery, prepared and executed.
If you have any questions or comments, then please feel free to post a Comment accordingly.
Further Reading
https://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html (for information on the DATE functions used)
Thanks for all the answers
I managed to write a query based on the Pivot Function.
You can control the range of dates with something like this:
select (...) where v_date >= 1-April and v_date <= 30-April
(consider v_date the name of the column and 1-April in date format)
Related
I'm working on a legacy database system which deals with property reports and I am attempting to update a query which shows multiple reports within a time period for the same address. I am using MySQL Workbench to run this query.
The table has a UPRN field for unique addresses, which is fine, however addresses can be manually added without a UPRN. The table also has address1, address2, address3, town, postcode fields. There is NO field which contains the whole address as one line.
We already have a query which uses the UPRN field to generate a list of multiple records within a timescale; however, this will obviously exclude multiple reports without a UPRN.
SELECT * FROM
`rep_base_report` `rep`
WHERE
`rep`.`UPRN` IN (SELECT
`rep2`.`UPRN` AS `UPRN`
FROM
`rep_base_report` `rep2`
WHERE
`rep2`.`STATUS` = 'LODGED'
AND `rep2`.`GENERATED_DATE` > (CURDATE() - INTERVAL 3 MONTH)
GROUP BY `rep2`.`UPRN`
HAVING (COUNT(0) > 1)
ORDER BY `rep2`.`GENERATED_DATE` DESC)
GROUP BY `rep`.`RepReference`
ORDER BY `rep`.`UPRN`
In my head, if I could CONCAT address1, address2 and postcode and use this as the basis for the check, it would give the appropriate output, but I've no idea how to accomplish this.
I've attempted the following but it's giving fewer values that I was expect (I've manually checked some sample data in Excel to see what I should be expecting).
SELECT *
FROM `rep_base_report` `rep`
WHERE
#property :=CONCAT(`rep`.`ADDRESS1`, ' ' ,`rep`.`ADDRESS2`, ' ' ,`rep`.`POSTCODE`) IN (
SELECT #property2 :=CONCAT(`rep2`.`ADDRESS1`, ' ' ,`rep2`.`ADDRESS2`, ' ' ,`rep2`.`POSTCODE`) AS `PROPERTY`
FROM `rep_base_report` `rep2`
WHERE `rep2`.`STATUS` = 'LODGED'
AND `rep2`.`GENERATED_DATE` > CURDATE() - INTERVAL 3 MONTH
GROUP BY #property2
HAVING COUNT(0) > 1
ORDER BY `rep2`.`GENERATED_DATE` DESC
)
GROUP BY `rep`.`RepReference`
ORDER BY `rep`.`UPRN`
SELECT *
FROM `rep_base_report` `rep`
WHERE
CONCAT(`rep`.`ADDRESS1`, ' ', `rep`.`ADDRESS2`, ' ', `rep`.`POSTCODE`) IN (
SELECT CONCAT(`rep2`.`ADDRESS1`, ' ', `rep2`.`ADDRESS2`, ' ', `rep2`.`POSTCODE`) AS `PROPERTY`
FROM `rep_base_report` `rep2`
WHERE `rep2`.`STATUS` = 'LODGED'
AND `rep2`.`GENERATED_DATE` > CURDATE() - INTERVAL 3 MONTH
GROUP BY CONCAT(`rep2`.`ADDRESS1`, ' ', `rep2`.`ADDRESS2`, ' ', `rep2`.`POSTCODE`)
HAVING COUNT(0) > 1
ORDER BY `rep2`.`GENERATED_DATE` DESC
)
GROUP BY `rep`.`RepReference`
ORDER BY `rep`.`UPRN`
I have this MySQL query that works almost perfectly:
select ConfirmationNumber, ReservationDate, ifnull(CASE TableNumber WHEN 0 THEN 'UNASSIGNED' ELSE TableNumber END,'UNASSIGNED') AS 'Table', CONCAT(LastName, ', ', FirstName) AS 'Customer', Email, Phone, PublicNotes, sum(SleighSeats) + sum(CabSeats) AS Seats
from Reservations
where ReservationDate = '2018-1-25'
and ConfirmationNumber IS NOT NULL
and CancelDate IS NULL
group by TableNumber, Customer
order by TableNumber
It produces this result:
Focus on customer Corbosie... above.
Because there is grouping going on, there are PublicNotes data that I'm missing, because some records are being compressed out of the query. I simply want to concatenate the PublicNotes from all records included in the query, including those being aggregated out.
For example, when I query this way:
select ConfirmationNumber, ReservationDate, ifnull(CASE TableNumber WHEN 0 THEN 'UNASSIGNED' ELSE TableNumber END,'UNASSIGNED') AS 'Table', CONCAT(LastName, ', ', FirstName) AS 'Customer', Email, Phone, PublicNotes, SleighSeats + CabSeats AS Seats
from Reservations
where ConfirmationNumber in ('092550', '764352', '661800')
...it shows you 3 records that got compressed into 1 record (due to grouping) in the first query:
The 2 PublicNotes circled in red are missing because they got aggregated out. How can I maintain the grouping of my first query, while concatenating in the missing PublicNotes with their grouped record?
I think you want group_concat():
select ConfirmationNumber, ReservationDate,
(CASE TableNumber WHEN 0 THEN 'UNASSIGNED' ELSE TableNumber END) AS "Table",
CONCAT(LastName, ', ', FirstName) AS Customer,
Email, Phone,
GROUP_CONCAT(PublicNotes, '|'),
(SUM(SleighSeats) + SUM(CabSeats)) AS Seats
from Reservations
where ReservationDate = '2018-1-25' and
ConfirmationNumber IS NOT NULL and
CancelDate IS NULL
group by ConfirmationNumber, ReservationDate, TableNumber, Customer, email, phone
order by TableNumber;
Notes:
I don't think TableNumber can be NULL, based on the logic, so I removed the IFNULL(). If it can be, then add it back in (or use COALESCE().
I added the additional unaggregated columns to the GROUP BY. This is a good habit for you to use.
The separator for the different notes is a vertical bar. You can choose whatever you like.
Help me with next SQL:
SELECT
date_format(from_unixtime(`ticket_logs`.`created`),'%Y-%m-%d') AS `datac`,
`ticket_logs`.`ticket_id` AS `ticket_id`,
ticket_logs.value_old,
ticket_logs.value_new,
max(`ticket_logs`.`action`) AS `ultima_act`
FROM
`ticket_logs`
WHERE
(
(`ticket_logs`.`action` = 6)
OR (`ticket_logs`.`action` = 16)
)
GROUP BY
date_format(
from_unixtime(`ticket_logs`.`created`),
'%Y-%m-%d'
),
`ticket_logs`.`ticket_id`
ORDER BY
`datac` DESC,
`ticket_logs`.`ticket_id` DESC
The problem is that "value_old" and "value_new", always take the first value per date and not the value corresponding with the max value of "action"
I don't see how this is a problem. That is how SQL works -- the order by takes place after the group by. In addition, MySQL is just confusing you, because you are using an extension to group by that you don't fully understand -- having extra columns in the select that are not in the group by. (See this.)
Fortunately, MySQL supports a hack to get what you want, without writing a much more complicated SQL statement. The expressions you want are:
substring_index(group_concat(ticket_logs.value_old order by `ticket_logs`.`action` desc), ',', 1)
substring_index(group_concat(ticket_logs.value_new order by `ticket_logs`.`action` desc), ',', 1)
Found another approach. I used the "created" column to obtain a max and joined:
SELECT
date_format(
from_unixtime(`ticket_logs`.`created`),
'%Y-%m-%d'
) AS `datax`,
ticket_logs.ticket_id,
ticket_logs.action,
ticket_logs.value_old,
ticket_logs.value_new
FROM
ticket_logs
INNER JOIN (
SELECT
date_format(
from_unixtime(`ticket_logs`.`created`),
'%Y-%m-%d'
) AS `datac`,
max(ticket_logs.created) AS maxts,
ticket_id
FROM
ticket_logs
WHERE
ticket_logs.action = 6
OR ticket_logs.action = 16
GROUP BY
date_format(
from_unixtime(`ticket_logs`.`created`),
'%Y-%m-%d'
),
ticket_id
) maxtbl ON ticket_logs.ticket_id = maxtbl.ticket_id
AND ticket_logs.created = maxtbl.maxts
ORDER BY
datax DESC,
ticket_id DESC
I'm trying to do something that sounds really simple but I have been going round in circles a little with it..
I have a stored procedure that currently works as required missing only one bit of functionality, to return a name for a corrosponding max calculation...
So I return
average calculation &
max calculation but want to return 'the name from another column' for the max value.
Here is an example of my SP, apologies that it may not seem very natural as I have had to rename and omit non relevant bits so may seem a little contrived::
SELECT
IFNULL(ROUND(AVG(TABLE1.TotalCapacityPercentageUsage / TABLE1.TotalSnapshotsForTimeSegment), 2), 0.0) AS TotalAvgCapacityPercentageUsage,
IFNULL(ROUND(MAX(TABLE1.MaxCapacityPercentageUsage), 2), 0.0) AS TotalMaxCapacityPercentageUsage,
-- TODO return the QueuesTmp.QueueName for max calculation (This could be more than one row, so I was going to use something like the following:
-- (SELECT GROUP_CONCAT(QueuesTmp.QueueName SEPARATOR ' ') to ensure only one field is returned..
FROM TABLE1
INNER JOIN QueuesTmp ON QueuesTmp.QueueID = TABLE1.QueueID
RIGHT JOIN TimesTmp ON TABLE1.TimeSegment = TimesTmp.QuarterHour AND
TABLE1.Date = DATE(TimesTmp.StartOfRangeUTC)
GROUP BY TimesTmp.QuarterHour;
I started by doing a Sub select but it seems I would then have to repeat all of the Joins, WHERE and Group By (Seems this is not even possible because that's what having is for)..
Can anybody guide me in the right direction as to how this can be achieved?
Thanks in advance.
WORKING SOLUTION
GROUP_CONCAT(DISTINCT QueuesTmp.QueueName ORDER BY MYCOLUMN DESC
SEPARATOR ':') AS MaxColumnQueueName,
I'm not sure that I'm on the right way. You need the QueueName of that row with the max - calculation. So use the group_concat with an ORDER BY of this calculation and get with SUBSTRING_INDEX the first element of this list.
substring_index(
GROUP_CONCAT(DISTINCT QueuesTmp.QueueName ORDER BY `maxCalculation` DESC) SEPARATOR ':',
':',
1
)
Additional question.
Sorry unfortunately the max comment space has reached. Here a query.
I used your example - query for sub and select the queueId as comma-separated list and the max(maxColumn) as additional.
After that I join to queue-table again with queueId and maxColumn. I can't guarantee if that works.
SELECT
sub.TotalAvgCapacityPercentageUsage,
sub.TotalMaxCapacityPercentageUsage,
GROUP_CONCAT(DISTINCT QueuesTmp.QueueName ORDER BY MYCOLUMN DESC SEPARATOR ':') AS MaxColumnQueueName
FROM(
SELECT
TimesTmp.QuarterHour,
IFNULL(
ROUND(
AVG(
TABLE1.TotalCapacityPercentageUsage /
TABLE1.TotalSnapshotsForTimeSegment
),
2
),
0.0
) AS TotalAvgCapacityPercentageUsage,
IFNULL(
ROUND(
MAX(TABLE1.MaxCapacityPercentageUsage),
2
),
0.0
) AS TotalMaxCapacityPercentageUsage,
max(QueuesTmp.maxColumn) AS maxColumn,
group_concat(DISTINCT QueueID) AS QueueID
FROM TABLE1
INNER JOIN QueuesTmp
ON QueuesTmp.QueueID = TABLE1.QueueID
RIGHT JOIN TimesTmp
ON TABLE1.TimeSegment = TimesTmp.QuarterHour
AND TABLE1.Date = DATE(TimesTmp.StartOfRangeUTC)
GROUP BY TimesTmp.QuarterHour
) AS sub
LEFT JOIN QueuesTmp
ON QueuesTmp.QueueID IN(sub.QueueID)
AND QueuesTmp.maxColumn = sub.maxColumn
I am working ona small project for a client, and the data in the database is causing me a bit of bother. I am completing a search form for paid installments from the database. I can get the data fine, its the date fields which are giving me bother.
There are separate fields for the date, month and year for each installments. Is there a way I can use CONCAT or DATE or maybe even use CAST on the combined fields to search for the data.
I have tried the following
SELECT DATE_FORMAT( CONCAT( received_date, received_month, received_year ) , '%d/%m/%Y' ) FROM policy_installments
AND
SELECT DATE_FORMAT(STR_TO_DATE(CONCAT('pit.received_date', '-', 'pit.received_month','-', 'pit.received_year'), '%d-%m-%Y') AS mydate, '%d-%m-%Y') AS modate FROM policy_installments pit ORDER BY mydate
AND
SELECT * FROM policy_installments WHERE CONCAT(CAST(received_date AS CHAR),"-",CAST(received_month AS CHAR),"-",CAST(received_year AS CHAR)) >= '1-1-2014' AND CONCAT(CAST(received_date AS CHAR),"-",CAST(received_month AS CHAR),"-",CAST(received_year AS CHAR)) <= '31-3-2014'
When I use the following I get some unexpected data:
SELECT CONCAT_WS( received_date, "-", received_month, "-", received_year ) FROM policy_installments
RESULT
2d313130312d3132303039
2d3237313032372d323732303039 etc etc
Each of the 3 fields are type INT(11).
However, none of these give me the correct data. Is there a way i can convert all 3 fields and use a date to pass into the query to enable me to correctly grab the correct data?
Any help would be much appreciated!
To convert a concatenated string to date, string should be in the same format as what you are using to convert.
For a date-string in dd/mm/yyyy format, should be converted to sql date using %d/%m%Y format.
Example:
SELECT STR_TO_DATE(
CONCAT( received_date, '/', received_month, '/', received_year ),
'%d/%m/%Y'
) as rcvd_date
FROM policy_installments
And to compare with other dates, you can use BETWEEN clause.
Example:
SELECT * FROM policy_installments
WHERE
STR_TO_DATE(
CONCAT( received_date, '-', received_month, '-', received_year ),
'%d-%m-%Y'
) BETWEEN STR_TO_DATE( '1-1-2014', '%d-%m-%Y' )
AND STR_TO_DATE( '31-3-2014', '%d-%m-%Y' )
Refer to:
MySQL: STR_TO_DATE(str,format): Convert a string to a date
Generally, you want to avoid performing the function on the column. Instead, perform the function on the literal, so that you can utilize indexes on the columns:
SELECT *
FROM policy_installments
WHERE received_year = YEAR('2014-03-31')
AND received_month = MONTH('2014-03-31')
AND received_date = DAY('2014-03-31')
Add a multi-column index on received_year, received_month, and received_date, you'll be doing pretty good.
Obviously, this method will only work for = comparisons. For <=, it becomes a bit more complicated:
SELECT *
FROM policy_installments
WHERE
received_year < YEAR('2014-03-31')
UNION
SELECT *
FROM policy_installments
WHERE
received_year = YEAR('2014-03-31')
AND received_month < MONTH('2014-03-31')
UNION
SELECT *
FROM policy_installments
WHERE
received_year = YEAR('2014-03-31')
AND received_month = MONTH('2014-03-31')
AND received_date <= DAY('2014-03-31')
But this longer query is still able to utilize the index.
To simplify things, consider creating an actual date column and keep it updated with the proper value.