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;
Related
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.
I have the following tables
ea_users
id
first_name
last_name
email
password
id_roles
ea_user_cfields
id
c_id = custom field ID
u_id = user ID
data
ea_customfields
id
name = name of custom field
description
I want to get all users which have a certain role, but I also want to retrieve all the custom fields per user. This is for the backend of my software where all the ea_users and custom fields should be shown.
I tried the following, but for each custom field, it duplicates the same user
$this->db->join('(SELECT GROUP_CONCAT(data) AS custom_data, id AS dataid, u_id, c_id
FROM ea_user_cfields userc
GROUP BY id) AS tt', 'tt.u_id = ea.id','left');
$this->db->join('(SELECT GROUP_CONCAT(name) AS custom_name, id AS customid
FROM ea_customfields AS cf
GROUP BY id) AS te', 'tt.c_id = te.customid','left');
$this->db->where('id_roles', $customers_role_id);
return $this->db->get('ea_users ea')->result_array();
the problem that u did not understand properly how join works.
its ok, that u have duplicates in select when u have relation one to many.
in few words your case: engine tries to fetch data from table "A" (ea_users) then JOIN according to the conditions another table "B" (ea_customfields). If u have one to many relation between tables (it means that one record from table "A" (lets say that we have in this table A1 record) can contain few related rows in table "B", lets call them as B1.1, B1.2 and B1.3 and B1.4), in this case it will join this records and put join result in memory. So in memory u would see something like
| FromTable A | FromTableB |
| A1 | B1.1 |
| A1 | B1.2 |
| A1 | B1.3 |
| A1 | B1.4 |
if u have 10 records in table "B", which related to the table "A" it would put 10 times in memory copy of data from table "A" during fetching. And then will render it to u.
depending on join type rows, with missing related records, can be skipped at all (INNER JOIN), or can be filled up with NULLs (LEFT JOIN or RIGHT JOIN), etc.
When u think about JOINs, try to imagine yourself, when u try to join on the paper few big tables. U would always need to mark somehow which data come from which table in order to be able to operate with it later, so its quite logically to write row "A1" from table "A" as many times as u need to fill up empty spaces when u find appropriate record in table "B". Otherwise u would have on your paper something like:
| FromTable A | FromTableB |
| A1 | B1.1 |
| | B1.2 |
| | B1.3 |
| | B1.4 |
Yes, its looks ok even when column "FromTable A" contains empty data, when u have 5-10 records and u can easily operate with it (for example u can sort it in your head - u just need to imagine what should be instead of empty space, but for it, u need to remember all the time order how did u wrote the data on the paper). But lets assume that u have 100-1000 records. if u still can sort it easily, lets make things more complicated and tell, that values in table "A" can be empty, etc, etc.. Thats why for mysql engine simpler to repeat many times data from table..
Basically, I always stick to examples when u try to imagine how would u join huge tables on paper or will try to select something from this tables and then make sorting there or something, how would u look through the tables, etc.
GROUP_CONCAT, grouping
Then, next mistake, u did not understand how GROUP_CONCAT works:
The thing is that mysqlEngine fetch on the first step structure into memory using all where conditions, evaluating subqueries + appends all joins. When structure is loaded, it tried to perform GROUPing. It means that it will select from temporary table all rows related to the "A1". Then will try to apply aggregation function to selected data. GROUP_CONCAT function means that we want to apply concatenation on selected group, thus we would see something like "B1.1, B1.2, B1.3, B1.4". Its in few words, but I hope it will help a little to understand it.
I googled table structure so u can write some queries there.
http://www.mysqltutorial.org/tryit/query/mysql-left-join/#1
and here is example how GROUP_CONCAT works, try to execute there query:
SELECT
c.customerNumber, c.customerName, GROUP_CONCAT(orderNumber) AS allOrders
FROM customers c
LEFT JOIN orders o ON (c.customerNumber = o.customerNumber)
GROUP BY 1,2
;
can compare with results with previous one.
power of GROUP in aggregation functions which u can use with it. For example, u can use "COUNT()", "MAX()", "GROUP_CONCAT()" or many many others.
or example of fetching of count (try to execute it):
SELECT c.customerName, count(*) AS ordersCount
FROM customers AS c
LEFT JOIN orders AS o ON (c.customerNumber = o.customerNumber)
GROUP BY 1
;
so my opinion:
simpler and better to solve this issue on client side or on backend, after fetching. because in term of mysql engine response with duplication in column is absolutely correct. BUT of course, u can also solve it using grouping with concatenations for example. but I have a feeling that for your task its overcomplicating of logic
PS.
"GROUP BY 1" - means that I want to group using column 1, so after selecting data into memory mySql will try to group all data using first column, better not to use this format of writing on prod. Its the same as "GROUP BY c.customerNumber".
PPS. Also I read comments like "use DISTINCT", etc.
To use DISTINCT or order functions, u need to understand how does it work, because of incorrect usage it can remove some data from your selection, (same as GROUP or INNER JOINS, etc). On the first look, you code might work fine, but it can cause bugs in logic, which is the most complicated to find out later.
Moreover DISTINCT will not help u, when u have one-to-many relation(in your particular case). U can try to execute queries:
SELECT
c.customerName, orderNumber AS nr
FROM customers c
INNER JOIN orders o ON (c.customerNumber = o.customerNumber)
WHERE c.customerName='Alpha Cognac'
;
SELECT
DISTINCT(c.customerName), orderNumber AS nr
FROM customers c
INNER JOIN orders o ON (c.customerNumber = o.customerNumber)
WHERE c.customerName='Alpha Cognac'
;
the result should be the same. Duplication in customer name column and orders numbers.
and example how to loose data with incorrect query ;):
SELECT
c.customerName, orderNumber AS nr
FROM customers c
INNER JOIN orders o ON (c.customerNumber = o.customerNumber)
WHERE c.customerName='Alpha Cognac'
GROUP BY 1
;
So I have a mysql database with three tables that has three table I am trying to retrieve rows of data from content based on a condition on data phpro_tag_types
The structures of the tables is like so
phpro_tag_types
tag_type_id | tag_type_name
<pk>
phpro_tag_targets
tag_target_id | tag_id | sub_tag_id | tag_target_name | tag_type_id
<pk> | <FK> | <FK> | | <FK>
content
content_id | tag_target_id | bunch of other things|
<pk> | <fk> |
The relationships between the table is like so
content.tag_target_id : phpro_tag_targets.tag_target_id
1 : m //each tag_target_id is in content once
//and phpro_tag_targets many times
phpro_tag_targets.tag_type_id : phpro_tag_types.tag_type_id
M: 1 // there is many occurrences of tag_type_id
//in phpro_tag_targets and one occurrence in of tag_type_id in phpro_tag_type
(I hope I have explained this thoroughly enough using the correct terms if not I apologize, clearly I am still kind of green with this)
Now I have a SQL query that looks like this
SELECT *
FROM phpro_tag_types types
INNER JOIN phpro_tag_targets targets ON types.tag_type_id=targets.tag_type_id
INNER JOIN content c ON targets.tag_target_id = c.tag_target_id
WHERE types.tag_type_id=14
ORDER BY update_time DESC
Now this query works however not exactly quite as I intended. The problem is the resulting array that is returned has multiple instance of the same piece of content ie. a single content_id (I believe because the same tag_target_id exists in phpro_tag_targets multiple times) however I would only like the results array to only contain unique content_id's as this is the data I am actually outputting to users.
As a side note putting Distinct into the query also doesn't seem to work as there is no way to only make sure content is DISTINCT (at least I could find)
Any help with this would be greatly appreciated as I am kind of lost on how to achieve this
"I am trying to retrieve rows of data from content based on a condition on data phpro_tag_types"
Assuming you are trying to fetch fields of content. What about following IN() sub-query
SELECT *
FROM content c
WHERE tag_target_id IN (
SELECT DISTINCT tag_target_id
FROM phpro_tag_types types
INNER JOIN phpro_tag_targets targets ON types.tag_type_id=targets.tag_type_id
WHERE types.tag_type_id=14
)
ORDER BY update_time DESC;
BTW,
"As a side note putting Distinct into the query also doesn't seem to work as there is no way to only make sure content is DISTINCT (at least I could find)"
DISTINCT c.* does not make sense?
SELECT DISTINCT c.*
FROM phpro_tag_types types
INNER JOIN phpro_tag_targets targets ON types.tag_type_id=targets.tag_type_id
INNER JOIN content c ON targets.tag_target_id = c.tag_target_id
WHERE types.tag_type_id=14
ORDER BY update_time DESC;
There's a general trick to do a left outer join on the duplicating table in such a way that only one entry has null values and then limit the query to that row:
SELECT *
FROM phpro_tag_types types
INNER JOIN phpro_tag_targets targets ON types.tag_type_id=targets.tag_type_id
INNER JOIN content c ON targets.tag_target_id = c.tag_target_id
LEFT OUTER JOIN phpro_tag_targets t2 on targets.tag_target_id=t2.tag_target_id
AND t2.tag_id < targets.tag_id
WHERE types.tag_type_id=1
AND t2.tag_target_id IS NULL;
This seems a bit crazy but does work and in my experience is performant unless you're dealing with a 'very large' data set (whatever that means).
I'm not 100% sure what the semantics here are, but this query is assuming that you don't care what 'tag_id' you get, you just want to get any tag that matches the 'type_id', which looks to me to match your intent.
NOTE: this will cause duplicate column names in the '*' select, so you need to limit to types.*, targets.*, content.* or spell out the column names.
I have three tables overall, one with player names and their last login, and another table with the player name and their IP. These are from a game server, but it's two separate "plugins" of the server, so I cannot merge these into one table.
I successfully join these two on the playername column like so:
SELECT
u.`user` as `ign`,
lb.`lastlogin` as `date`,
lb.`ip`
FROM `mcmmo_users` u
LEFT JOIN `lb-players` lb
ON u.`user`=lb.`playername`
These produce the following array: Array(ign,date,ip);
However, I have an IP2C (IP-Country) table as well, and I would like to get these results at the same time. However, this table is extremely large, and would heavily slow down the query if I did a standard LEFT JOIN.
Is there a quicker way to join this? I would prefer to not query on every PHP loop of the data.
I am using MySQL and PHP
The IP2C database is layed out as follows:
begin_ip | end_ip | begin_ip_num | end_ip_num | country_code | country_name
And is queried as follows:
$IPNUM = sprintf("%u",ip2long($ip));
SELECT `country_code`
FROM `cpanel_ip2c`
WHERE `$IPNUM` BETWEEN `begin_ip_num` AND `end_ip_num`
A between condition is hard to optimize for a database. Instead, consider querying for the first IP block that is greater or equal to the user's IP:
select *
from mcmmo_users u
left join
`lb-players` lb
on u.user = lb.playername
left join
cpanel_ip2c ip
on ip.begin_ip_num =
(
select begin_ip_num
from cpanel_ip2c ip
where ip.begin_ip_num <= inet_aton(lb.ip)
order by
ip.begin_ip_num desc
limit 1
)
and inet_aton(lb.ip) <= ip.end_ip_num
With an index on cpanel_ip2c(begin_ip_num ), the country can be resolved with an index seek.
Here's an example on SQL Fiddle, with the mcmmo_users table omitted for simplicity.
I have two tables, one called episodes, and one called score. The episode table has the following columns:
id | number | title | description | type
The score table has the following columns:
id | userId | showId | score
The idea is that users will rate a show. Each time a user rates a show, a new row is created in the score table (or updated if it exists already). When I list the shows, I average all the scores for that show ID and display it next to the show name.
What I need to be able to do is sort the shows based on their average rating. I've looked at joining the tables, but haven't really figured it out.
Thanks
To order the results, use and ORDER BY clause. You can order by generated columns, such as the result of an aggregate function like AVG.
SELECT e.title, AVG(s.score) AS avg_score
FROM episodes AS e
LEFT JOIN scores AS s ON e.id=s.showId
GROUP BY e.id
ORDER BY avg_score DESC;
You're right. You have to JOIN these tables, then use GROUP BY on the 'episodes' table's 'id' column. Then you'll be able to use AVG() function on 'the scores' tables's 'score' column.
SELECT AVG(scores.score) FROM episodes LEFT JOIN scores ON scores.showId = episodes.id GROUP BY episodes.id
SELECT episodes.*, AVG(score.score) as AverageRating FROM episodes
INNER JOIN score ON (episodes.id = score.showId)
GROUP BY episodes.id
ORDER BY AVG(score.score) DESC