MySQL SELECT order by values of 2 columns - mysql

I have a table like this:
CREATE TABLE rows(
UniqueID VARCHAR(225),
Previous VARCHAR(225),
Next VARCHAR(225)
);
With content, that looks like this:
+----------+-----------+-----------+
| UniqueID | Previous | Next |
+----------+-----------+-----------+
| 676 | undefined | 219 |
| 890 | 219 | undefined |
| 219 | 676 | 890 |
+----------+-----------+-----------+
As you can see, the rows have UID's, which the Previous and Next columns refer to.
What I now want, is to write a SELECT * statement, that would order all the results, by the Previous and Next fields. The undefined values mark the end elements. How could I achieve that? In the case of the table showed above, the order I'd want is what's shown there, with the last 2 row positions swapped, so Next of row X Points to a UID of row Y, that has a Previous that points to the UID of the row X. etc.

What you're trying to create is a recursive query. Unfortunately, MySQL does not make this easy. There are relatively simple solutions if the parents always have an index greater than the children, but that is not the case here. There are several questions discussing this type of problem. The following question has answers that explore the different ways to attempt this type of query including using stored procedures.
How to do the Recursive SELECT query in MySQL?
Going with the stored procedure idea, you could try something like:
CREATE PROCEDURE getInOrder()
BEGIN
DECLARE child_id VARCHAR(256);
DECLARE prev_id VARCHAR(256);
SELECT UniqueID INTO prev_id FROM rows WHERE Previous = 'undefined';
SELECT `Next` INTO child_id
FROM rows WHERE UniqueID = prev_id;
CREATE TEMPORARY TABLE IF NOT EXISTS temp_table AS (SELECT * FROM rows WHERE 1=0);
TRUNCATE TABLE temp_table;
WHILE child_id <> 'undefined' DO
INSERT INTO temp_table SELECT * FROM rows WHERE UniqueID = prev_id;
SET prev_id = child_id;
SELECT `Next` INTO child_id
FROM rows WHERE UniqueID = prev_id;
END WHILE;
INSERT INTO temp_table SELECT * FROM rows WHERE UniqueID = prev_id;
SELECT * FROM temp_table;
END;
You can then call the stored procedure to retrieve the table in order.
Working example: http://sqlfiddle.com/#!9/085dec/2

ORDER BY IFNULL(prev, ''), -- some value lower than the rest
IFNULL(next, 'zzzzz') -- some value higher than all values
(Technically, the first part could be simply prev, without the IFNULL.)
If the ids are really numbers, you should use a numeric datatype such as INT UNSIGNED. If they are really strings, do you need 225?
This assumes that prev < next -- Is that necessarily the case? It seems like arbitrary links might not maintain that. If you need to look at next to load the next row based on UniqueId, the code is much more complex.

I think this request lacks on details.
But, you want the final result to be like this?
+----------+-----------+-----------+
| UniqueID | Previous | Next |
+----------+-----------+-----------+
| 676 | undefined | 219 |
| 219 | 676 | 890 |
| 890 | 219 | undefined |
+----------+-----------+-----------+
If I'm right, you can achieve it with (I named the table as demo):
SELECT d.* FROM (
SELECT UniqueID, IF(Previous IS NULL, -1, Previous) AS Previous, IF(Next IS NULL, 999999999999, Next) as Next
FROM demo
)t
JOIN demo d ON d.UniqueID = t.UniqueID
ORDER BY t.Next, t.Previous
;
So, when Previous is NULL you put it with -1 to ensure he's is the first on the list and when Next is NULL you put it with a very high value to ensure it will be the last on the list... then you just have to order the query by Previous and Next.
I must stress that this solution is focused on presented data.

Related

Sql Query to retrive data from table

