Mysql process data by groups - mysql

I have a few groups of data. Each group has a some property field.
For example:
_________________________
| id | value | property |
--------------------------
| 1 | 2 | 3 |
--------------------------
| 2 | 2 | 3 |
--------------------------
| 3 | 2 | 3 |
--------------------------
| 4 | 2 | 4 |
-------------------------
| 5 | 2 | 4 |
--------------------------
| 6 | 2 | 4 |
--------------------------
How can I update two strings ordered by id ASC with property = 3, and 2 strings ordered by id ASC with property = 4 by one query?
I want to update 2 of 3 rows with property = 3 and update 2 of 3 rows with property = 4. For example: rows with id 1 and 2, and rows with id 4 and 5
i.e. i want update groups of data with different conditions by one query

You can do it using calculated rank field, e.g. -
SELECT p1.*, COUNT(*) rank FROM properties p1
LEFT JOIN properties p2
ON p2.property = p1.property AND p2.id <= p1.id
GROUP BY p1.property, p1.id
This query will return dataset with row-number by property:
+------+-------+----------+------+
| id | value | property | rank |
+------+-------+----------+------+
| 1 | 2 | 3 | 1 |
| 2 | 2 | 3 | 2 |
| 3 | 2 | 3 | 3 |
| 4 | 2 | 4 | 1 |
| 5 | 2 | 4 | 2 |
| 6 | 2 | 4 | 3 |
+------+-------+----------+------+
Then you should update records with rank < 3:
UPDATE properties p
JOIN (SELECT p1.*, COUNT(*) rank FROM properties p1
LEFT JOIN properties p2
ON p2.property = p1.property AND p2.id <= p1.id
GROUP BY p1.property, p1.id) r
ON p.id = r.id
SET p.value = 100 -- set new value here
WHERE r.rank < 3

Here's the solution, and see discussion following:
update
t,
(select GROUP_CONCAT(ids) as matching_ids from (
select
SUBSTRING_INDEX(GROUP_CONCAT(id order by id), ',', 2) AS ids
from
t
where
property in (3,4)
group by
property
) s1
) s2
set value=12345
where
FIND_IN_SET(id, matching_ids) > 0
;
To illustrate, and assuming your table is called t, and the initial state is:
root#mysql-5.1.51> select * from t;
+----+-------+----------+
| id | value | property |
+----+-------+----------+
| 1 | 2 | 3 |
| 2 | 2 | 3 |
| 3 | 2 | 3 |
| 4 | 2 | 4 |
| 5 | 2 | 4 |
| 6 | 2 | 4 |
+----+-------+----------+
The result of running this query is:
root#mysql-5.1.51> select * from t;
+----+-------+----------+
| id | value | property |
+----+-------+----------+
| 1 | 12345 | 3 |
| 2 | 12345 | 3 |
| 3 | 2 | 3 |
| 4 | 12345 | 4 |
| 5 | 12345 | 4 |
| 6 | 2 | 4 |
+----+-------+----------+
A brief explanation of the query:
I pick up the first two ids for each property using the SUBSTRING_INDEX(GROUP_CONCAT(id order by id), ',', 2) statement.
I combine the above using GROUP_CONCAT(ids) as matching_ids to get all valid ids.
Finally, I update all rows in the table where the id is within combined matching_ids text.
Notes:
You should verify your group_concat_max_len variable is long enough. Default is 1024. You most probably want to have this in the millions, anyhow (regardless of my answer).
The query is far from being optimal. It answers your question, but you can't have an optimal query here.
You are most probably better off with a transaction containing two or three queries.
Good luck!

I'm assuming you mean to limit your two updates to two rows each. You can use ORDER BY and LIMIT in your update statements:
UPDATE yourtable
SET property = 'new_value'
WHERE value=2 AND property = 4
ORDER BY id ASC LIMIT 2
UPDATE yourtable
SET property = 'new_value'
WHERE value=2 AND property = 3
ORDER BY id DESC LIMIT 2
Update:
To force this into one query, you would need to JOIN against a subquery which retrieves the ids to update via UNION. I think this is legal:
UPDATE yourtable
JOIN (
(SELECT id FROM yourtable WHERE value=2 AND property=4 ORDER BY id ASC LIMIT 2)
UNION ALL
(SELECT id FROM yourtable WHERE value=2 AND property=3 ORDER BY id DESC LIMIT 2)
) updaterows ON yourtable.id = updaterows.id
SET property = 'new value'

