I have a query that needs a custom sorting, trimmed down to the bare minimums something like:
SELECT u.*, p.*, p.id as product_id
FROM users u, products p
WHERE u.id = p.user_id
ORDER BY product_id DESC
And I get returned a set of rows like:
UserID ProductID
2 5
2 4
3 3
1 2
1 1
But I want it to actually sort SOMETHING like this (so no 2 UserIDs are adjacent to eachother):
UserID ProductID
1 2
2 4
3 3
2 5
1 1
Is this even possible with MySQL, or do I need some PHP magic?
A canonical way of solving this problem is by enumerating the duplicate rows and then ordering by that value:
select t.*
from (SELECT u.*, p.*, p.id as product_id,
row_number() over (partition by u.id order by (select NULL)) as seqnum
FROM users u join
products p
on u.id = p.user_id
) t
order by seqnum, id;
This will work, as long as no one user has a really long sequence (as in your example).
There is no "always-works" solution, because it is easy to come up with a situation where your goal is not possible.
Here fetch your sorted results into an array. Then do something like this.
$records = $res->fetchAll();
$count = count($records);
$records = array_chunk($records, ceil(count($records)/2);
$unsorted = array();
for($x = 0; $x < $count; $x++){
$unsorted[] = $records[$x%2][floor($x/2)];
}
Consider the following...
CREATE TABLE sortable(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,player_id INT NOT NULL);
INSERT INTO sortable(player_id) VALUES (1),(1),(2),(3),(4),(3),(3),(2),(1),(2),(4),(4);
SELECT * FROM sortable;
+----+-----------+
| id | player_id |
+----+-----------+
| 1 | 1 |
| 2 | 1 |
| 3 | 2 |
| 4 | 3 |
| 5 | 4 |
| 6 | 3 |
| 7 | 3 |
| 8 | 2 |
| 9 | 1 |
| 10 | 2 |
| 11 | 4 |
| 12 | 4 |
+----+-----------+
SELECT x.*,COUNT(*) rank FROM sortable x JOIn sortable y ON y.player_id = x.player_id AND y.id <= x.id GROUP BY x.id ORDER BY player_id,rank;
+----+-----------+------+
| id | player_id | rank |
+----+-----------+------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 9 | 1 | 3 |
| 3 | 2 | 1 |
| 8 | 2 | 2 |
| 10 | 2 | 3 |
| 4 | 3 | 1 |
| 6 | 3 | 2 |
| 7 | 3 | 3 |
| 5 | 4 | 1 |
| 11 | 4 | 2 |
| 12 | 4 | 3 |
+----+-----------+------+
SELECT x.*,COUNT(*) rank FROM sortable x JOIn sortable y ON y.player_id = x.player_id AND y.id <= x.id GROUP BY x.id ORDER BY rank;
+----+-----------+------+
| id | player_id | rank |
+----+-----------+------+
| 1 | 1 | 1 |
| 3 | 2 | 1 |
| 4 | 3 | 1 |
| 5 | 4 | 1 |
| 2 | 1 | 2 |
| 8 | 2 | 2 |
| 6 | 3 | 2 |
| 11 | 4 | 2 |
| 9 | 1 | 3 |
| 10 | 2 | 3 |
| 7 | 3 | 3 |
| 12 | 4 | 3 |
+----+-----------+------+
So if your problem is just that you dont want two records with same id should not come next to each other wha I can think simplest is use
SELECT u.*, p.*, p.id as product_id
FROM users u, products p
WHERE u.id = p.user_id
ORDER BY user_id%2 DESC
Or you can even use other number than 2 to meet any certain order you want....
Related
I wanted to UPDATE the value of my below table (row & col_md) :
Current Data
| id | id_cat | row | col_md |
| --- | ------ | ---- | ------ |
| 1 | 1 | 1 | 4 |
| 2 | 1 | 2 | 5 |
| 3 | 1 | 3 | 5 |
| 4 | 2 | 1 | 3 |
| 5 | 2 | 2 | 4 |
| 6 | 2 | 2 | 4 |
| 7 | 3 | 1 | 12 |
| 8 | 3 | 1 | 12 |
| 9 | 3 | 2 | 3 |
That may look something like the below table. (I want to have the same content of rows that id_cat=1 have, in rows with id_cat=2 & 3).
Required Data:
| id | id_cat | row | col_md |
| --- | ------ | ---- | ------ |
| 1 | 1 | 1 | 4 |
| 2 | 1 | 2 | 5 |
| 3 | 1 | 3 | 5 |
| 4 | 2 | 1 | 4 |
| 5 | 2 | 2 | 5 |
| 6 | 2 | 3 | 5 |
| 7 | 3 | 1 | 4 |
| 8 | 3 | 2 | 5 |
| 9 | 3 | 3 | 5 |
id_cat 2 and 3 should have the same "row" and "col_md" values as in id_cat=1.
I've tried with this post first answer like this:
UPDATE `myTable` AS t1 JOIN `myTable` AS t2 ON t2.id_cat=1
SET t1.row = t2.row, t1.col_md = t2.col_md
WHERE t1.id_cat = 2 or t1.id_cat=3;
but that results on all "row" column values equal to 1.
What I'm doing wrong and what's the way to do this right?
EDIT:
The tables above are just examples to make this ask easier to understand, but the real table is bigger (4k rows) and:
"row" column with id_cat=1 can have any number and not a sequence as in the example.
"col_md" columns can have any number too.
That's why the update must set a copy of the id_cat=1 "row" and "col_md" values in the id_cat!=1 "row" and "col_md" values.
If this can't be done with just MySQL, a php script will be nice too.
In the example query you gave, you are updating t1.row with t2.row. As you are joining on the id_cat, this will result in multiple rows selected to update a single row, so the outcome just takes the first row.
What you actually want, is to make the 1-to-1 relation in the update, so what needs to be changed in your query is to add the row matching in the join and remove the assignment in the SET, like this:
UPDATE `myTable` AS t1 JOIN `myTable` AS t2 ON t2.id_cat=1 AND t1.row = t2.row
SET t1.col_md = t2.col_md
WHERE t1.id_cat = 2 or t1.id_cat=3;
Which then gives the output of:
MariaDB [testart]> select * from myTable;
+------+--------+------+--------+
| id | id_cat | row | col_md |
+------+--------+------+--------+
| 1 | 1 | 1 | 4 |
| 2 | 1 | 2 | 5 |
| 3 | 1 | 3 | 5 |
| 4 | 2 | 1 | 4 |
| 5 | 2 | 2 | 5 |
| 6 | 2 | 3 | 5 |
| 7 | 3 | 1 | 4 |
| 8 | 3 | 1 | 4 |
| 9 | 3 | 2 | 5 |
+------+--------+------+--------+
9 rows in set (0.00 sec)
Currently able to achieve the SQL query for your desired result.
SELECT t2.id_cat, t1.row, t1.col_md
FROM (SELECT row, col_md from mytable WHERE id_cat=1) as t1 , mytable as t2
GROUP BY t2.id_cat, t1.row, t1.col_md
The above will return the following..
I suggest to use INSERT statement along with the above query to put the record into a new table and drop the old one.
Cheers!
EDITED...
Instead of Updating table, alternate approach could be to Insert the required record into a new table.
This can be achieved with following four steps
Create a tmp table with same fileds (id Auto_Increment, id_cat, row, col_md)
Insert to tmp table with this statement...
INSERT INTO tmp(id_cat, row, col_md)
SELECT t2.id_cat, t1.row, t1.col_md
FROM (SELECT row, col_md from mytable WHERE id_cat=1) as t1 , mytable as t2
GROUP BY t2.id_cat, t1.row, t1.col_md
Remove/Rename 'myTable'.
Rename 'tmp' table to 'myTable'.
Hope this will serve the purpose...
Cheers!
it's not enough to tell which group you want the data from, you need to match id to id.
in your case t2.id 4 and 7 to t1.id 1, t2.id 5 and 8 to t1.id 2, and t2.id 6 and 9 to t1.id 3.
SELECT #d := COUNT(*) FROM myTable WHERE id_cat = 1;
UPDATE `myTable` AS t1
JOIN `myTable` AS t2 ON t2.id_cat=1 AND
t2.id = IFNULL(NULLIF(t1.id MOD #d, 0), #d)
SET t1.row = t2.row, t1.col_md = t2.col_md
WHERE t1.id_cat = 2 or t1.id_cat=3;
#d holds the number of lines where id_cat = 1
we divide t1.id by #d and match the remainder (MOD) to t2.id.
when t1.id is multiple of #d the remainder is 0 and we have to match it to #d
so we make 0 into NULL and NULL into #d
In my understanding, the difficult part about this question is to relate each record to update (ie each record with id_cat IN (2, 3)) to the relevant original record (record with id_cat = 1).
Based on your sample data, I understand that you expect series of records for each id_cat (I can see three groups of three records, sorted by increasing id), so I would assume that you want to relate each record to the original that has the same sequence in the group of record where id_cat = 1.
Assuming MySQL 8.0, a typical approach to assign a number to a record within a group is ROW_NUMBER(). Consider this simple query:
SELECT
t.*,
ROW_NUMBER() OVER(PARTITION BY id_cat ORDER BY id) rn
FROM t
Yields:
| id | id_cat | rw | col_md | rn |
| --- | ------ | --- | ------ | --- |
| 1 | 1 | 1 | 4 | 1 |
| 2 | 1 | 2 | 5 | 2 |
| 3 | 1 | 3 | 5 | 3 |
| 4 | 2 | 1 | 3 | 1 |
| 5 | 2 | 2 | 4 | 2 |
| 6 | 2 | 2 | 4 | 3 |
| 7 | 3 | 1 | 12 | 1 |
| 8 | 3 | 1 | 12 | 2 |
| 9 | 3 | 2 | 3 | 3 |
Now with this set-up in mind, we can turn this query to a Common Table Expression (available also starting MySQL 8.0), and JOIN it as need with the original table to do the UPDATE:
WITH cte AS (
SELECT
t.*,
ROW_NUMBER() OVER(PARTITION BY id_cat ORDER BY id) rn
FROM t
)
UPDATE t t0
INNER JOIN cte t1 ON t1.id = t0.id
INNER JOIN cte t2 ON t2.id_cat = 1 AND t2.rn = t1.rn
SET t0.rw = t2.rw, t0.col_md = t2.col_md
WHERE t0.id_cat IN (2, 3)
Details:
t0 is the original table, where records having id_cat IN (2, 3) need to be updated
t1 is the corresponding record in the CTE (to which a row number was assigned)
t2 is the record in the CTE that has id_cat = 1 and the same row number as the record being updated
Demo on DB Fiddle:
| id | id_cat | rw | col_md |
| --- | ------ | --- | ------ |
| 1 | 1 | 1 | 4 |
| 2 | 1 | 2 | 5 |
| 3 | 1 | 3 | 5 |
| 4 | 2 | 1 | 4 |
| 5 | 2 | 2 | 5 |
| 6 | 2 | 3 | 5 |
| 7 | 3 | 1 | 4 |
| 8 | 3 | 2 | 5 |
| 9 | 3 | 3 | 5 |
I got to table need to combine into 1
Table 1 :
| ID | FEEDBACK_VALUE |
| 1 | EMAILS |
| 2 | WALK IN |
| 3 | SMS BLAST |
| 4 | SOCIAL MEDIA |
| 5 | NEWSPAPER |
| 6 | FAMILY & FRIEND |
| 7 | OTHERS |
Table 2 :
| ID | FEEDBACK_ID |
| 1 | 1 |
| 2 | 2 |
| 3 | 2 |
| 4 | 7 |
| 5 | 7 |
| 6 | 7 |
| 7 | 4 |
| 8 | 4 |
| 9 | 3 |
Table 3 :
| ID | FEEDBACK_VALUE | FEEDBACK_RECEIVE |
| 1 | EMAILS | 1 |
| 2 | WALK IN | 2 |
| 3 | SMS BLAST | 1 |
| 4 | SOCIAL MEDIA | 2 |
| 5 | NEWSPAPER | 0 |
| 6 | FAMILY & FRIEND | 0 |
| 7 | OTHERS | 3 |
From table 1 and 2, How can i get result like table 3 using mysql? Thanks
You could use a left jojn, and subquery with count group by
select t1.ID, t1.FEEDBACK_VALUE, ifnull( my_count,0) feedback_receive
from table1 t1
left join (
select FEEDBACK_ID, count(*) as my_count
from table 2
group by FEEDBACK_ID
) t on t1.ID = t.FEEDBACK_ID
Just use a subquery as shown below:
SELECT A.*, (SELECT COUNT(*) FROM TABLE2 B WHERE A.ID=B.FEEDBACK_ID) AS FEEDBACK_RECEIVE
FROM TABLE1 A;
See DEMO on SQL Fiddle
Or, if less code is your thing...
SELECT x.*
, COUNT(y.id) total
FROM table_1 x
LEFT
JOIN table_2 y
ON y.feedback_id = x.id
GROUP
BY x.id;
See my table(sample_table),
-----------------------------
id | from | to |
-----------------------------
1 | 2 | 1 |
3 | 2 | 1 |
4 | 2 | 4 |
5 | 3 | 2 |
9 | 3 | 1 |
11 | 4 | 1 |
12 | 4 | 3 |
-----------------------------
For each from, I would like the row holding the most recent to, where to = 1
I mean I want only following,
-----------------------------
id | from | to |
-----------------------------
3 | 2 | 1 |
9 | 3 | 1 |
11 | 4 | 1 |
-----------------------------
I Try following Query,
SELECT * FROM sample_table WHERE to = 1 GROUP BY from
It's giving first row of each. Help me.
Thanks,
There are many ways to do it and here is one way
select t1.* from sample_table t1
join(
select max(id) as id,`from` from
sample_table where `to` = 1
group by `from`
)t2
on t1.id= t2.id and t1.`from` = t2.`from`
https://dev.mysql.com/doc/refman/5.0/en/example-maximum-column-group-row.html
Try this
select t1.id, t1.from, t1.to from table as t1 inner join
(
select to, from,min(id) as id from table
where to=1
group by to,from
) as t2
on t1.to=t2.to and t1.id=2.id and t1.from=t2.from
I have two tables:
'posts' (where 'post_text' field rows are comments users have posted); and
'votes' (the users' votes (thumbs up/down) on the posts, where 'post_id' matches 'id' from the 'posts' table):
SELECT *
FROM `posts`
+----+-----------+
| id | post_text |
+----+-----------+
| 1 | test0 |
| 2 | test1 |
| 3 | test2 |
| 4 | test3 |
| 5 | test4 |
| 6 | test5 |
| 7 | test6 |
| 8 | test7 |
| 9 | test8 |
| 10 | test9 |
| 11 | test10 |
+----+-----------+
SELECT *
FROM `votes`
+----+---------+--------+
| id | post_id | rating |
+----+---------+--------+
| 1 | 1 | 1 |
| 2 | 2 | 0 |
| 3 | 4 | 1 |
| 4 | 4 | 1 |
| 5 | 6 | 1 |
| 6 | 6 | 1 |
| 7 | 7 | 0 |
+----+---------+--------+
What I'd like to do is get all the 'post_text' values from 'posts' table, but sort them by highest no. of thumbs-up ('1') ratings first, then thumbs-downs ('0') to come next, then the posts without ratings (i.e. no corresponding votes in the 'votes' table) to come last. With a join, I can achieve this but I don't know how to get the 'post_text' values without ratings to also be in the result. This is what I got:
SELECT posts.id, post_id, rating, COUNT( * )
FROM posts
INNER JOIN votes ON posts.id = votes.post_id
GROUP BY post_id
ORDER BY rating DESC , COUNT( * ) DESC , post_id DESC
LIMIT 0 , 30
+----+---------+--------+----------+
| id | post_id | rating | COUNT(*) |
+----+---------+--------+----------+
| 6 | 6 | 1 | 2 |
| 4 | 4 | 1 | 2 |
| 1 | 1 | 1 | 1 |
| 7 | 7 | 0 | 1 |
| 2 | 2 | 0 | 1 |
+----+---------+--------+----------+
SELECT posts.id, posts.post_text, post_id, rating, COUNT( * )
FROM posts
LEFT JOIN votes ON posts.id = votes.post_id
GROUP BY post_id
ORDER BY rating DESC , COUNT( * ) DESC , post_id DESC
LIMIT 0 , 30
i have this table called bag:
+--------+----------+---------+----------+
| bag_id | chara_id | item_id | item_qty |
+--------+----------+---------+----------+
| 1 | 1 | 2 | 22 |
| 2 | 1 | 1 | 55 |
| 3 | 3 | 1 | 2 |
| 6 | 3 | 4 | 2 |
| 7 | 4 | 4 | 2 |
| 8 | 5 | 4 | 2 |
| 9 | 6 | 4 | 2 |
| 10 | 1 | 5 | 1 |
| 11 | 1 | 2 | 1 |
| 12 | 1 | 2 | 1 |
| 13 | 1 | 2 | 1 |
| 14 | 1 | 8 | 1 |
| 15 | 1 | 6 | 1 |
| 16 | 1 | 8 | 1 |
| 17 | 1 | 6 | 1 |
+--------+----------+---------+----------+
the relationship goes as 1 chara = many item
now i dont want 1 chara = many duplicated item.
how can i make a query that delete's the duplicated values?
like chara_id: 1 has 3 duplicated item_id: 2
i want to delete the other 2.
Not the best way to do it. But the below should definetly work:
Delete from Bag
where bag_id
not in (
select min(bag_id) from bag a,
(select chara_id, item_id
from bag group by chara_id, item_id
having count(*) > 1) b
where a.chara_id = b.chara_id and a.item_id = b.item_id
UNION
select bag_id from bag a,
(select chara_id, item_id
from bag group by chara_id, item_id
having count(*) = 1) b
where a.chara_id = b.chara_id and a.item_id = b.item_id
)
You can simply join table bag with a subquery which gets the minimum bag_id for every combination of chara_ID and item_ID. Records that have null values on any fields on the subquery are the records that will be deleted.
DELETE a
FROM bag a
LEFT JOIN
(
SELECT chara_ID, item_ID, MIN(bag_ID) min_ID
FROM bag
GROUP BY chara_ID, item_ID
) b ON a.bag_ID = b.min_ID AND
a.chara_ID = b.chara_ID AND
a.item_ID = b.item_ID
WHERE b.min_ID IS NULL
SQLFiddle Demo