Grouping by Column with Dependence on another Column - php

Here is a simplified look at the problem I am trying to cleanly solve via a MySQL query. This is not the actual table I am dealing with.
If I have the following table:
Name Buyer ID
John Fred 4
John Smith 3
Fred Sally 2
John Kelly 1
I would like a query to return the following:
Name Buyer ID
John Fred 4
Fred Sally 2
Such that we group by 'name' and show the latest row / buyer / ID.
I tried to implement this by performing a nested select statement, wherein I first performed "ORDER BY ID DESC" then, on the outermost SELECT, "GROUP BY NAME". And, while this is a roundabout way of solving the problem, it seemed that, by virtue of the ordering, the correct selection would be returned to me. Unfortunately, "GROUP BY" does not 'guarantee' that the 'Buyer' column will contain the expected entry.
Any helpful suggests for implementing this as a query? At the moment, I have a highly-inefficient PHP 'version' of the query running on a large table dump - definitely not the best choice.

Try this one, the idea behind the subquery is that it gets the latest ID for each Name using MAX (aggregate function). Then join it against the table itself on the two columns of the subquery.
SELECT a.*
FROM tableName a
INNER JOIN
(
SELECT name, MAX(ID) maxID
FROM tableName
GROUP BY name
) b ON a.Name = b.Name AND
a.ID = b.MaxID
SQLFiddle Demo

Another alternative is to load the data sorted in a subquery, then group on the results. I can't cite this, but I've read in a few places there's no (discernable) performance hit on this.
So something like:
SELECT *
FROM (
SELECT *
FROM `yourtable`
ORDER BY `id` DESC
) as `tmp`
GROUP BY `name`

Related

Trying to write count query loop based on a different table printing

I have two tables:
access(name, id, check, key)
events(name, key, event_name)
I am trying to print some things from these tables into a php/html table with these columns:
name, key, event_name, access count
My trouble being I would like each event to display the "count" of access rows that have the same key.
Event table example:
name key event_name
test 1 first
joe 2 second
And the access table...
name id check key
test 123 yes 1
test 1235 yes 1
joe 175 yes 2
joe 852 yes 2
test 5843 yes 1
test 123455 yes 1
The resulting table I am hoping to look like this:
name key event_name access count
test 1 first 4
joe 2 second 2
Does anybody know how to do this? I've gotten to this but it obviously doesn't work because the key isn't given to the inner select query...
select event_name, name, key,
(SELECT COUNT(key) FROM access WHERE key=key AND name=name)
from event;
Thank you to anyone who takes a look and might have any ideas! I've been staring at this and w3schools for hours
At present your subquery will return a count of all rows as it is not correlated to the main query, so both occurrences of key in key=key will refer to the same column and the expression will always be true (likewise for name). To correlate the subquery, add table references:
select event_name, name, key,
(SELECT COUNT(key) FROM access a WHERE a.key=e.key AND a.name=e.name) AS `access count`
from event e
You can also get the same results with a join and aggregattion:
select e.name, e.key, e.event_name, count(*) access_count
from event e
left join access a on a.key = e.key and a.name = e.name
group by e.name, e.key, e.event_name

How can I select mysql data with a union statement where the data matches each select statement, i.e. combine the AND operator with UNION?

