Below is the standard query that sort weight in descending order.
SELECT * FROM article ORDER BY weight DESC LIMIT 0, 4
+-------------+--------+
| title | weight |
+-------------+--------+
| B | 2 |
| E | 2 |
| Y | 2 |
| A | 1 |
| C | 1 |
| D | 1 |
| F | 1 |
| G | 1 |
| X | 1 |
| Z | 1 |
| I | 1 |
| G | 1 |
+-------------+--------+
However, I wish to sort it differently as following based on the weight value.
+-------------+--------+
| title | weight |
+-------------+--------+
| B | 2 |
| A | 1 |
| C | 1 |
| D | 1 |
| E | 2 |
| F | 1 |
| G | 1 |
| X | 1 |
| Y | 2 |
| Z | 1 |
| I | 1 |
| G | 1 |
+-------------+--------+
The record with weight value 2 only selected once and sorted at the top. Then followed by records with weight value 1.
Using the approach like in these answers:
ROW_NUMBER() in MySQL
MSSQL Row_Number() over(order by) in MySql
you could obtain row numbers, separately, for rows with weight value of 2 and for rows with any other weight value, then use the resulting numbers for sorting.
Before I continue, please note that, even though the official documentation admits that
You might get the results you expect,
it also advises that,
As a general rule, you should never assign a value to a user variable and read the value within the same statement.
(By the same statement it means a statement other than SET.)
Below is a way of getting the expected order if the results are as they are expected.
SET #row2 = -1;
SET #row_other = -1;
SELECT
title, weight
FROM (
SELECT
title, weight,
#row2 := #row2 + CASE weight WHEN 2 THEN 1 ELSE 0 END AS weight2_row,
#row_other := #row_other + CASE weight WHEN 2 THEN 0 ELSE 1 END AS other_weight_row
FROM article
) s
ORDER BY
CASE weight WHEN 2 THEN #row2 ELSE #row_other DIV 3 END,
weight = 2 DESC
The specific order for non-Weight=2 rows is undefined, just like it is in your question.
Taking the same precautions that Andriy states, you can also use this:
SELECT title, weight
FROM
( SELECT title, weight
, #rownumber2 := #rownumber2 + 3 AS rn
FROM article
, ( SELECT #rownumber2 := 1 ) AS dummy
WHERE weight = 2
ORDER BY title --- optional, configure it for the
--- ordering of rows with weight = 2
UNION ALL
SELECT title, weight
, #rownumber1 := #rownumber1 + 1 AS rn
FROM article
, ( SELECT #rownumber1 := 3 ) AS dummy
WHERE weight = 1
ORDER BY title DESC --- optional, configure it for the
--- ordering of rows with weight = 1
) AS insaneOrdering
ORDER BY rn
, weight DESC ;
Related
I have following table with data:
| predp_id | strp_ID | predp_nas |
| -------- | ------- | --------- |
| 1 | 1 | null |
| 2 | 1 | null |
| 3 | 1 | null |
| 4 | 2 | null |
| 5 | 2 | null |
| 6 | 3 | null |
predp_nas column should be count of strp_ID column + 1 for same strp_ID on every row.
I am currently using next query to achieve this on every new insert:
INSERT INTO PREDMETIP
(`strp_ID`, `predp_nas`)
VALUES(
1,
(SELECT counter + 1 FROM (SELECT COUNT(strp_ID) counter FROM PREDMETIP WHERE strp_ID = '1') t)
);
This gives me:
| predp_id | strp_ID | predp_nas |
| -------- | ------- | --------- |
| 1 | 1 | null |
| 2 | 1 | null |
| 3 | 1 | null |
| 4 | 2 | null |
| 5 | 2 | null |
| 6 | 3 | null |
| 7 | 1 | 4 |
But now I have imported large amount of data and I need to update all predp_nas fields at once to give me result:
| predp_id | strp_ID | predp_nas |
| -------- | ------- | --------- |
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 1 |
| 5 | 2 | 2 |
| 6 | 3 | 1 |
| 7 | 1 | 4 |
I have DB fiddle with insert query View on DB Fiddle , I am having trouble understanding how to write query for same thing but to update all fields at once. Any help is appreciated.
What you're looking for is ROW_NUMBER() (if you're using MySQL 8+), but since your fiddle is on MySQL 5.7 I'm assuming that's your version and so you can emulate it by counting the number of rows for a given strp_ID that have a lower predp_id and using that to update the table:
UPDATE PREDMETIP p1
JOIN (
SELECT p1.predp_id,
COUNT(p2.predp_id) + 1 AS rn
FROM PREDMETIP p1
LEFT JOIN PREDMETIP p2 ON p2.strp_ID = p1.strp_ID AND p2.predp_id < p1.predp_id
GROUP BY p1.predp_id
) p2 ON p1.predp_id = p2.predp_id
SET p1.predp_nas = p2.rn
;
SELECT *
FROM PREDMETIP
Output after update:
predp_id strp_ID predp_nas
1 1 1
2 1 2
3 1 3
4 2 1
5 2 2
6 3 1
7 1 4
You seeem to be looking for an update query. If you are running MySQL 8.0, you can do this with row_number():
update predmetip p
inner join (
select p.*, row_number() over(partition by predp_id order by strp_id) rn
from predmetip p
) p1 on p1.predp_id = p.predp_id and p1.strp_id = p.strp_id
set p.predp_nas = p1.rn
On the other hand, if you are running a MySQL 5.x version, then one option is to use correlated subqueries, as demonstrated in Nick's answer. This works fine - and I upvoted Nick's answer - but the performance tends to quickly degrade when the volume of data gets larger, because you need to scan the table for each and every row in the resultset.
You can do this with user variables, but it's is tricky: since, as explained in the documentation, the order of evaluation of expressions in the select clause is undefined, we need to evaluate and assign in the same expression ; case comes handy for this. Another important thing is that we need to order the rows in a subquery before variables come into play.
You would write the select statement as follows:
set #rn := 0, #strp_id = '';
select
predp_id,
strp_id,
#rn := case
when #strp_id = strp_id then #rn + 1 -- read
when #strp_id := strp_id then 1 -- assign
end as predp_nas
from (
select *
from predmetip
order by strp_id, predp_id
) t
You can then turn it to an update:
set #rn := 0, #strp_id = '';
update predmetip p
inner join (
select
predp_id,
strp_id,
#rn := case
when #strp_id = strp_id then #rn + 1
when #strp_id := strp_id then 1
end as predp_nas
from (
select *
from predmetip
order by strp_id, predp_id
) t
) p1 on p1.predp_id = p.predp_id and p1.strp_id = p.strp_id
set p.predp_nas = p1.predp_nas;
Demo on DB Fiddle (with credits to Nick for creating it in the first place).
To read more about user variables and their tricks, I recommend this excellent answer by Madhur Bhaiya, which also contains another interesting blog link.
I have a table like this
|num|id|name|prj|
| 1 | 1|abc | 1 |
| 2 | 1|efg | 1 |
| 3 | 1|cde | 1 |
| 4 | 2|zzz | 1 |
I want to run a query like this:
SELECT * FROM table WHERE prj=1 ORDER BY name
but printing out repeated values only once. I want to keep all the rows and I would like to do this at database level and not on the presentation layer (I know how to do it in php).
Desired result is
|num|id|name|prj|
| 1 | 1|abc | 1 |
| 3 | |cde | 1 |
| 2 | |efg | 1 |
| 4 | 2|zzz | 1 |
any hint on where to start from to build that query?
Use a session variable to test if the previous ID is the same as the current ID:
SELECT num, IF(#lastid = id, '', #lastid := id) AS id, name, prj
FROM table
CROSS JOIN (SELECT #lastid := null) x
ORDER BY table.id, name
DEMO
Note that you need to qualify table.id, because ORDER BY defaults to using the alias from the SELECT list if it's the same as a table column, and that would order the empty fields first.
I have an abstract problem which can be simplified as the following problem: Assume that we have two tables persons and names that look as follows:
SELECT * FROM persons;
+----+-------+--------+
| id | name | fan_of |
+----+-------+--------+
| 1 | alice | 2 |
| 2 | bob | 4 |
| 3 | carol | 1 |
| 4 | dave | 3 |
| 5 | bob | 2 |
+----+-------+--------+
and
SELECT * FROM names;
+----+-------+--------+
| id | name | active |
+----+-------+--------+
| 1 | alice | 1 |
| 2 | bob | 1 |
| 3 | carol | 0 |
| 4 | dave | 1 |
+----+-------+--------+
Every person (a row in the persons) table is a fan of itself or another person (represented by that other persons id in the fan_of column). The names table contains names that can be active or inactive.
For a given offset k, I want to SELECT the persons (rows of persons) that have the k+1-th active name as their name or that have one of these people as their fans. For example, if the offset is 1, the second active name is bob and hence I want to select all people with the name bob plus the people that have one of these bobs as their fans, which is in this example the row of persons with id=4. This means that I want to have the result:
+----+------+--------+
| id | name | fan_of |
+----+------+--------+
| 2 | bob | 4 |
| 4 | dave | 3 |
| 5 | bob | 2 |
+----+------+--------+
What I have so far is the following query:
1 SELECT * FROM persons WHERE
2 EXISTS (
3 SELECT * FROM (
4 SELECT * FROM names WHERE active=true LIMIT 1 OFFSET 1
5 ) AS selectedname WHERE (selectedname.name=persons.name)
6 )
7 OR
8 EXISTS (
9 SELECT * FROM(
10 SELECT * FROM persons WHERE EXISTS (
11 SELECT * FROM (
12 SELECT * FROM names WHERE active=true LIMIT 1 OFFSET 1
13 ) AS selectedname WHERE (selectedname.name=persons.name)
14 )
15 ) AS personswiththatname WHERE persons.id=personswiththatname.fan_of
16 );
It gives me the desired result from above but please note that it is inefficient because the lines 3-5 and 11-13 are the same.
I have the following two questions:
What can be done to avoid this inefficiency?
I actually need to distinguish between those rows that came from the
name condition (here the rows with name=bob) and those that came
from the fan_of condition (here the row with name=dave). This
could be done in the application code but then I would need another
database query before to find out the k+1-th active name and this might
be slow (please correct me if this is the better solution). I would
rather prefer an additional column z that helps me to distinguish
like
+----+------+--------+---+
| id | name | fan_of | z |
+----+------+--------+---+
| 2 | bob | 4 | 1 |
| 4 | dave | 3 | 0 |
| 5 | bob | 2 | 1 |
+----+------+--------+---+
How can such an output be achieved?
It looks like I can get the minimum you want to achieve using parameters (should this be an option).
It's not pretty, but I can't see a simple way of achieving what you're asking for, so this is what I have so far....(set #offset to suit 'k')
SET #offset = 1;
SET #name = (SELECT name FROM (select name, #rank := #rank +1 as Rank from names n, (SELECT #rank := 0) r where active !=0) as activeRanked where activeRanked.rank = (1 + #offset));
select
a.*
From persons a
where (a.name = #name) OR (a.id IN (SELECT fan_of from persons where name = #name));
If you still don't have an answer by the time I've had food, I'll look at part 2.
(hopefully I've read your brief correctly)
P.S. I've kept the #name SQL in a single line as it seems to read better in this context.
Edit: Here's a pretty messy but functional indicator of source, using your example. Z = 1 is where the row is from the name, '0' is from fan_of
SET #offset = 1;
SET #name = (SELECT name FROM (select name, #rank := #rank +1 as Rank from names n, (SELECT #rank := 0) r where active !=0) as activeRanked where activeRanked.rank = (1 + #offset));
select
a.*,'1' as z
From persons a
where (a.name = #name)
union
select
a.*,'0' as z
From persons a
where (a.id IN (SELECT fan_of from persons where name = #name));
Distinct ID Query:
SET #offset = 1;
SET #name = (SELECT name FROM (select name, #rank := #rank +1 as Rank from names n, (SELECT #rank := 0) r where active !=0) as activeRanked where activeRanked.rank = (1 + #offset));
SELECT id, name, fan_of, z FROM
(select
distinct a.id,
a.name,
a.fan_of,
1 as z
From persons a
where (a.name = #name)
union
select
distinct a.id,
a.name,
a.fan_of,
0 as z
From persons a
where (a.id IN (SELECT fan_of from persons where name = #name))
ORDER BY z desc) qry
GROUP BY id;
This produces:
+----+------+--------+---+
| id | name | fan_of | z |
+----+------+--------+---+
| 2 | bob | 4 | 1 |
| 5 | bob | 2 | 1 |
| 4 | dave | 3 | 0 |
+----+------+--------+---+
I have a big MySQL table on which I'd like to calculate a cumulative product. This product has to be calculated for each group, a group is defined by the value of the first column.
For example :
name | number | cumul | order
-----------------------------
a | 1 | 1 | 1
a | 2 | 2 | 2
a | 1 | 2 | 3
a | 4 | 8 | 4
b | 1 | 1 | 1
b | 1 | 1 | 2
b | 2 | 2 | 3
b | 1 | 2 | 4
I've seen this solution but don't think it would be efficient to join or subselect in my case.
I've seen this solution which is what I want except it does not partition by name.
This is similar to a cumulative sum:
select t.*,
(#p := if(#n = name, #p * number,
if(#n := name, number, number)
)
) as cumul
from t cross join
(select #n := '', #p := 1) params
order by name, `order`;
I know it's not possible to use limits within nested INs, but I think there is a way to do this, I'm just not sure how.
I have a table that contains both ratings and comments (simplified for explanation)
mySingleTable:
+----+------------------+-----------+-----------+-----------------+
| id | reviewer_comment | is_rating | parent_id | reviewer_rating |
+----+------------------+-----------+-----------+-----------------+
| 1 | well done rateA | 1 | 0 | 5 Stars |
| 2 | commentAonRateA | 0 | 1 | |
| 3 | commentBonRateA | 0 | 1 | |
| 4 | commentConRateA | 0 | 1 | |
| 5 | commentDonRateA | 0 | 1 | |
| 6 | commentEonRateA | 0 | 1 | |
| 7 | commentFonRateA | 0 | 1 | |
| 8 | well done rateB | 1 | 0 | 4 Stars |
| 9 | well done rateC | 1 | 0 | 5 Stars |
| 11 | well done rateD | 1 | 0 | 3 Stars |
| 12 | well done rateE | 1 | 0 | 2 Stars |
| 13 | well done rateF | 1 | 0 | 5 Stars |
| 14 | well done rateG | 1 | 0 | 3 Stars |
| 15 | commentAonRateD | 0 | 11 | |
+----+------------------+-----------+-----------+-----------------+
So,
if is_rating = 1, its a rating.
if is_rating = 0, its a comment on the rating (its parent rating is where parent_id=id)
so this would look like:
well done rateA *****
commentAonRateA
commentBonRateA
commentConRateA
commentDonRateA
commentEonRateA
commentFonRateA
well done rateB ****
well done rateC *****
well done rateD ***
commentAonRateD
well done rateE **
well done rateF *****
well done rateG ***
What I want to do is select the newest five ratings, with the ASSOCIATED newest 5 comments, using only 1 query
So, some how join these two:
SELECT ratings.*
FROM mySingleTable as ratings
WHERE
is_rating = 1
ORDER BY timestamp DESC LIMIT 0, 5
SELECT comments.*
FROM mySingleTable as comments
Where
comments.parent_id = ratings.id
AND is_rating = 0
ORDER BY timestamp DESC LIMIT 0, 5
The 2nd query needs to somehow know about the ratings query
Please try this query which is now simplified and tested.
SELECT *
FROM
(SELECT *,
IF (group_id = #prev,
#n := #n + 1,
#n := 1 AND #prev := group_id) as position
FROM (
SELECT mySingleTable.*, group_id
FROM mySingleTable
INNER JOIN
(SELECT id AS group_id
FROM mySingleTable
WHERE is_rating = 1
ORDER BY timestamp DESC LIMIT 0, 5
) AS top5ratings
ON mySingleTable.id = group_id OR mySingleTable.parent_id = group_id
ORDER BY group_id DESC,
mySingleTable.parent_id ASC,
timestamp DESC) AS all_reviews
JOIN (SELECT #n := 0, #prev := 0) AS setup) AS reviews
WHERE reviews.position < 7
Keep in mind that SELECT * is bad practice. I used it to simplify reading.
TRY THIS QUERY
SELECT comments.*
FROM
(
SELECT ratings.id
FROM mySingleTable as ratings
WHERE
is_rating = 1
ORDER BY timestamp DESC LIMIT 0, 5 ) AS Top5
INNER JOIN mySingleTable AS comments ON comments.parent_id = Top5.id
WHERE comments.is_rating = 0
ORDER BY comments.timestamp DESC LIMIT 0, 5