Related

MySQL How to Select smth by MAX(id)....WHERE userID = some number GROUP BY smth

I have next table in my DB:
personal_prizes
___________ ___________ _________ __________
| id | userId | specId| grp |
|___________|___________|_________|__________|
| 1 | 1 | 1 | 1 |
| 2 | 1 | 2 | 1 |
| 3 | 2 | 3 | 1 |
| 4 | 2 | 4 | 2 |
| 5 | 1 | 5 | 2 |
| 6 | 1 | 6 | 2 |
| 7 | 2 | 7 | 3 |
| 8 | 1 | 13 | 4 |
|___________|___________|_________|__________|
I need to select specId by max id group by grp.
So I have composed next query
SELECT pp.specId
FROM personal_prizes pp
WHERE pp.specId IN (SELECT MAX(pp1.id)
FROM personal_prizes pp1
WHERE pp1.userId = 1
GROUP BY pp1.grp)
And it's work for my little table. But when I try to implement it for my prod db with personal_prizes > 100,000.
Please help me optimize it
The query you have should work fine. Make sure though that you not only have an index on id (which I suppose is the primary key), but also one on specId.
Just as an alternative, you might try this one:
select group_concat(pp.specId order by pp1.id desc)+0 as result_specId
from personal_prizes pp1
left join personal_prizes pp on pp.specId = pp1.id
where pp1.userId = 1
group by pp1.grp
having result_specId is not null;
The idea here is that the sub query is promoted to the main query, and the specId is retrieved by an outer join. The group_concat aggregation function will list the one of interest as the first. The having clause will exclude the cases where no matching specId was found.
Note that this will only give the same results if the specId field is guaranteed to be non-null.

Copy value from one row to all rows having the same groupID

Here's my MySQL table:
ID | groupID | value
------------------------------
1 | 1 |
2 | 1 | 0.34353
3 | 1 |
4 | 2 |
5 | 2 | 0.23232
6 | 3 |
7 | 3 |
8 | 3 | 1.23234
9 | 3 |
I want to copy the available values from each group to ALL rows with the same groupID, so that my final table will look like this:
ID | groupID | value
------------------------------
1 | 1 | 0.34353
2 | 1 | 0.34353
3 | 1 | 0.34353
4 | 2 | 0.23232
5 | 2 | 0.23232
6 | 3 | 1.23234
7 | 3 | 1.23234
8 | 3 | 1.23234
9 | 3 | 1.23234
There is no fixed amount of how many rows one group has. How can I do this?
You can accomplish this using an update join. Join your initial table to a subquery which identifies the non NULL value for each groupID. After bringing in that information, update non NULL value columns to the value from the subquery.
UPDATE yourTable t1
INNER JOIN
(
SELECT groupID, MAX(value) AS value
FROM yourTable
GROUP BY groupID
) t2
ON t1.groupID = t2.groupID
SET t1.value = t2.value
WHERE COALESCE(t1.value, '') = ''
Update:
It appears that you may have empty string for the missing data, and/or NULL in addition to that. In this case, MAX() should still pickup on the non missing data, but I changed the WHERE clause appropriately.

Group by two values

I have the following query:
SELECT
items.*
FROM
`items`
INNER JOIN
`users` ON `items`.`owner` = `users`.`id`
GROUP BY
`items`.`owner`
LIMIT
10
I ensures it is grouped by the user (only one item fetched per user), but I also wish ensure that items with the category, say, "1" only appears once.
But that does not work. Well, query succeeds, but it does not group by category. Multiple categories is still shown. Any ideas?
I have created a SQLFiddle here: http://sqlfiddle.com/#!2/0a4bad/1
Instead of outputting:
+----+----------+-------+
| ID | CATEGORY | OWNER |
+----+----------+-------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 4 |
| 5 | 2 | 5 |
+----+----------+-------+
It should be outputting:
+----+----------+-------+
| ID | CATEGORY | OWNER |
+----+----------+-------+
| 1 | 1 | 1 |
| 4 | 2 | 2 |
| 5 | 2 | 4 |
| 5 | 2 | 5 |
| 8 | 3 | 3 |
+----+----------+-------+
(notice category 1 is only shown ONCE).
I want to ensure that only one item per owner is shown, and then adtionally ensure that a specific category (say 1 and 5) is only shown once. The category 1 and 5 are overpopulated, and if they are not limited, they will be 90% of the output.
You can use DISTINCT to retrieve unique data:
SELECT DISTINCT items.category
select * from items t1
where category not in (1,2)
or not exists (
select 1 from items t2
where t2.id < t1.id
and t2.category = t1.category
)
group by owner
http://sqlfiddle.com/#!2/0a4bad/27