How to retrieve odd rows from the table?
In the Base table always Cr_id is duplicated 2 times.
Base table
I want a SELECT statement that retrieves only those c_id =1 where Cr_id is always first as shown in the output table.
Output table
Just see the base table and output table you should automatically know what I want, Thanx.
Just testing min date should be enough
drop table if exists t;
create table t(c_id int,cr_id int,dt date);
insert into t values
(1,56,'2020-12-17'),(56,56,'2020-12-17'),
(1,8,'2020-12-17'),(56,8,'2020-12-17'),
(123,78,'2020-12-17'),(1,78,'2020-12-18');
select c_id,cr_id,dt
from t
where c_id = 1 and
dt = (select min(dt) from t t1 where t1.cr_id = t.cr_id);
+------+-------+------------+
| c_id | cr_id | dt |
+------+-------+------------+
| 1 | 56 | 2020-12-17 |
| 1 | 8 | 2020-12-17 |
+------+-------+------------+
2 rows in set (0.002 sec)
What you're looking for could be "partition by", at least if you're working on mssql.
(In the future, please include more background, SQL is not just SQL)
https://codingsight.com/grouping-data-using-the-over-and-partition-by-functions/
I have an old query lying around, that is able to put a sorting index on data who lacks this, although the underlying reason is 99.9% sure to be a bad data design.
Typically I use this query to remove bad data, but you may rewrite it to become a join instead, so that you can identify the data you need.
The reason why I'm not putting that answer here, is to point out, bad data design results in more work when reading it afterwards, whom seems to be the real root cause here.
DELETE t
FROM
(
SELECT ROW_NUMBER () OVER (PARTITION BY column_1 ,column_2, column_3 ORDER BY column_1,column_2 ,column_3 ) AS Seq
FROM Table
)t
WHERE Seq > 1

Update table row value to a random row value from another table

