I'm having trouble with a Mysql Query that require some "complex" ordering.
I've 2 tables:
Training
+--------------+------------------+
| training_id | training_name |
+--------------+------------------+
| 1 | test1 |
| 2 | test2 |
| 3 | test3 |
+--------------+------------------+
Training_venue
+----------+--------------+------------+
| venue_id | training_id | venue_date |
+----------+--------------+------------+
| 1 | 2 | 2009-06-01 |
| 2 | 2 | 2012-06-01 |
| 3 | 2 | 2011-06-01 |
| 4 | 1 | 2009-09-01 |
| 5 | 1 | 2011-09-01 |
| 6 | 1 | 2012-09-01 |
| 7 | 3 | 2009-01-01 |
+----------+--------------+------------+
And I'm expecting the following results:
+--------------+------------------+------------+--------------+
| training_id | training_name | venue_id | venue_date |
+--------------+------------------+------------+--------------+
| 2 | test2 | 2 | 2011-06-01 |
| 2 | test2 | 3 | 2012-06-01 |
| 1 | test1 | 6 | 2011-09-01 |
| 1 | test1 | 5 | 2012-09-01 |
+--------------+------------------+------------+--------------+
As you can see, the result requirement are:
A training with no future venue is discarded.
Expired venue are discarded
The trainings are "grouped" together
The trainings with the soonest venue is first, the training with the "latest soonest venue" is last
Inside the training, the venues are ordered from the soonest to the latest.
What mysql query will return that result set ?
Edit:
Here's what I've tried so far:
SELECT *
FROM `training` AS t
LEFT JOIN `training_venue` AS v USING ( `training_id` )
WHERE `venue_date` >= NOW()
ORDER BY `training_id;
But if the order by training_id take care of keeping all the training "grouped" together, it doesnt permit to order the training from the training with the soonest venue to the training with the lastest venue.
I also edited the data table to illustrate that problematic. See how the results are ordered, it's not by training_id, but by soonest venue.
Edit:
Corrected the dates.
SELECT t1.training_id, t1.training_name, t2.venue_id, t2.venue_date
FROM Training t1
INNER JOIN Training_venue t2 ON t1.training_id = t2.training_id
WHERE t2.venue_date >= NOW()
ORDER BY t1.training_id ASC, t2.venue_date ASC
I don't know what your last point is getting at: "Inside the training, the venues are ordered from the soonest to the latest." But the above query seems to match the rest of your needs.
EDIT: I now sort of understand better what you are after. And it is a tad complicated I think. I'll have another think about it.
EDIT: I think I have it!
SELECT t1.training_id, t1.training_name, t2.venue_id, t2.venue_date
FROM Training t1
INNER JOIN (SELECT training_id, venue_date
FROM training_venue
WHERE venue_date >= NOW()
GROUP BY training_id
ORDER BY MIN(venue_date)) t3 ON t1.training_id = t3.training_id
INNER JOIN Training_venue t2 ON t1.training_id = t2.training_id
WHERE t2.venue_date >= NOW()
ORDER BY t3.venue_date DESC, t2.venue_date ASC
Try it!
EDIT: Was using '2010-01-01' instead of NOW() as NOW() would lose the 2010 dates that you seemed to want included.
Related
I'm having a hard time setting up a query(select). Database is not my specialty, so I'm turning to the experts. Let me show what I need.
----companies--- ----company_server----- -----servers---- -----print------------------------
| id | name | | company | server | | id | name | | id |page|copy | date |server
|----|-------- | |---------|----------| |----|-------- | |----|----|-----|-------------
| 1 | Company1 |1--N| 1 | 1 |N*--1| 1 | Server1 |1--N| 1 | 2 | 3 | 2020-1-11 | 1
| 2 | Company2 | | 2 | 1 | | 2 | Server2 | | 2 | 1 | 6 | 2020-1-12 | 3
| 3 | Company3 | | 3 | 2 | | 3 | Server3 | | 3 | 4 | 5 | 2020-1-13 | 4
| 3 | 3 | | 4 | Server4 | | 4 | 5 | 3 | 2020-1-15 | 2
| 5 | 3 | 4 | 2020-1-15 | 4
| 6 | 1 | 2 | 2020-1-16 | 3
| 7 | 2 | 2 | 2020-1-16 | 4
What I need?
Example where date between CAST(2020-1-12 AS DATE) AND CAST(2020-1-15 AS DATE) group by servers.id
| companies | server | sum | percent
------------------------------------------------------------------------------------
| company1,company2 | server1 | sum(page*copy) = 0 or null | 0 or NULL
| company3 | server2 | sum(page*copy) = 15 | 28.30
| company3 | server3 | sum(page*copy) = 6 | 11.32
| NULL | server4 | sum(page*copy) = 32 | 60.38
Few notes:
I need this query for MYSQL;
Every Company is linked to at least one server.
I need result grouped by server. So, every company linked to that server must be concatenated by a comma.
If the company has not yet been registered, the value null should be presented.
The sum (page * copie) must be presented as zero or null (I don't care) in the case that there was no printing in the date range.
The percentage should be calculated according to the date range entered and not with all records in the database.
The field date is stored as MYSQL DATE.
Experts, I thank you in advance for your help. I currently solve this problem with at least 03 queries to the database, but I have a conviction that I could do it with just one query.
Added a fiddle. Sorry. Im still learing how to use this.
https://www.db-fiddle.com/f/dXej7QCPe9iDopfYd1SfVh/2
Follows the query that more or less represents how far I had arrived. Notice that in the middle of the way 'server4' disappeared because there are no values for it in print in the period searched for him and I am in possession of the total of the period but I cannot calculate the percentage.
i'm stuck
select
*
from
(select
sum(p.copy * p.page) as sum1,
s.name as s_name,
s.id as s_id
from
print p
join servers s on s.id = p.server
where p.date between cast('2020-1-12' as date) and cast('2020-1-15' as date)
group by s.id) as t1
join company_server cs on cs.server = t1.s_id
right join companies c on c.id = cs.company
cross join(
select
sum(p1.copy * p1.page) sum2
from
print p1
where p1.date between cast('2020-1-12' as date) and cast('2020-1-15' as date)
) as c;
I did this query before you add fiddle, so may be name of column of mine is not same as you. Anyway, this is my solution, hope it help you.
select group_concat(c.name separator ',') as name_company,
ss.name,
sum_print as sum,
(sum_print/total) *100 as percentage
from companies c
inner join company_server cs on c.id = cs.company
right join servers ss on ss.id = cs.id
left join
(
select server,sum(page*copy) as sum_print, date from print
where date between CAST('2020-1-12' AS DATE) AND CAST('2020-1-15' AS DATE)
group by server
) tmp on tmp.server = ss.id
cross join
(select sum(page*copy) as total from print where date between CAST('2020-1-12' AS DATE) AND CAST('2020-1-15' AS DATE)) tmp2
group by id
Group and concat by comma, using GROUP_CONCAT .
You can reference this image for JOIN clause.
https://i.stack.imgur.com/6cioZ.png
Hi I am looking for a solution to my inability to understand how I can get an overall total for a column in my query.
This query gets engineers names and the number of jobs they have that are out of SLA i.e. the data the job should have been completed has past and the job has still to be completed.
SELECT Engineer,Job_Status,COUNT(*) as 'Out Of SLA'
FROM import
WHERE (Job_Status = 'P' or Job_Status='P2' or Job_Status='P8')
and (isnull(Job_Completed_Date)
or Job_Completed_Date='0000-00-00')
and (Job_SLA_Due_Date < CURDATE()
)
GROUP BY import.Engineer,Job_Status
The code above produces the following results from the import table.
+----------------+------------+------------+
| Engineer | Job_Status | Out of SLA |
+----------------+------------+------------+
| Andy Beeres | P | 15 |
| Andy Broad | P | 4 |
| Darren Goodwin | P | 6 |
+----------------+------------+------------+
I want to be able to show the total number of the Out of SLA column as well as the rest of the table data if that makes sense something like the table below.
| Engineer | Job_Status | Out of SLA |
|------------- |------------ |------------ |
| Andy Beeres | P | 14 |
| | P2 | 3 |
| | P8 | 1 |
| Total | | 18 |
| Andy Broad | P | 12 |
| | P2 | 2 |
| Total | | 14 |
| Grand Total | | 32 |
Regards
Alan
Use with rollup with group by to get total_sla
According to MySql Docs:
The GROUP BY clause permits a WITH ROLLUP modifier that causes summary output to include extra rows that represent higher-level (that is, super-aggregate) summary operations. ROLLUP thus enables you to answer questions at multiple levels of analysis with a single query.
SELECT Engineer,Job_Status,COUNT(*) as 'Out Of SLA'
FROM import
WHERE (Job_Status = 'P' or Job_Status='P2' or Job_Status='P8')
and (isnull(Job_Completed_Date)
or Job_Completed_Date='0000-00-00')
and (Job_SLA_Due_Date < CURDATE()
)
GROUP BY import.Engineer,Job_Status WITH ROLLUP
One option is to use a subquery which finds the SLA total:
SELECT Engineer,
Job_Status,
COUNT(*) AS `Out Of SLA`,
(SELECT COUNT(*) FROM import) AS total_sla
FROM import
WHERE (Job_Status = 'P' OR Job_Status='P2' OR Job_Status='P8') AND
(ISNULL(Job_Completed_Date) OR Job_Completed_Date = '0000-00-00') AND
Job_SLA_Due_Date < CURDATE()
GROUP BY Engineer,
Job_Status
I am working on a product sample inventory system where I track the movement of the products. The status of each product can have a status of "IN" or "OUT" or "REMOVED". Each row of the table represents a new entry, where ID, status and date are unique. Each product also has a serial number.
I need help with a SQL query that will return all products that are currently "OUT". If I simply just select SELECT * FROM table WHERE status = "IN", it will return all products that ever had status IN.
Every time product comes in and out, I duplicate the last row of that specific product and change the status and update the date and it will get a new ID automatically.
Here is the table that I have:
id | serial_number | product | color | date | status
------------------------------------------------------------
1 | K0T4N | XYZ | silver | 2016-07-01 | IN
2 | X56Z7 | ABC | silver | 2016-07-01 | IN
3 | 96T4F | PQR | silver | 2016-07-01 | IN
4 | K0T4N | XYZ | silver | 2016-07-02 | OUT
5 | 96T4F | PQR | silver | 2016-07-03 | OUT
6 | F0P22 | DEF | silver | 2016-07-04 | OUT
7 | X56Z7 | ABC | silver | 2016-07-05 | OUT
8 | F0P22 | DEF | silver | 2016-07-06 | IN
9 | K0T4N | XYZ | silver | 2016-07-07 | IN
10 | X56Z7 | ABC | silver | 2016-07-08 | IN
11 | X56Z7 | ABC | silver | 2016-07-09 | REMOVED
12 | K0T4N | XYZ | silver | 2016-07-10 | OUT
13 | 96T4F | PQR | silver | 2016-07-11 | IN
14 | F0P22 | DEF | silver | 2016-07-12 | OUT
This query will give you all the latest records for each serial_number
SELECT a.* FROM your_table a
LEFT JOIN your_table b ON a.serial_number = b.serial_number AND a.id < b.id
WHERE b.serial_number IS NULL
Below query will give your expected result
SELECT a.* FROM your_table a
LEFT JOIN your_table b ON a.serial_number = b.serial_number AND a.id < b.id
WHERE b.serial_number IS NULL AND a.status LIKE 'OUT'
There are two good ways to do this. Which way is best,in terms of performance, can depend on various factors, so try both.
SELECT
t1.*
FROM table t
LEFT OUTER JOIN table later_t
ON later_t.serial_number = t.serial_number
AND later_t.date > t.date
WHERE later_t.id IS NULL
AND t.status = "OUT"
Which column you check from later_t for IS NULL does not matter, so long as that column is declared NOT NULL in the table definition.
The other logically equivalent method is:
SELECT
t.*
FROM table t
INNER JOIN (
SELECT
serial_number,
MAX(date) AS date
FROM table
GROUP BY serial_number
) latest_t
ON later_t.serial_number = t.serial_number
AND latest_t.date = t.date
WHERE t.status = "OUT"
For each of these queries, I strongly suggest the following index:
ALTER TABLE table
ADD INDEX `LatestSerialStatus` (serial_number,date)
I use this type of query a lot in my own work, and have the above index as the primary key on tables. Query performance is extremely fast in such cases, for these type of queries.
See also the documentation on this query type.
i need help with a mysql query. My tables:
objects
+---------+--------+
| id | name |
+---------+--------+
| 1 | house 1|
| 2 | house 2|
| 3 | house 3|
+---------+--------+
objects_expire
+----------+-----------+
| object_id| expire |
+----------+-----------+
| 1 | 2014-09-11|
| 1 | 2015-09-11|
| 2 | 2014-09-11|
| 2 | 2015-09-11|
| 2 | 2016-09-11|
| 3 | 2013-09-11|
| 3 | 2014-09-11|
| 3 | 2015-09-15|
+----------+-----------+
Now i need objects where max 'expire' is bigger then 2015-09-04 and smaller then 2015-09-18 (+/- 7 days)
Like this result:
+----------+-----------+-----------+
| object_id| expire | name |
+----------+-----------+-----------+
| 1 | 2015-09-11| house 1 |
| 3 | 2015-09-15| house 3 |
+----------+-----------+-----------+
This is what i have now:
SELECT o.id, MAX(uio.expire) AS object_expires
FROM objects AS o
LEFT JOIN objects_expire AS oe ON oe.object_id = o.id
WHERE expire < '2015-09-18'
AND expires > '2015-09-04'
GROUP BY o.id
But thats not correct.
Thanks for any help!!!
One usual approach is to do the grouping first and then join back, also if you do not want to hardcode the dates you can always use date_sub and date_add function to get -/+ 7 days from the current date.
select
o.id,
e.mexpire as expire,
o.name
from objects o
join(
select object_id,max(expire) as mexpire
from objects_expire
group by object_id
having mexpire > date_sub(curdate(),interval 7 day) and mexpire < date_add(curdate(),interval 7 day)
)e
on o.id = e.object_id
You need to group, and to use HAVING as a filter for the grouped column
select object_id, max(expire) as expire, name
from objects_expire
left join objects on objects_expire.object_id=objects.id
group by object_id, name
having max(expire) < '2015-09-17'
and max(expire) > '2015-09-03'
I have two tables, one that store product information and one that stores reviews for the products.
I am now trying to get the number of reviews submitted for the products between two dates but for some reason I get the same results regardless of the dates i put.
This is my query:
SELECT
productName,
COUNT(*) as `count`,
avg(rating) as `rating`
FROM `Reviews`
LEFT JOIN `Products` using(`productID`)
WHERE `date` BETWEEN '2015-07-20' AND '2015-07-30'
GROUP BY
`productName`
ORDER BY `count` DESC, `rating` DESC;
This returns:
+------------+---------------------+
| productName| count|rating |
+------------+------+--------------+
| productA | 23 | 4.3333333 |
| productB | 17 | 4.25 |
| productC | 10 | 3.5 |
+------------+---------------------+
Products table:
+---------+-------------+
|productID | productName|
+---------+-------------+
| 1 | productA |
| 2 | productB |
| 3 | productC |
+---------+-------------+
Reviews table
+---------+-----------+--------+---------------------+
|reviewID | productID | rating | date |
+---------+-----------+--------+---------------------+
| 1 | 1 | 4.5 | 2015-07-27 17:47:01|
| 2 | 1 | 3.5 | 2015-07-27 18:54:22|
| 3 | 3 | 2 | 2015-07-28 13:28:37|
| 4 | 1 | 5 | 2015-07-28 18:33:14|
| 5 | 2 | 1.5 | 2015-07-29 11:58:17|
| 6 | 2 | 3.5 | 2015-07-30 15:04:25|
| 7 | 2 | 2.5 | 2015-07-30 18:11:11|
| 8 | 1 | 3 | 2015-07-30 18:26:23|
| 9 | 1 | 3 | 2015-07-30 21:35:05|
| 10 | 1 | 4.5 | 2015-07-31 14:25:47|
| 11 | 3 | 0.5 | 2015-07-31 14:47:48|
+---------+-----------+--------+---------------------+
when I put two random dates that I do know for sure they not on the date column, I will still get the same results. Even when I want to retrieve records only on a certain day, I get the same results.
You should not use left join, because by doing so you retrieve all the data from one table. What you should use is something like :
select
productName,
count(*) as `count`,
avg(rating) as `rating`
from
products p,
reviews r
where
p.productID = r.productID
and `date` between '2015-07-20' and '2015-07-30'
group by productName
order by count desc, rating desc;
If the result, given your sample data, that you're looking for is:
| productName | count | rating |
|-------------|-------|--------|
| productA | 5 | 4 |
| productB | 3 | 3 |
| productC | 1 | 2 |
This is the count and average of reviews made on any date between 2015-07-20 and 2015-07-30 inclusive.
Then the there are two issues with your query. First, you need to change the join to a inner join instead of a left join, but more importantly you need to change the date condition as you are currently excluding reviews that fall on the last date on the range, but after midnight.
This happens because your between clause compares datetime values with date values so the comparison ends up being date between '2015-07-20 00:00:00' and '2015-07-30 00:00:00' which clearly excludes some dates at the end.
The fix is to either change the date condition so that the end is a day later:
where date >= '2015-07-20' and date < '2015-07-31'
or cast the date column to a date value, which will remove the time part:
where date(date) between '2015-07-20' and '2015-07-30'
Sample SQL Fiddle
You are using a LEFT JOIN between your reviews and your products tables. This will result in all the rows of reviews being shown with some rows having all product columns left empty.
You should use INNER JOIN, as this will filter only the wanted results.
(In the end I can only guess, since I don't even know which column belongs to which table ...)
The full query (very similar to Angelo Giannis's solution):
select
productName,
count(*) as `count`,
avg(rating) as `rating`
from
products INNER JOIN reviews USING(productId)
where date between '2015-07-20' and '2015-07-30'
group by productName
order by count desc, rating desc;
Here a fiddle with my and Angelo's solution (they both work).