Order result by IDs with the most recent date - mysql

I tried a lot, but I cannot figure out a way to do this:
I have a table with (not unique) IDs and dates. All entries should be selected in the end, but they need to be sorted.
Table:
+----+------------+
| id | date |
+----+------------+
| 1 | 2017-12-10 |
| 1 | 2015-05-22 |
| 7 | 2016-04-05 |
| 2 | 2017-12-12 |
| 2 | 2014-03-10 |
| 7 | 2016-01-14 |
| 1 | 2016-08-17 |
+----+------------+
What I need:
+----+------------+
| id | date |
+----+------------+
| 2 | 2017-12-12 |
| 2 | 2014-03-10 |
| 1 | 2017-12-10 |
| 1 | 2016-08-17 |
| 1 | 2015-05-22 |
| 7 | 2016-04-05 |
| 7 | 2016-01-14 |
+----+------------+
I need everything "grouped" by the ids, starting with the id that has the most recent date linked to it.
id: 2 / date: 2017-12-12
has the most recent date, so now all rows with Id 2 follow, ordered by the date descending. After that, which "block" of ids comes next is determined again by the next most recent date and so on.

Using a subquery that groups by id, we get the max date, then joining this to the source data gives us the max date on every row to sort by.
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE Table1
(`id` int, `date` datetime)
;
INSERT INTO Table1
(`id`, `date`)
VALUES
(1, '2017-12-10 00:00:00'),
(1, '2015-05-22 00:00:00'),
(7, '2016-04-05 00:00:00'),
(2, '2017-12-12 00:00:00'),
(2, '2014-03-10 00:00:00'),
(7, '2016-01-14 00:00:00'),
(1, '2016-08-17 00:00:00')
;
Query 1:
select t.*
from table1 t
inner join (
select id, max(`date`) maxdate
from table1
group by id
) g on t.id = g.id
order by g.maxdate DESC, t.id, t.date DESC
Results:
| id | date |
|----|----------------------|
| 2 | 2017-12-12T00:00:00Z |
| 2 | 2014-03-10T00:00:00Z |
| 1 | 2017-12-10T00:00:00Z |
| 1 | 2016-08-17T00:00:00Z |
| 1 | 2015-05-22T00:00:00Z |
| 7 | 2016-04-05T00:00:00Z |
| 7 | 2016-01-14T00:00:00Z |

if your table is stack
SELECT * FROM `stack` LEFT OUTER JOIN (SELECT * FROM `stack` GROUP BY `id` )t1 ON
`stack`.`id` = t1.`id` ORDER BY t1.`date` DESC,`stack`.`date` DESC

Related

mysql inflation value calculation