MySQL Priority to Single Row of Type X

I have a select statement as follows that I want to give priority to a single row of type X. For example, I want the query to select as indicated, but put a result with "type = X" first. For example, my basic query would be:
SELECT id,type,rank
FROM stores
WHERE zip = "11217"
ORDER BY rank DESC;
This would return:
+----+------+------+
| id | type | rank |
+----+------+------+
| 1 | 1 | 10 |
+----+------+------+
| 2 | 5 | 9 |
+----+------+------+
| 3 | 4 | 8 |
+----+------+------+
| 4 | 3 | 7 |
+----+------+------+
| 5 | 3 | 6 |
+----+------+------+
| 6 | 1 | 5 |
+----+------+------+
However, I would want to return just the top 3 results which would be:
SELECT id,type,rank
FROM stores
WHERE zip = "11217"
ORDER BY rank DESC
LIMIT 3;
+----+------+------+
| id | type | rank |
+----+------+------+
| 1 | 1 | 10 |
+----+------+------+
| 2 | 5 | 9 |
+----+------+------+
| 3 | 4 | 8 |
+----+------+------+
Now I would want to return the best row (ordered by rank descending) where type = 3, followed by the other rows, ranked normally by rank. This would result in:
+----+------+------+
| id | type | rank |
+----+------+------+
| 4 | 3 | 7 |
+----+------+------+
| 1 | 1 | 10 |
+----+------+------+
| 2 | 5 | 9 |
+----+------+------+
I know that I could do this with a UNION, but in my actual query, the rank is very expensive to calculate (includes multiple decay and distance functions). So I'm trying to see if there is any way to return the above results in a single query.
You just need to change the order by:
SELECT id, type, rank
FROM stores
WHERE zip = '11217'
ORDER BY (type = 3) DESC,
rank DESC
LIMIT 3;
MySQL treats a boolean as an integer in a numeric context, with 1 being true and 0 being false. TO get the "true" values first, DESC is needed after the condition.
EDIT:
If you have multiple rows with id = 3 and you only want one, you could try something like this:
SELECT s.id, s.type, s.rank
FROM stores s cross join
(select min(s.id) as id3 from stores s where type = 3) s3
WHERE zip = '11217'
ORDER BY (s.id = sd.id3) DESC,
rank DESC
LIMIT 3;
This is the same idea, but you need to calculate the id that you are using.

mysql return count = 0 without joins using group by

I have a table
Image
| ImageId | UserId | SourceId |
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
| 4 | 2 | 2 |
| 5 | 3 | 1 |
| 6 | 3 | 1 |
| 7 | 3 | 1 |
| 8 | 3 | 1 |
| 9 | 3 | 1 |
I then have a query:
SELECT UserId, IFNULL( COUNT( ImageId ) , 0 ) AS ImageCount, SourceId
FROM Image
GROUP BY UserId, SourceId
When I do the query, I get
| UserId | SourceId | ImageCount
| 1 | 1 | 1
| 1 | 2 | 1
| 2 | 1 | 1
| 1 | 2 | 1
| 3 | 1 | 5
However, the one row that I do NOT get back (which I want) is:
| 3 | 1 | 0
How do I go about fetching the row, even if the count is 0?
All of the questions I've seen have had to deal with joins (usually left joins) but as this doesn't require a join I'm a little confused as to how to go about this.
This does require a left join and a bit more. You need to start with all possible combinations, then use left join to bring in the existing values:
SELECT u.UserId, COUNT(i.ImageId ) AS ImageCount, s.SourceId
FROM (select distinct UserId from Image) u cross join
(select distinct SourceId from Image) s left join
Image i
on i.UserId = u.UserId and i.SourceId = s.SourceId
GROUP BY u.UserId, s.SourceId;
The count() will return 0 if there are no matches. There is no need for an if, coalesce, or case statement.
What this does is create every possible combination of UserId and SourceId (based on the values in the Image table). It then uses a left outer join to connect back to the image. What does this do? Well, for existing records, it actually does nothing. But for combinations that don't appear in the table, it will add a new row with u.UserId, s.SourceId, and NULL in the other fields. This is the basis for the final aggregation.