Change Group by Order - php

Consider this table:
id | machine | manual| etc.
1 1 0
2 2 0
3 1 1
I want to retrieve the records grouped by Machine. I know that the manual records are always inserted after the automatic ones.
In MySQL I can do this:
SELECT * FROM table GROUP BY machine DESC
And the records retrieved are:
id | machine | manual| etc.
2 2 0
3 1 1
Because the GROUP BY of MySQL works in ASC and the first record it finds is the one it considers. So by placing the DESC in the GROUP BY it works in reverse and since I know that the manuals always come after this would work for my solution. But how can I do this in Doctrine?
$qb = Query Builder 'b'
$qb->groupBy('b.machine DESC');
Does anyone know how to do this without sub querys?
Thank you

Your solution with group by is against the sql standard, and may not work if only full group by sql mode is configured in MySQL. This is the default setting of the recent versions of MySQL.
Since you want the latest record identified by an auto increment id, you can achieve the expected outcome with a self left join:
select t1.*
from table t1
left join table t2 on t1.machine=t2.machine and t1.id<t2.id
where t2.id is null
Basically, with the t1.id<t2.id join condition you get all records where t2.id is greater for the same machine. If a greater t2.id is not found, a null value is returned - indicating that this is the latest record for the given machine. This is the record we keep with the where t2.id is null clause.

Related

MySQL Selecting from two tables ordered by date

Beginner here so please go easy on me :)
So I have these two tables in my DB
Reply Table
+------------------------------------------------+
| message_id | client_id | message | date_posted |
+------------------------------------------------+
Request Table (Exactly the same)
+------------------------------------------------+
| message_id | client_id | message | date_posted |
+------------------------------------------------+
Problem:
They serve a messaging app I was testing but now I don't know how to query these tables to get all chat ordered by date from two tables. For example
Client 14 (2 hours ago): Hello there // Coming from request table
Admin (1 hour ago): Welcome // Coming from reply table
So the messages are displayed oldest first...
I tried using JOIN on clien_id since that is what I want. However, it doesn't seem to work.
I also tried selecting from a subquery containing UNION ALL, also no luck... Any ideas on how this can be done? Thanks in advance!
A union is what you're looking for. In your case, a join would combine columns from the two tables into a single row, where as you're looking to union rows from multiple tables into a single result set.
You'll want to enclose your select statements individually, and then add the order clause.
Edit: Updating this answer to include a column for the source table, as per OP's comment
(select source='reply_table', * from reply_table)
union
(select source='request_table', * from request_table)
order by date_posted desc
MySQL's docs are pretty good, and its page on unions outlines several sorting scenarios: https://dev.mysql.com/doc/refman/5.7/en/union.html
But the instruction specific to your case is:
To use an ORDER BY or LIMIT clause to sort or limit the entire UNION result, parenthesize the individual SELECT statements and place the ORDER BY or LIMIT after the last one.
select a.message
from table1 a
inner join
table2 b
on a.client_id=b.client_id
order by a.date_posted desc;

MYSQL find rows where foreign key are not in related table

I have reservations that can be made in advance. On the day of the reservation, the devices reserved can be checked out.
I have two tables, reservations and checkouts. Reservations have many checkouts.
How would I construct a query that would return all reservations for a particular date that do NOT have associated checkout records?
To put it another way, all rows from reservations where reservation_id column does not contain the reservation's ID?
So far, my best guess is
SELECT * FROM reservations WHERE reservations.id NOT IN (SELECT reservation_id
FROM checkouts)
But that returns empty. Here's a rough idea what the tables look like
|reservations| |checkouts |
|id = 1 | |reservation_id = 1|
|id = 2 | |reservation_id = 2|
|id = 3 |
My result should be reservation 3.
P.S. If php is required, that's fine.
Most likely explanation for the query returning no rows is that there are rows in checkouts that have a NULL value for reservation_id. Consider:
4 NOT IN (2,3,5,NULL,11)
In interpreting this, the NULL value is seen as meaning "unknown what the value is". Is 4 in that list? The answer (coming back from SQL) is basically "unknown" whether 4 matches the "unknown" value in the list.
If that's what's causing the behavior, you can "fix" your current query by including WHERE reservation IS NOT NULL in the subquery.
SELECT r.*
FROM reservations r
WHERE r.id NOT IN ( SELECT c.reservation_id
FROM checkouts c
WHERE c.reservation_id IS NOT NULL
)
This may not be the most efficient approach to returning the specified result. An anti-join is a common pattern for returning this type of result. In your case, that would be an outer join, to return all rows from reservations, along with matching rows from checkouts, and then a predicate in the WHERE clause to filter out all the rows that had a match, leaving us with rows from reservations that didn't have a match.
For example:
SELECT r.*
FROM reservations r
LEFT
JOIN checkouts c
ON c.reservation_id = r.reservation_id
WHERE c.reservation_id IS NULL
It's also possible to get an equivalent result with a NOT EXISTS with a correlated subquery.
SELECT r.*
FROM reservations r
WHERE NOT EXISTS ( SELECT 1
FROM checkouts c
WHERE c.reservation_id = r.reservation_id
)