I have a mysql table like this:
| data | valore | inflation |
+------------+---------+-----------+
| 2022-06-01 | 296.311 | NULL |
| 2022-05-01 | 292.296 | NULL |
| 2022-04-01 | 289.109 | NULL |
| 2022-03-01 | 287.504 | NULL |
| 2022-02-01 | 283.716 | NULL |
| 2022-01-01 | 281.148 | NULL |
| 2021-12-01 | 278.802 | NULL |
| 2021-11-01 | 277.948 | NULL |
| 2021-10-01 | 276.589 | NULL |
| 2021-09-01 | 274.310 | NULL |
| 2021-08-01 | 273.567 | NULL |
| 2021-07-01 | 273.003 | NULL |
| 2021-06-01 | 271.696 | NULL |
I would need to insert (update) inflation value, calculated as current year value / past year value.
For examle the inflation for 2022-06-01 should be given by 296.311/271.696 * 100 - 100 (or as percentage anyway).
How can I do?
Thank You
Use a self join in the UPDATE statement:
UPDATE tablename t1
INNER JOIN tablename t2 ON t2.data = t1.data - INTERVAL 1 YEAR
SET t1.inflation = t1.valore / t2.valore * 100 - 100;
See the demo.
Ho usato mySQL 8.0:
CREATE TABLE inflation_tab
(data DATE, valore FLOAT, inflazione FLOAT);
INSERT INTO inflation_tab VALUES
('2022-06-01', 296.311, NULL),
('2022-05-01', 292.296, NULL),
('2022-04-01', 289.109, NULL),
('2022-03-01', 287.504, NULL),
('2022-02-01', 283.716, NULL),
('2022-01-01', 281.148, NULL),
('2021-12-01', 278.802, NULL),
('2021-11-01', 277.948, NULL),
('2021-10-01', 276.589, NULL),
('2021-09-01', 274.310, NULL),
('2021-08-01', 273.567, NULL),
('2021-07-01', 273.003, NULL),
('2021-06-01', 271.696, NULL);
SET #last_val=271.696;
WITH cte AS (
SELECT data, valore, ROUND(valore/#last_val*100 - 100, 3) AS inflazione
FROM inflation_tab IT
)
UPDATE inflation_tab IT INNER JOIN cte ON (IT.data=cte.data)
SET IT.inflazione=cte.inflazione

How to know if a set of date range covers a bigger date range?

I have to entities: postes and bookings. There is a oneToMany relationship between poste and booking: one poste may have many bookings (at different dates).
bookings is defined by 4 columns:
booking_id: id
poste_id: jointure postes table
start_datetime: start date booking
number_day: number of days (integer)
postes is defined by 4 columns:
poste_id: poste id
pattern (string): defined the allowed day (1 is allowed, 0 no). The 8th day is defined as the 1st day of the pattern (modulo 7)
start: poste beginning date (all the date in bookings are included between start and end)
end: poste end date
Goal: I would like to define a query that selects all postes that are not fully reserved (e.g. some new reservations are possible). I'm stuck because I can't select any data for the free date range since only bookings are stored.
Example
Booking table
| booking_id | poste_id | start_datetime | number_day |
|------------|----------|----------------------|------------|
| 1 | 1 | 2019-07-10T00:00:00Z | 4 |
| 4 | 1 | 2019-07-14T00:00:00Z | 1 |
| 7 | 1 | 2019-07-16T00:00:00Z | 4 |
| 2 | 2 | 2019-07-10T00:00:00Z | 2 |
| 9 | 2 | 2019-07-13T00:00:00Z | 2 |
| 5 | 3 | 2019-07-15T00:00:00Z | 2 |
| 8 | 3 | 2019-07-21T00:00:00Z | 3 |
| 11 | 3 | 2019-07-28T00:00:00Z | 1 |
| 12 | 3 | 2019-07-29T00:00:00Z | 1 |
| 3 | 4 | 2019-07-15T00:00:00Z | 1 |
| 13 | 4 | 2019-07-21T00:00:00Z | 2 |
Postes table:
| poste_id | pattern | start | end |
|----------|---------|----------------------|----------------------|
| 1 | 1111101 | 2019-07-10T00:00:00Z | 2019-07-20T00:00:00Z |
| 2 | 1101101 | 2019-07-10T00:00:00Z | 2019-07-20T00:00:00Z |
| 3 | 1100001 | 2019-07-15T00:00:00Z | 2019-07-30T00:00:00Z |
| 4 | 1011001 | 2019-07-15T00:00:00Z | 2019-07-30T00:00:00Z |
The expected output of the example is : 2,4. (no more booking are available for the poste 1 and 3).
Note:
This is a simple example. Actually, the date ranges are bigger e.g. over several months.
The database assumes bookings do not overlap.
The pattern length might be different from 7. There are no link between the first day of the pattern and the number of days in a week. For example, if the pattern is '1101' with a starting date of "10-07-2019", that means the days 10, 11, 13, 14, 15, 17 and so on are available, not the others.
The booking dates are always between the starting and ending date of the poste.
Reproductibility:
// Build the tables:
CREATE TABLE bookings
(`booking_id` int, `poste_id` int, `start_datetime` datetime, `number_day` int)
;
INSERT INTO bookings
(`booking_id`, `poste_id`, `start_datetime`, `number_day`)
VALUES
(1, 1, '2019-07-10', '4'),
(4, 1, '2019-07-14', '1'),
(7, 1, '2019-07-16', '4'),
(2, 2, '2019-07-10', '2'),
(9, 2, '2019-07-13', '2'),
(5, 3, '2019-07-15', '2'),
(8, 3, '2019-07-21', '3'),
(11, 3, '2019-07-28', '1'),
(12, 3, '2019-07-29', '1'),
(3, 4, '2019-07-15', '1'),
(13, 4, '2019-07-21', '2')
;
CREATE TABLE postes
(`poste_id` int, `pattern` VARCHAR(7), `start` datetime, `end` datetime);
INSERT INTO postes VALUES
(1, "1111101", "2019-07-10", "2019-07-20"),
(2, "1101101", "2019-07-10", "2019-07-20"),
(3, "1100001", "2019-07-15", "2019-07-30"),
(4, "1011001", "2019-07-15", "2019-07-30");
My work: so far, I managed to find for a given day the available poste:
SELECT DISTINCT p.*
FROM postes p
LEFT JOIN bookings b
ON b.poste_id = p.poste_id
WHERE
/* Ignore date in past */
MOD(DATEDIFF("2019-07-16", p.start), LENGTH(p.pattern)) >= -1
AND
/* Filter poste with pattern = 1 */
SUBSTRING(p.pattern, MOD(DATEDIFF("2019-07-16", p.start),
LENGTH(p.pattern)) + 1 , 1) = 1
AND
/* Filter those available this day */
p.poste_id NOT IN (
SELECT b.poste_id
FROM bookings b
WHERE b.start_datetime <= "2019-07-16"
AND "2019-07-16" < DATE_ADD(b.start_datetime, INTERVAL b.number_day DAY)
);
Output:
| poste_id | pattern | start | end |
|----------|---------|----------------------|----------------------|
| 2 | 1101101 | 2019-07-10T00:00:00Z | 2019-07-20T00:00:00Z |
(Not yet a complete answer, but at least some tips...)
Is the 2nd column of bookings poste_id or room_id? (I guess "room" would be the better word for English??)
Use DATE datatype instead of DATETIME. (Also, MySQL will choke on T and Z in datetime literals`.)
Instead of VARCHAR(7), use TINYINT UNSIGNED. This will let you use Boolean operations, shift operations, and the BIT_COUNT() function. (See https://dev.mysql.com/doc/refman/8.0/en/bit-functions.html .) These should facilitate the desired computations.
Your bit string is 7 long, as if it is related to days of the week. But is it? That is, is the first bit tied to, say, Sunday? Or is it tied to poste.start?
What version of MySQL are you using? Before 8.0, bit operations are limited to 64, hence limiting a bit-manipulation solution to about 2 months. With 8.0, the operations are virtually unlimited in size.
So, with 8.0, I might
Replicate the bit (not char) pattern a sufficient number of times. (Hmmm... REPEAT works easily for chars, but not bits. Maybe do the repeat with char strings, then convert to bits.)
Chop off bits after the end date.
BIT_COUNT() to see how many days are available in the range.
SUM(number_day) to get the number of days reserved.
Subtract to see how many days are not reserved. (Note: this assumes the data is 'valid', that is, not having any "overlaps" in bookings.
(I would probably write the code in a 'real' programming language, as suggested by Aprillion. My steps above might be useful there.)
With an older version of MySQL, and/or with VARCHAR(7) instead of TINYINT, the above steps might work, but with some substitutions. For example, BIT_COUNT could be replaced by LENGTH(s) - LENGTH(REPLACE(s, '1', ''))
Because of poste.pattern, I don't see how it would be possible to work with date ranges directly. It is possible to extend the 1-day solution by joining to a table that lists all dates from a range in separate rows, which can be generated as in:
How to populate a table with a range of dates?
...replacing "2019-07-16" with the _date column from that table.
Note: for this task, using a programming language could be more performant than a SQL-only approach.
This is a pretty common problem in SQL questions — how to match data that isn't present in the database? SQL is better at matching data that is present.
Another quality of SQL is that it works better against sets of rows, instead of imaginary ranges defined by start and end. So my strategy is to transform the range into sets of actual rows.
First, create a table of all dates:
CREATE TABLE dates (date DATE PRIMARY KEY);
INSERT INTO dates SET date = '2019-07-01';
...
INSERT INTO dates SET date = '2019-07-30';
Create a table of all dates used by any poste:
CREATE TABLE poste_dates (
poste_id INT,
date DATE,
booking_id INT,
PRIMARY KEY (poste_id, date)
);
Populate it with all dates for each poste, between the start and end of the poste date range. Filter using a join condition such that days of the week corresponding to your pattern are '1'.
INSERT INTO poste_dates (poste_id, date)
SELECT poste_id, d.date FROM postes p JOIN dates d
ON SUBSTR(p.pattern, MOD(DATEDIFF(d.date, p.start), LENGTH(p.pattern))+1, 1)
WHERE d.date BETWEEN p.start AND p.end;
Query OK, 34 rows affected (0.01 sec)
Now you have all the dates for all the postes:
+----------+------------+------------+
| poste_id | date | booking_id |
+----------+------------+------------+
| 1 | 2019-07-10 | NULL |
| 1 | 2019-07-11 | NULL |
| 1 | 2019-07-12 | NULL |
| 1 | 2019-07-13 | NULL |
| 1 | 2019-07-14 | NULL |
| 1 | 2019-07-17 | NULL |
| 1 | 2019-07-18 | NULL |
| 1 | 2019-07-19 | NULL |
| 1 | 2019-07-20 | NULL |
| 2 | 2019-07-10 | NULL |
| 2 | 2019-07-11 | NULL |
| 2 | 2019-07-13 | NULL |
| 2 | 2019-07-14 | NULL |
| 2 | 2019-07-17 | NULL |
| 2 | 2019-07-18 | NULL |
| 2 | 2019-07-20 | NULL |
| 3 | 2019-07-15 | NULL |
| 3 | 2019-07-16 | NULL |
| 3 | 2019-07-22 | NULL |
| 3 | 2019-07-23 | NULL |
| 3 | 2019-07-29 | NULL |
| 3 | 2019-07-30 | NULL |
| 4 | 2019-07-15 | NULL |
| 4 | 2019-07-17 | NULL |
| 4 | 2019-07-18 | NULL |
| 4 | 2019-07-22 | NULL |
| 4 | 2019-07-24 | NULL |
| 4 | 2019-07-25 | NULL |
| 4 | 2019-07-29 | NULL |
+----------+------------+------------+
For each booking, use UPDATE to set the booking id in the poste_dates table. Use LIMIT with the length of the booking. We have to do this one at a time, because in MySQL, LIMIT doesn't work when an UPDATE has a JOIN.
UPDATE poste_dates SET booking_id = 1 WHERE poste_id = 1 AND date >= '2019-07-10' ORDER BY date LIMIT 4;
UPDATE poste_dates SET booking_id = 4 WHERE poste_id = 1 AND date >= '2019-07-14' ORDER BY date LIMIT 1;
UPDATE poste_dates SET booking_id = 7 WHERE poste_id = 1 AND date >= '2019-07-16' ORDER BY date LIMIT 4;
UPDATE poste_dates SET booking_id = 2 WHERE poste_id = 2 AND date >= '2019-07-10' ORDER BY date LIMIT 2;
UPDATE poste_dates SET booking_id = 9 WHERE poste_id = 2 AND date >= '2019-07-13' ORDER BY date LIMIT 2;
UPDATE poste_dates SET booking_id = 5 WHERE poste_id = 3 AND date >= '2019-07-15' ORDER BY date LIMIT 2;
UPDATE poste_dates SET booking_id = 8 WHERE poste_id = 3 AND date >= '2019-07-21' ORDER BY date LIMIT 3;
UPDATE poste_dates SET booking_id = 11 WHERE poste_id = 3 AND date >= '2019-07-28' ORDER BY date LIMIT 1;
UPDATE poste_dates SET booking_id = 12 WHERE poste_id = 3 AND date >= '2019-07-29' ORDER BY date LIMIT 1;
UPDATE poste_dates SET booking_id = 3 WHERE poste_id = 4 AND date >= '2019-07-15' ORDER BY date LIMIT 1;
UPDATE poste_dates SET booking_id = 13 WHERE poste_id = 4 AND date >= '2019-07-21' ORDER BY date LIMIT 2;
Now the dates look like:
+----------+------------+------------+
| poste_id | date | booking_id |
+----------+------------+------------+
| 1 | 2019-07-10 | 1 |
| 1 | 2019-07-11 | 1 |
| 1 | 2019-07-12 | 1 |
| 1 | 2019-07-13 | 1 |
| 1 | 2019-07-14 | 4 |
| 1 | 2019-07-16 | 7 |
| 1 | 2019-07-17 | 7 |
| 1 | 2019-07-18 | 7 |
| 1 | 2019-07-19 | 7 |
| 1 | 2019-07-20 | NULL |
| 2 | 2019-07-10 | 2 |
| 2 | 2019-07-11 | 2 |
| 2 | 2019-07-13 | 9 |
| 2 | 2019-07-14 | 9 |
| 2 | 2019-07-16 | NULL |
| 2 | 2019-07-17 | NULL |
| 2 | 2019-07-18 | NULL |
| 2 | 2019-07-20 | NULL |
| 3 | 2019-07-15 | 5 |
| 3 | 2019-07-16 | 5 |
| 3 | 2019-07-21 | 8 |
| 3 | 2019-07-22 | 8 |
| 3 | 2019-07-23 | 8 |
| 3 | 2019-07-28 | 11 |
| 3 | 2019-07-29 | 12 |
| 3 | 2019-07-30 | NULL |
| 4 | 2019-07-15 | 3 |
| 4 | 2019-07-17 | NULL |
| 4 | 2019-07-18 | NULL |
| 4 | 2019-07-21 | 13 |
| 4 | 2019-07-22 | 13 |
| 4 | 2019-07-24 | NULL |
| 4 | 2019-07-25 | NULL |
| 4 | 2019-07-28 | NULL |
| 4 | 2019-07-29 | NULL |
+----------+------------+------------+
Now it's quite simple to search for any postes that have any dates in this table with a NULL booking_id.
SELECT DISTINCT poste_id FROM poste_dates WHERE booking_id IS NULL;
This still differs from your expected result of poste 2 and 4.
poste 1 includes the date 2019-07-20 because the pattern is 1111101-1111 which puts a 1 on the 20th, but no booking for poste 1 covers the 20th. Therefore 1 is not fully booked.
poste 3 includes the date 2019-07-30 because the pattern is 1100001-1100001-11 which puts a 1 on the 30th, but no booking for poste 3 covers the 30th. Therefore 3 is not fully booked.
Since 8.0 you can do it with on-the-fly generated table of numbers and a little help of LATERAL. You may wish to create a persistant table of numbers instead.
with e1(n) as (
select 1 union all select 1 union all select 1 union all
select 1 union all select 1 union all select 1 union all
select 1 union all select 1 union all select 1 union all select 1
), e2(n) as (select 1 from e1 a, e1 b), -- 100 rows
e4(n) as (select 1 from e2 a, e2 b), -- 10,000 rows
numbers(n) as (
select row_number() over(order by n) N from e4
)
select distinct poste_id, pattern, start, `end`
from postes p
join numbers n on adddate(start, n.N-1) <= `end`
-- compute the date and respective position in the pattern for further usage
, lateral (select adddate(start, n.N-1) dt, (n.N-1) % length(pattern) + 1 pos) x
where substring(pattern, x.pos, 1)
and not exists (
select 1
from bookings b
where b.poste_id = p.poste_id and x.dt >= b.start_datetime and x.dt <= adddate(b.start_datetime, b.number_day))
order by p.poste_id;
Fiddle

Mysql Query Group by Last record and first record

I have 2 tables which status table are connected with profile table. Profile table store unique tagId and status table store multiple duplicate tagid entry. I want to show group by tagid's last record and first record.
Table: profile
Note: tagId is Unique
--------------------------------------------------------------------
| tagId(PK) | blah2 | blah3 | blah4 |
--------------------------------------------------------------------
101 |
102 |
103 |
104 |
105 |
106 |
Table: status
--------------------------------------------------------------------
statusId | tagId | date | height| weight | statusType | blah2 |
--------------------------------------------------------------------
1 | 101 | 2010-01-01 | 5.6 | 300 | single |
2 | 102 | 2010-01-01 | 5.7 | 300 | single |
3 | 101 | 2015-01-01 | 5.6 | 310 | married |
4 | 103 | 2010-01-01 | 5.6 | 300 | single |
5 | 104 | 2010-01-01 | 5.6 | 300 | single |
6 | 101 | 2016-01-01 | 5.6 | 300 | pregnant |
7 | 101 | 2016-09-01 | 5.6 | 300 | delivery |
8 | 105 | 2010-01-01 | 5.6 | 300 | single |
What I want to try group by first date and group by last statusType
Query Result will be:
--------------------------------------------------------------------
| tagId | date | height| weight | statusType | blah2 |
--------------------------------------------------------------------
| 101 | 2010-01-01 | 5.6 | 300 | delivery |
| 102 | 2010-01-01 | 5.7 | 300 | single |
| 103 | 2010-01-01 | 5.6 | 300 | single |
| 104 | 2010-01-01 | 5.6 | 300 | single |
| 105 | 2010-01-01 | 5.6 | 300 | single |
But I can't success with it, I tried with this MySQL code
SELECT DISTINCT Profile.TagId,Status.date,Status.StatusType,Status.height,Status.weight FROM Profile
LEFT JOIN Status ON Status.TagId = Profile.TagId
Where Status.StatusId In( Select Max(Status.StatusId) From Status Group By Status.TagId )
Group By Status.TagId ORDER BY Profile.TagId ASC, Status.TagId DESC
But it returns last date and last statustype , like this
Query Result:
--------------------------------------------------------------------
| tagId | date | height| weight | statusType | blah2 |
--------------------------------------------------------------------
| 101 | 2016-09-01 | 5.6 | 300 | delivery |
| 102 | 2010-01-01 | 5.7 | 300 | single |
| 103 | 2010-01-01 | 5.6 | 300 | single |
| 104 | 2010-01-01 | 5.6 | 300 | single |
| 105 | 2010-01-01 | 5.6 | 300 | single |
I think you are probably looking for something like this:
SELECT DISTINCT p.tagId,
smin.`date`, smin.height, smin.weight,
smax.StatusType
FROM Profile AS p
LEFT JOIN (
SELECT tagId, MAX(`date`) AS max_date, MIN(`date`) AS min_date
FROM Status
GROUP BY tagId
) AS s ON p.tagId = s.tagId
LEFT JOIN Status AS smin ON smin.tagId = p.tagId AND s.min_date = smin.`date`
LEFT JOIN Status AS smax ON smax.tagId = p.tagId AND s.max_date = smax.`date`
The query uses a derived table which returns minimum and maximum date values per tagId. Using these values we can join back to Status table and get date, height and weight values from the Status record having the minimum date value and StatusType value for the Status record having the maximum date value.
Not sure if you want this or not, but just try it, and hope I do not mistake your question;)
SQL Fiddle
MySQL 5.6 Setup:
CREATE TABLE status
(`statusId` int, `tagId` int, `date` date, `height` int, `weight` int, `statusType` varchar(8))
;
INSERT INTO status
(`statusId`, `tagId`, `date`, `height`, `weight`, `statusType`)
VALUES
(1, 101, '2010-01-01', 5.6, 300, 'single'),
(2, 102, '2010-01-01', 5.7, 300, 'single'),
(3, 101, '2015-01-01', 5.6, 310, 'married'),
(4, 103, '2010-01-01', 5.6, 300, 'single'),
(5, 104, '2010-01-01', 5.6, 300, 'single'),
(6, 101, '2016-01-01', 5.6, 300, 'pregnant'),
(7, 101, '2016-09-01', 5.6, 300, 'delivery'),
(8, 105, '2010-01-01', 5.6, 300, 'single')
;
Query 1:
select f.`tagId`, date_format(l.`date`, '%Y-%m-%d') as `date`, f.`statusType`
from (
select s.*
from `status` s
inner join (
select max(`statusId`) as `statusId`, `tagId`
from `status`
group by `tagId`) t on s.`statusId` = t.`statusId` and s.`tagId` = t.`tagId`
) f
inner join (
select s.*
from `status` s
inner join (
select min(`statusId`) as `statusId`, `tagId`
from `status`
group by `tagId`) t on s.`statusId` = t.`statusId` and s.`tagId` = t.`tagId`
) l on f.`tagId` = l.`tagId`
order by tagId
Results:
| tagId | date | statusType |
|-------|------------|------------|
| 101 | 2010-01-01 | delivery |
| 102 | 2010-01-01 | single |
| 103 | 2010-01-01 | single |
| 104 | 2010-01-01 | single |
| 105 | 2010-01-01 | single |
Subquery f is the first record by each group and l is the last record by each group, and if you want height or weight column, please choose one of them which you prefer to.
Also you can left join this all query to profile then you can get tagId = 106, but this record will give you null for column date and statusType.

How to query multiple tables using a single query?

I want my tables to output something like this
---------------------------------------------------------------------------------------------
| date | location | time | delegate 1 | delegate 2 |
|--------------------------------------------------------------------------------------------
| 2015-12-07 | Table 1 | 9:00 | first_name_4 last_name_4 | first_name_5 last_name_5 |
|--------------------------------------------------------------------------------------------
| | 9:30 | first_name_4 last_name_4 | first_name_6 last_name_6 |
|--------------------------------------------------------------------------------------------
| | 9:30 | first_name_3 last_name_3 | first_name_7 last_name_7 |
|--------------------------------------------------------------------------------------------
| | 9:00 | first_name_3 last_name_3 | first_name_7 last_name_7 |
|--------------------------------------------------------------------------------------------
Here are the tables on my db
meetings table
-------------------------------------------------------------------------------------------------
| id | date_id | time_id | location_id | delegate_id_1 | delegate_id_2 | status |
|------------------------------------------------------------------------------------------------
| 1 | 1 | 1 | 1 | 4 | 5 | A |
|------------------------------------------------------------------------------------------------
| 2 | 1 | 2 | 1 | 4 | 6 | A |
|------------------------------------------------------------------------------------------------
| 3 | 1 | 1 | 1 | 2 | 6 | P |
|------------------------------------------------------------------------------------------------
| 4 | 1 | 2 | 1 | 1 | 3 | A |
|------------------------------------------------------------------------------------------------
| 5 | 1 | 1 | 1 | 1 | 3 | A |
|------------------------------------------------------------------------------------------------
users table
-----------------------------------------
| id | first_name | last_name |
|----------------------------------------
| 1 | first_name_1 | last_name_1 |
|----------------------------------------
| 2 | first_name_2 | last_name_2 |
|----------------------------------------
| 3 | first_name_3 | last_name_3 |
|----------------------------------------
| 4 | first_name_4 | last_name_4 |
|----------------------------------------
| 5 | first_name_5 | last_name_5 |
|----------------------------------------
| 6 | first_name_6 | last_name_6 |
|----------------------------------------
locations table
-----------------------------
| id | location_name |
|----------------------------
| 1 | Table 1 |
|----------------------------
time table
-------------------------
| id | meeting_time |
|------------------------
| 1 | 9:00:00 |
|------------------------
| 1 | 9:30:00 |
|------------------------
dates table
-------------------------
| id | meeting_date |
|------------------------
| 1 | 2015-12-07 |
|------------------------
| 2 | 2015-12-08 |
|------------------------
| 3 | 2015-12-09 |
|------------------------
My initial query goes like this
-- $query_date
SELECT meeting_date
FROM dates
WHERE meeting_date = '2015-12-07'
-- $query_location
SELECT location_name.location
from location
LEFT JOIN meetings
ON meetings.location_id=location.id
LEFT JOIN date
ON meetings.date_id=date.id
WHERE meeting_date.dates = '2015-12-07'
Now, here's the part where I got it wrong.
-- $query_final
SELECT meeting_time.time, delegate1.first_name AS first_name_1,
delegate1.last_name AS last_name_1, delegate2.first_name AS first_name_2,
delegate2.last_name AS last_name_2
FROM meetings
INNER JOIN users delegate1
ON meetings.delegate_id_1=users.id
LEFT JOIN users delegate2
ON meetings.delegate_id_2=users.id
WHERE meetings.status='A'
The results on my last query give me unexpected results since the results show more entries than my meetings table.
I know the queries I made are costly but I don't know how to make a more optimized query. I don't even know if it's possible to get the results into a single query only. Any help well do. Thanks.
You can bring back everything with a single query with the right JOIN.
Be Careful, when you use column name on SQL, the syntax is TABLE.COLUMN_NAME, it seem you mistake on the order quit often...
I changed some table name as you sometime use an s at the end and sometime no.
As time and date are SQL keyword, it's better with s everywhere
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE meetings (`id` int, `date_id` int, `time_id` int, `location_id` int, `delegate_id_1` int, `delegate_id_2` int, `status` varchar(1));
INSERT INTO meetings (`id`, `date_id`, `time_id`, `location_id`, `delegate_id_1`, `delegate_id_2`, `status`)
VALUES (1, 1, 1, 1, 4, 5, 'A'),
(2, 1, 2, 1, 4, 6, 'A'),
(3, 1, 1, 1, 2, 6, 'P'),
(4, 1, 2, 1, 1, 3, 'A'),
(5, 1, 1, 1, 1, 3, 'A');
CREATE TABLE users (`id` int, `first_name` varchar(12), `last_name` varchar(11));
INSERT INTO users (`id`, `first_name`, `last_name`)
VALUES (1, 'first_name_1', 'last_name_1'),
(2, 'first_name_2', 'last_name_2'),
(3, 'first_name_3', 'last_name_3'),
(4, 'first_name_4', 'last_name_4'),
(5, 'first_name_5', 'last_name_5'),
(6, 'first_name_6', 'last_name_6');
CREATE TABLE locations (`id` int, `location_name` varchar(7));
INSERT INTO locations (`id`, `location_name`)
VALUES (1, 'Table 1');
CREATE TABLE times (`id` int, `meeting_time` varchar(7));
INSERT INTO times (`id`, `meeting_time`)
VALUES (1, '9:00:00'),
(2, '9:30:00') ;
CREATE TABLE dates (`id` int, `meeting_date` varchar(10)) ;
INSERT INTO dates (`id`, `meeting_date`)
VALUES (1, '2015-12-07'),
(2, '2015-12-08'),
(3, '2015-12-09') ;
Query 1:
-- $query_final
SELECT locations.location_name,
`times`.meeting_time,
delegate1.first_name AS first_name_1,
delegate1.last_name AS last_name_1,
delegate2.first_name AS first_name_2,
delegate2.last_name AS last_name_2
FROM meetings
LEFT JOIN locations
ON meetings.location_id=locations.id
LEFT JOIN dates
ON meetings.date_id=`dates`.id
LEFT JOIN times
ON meetings.time_id=`times`.id
INNER JOIN users delegate1
ON meetings.delegate_id_1 = delegate1.id
LEFT JOIN users delegate2
ON meetings.delegate_id_2 = delegate2.id
WHERE
meetings.status = 'A'
AND dates.meeting_date = '2015-12-07'
Results:
| location_name | meeting_time | first_name | last_name | first_name | last_name |
|---------------|--------------|--------------|-------------|--------------|-------------|
| Table 1 | 9:00:00 | first_name_1 | last_name_1 | first_name_3 | last_name_3 |
| Table 1 | 9:30:00 | first_name_1 | last_name_1 | first_name_3 | last_name_3 |
| Table 1 | 9:00:00 | first_name_4 | last_name_4 | first_name_5 | last_name_5 |
| Table 1 | 9:30:00 | first_name_4 | last_name_4 | first_name_6 | last_name_6 |

MySQL subquery/complex query question, tracking state changes

I have a table that tracks contact class state changes by date. The question that I am trying to answer is what is the current state of all contacts on a certain date.
DROP TABLE IF EXISTS `contact_class_state`;
CREATE TABLE `contact_class_state` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`contact_id` int unsigned DEFAULT NULL, -- the contact
`contact_class` int unsigned,
`state_date` date,
PRIMARY KEY (`id`),
INDEX (`contact_id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `contact_class_state` (`contact_id`, `contact_class`, `state_date`) VALUES
(1, 1, '2011-01-01'),
(2, 1, '2011-01-01'),
(3, 1, '2011-01-01'),
(4, 1, '2011-01-01'),
(5, 1, '2011-01-01'),
(1, 2, '2011-02-01'),
(3, 2, '2011-02-01'),
(5, 2, '2011-02-01'),
(1, 1, '2011-02-15'),
(5, 3, '2011-03-01');
For example, the following query:
SELECT contact_id, contact_class, state_date
FROM contact_class_state
WHERE state_date <= '2011-02-27'
ORDER BY contact_id, state_date DESC
returns
+------------+---------------+------------+
| contact_id | contact_class | state_date |
+------------+---------------+------------+
| 1 | 1 | 2011-02-15 |
| 1 | 2 | 2011-02-01 |
| 1 | 1 | 2011-01-01 |
| 2 | 1 | 2011-01-01 |
| 3 | 2 | 2011-02-01 |
| 3 | 1 | 2011-01-01 |
| 4 | 1 | 2011-01-01 |
| 5 | 2 | 2011-02-01 |
| 5 | 1 | 2011-01-01 |
+------------+---------------+------------+
While this is technically correct, I only need the first (or last if sorted ASC) row for each contact_id as the latest date will always give me current state of the contact, per the below:
+------------+---------------+------------+
| contact_id | contact_class | state_date |
+------------+---------------+------------+
| 1 | 1 | 2011-02-15 |
| 2 | 1 | 2011-01-01 |
| 3 | 2 | 2011-02-01 |
| 4 | 1 | 2011-01-01 |
| 5 | 2 | 2011-02-01 |
+------------+---------------+------------+
I am pretty sure a sub or a complex query would do the trick but I am having a mental block with the SQL. I am also open to other approaches to solve this issue.
Thanks!
If your query is in fact what you want (except then grouped by contact_id), then do exactly that.
SELECT * FROM
(SELECT contact_id, contact_class, state_date
FROM contact_class_state
WHERE state_date <= '2011-02-27'
ORDER BY contact_id, state_date DESC) table1
GROUP BY contact_id
This is tested and works perfect.