Lets say I have a table with just two columns: name and mood. A row holds a persons name, and their mood, if they have multiple moods, then multiple rows are stored in the DB.
For example, in the database is John, who is happy, excited, and proud.
This is represented as
John Happy
John Excited
John Proud
What I want to do is select the name based on several moods being met. Similiar to the UNION:
SELECT name WHERE mood=Happy
UNION
SELECT name WHERE mood=Excited
UNION
SELECT name WHERE mood=Proud
However using the above union would result in:
John
John
John
Union all would result in one single result of John, but it would also select any names that only match one of the queries.
I can think of a few algorithms which would take each individual mysql_result resource (I'm using PHP), and look for matching results, but what I want to know is whether MySQL already facilitates this.
I realise the above is quite a vague generalisation, needless to say my actual program is alot more complicated and I've dumbed it down a little for this question, please don't hesitate to ask questions.
Provided you have no duplicates, you can do it with a subquery:
SELECT `name` FROM (
SELECT `name`, COUNT(*) AS `count` FROM `moods`
WHERE `mood` IN ('Excited', 'Happy', 'Proud') GROUP BY `name`
) WHERE `count` = 3
Alternatively, you can use join:
SELECT `m1`.`name` FROM `moods` `m1`
JOIN `moods` `m2` USING (`name`)
JOIN `moods` `m3` USING (`name`)
WHERE `m1`.`mood` = 'Excited' AND `m2`.`mood` = 'Happy' AND `m3`.`mood` = 'Proud'
Not so cute, but might be faster if you use LIMIT. Or maybe not. Depends a lot on query planner.
UPD: thanks to Tudor Constantin for reminding me about HAVING, the first query can then be:
SELECT `name` FROM `moods`
WHERE `mood` IN ('Excited', 'Happy', 'Proud')
GROUP BY `name`
HAVING COUNT(*)>3
Replace UNION with INTERSECT .
your query is already correct. you can ADD an extra column using you searched word in the WHERE clause.
SELECT name,'Happy' as imood WHERE mood='Happy'
UNION
SELECT name,'Excited' as imood WHERE mood='Excited'
UNION
SELECT name,'Proud' as imood WHERE mood='Proud'
If I understand your question correctly, I think in this case you don't actually need a UNION but just multiple conditions in a WHERE statement.
Try this:
SELECT name, mood FROM myTable WHERE mood in ('Happy','Excited','Proud')
This should give the result:
John, Happy
John, Excited
John, Proud
If you don't care about getting multiple results for mood, try this:
SELECT DISTINCT name FROM myTable WHERE mood in ('Happy','Excited','Proud')
Which will just give:
This should give the result:
John
Updated
If you want to match ALL the conditions, you'd probably have to use subselects:
SELECT DISTINCT name FROM myTable
WHERE name IN
(SELECT name FROM myTable WHERE mood = 'Happy')
AND name IN
(SELECT name FROM myTable WHERE mood = 'Excited')
AND name IN
(SELECT name FROM myTable WHERE mood = 'Proud')
Try with:
SELECT name FROM user_moods um1
INNER JOIN user_moods um2 ON um1.name = um2.name
WHERE um1.mood IN ('Happy','Excited','Proud')
GROUP BY um2.mood
HAVING COUNT(um2.mood) = 3 # the number of different moods

combining 2 tables in MySQL select statement

I know I can do joins but its not exactly what I want.
I'm making a live chat system that has 2 tables mainly: the main chat table (call it table a), and then a mod table (call this one table b). If a user gets suspended, messages reach over 100 for that channel, or they are over 1 week, the messages get moved from the main chat table to the mod table.
I store the ID of the chat messages as ID(primary) on the main chat table and as chatID on the mod table.
What I'm doing is making a separate page for my Mods and I want to be able to combine the two tables into 1 area but I want them to be ordered by their respective tables.
So lets say we had the following:
Main table ID's: 1,2,4
Mod table ID: 3
I want my results to show up 1,2,3,4 no matter which table the ID is in.
Any help is greatly appreciated!
Edit: I got the answer and this is what I used to do so:
SELECT ab.* FROM
((SELECT ID as table_id FROM a
WHERE roomID = 'newUsers' ORDER BY ID ASC)
UNION ALL
(SELECT chatID as table_id FROM b
WHERE roomID = 'newUsers' ORDER BY chatID ASC)) ab
ORDER BY ab.table_id
Use a UNION in a subselect.
SELECT ab.* FROM (
SELECT 1 as table_id, * FROM a
UNION ALL
SELECT 2 as table_id, * FROM b
) ab
ORDER BY ab.id
If you want the result of table A to appear before table B, change the query to:
SELECT ab.* FROM (
SELECT 1 as table_id, * FROM a
UNION ALL
SELECT 2 as table_id, * FROM b
) ab
ORDER BY ab.table_id, ab.id
Some background
UNION ALL will merge two tables resultsets into one resultset.
UNION will do the same but will eliminate duplicate rows.
This takes time and slows things down, so if you know there will be no duplicate records (or you don't care about dups) use UNION ALL.
See also: http://dev.mysql.com/doc/refman/5.5/en/union.html

SQL find same column data

I have a table with around 15 columns. What I would like to be able to do, is select a range of IDs and have all column data that is the same, presented to me.
At the minute, I have it structured as the following:
SELECT id, col_a, col_b ... count(id)
FROM table
GROUP BY col_a, col_b ...
Which returns rows grouped together that have identical data within all the rows - which is half what I want, but ideally I would like to be able to get a single row with either the value (if it's the same for every row id) or NULL if there is a single difference.
I'm not sure that it is possible, but I would rather see if it's doable in an SQL query than write some looping logic for PHP to go through and check each row's similarity.
Thanks,
Dan
UPDATE:
Just to keep this up-to-date, I worked through the problem by writing a PHP function that would find which were duplicates and then display the differences. However I have now since made a table for each column, and made the columns as references to the other tables.
E.G. In MainTable, ColA now refers to the table ColA
I'm still solving the problem with the PHP for the time being, mainly as I think it still leaves the problem mentioned above, but at least now Im not storing duplicate information.
Its a hairy thing to do, but you could do it similarly to how David Martensson suggested, I would write it like this, however:
Select a.id, a.col1, a.col2, a.col3
FROM myTable a, myTable b
WHERE a.id != b.id
and a.col1 = b.col1
and a.col2 = b.col2
and a.col3 = b.col3
That would give you the ids that are unique, but each result would have the same values for columns 1, 2, and 3. However, I agree with some of the commenters to your question that you should consider an alternative data structure, as this could better take advantage of an RDBMS model. In that case you would want to have 2 tables:
Table name: MyTableIds
Fields: id, attrId
Table name: MyTableAttrs
Fields: attrId, attr1, attr2, attr3, ect
In general, if you have data that is going to be duplicated for multiple records, you should pull it into a second table and create a relationship so that you only have to store the duplicated data 1 time and then reference it multiple times.
Make a join to a subquery with the group by:
SELECT a.id, b.col_a, b.col_b ... b.count)
FROM table a
LEFT JOIN (
SELECT id, col_a, col_b ... count(id) "count"
FROM table GROUP BY col_a, col_b ...
)b on a.id = b.id
That way the outer will select all rows.
If you still want to group answers you could use a UNION instead
SELECT id, col_a ...
WHERE id NOT IN ("SUBQUERY WITH GROUP BY")
UNION
"SUBQUERY WITH GROUP BY"
Not the nicest solution but it should work
It seems doable from how I have understood your question.
And here's a possible pattern:
SELECT
/* GROUP BY columns */
col_A,
col_B,
...
/* aggregated columns */
CASE MIN(col_M) WHEN MAX(col_M) THEN MIN(col_M) ELSE NULL END,
CASE MIN(col_N) WHEN MAX(col_N) THEN MIN(col_N) ELSE NULL END,
...
COUNT(...),
SUM(...),
WHATEVER(...),
...
FROM ...
GROUP BY col_A, col_B, ...

Select Where With Multiple of the same

I have a table called 'matches' where I associate a items in the table "numbers" with an item in the table "letters" via there id.
So it looks like
number_id, letter_id
1,10
2,10
3,10
5,11
4,23
7,19
1,19
3,64
now the user inputs an array of numbers, say 1,2,3
and I have to find the letter where all of it's numbers are 1,2,3, which would be 10. If they gave 1,7 it would give them 19, if they gave 3 it would give them and 64.
How can I do that?
I've been trying to write a recursive function for this but it always breaks.
is there some sort of:
SELECT letter_id WHERE **number_id***s* = 1,2,3. That would be perfect. :)
This may or may not work in all cases, but I tried with (1,2,3) and (1,7):
select distinct letter_id
from r r1
inner join r r2 using (letter_id)
where r1.number_id in (1, 7)
and r2.number_id in (1,7)
and (r1.number_id r2.number_id);
You'll have to be able to provide the (1,7) or (1,2,3) dynamically with some programming language.
Rocking baby at 3:30am...
EDIT: To complete the #Martin's answer, you can use order by field()
select letter_id
from (
select letter_id,
group_concat(number_id order by field(number_id,2,1,3)) as numset
from r
group by letter_id
) as Martin
where numset = '2,1,3';
If you can construct a string from your list of number_ids, you could use the following query:
select letter_id
from (select letter_id, group_concat(number_id) as numset from `matches`
group by letter_id) as fred
where numset = '1,2,3';
It is sensitive to order (eg. '2,1,3' would not match).
Since the previous comments made the problem more than what the OP was, here's another answer...
You may be able to work it out by having temporary tables:
create temporary table r_sum as
select letter_id, count(*) as total
from r
group by letter_id;
create temporary table r_count as
select letter_id, count(*) as total
from r
where number_id in (1,2,3,7)
group by letter_id;
select letter_id
from r_sum
inner join r_count using (letter_id, total);
I think if this does not answer your question, I am not getting what you want and how to help you. If table is huge, you will have to create indexes on the temporary tables to help go faster. r is your original table in OP.
you have to use IN statement for that.
try below query for that.
SELECT letter_id WHERE number_id IN (1,2,3)
you can pass array variable into the IN statement if you have.
Thanks.

Categories