MySQL Issue with combined SELECT statements

I have two primary SELECT statements that return the desired results when run individually, but when combined, don't return the desired result.
Query 1
This works fine, returning the expected result.
SELECT feed_mode_id FROM user WHERE id=2;
+--------------+
| feed_mode_id |
+--------------+
| 1 |
+--------------+
Query 2
This is also fine. Sometimes the result will be empty, sometimes not.
SELECT
answer.id AS answer_id
FROM
answer
WHERE
answer.question_id = (
SELECT
question.id
FROM
question
ORDER BY
datetime_added_utc DESC
LIMIT 1
)
AND answer.user_id = 2;
Empty set (0.00 sec)
Query 1 and 2 combined
When combining these into two sub-SELECT statements as shown below, feed_mode_id is NULL, but the result for x.feed_mode_id should be as shown in Query 1. This is my lack of understanding as to how these kind of combined statements work.
SELECT
x.feed_mode_id,
IF (COUNT(y.answer_id) < 1, 0, 1) AS answered_question
FROM
(SELECT
user.feed_mode_id
FROM
user
WHERE
user.id = 2) AS x,
(SELECT
answer.id AS answer_id
FROM
answer
WHERE
answer.question_id = (
SELECT
question.id
FROM
question
ORDER BY
datetime_added_utc DESC
LIMIT 1
)
AND answer.user_id = 2) AS y
+--------------+-------------------+
| feed_mode_id | answered_question |
+--------------+-------------------+
| NULL | 0 |
+--------------+-------------------+
Why is feed_mode_id producing NULL and not 1? I'm open to different approaches to re-writing the query altogether as well. The desired result would be:
+--------------+-------------------+
| feed_mode_id | answered_question |
+--------------+-------------------+
| 1 | 0 |
+--------------+-------------------+
This is somehow related to the fact that Query 2's result is empty for this case. For cases where Query 2 returns a value (not empty) then the combined query works as desired.
You have a Cartesian product between x and y. As long as each of those rowsources return only one row, the query will return a single row.
I recommend you ditch the old-school comma syntax for the join operation, and use the JOIN keyword instead.
Also, it's not clear why you need inline views and subqueries, apart from the subquery that returns that "most recently added" question.
I'm not entirely clear what resultset you are actually attempting to return, but it looks like you are determining whether a particular user (uniquely identified by the "id" column of the "user" table), has provided an "answer" to the "most recently added question".
If that's the result you are trying to return, I believe this query will return that result:
SELECT u.feed_mode_id
, IF( COUNT(a.id) < 1, 0, 1) AS answered_question
FROM ( SELECT q.id
FROM question q
ORDER BY q.datetime_added_utc DESC, q.id DESC
LIMIT 1
) r
JOIN user u
ON u.id = 2
LEFT
JOIN answer a
ON a.user_id = u.id
AND a.question_id = r.id
GROUP BY u.id
NOTES: The inline view aliased as r return the id of the "most recently added" question. (In the original query, if two or more question have the same datetime_added_utc, it's indeterminate which row will be returned. This query makes it determinate by adding another expression to the ORDER BY clause. (The inline view query could be pulled out and run separately, to verify it's returning the expected result.)
The row returned from r (if there is one) is then joined to a row retrieved from the "user" table u. We're assuming here that the id column in the "user" table is a unique identifier, and likely the primary key of the "user" table.
If we have a "most recently added" question (i.e. a row from r), and there's a row from "user" that matches the u.id=2 predicate in the ON clause, then so far, we are guaranteed that the query is going to return a single row.
Next, we perform an "outer join" operation, to find matching rows from the "answer" table. The predicates in the ON clause restrict the rows returned to only those that have a user_id matching the id from u (in this example, equivalent to specifying a.user_id=2, and having a question_id that matches the id of the "most recently added" question r.
The LEFT keyword identifies this as an "outer join"; if there are no matching rows from the "answer" table, then the query will still return the row from r and u. (If this were an inner join, that is, if we removed the LEFT keyword, then if there were no matching rows from the "answer" table, then the query would not return a row.)
We add a GROUP BY u.id clause, in case we get more than one matching row from answer; the GROUP BY causes all rows that have the same value of u.id to be collapsed into a single row.
The COUNT() aggregate counts the number of non-null occurrences of the id from the "answer" table. If there were no matching rows found, then a.id will be NULL, so COUNT(a.id) will return 0.
This same query would also work if we were looking for multipler users, if we specified multiple values for user.id to match, e.g.
ON u.id IN (2,3,5,7)
or also if we left that predicate off entirely, so we got a row back for every user. This query would still work.
But in either of those cases, we'd also want to add u.id AS user_id to the SELECT list of the query, so we would know which row was for which user.
If we wanted to return the two most recently added questions, we could change the LIMIT clause in r, and then add r.id to the GROUP BY clause. Again, we'd then likely also want to return r.id AS question_id in the SELECT list, so we knew which row was for which question.
Exactly query
SELECT
x.feed_mode_id,
IF (COUNT(y.answer_id) < 1, 0, 1) AS answered_question
FROM
(SELECT
user.feed_mode_id
FROM
user
WHERE
user.id = 2) AS x,
(SELECT
answer.id AS answer_id
FROM
answer
WHERE
answer.question_id IN (
SELECT
question.id
FROM
question
ORDER BY
datetime_added_utc DESC
LIMIT 1
)
AND answer.user_id = 2) AS y
answer.question_id = to answer.question_id IN
I don't like to answer my own question, but I found a solution that works. I've moved the IF statement to the sub-SELECT rather than the primary SELECT. I don't know why this works and the previous attempt doesn't, but it's producing the desired result now.
SELECT
x.feed_mode_id,
y.question_answered
FROM
(SELECT
user.feed_mode_id
FROM
user
WHERE
user.id = 2) AS x,
(SELECT
IF (COUNT(answer.id < 1), 1, 0) AS question_answered
FROM
answer
WHERE
answer.question_id = (
SELECT
question.id
FROM
question
ORDER BY
datetime_added_utc DESC
LIMIT 1
)
AND answer.user_id = 2) AS y;
+--------------+-------------------+
| feed_mode_id | question_answered |
+--------------+-------------------+
| 1 | 0 |
+--------------+-------------------+

MySQL Inner Join Returning Multiples of the Same Row

I have two MySql Tables as follows:
resource
-----------------------------------------------
id name group owner_id
-----------------------------------------------
1 MyResource1 hs 11
2 MyResource2 ms 24
3 MyResource3 ps 11
...
resource_access
-----------------------------------------------
id resource_id user_id
-----------------------------------------------
1 1 12
2 2 24
3 2 11
4 3 15
...
Now, the first table is a list of resources, of course, and their respective owners in the owner_id column. The second table is the result of "sharing" this resource with another user. The table resource_access may contain records with a user_id that is equivalent to the owner_id in a row of the resource_access as a result of messy cleanup from an owner exchange.
I simply want to get the id, name, and group of any resource that a user has access to, whether they are the owner or it has been shared with them. Here is my MySQL query for an example user (24):
SELECT resource.id, resource.name, resource.group
FROM `resource`
INNER JOIN resource_access ON (
resource.owner_id='24'
OR (
resource_access.user_id='24' AND
resource_access.resource_id=resource.id
)
)
Right now, it returns the id, name, and group for resource number 2 multiple times (like twelve). Is there a possible cause for this? I have tried LEFT and RIGHT joins and am getting the same result. There are many records in the resource table, but none with the id of 2. There are no duplicate rows in resource_access sharing the resource with the same user twice.
Thanks in advance.
Use:
SELECT DISTINCT resource.id, resource.name, resource.group
to remove duplicates.
The way an inner join conceptually works is that it produces a full cross-product between the two tables. This cross-product contains a row for each pair of rows in the input tables. Then it keeps the rows that match all the ON and WHERE conditions, and returns this as the result set. If there are multiple matching rows between the two tables, you'll get multiple rows in the result set.
If you were selecting columns from both tables, you would see that they're not actually the same row. They just have the same data from the resource table, but different data from the resource_access table. But you're not showing those latter columns in your result. Using DISTINCT merges all these rows in the result.
Because you are only selecting from the resource table, I would suggest putting the conditions in the where clause rather than using an explicit join:
SELECT r.id, r.name, r.group
FROM `resource` r
WHERE r.owner_id='24' or
EXISTS (select 1
from resource_access ra
where ra.resource_id = r.id and
ra.user_id = '24'
);
With this logic, the "join" cannot product duplicates.
Select the ownership of resources then union it to resources with access.
Resulting user_id column that is different from your WHERE RA.user_id value just means that resource was shared to them instead of them owning the resource. Hope this helps.
SELECT resource.name,resource.group,resource.owner_id AS user_id
FROM resource
WHERE resource.owner_id = '11'
UNION
SELECT R.name,R.group,R.owner_id AS user_id
FROM resource_access RA
LEFT JOIN resource R
ON (R.id=RA.resource_id)
WHERE RA.user_id = '11';

Complicated MySQL Database Query

I have the following database structure:
Sites table
id | name | other_fields
Backups table
id | site_id | initiated_on(unix timestamp) | size(float) | status
So Backups table have a Many to One relationship with Sites table connected via site_id
And I would like to output the data in the following format
name | Latest initiated_on | status of the latest initiated_on row
And I have the following SQL query
SELECT *, `sites`.`id` as sid, SUM(`backups`.`size`) AS size
FROM (`sites`)
LEFT JOIN `backups` ON `sites`.`id` = `backups`.`site_id`
WHERE `sites`.`id` = '1'
GROUP BY `sites`.`id`
ORDER BY `backups`.`initiated_on` desc
The thing is, with the above query I can achieve what I am looking for, but the only problem is I don't get the latest initiated_on values.
So if I had 3 rows in backups with site_id=1, the query does not pick out the row with the highest value in initiated_on. It just picks out any row.
Please help, and
thanks in advance.
You should try:
SELECT sites.name, FROM_UNIXTIME(b.latest) as latest, b.size, b.status
FROM sites
LEFT JOIN
( SELECT bg.site_id, bg.latest, bg.sizesum AS size, bu.status
FROM
( SELECT site_id, MAX(initiated_on) as latest, SUM(size) as sizesum
FROM backups
GROUP BY site_id ) bg
JOIN backups bu
ON bu.initiated_on = bg.latest AND bu.site_id = bg.site_id
) b
ON sites.id = b.site_id
In the GROUP BY subquery - bg here, the only columns you can use for SELECT are columns that are either aggregated by a function or listed in the GROUP BY part.
http://dev.mysql.com/doc/refman/5.5/en/group-by-hidden-columns.html
Once you have all the aggregate values you need to join the result again to backups to find other values for the row with latest timestamp - b.
Finally join the result to the sites table to get names - or left join if you want to list all sites, even without a backup.
Try with this:
select S.name, B.initiated_on, B.status
from sites as S left join backups as B on S.id = B.site_id
where B.initiated_on =
(select max(initiated_on)
from backups
where site_id = S.id)
To get the latest time, you need to make a subquery like this:
SELECT sites.id as sid,
SUM(backups.size) AS size
latest.time AS latesttime
FROM sites AS sites
LEFT JOIN (SELECT site_id,
MAX(initiated_on) AS time
FROM backups
GROUP BY site_id) AS latest
ON latest.site_id = sites.id
LEFT JOIN backups
ON sites.id = backups.site_id
WHERE sites.id = 1
GROUP BY sites.id
ORDER BY backups.initiated_on desc
I have removed the SELECT * as this will only work using MySQL and is generally bad practice anyway. Non-MySQL RDBSs will throw an error if you include the other fields, even individually and you will need to make this query itself into a subquery and then do an INNER JOIN to the sites table to get the rest of the fields. This is because they will be trying to add all of them into the GROUP BY statement and this fails (or is at least very slow) if you have long text fields.

Categories