I have 2 MySQL tables.
One table has a column that lists all the states
colStates | column2 | column 3
------------------------------
AK | stuff | stuff
AL | stuff | stuff
AR | stuff | stuff
etc.. | etc.. | etc..
The second table has a column(randomStates) with all NULL values that need to be populated with a randomly selected state abbreviation.
Something like...
UPDATE mytable SET `randomStates`= randomly selected state value WHERE randomStates IS NULL
Can someone help me with this statement. I have looked around at other posts, but I don't understand them.
this works for me with trial data in SQLite:
UPDATE mytable
SET randomStates = (SELECT colStates FROM
(SELECT * FROM first_table ORDER BY RANDOM())
WHERE randomStates IS NULL)
without the first SELECT portion, you end up with the same random value inserted into all the NULL randomStates field. (i.e. if you just do SELECT StateValue FROM counts ORDER BY RANDOM() you don't get what you want).

How do you get multiple variable values to output as a table in MySQL?

so I'm going to jump right into the problem I'm trying to solve.
I have a MySQL table called groups with columns GROUP_ID, GROUP_PARENT_ID, NAME
where GROUP_PARENT_ID can be NULL.
What I want to do is write a PROCEDURE where I can input the GROUP_ID of a distant child group and get all the parent groups.
For example, if I have
GROUP ID | GROUP_PARENT_ID | NAME
1 | NULL | Group One
2 | 1 | Group Two
3 | 2 | Group Three
and if I CALL getGroupParent(3), I want to get the chain of groups, i.e 3,2,1 as the output
DROP PROCEDURE IF EXISTS some_schema.getGroupParent;
CREATE DEFINER =`root`#`localhost` PROCEDURE `getGroupParent`(
IN childGroupID INT(10)
)
BEGIN
SELECT GROUP_PARENT_ID
FROM groups
WHERE GROUP_ID = childGroupID
INTO #currentGroup;
WHILE (NOT ISNULL(#currentGroup)) DO
SELECT GROUP_PARENT_ID
FROM groups
WHERE GROUP_ID = #currentGroup
INTO #newGroup;
SET #currentGroup = #newGroup;
END WHILE;
SELECT #currentGroup;
END;
^ Something like that, except I want all the groups the procedure went through.

Row number for query results grouped by a column

I have a table that has the following columns:
id | fk_id | rcv_date
There may be multiple records with a common fk_id, which represents a foreign key id in a related table.
I need to create a query that will assign a row number to each record, grouped by fk_id, sorted by rcv_date.
I originally began with the following query, which works quite well for sorting and assigning row numbers:
SELECT #row:=#row +1 AS ordinality, c.fk_id, rcv_date
FROM (SELECT #row:=0) r, mytable c
ORDER BY rcv_date
However -- the row count and sorting is done across the entire dataset. I need the counting to be within a common fk_id. For example, the following sample data would return (the first column represents the row count/ordinality):
1 | 5 | 2011-10-01
2 | 5 | 2011-10-14
3 | 5 | 2011-11-02
4 | 5 | 2011-12-17
1 | 8 | 2011-09-03
2 | 8 | 2011-11-12
1 | 9 | 2011-10-08
2 | 9 | 2011-10-10
3 | 9 | 2011-11-19
The middle column represents the fk_id. As you can see, the sorting and row count is within the fk_id "grouping."
UPDATE
I have a query that seems to be working, but would like some input as to whether it can be improved:
SELECT IF(#last = c.fk_id, #row:=#row +1, #row:=1) AS ordinality, #last:=c.fk_id, c.fk_id, rcv_date
FROM (SELECT #row:=0) r, (SELECT #last:=0) l, mytable c
ORDER BY c.fk_id, rcv_date
So what this does is order by fk_id and then rcv_date -- which essentially handles my grouping. Then I use a second variable to compare the fk_id in the previous record with the current record: if it's the same, we increment the row; if different, we reset to 1.
My tests with real data appear to be working. I suspect it's a pretty inefficient query though -- so if anyone has ideas for improving it, or see possible flaws, I would love to hear.
This should be pretty straightforward.
SELECT (CASE WHEN #fk <> fk_id THEN #row:=1 ELSE #row:=#row + 1 END) AS ordinality,
#fk:=fk_id, rcv_date
FROM (SELECT #row:=0) AS r,
(SELECT #fk:=0) AS f,
(SELECT fk_id, rcv_date FROM files ORDER BY fk_id, rcv_date) AS t
I ordered by fk_id first to ensure all your foreign keys come together (what if they are not really in the table?), then I did your preferred ordering, ie by rcv_date. The query checks for a change in fk_id and if there is one, then row number variable is set to 1, or else the variable is incremented. Its handled in case statement. Notice that #fk:=fk_id is done after the case checking else it will affect the row number.
Edit: Just noticed your own solution which happened to be the same as I ended up with. Kudos! :)

MySQL : interval around id column and return another one from subquery with multiple columns

I would like to run a query from a table where the content is like that :
id | col1 | col2 | col3
-----------------------
1 | i_11 | i_12 | i_13
2 | i_21 | i_22 | i_23
3 | i_31 | i_32 | i_33
.. | ... | ... | ...
SELECT col1 FROM table WHERE id IN
(SELECT id-1, id+1 FROM table WHERE col1='xxx' AND col2='yyy' AND col3='zzz')
The aim is to get an interval [id-1, id+1] based on the id column which returns the content stored in col1 for id-1 and id+1. The subquery works but I guess I have a problem with the query itself, since I'm having an error "Operand should contain only one column". I understand it, but I don't see any other way to do it in one query ?
I'm quite sure there's a pretty easy solution but I can't figure it out for the moment, even after having carefully read other posts about multiples columns' subqueries...
Thank you for any help :-)
The only way I can think to do it right now is like this:
SELECT col1
FROM table T
WHERE id BETWEEN (SELECT id FROM table WHERE col1='xxx' AND col2='yyy' AND col3='zzz') -1
and (SELECT id FROM table WHERE col1='xxx' AND col2='yyy' AND col3='zzz') +1
Your problem is that you are retrieving two values - but as a list rather than a set. The SQL optimizer can't see 1,3 as a set of two items when they are presented in a single row. There may also be a cast needed.
This should work.
SELECT col1 FROM table WHERE id in
(
select cast(id as int) -1 from table where col1='i_21'
union
select cast(id as int) +1 from table where col1='i_21'
)