sort child / parent in same table - mysql

I have a MySQL table which is as follows:
member_id | name | parent |....
1 | john | 0 |
2 | alex | 0 |
3 | nikita | 1 |
4 | sarah | 1 |
.
.
.
i want to sort with parrent.
i try this but not work:
SELECT * FROM `members` ORDER BY COALESCE(`parrent`,`member_id`),`parrent` !=0,`member_id`
all child sorted, but parent not with them.
i want this result :
member_id | name | parent |....
2 | alex | 0 |
1 | john | 0 |
3 | nikita | 1 |
4 | sarah | 1 |
.
.
.
parents first and then childs.
is there a better solution to implement this table?
I need a table that contain families

coalesce() doesn't work because the parent is 0. You can use nullif() instead:
ORDER BY COALESCE(NULLIF(parent, 0), member_id),
(parent = 0) DESC,
member_id
Notice that I use (parent = 0) DESC for the second key. I prefer the logic to express the matches we want first, with DESC to put true values before false ones.

Related

nested set binary tree, query leftmost and rightmost nodes

I have a MySQL table storing a binary tree in a nested set model with some extra properties. The binary tree is neither full nor complete, so there may be nodes on the middle with only one child.
It looks something like this (but with much more data):
+----+------+-------+-----------+-------+----------+--------------------+
| id | left | right | parent_id | level | position | number_of_children |
+----+------+-------+-----------+-------+----------+--------------------+
| 1 | 1 | 8 | NULL | 1 | LEFT | 3 |
| 2 | 2 | 3 | 1 | 2 | LEFT | 0 |
| 3 | 4 | 7 | 1 | 2 | RIGHT | 1 |
| 4 | 5 | 6 | 2 | 3 | LEFT | 0 |
+----+------+-------+-----------+-------+----------+--------------------+
How could I query the leftmost or the rightmost node of a tree or sub-tree?
Some graphic explanation: https://imgur.com/w7gxtOC
I tried it multiple ways, but these are not working on all scenarios (these are examples for the leftmost):
SELECT * FROM nodes
WHERE position = 'LEFT'
AND number_of_children < 2
HAVING `right` = `left` + 1
ORDER BY element.`left` ASC
LIMIT 1;
Or this one, it definitely finds the solution but finds some false ones too:
SELECT * FROM nodes AS element
JOIN nodes AS upline ON element.parent_id = upline.id
WHERE upline.position = 'LEFT'
AND element.position = 'LEFT'
AND element.number_of_children < 2
ORDER BY element.`left` ASC;

One to Many Count with one query?

I haven't touched the backend in a while.. so forgive me if this is super simple. I'm working with Lumen v.5.6.1.
| table.sets | | table.indexed_items |
|----------------| |---------------------------------|
| ID | SET | | ID | setId | itemId | have |
|----|-----------| |----|-------|--------|-----------|
| 1 | set name 1| | 1 | 3 | 1 | 2 |
| 2 | set name 2| | 2 | 3 | 2 | 1 |
| 3 | set name 3| | 3 | 3 | 3 | 4 |
| 4 | 2 | 4 | 1 |
| 5 | 2 | 5 | 3 |
| 6 | 2 | 6 | 1 |
How would I return in one query, groupedBy/distinct by setId (with set name as a left join?) to have a return like this:
[
setId: 2,
name: 'set name 2',
haveTotal: 5,
],
[
setId: 3,
name: 'set name 3',
haveTotal: 7,
]
Here is a raw MySQL query which should work. To convert this to Laravel should not be too much work, though you might need to use DB::raw once or twice.
SELECT
s.ID AS setId,
s.`SET` AS name,
COALESCE(SUM(ii.have), 0) AS haveTotal
FROM sets s
LEFT JOIN indexed_items ii
ON s.ID = ii.setId
GROUP BY
s.ID;
Demo
If you don't want to return sets having no entries in the indexed_items table, then you may remove the call to COALESCE, and you may also use an inner join instead of a left join.
Note that using SET to name your tables and columns is not a good idea because it is a MySQL keyword.
If you are using or want to use eloquent, you can do something like:
$sets = App\Sets::withCount('indexed_items')->get();
This will return a collection with a column name indexed_items_count
Obviously you will need to change depending on your model names.
Here are the docs
I always use in my project for count relation ship record.
$sets->indexed_items->count();

MySQL re-order row priority field values and also make them sequential

Given the following table:
+--------+-------------------+-----------+
| ID | Name | Priority |
+--------+-------------------+-----------+
| 1 | Andy | 1 |
| 2 | Bob | 2 |
| 3 | David | 8 |
| 4 | Edward | 9 |
| 5 | CHARLES | 15 |
+--------+-------------------+-----------+
I would like to move CHARLES to between Bob and David by Priority value (ignore the alphabetical list, this is just to make the desired result obvious).
(Also note the Priority values may not be sequential)
To do this I need to change CHARLES' current Priority (15) to Bob's Priority+1, and update David and Edward's Priority to Priority+1.
I can DO this if I know two things, the id of CHARLES and the Priority value of the row he must be after (Bob):
UPDATE mytable SET Priority =
IF(ID = :charles_id, :bob_priority + 1,
IF(Priority >= :bob_priority,
Priority + 1, Priority))
The PROBLEM or at least question is, how could I compress the resulting values to 1,2,3,4,5 instead of 1,2,3,9,10 - and do it in one shot?
Oracle has a "pseudo field" which is the index of the row, but I don't know of anything equivalent in MySQL.
The first part of the problem is fairly trivial...
DROP TABLE IF EXISTS priorities;
CREATE TABLE priorities
(ID SERIAL PRIMARY KEY
,Name VARCHAR(12) NOT NULL
,Priority INT NOT NULL
,INDEX(priority)
);
INSERT INTO priorities VALUES
(101,'Andy',1),
(108,'Bob',2),
(113,'David',8),
(124,'Edward',9),
(155,'CHARLES',15);
UPDATE priorities a
JOIN
( SELECT x.id,x.name, #i:=#i+1 priority FROM priorities x, (SELECT #i:=0) vars ORDER BY id) b
ON b.id = a.id
SET a.priority = b.priority;
SELECT * FROM priorities
+-----+---------+----------+
| ID | Name | Priority |
+-----+---------+----------+
| 101 | Andy | 1 |
| 108 | Bob | 2 |
| 113 | David | 3 |
| 124 | Edward | 4 |
| 155 | CHARLES | 5 |
+-----+---------+----------+

Joining three tables to get a list of tags

I have these three tables:
user_submitted_value
id | owner_id | value |
-----------------------
1 | 1 | 1337 |
2 | 2 | 1337 |
3 | 2 | 1337 |
4 | 1 | 1337 |
tag
id | owner_id | text |
---------------------------
1 | 1 | 'Tag 01' |
2 | 1 | 'Tag 02' |
3 | 1 | 'Tag 03' |
4 | 2 | 'Tag 04' |
user_submitted_value_tag
id | owner_id | tag_id | value_id |
-----------------------------------
1 | 1 | 1 | 1 |
2 | 1 | 2 | 1 |
3 | 1 | 3 | 1 |
So basically, users can submit values and enter any number of freetext tags to attach to that value. I need to store the tags as belonging to a specific user, and I need to be able to count how many times they've used each tag.
What I want to accomplish is a query that gets rows from user_submitted_value with the tags appended onto them. For example:
Query value with id 1:
id | owner_id | value | tags |
------------------------------------------------------
1 | 1 | 1337 | "'Tag 01','Tag 02','Tag 03'" |
Query all values belonging to user with id 1:
id | owner_id | value | tags |
------------------------------------------------------
1 | 1 | 1337 | "'Tag 01','Tag 02','Tag 03'" |
4 | 1 | 1337 | "" |
I know I need to JOIN one or more times, somehow, but I am not comfortable enough with SQL to figure out exactly how.
This seems like a rather arcane data format -- particularly because owner_id is repeated in all the tables.
In any case, I think the basic query that you want to get the values and tags for a given user looks like this:
select usv.owner_id,
group_concat(distinct usvt.value_id) as values,
group_concat(distinct t.text) as tags
from user_submitted_value usv join
user_submitted_value_tag usvt
on usv.value_id = usvt.value_id and usv.owner_id = usvt.owner_id join
tags t
on usvt.tag_id = t.id and usvt.owner_id = t.owner_id
group by usv_owner_id;
Here's the final solution in my case. Heavily based on the answer submitted by Gordon Linoff.
SELECT
user_submitted_value.id,
user_submitted_value.creator_id,
user_submitted_value.value,
group_concat(tag.text) AS tags
FROM user_submitted_value
LEFT JOIN user_submitted_value_tag
ON user_submitted_value.id = user_submitted_value_tag.value_id
AND user_submitted_value.creator_id = user_submitted_value_tag.creator_id
LEFT JOIN tag
ON user_submitted_valuetag.tag_id = tag.id
AND user_submitted_value_tag.creator_id = tag.creator_id
WHERE user_submitted_value.id = ?
GROUP BY user_submitted_value.id
The WHERE clause on the second JOIN can be modified to get all values for a given user.

Sort table records in special order

I have table:
+----+--------+----------+
| id | doc_id | next_req |
+----+--------+----------+
| 1 | 1 | 4 |
| 2 | 1 | 3 |
| 3 | 1 | 0 |
| 4 | 1 | 2 |
+----+--------+----------+
id - auto incerement primary key.
nex_req - represent an order of records. (next_req = id of record)
How can I build a SQL query get records in this order:
+----+--------+----------+
| id | doc_id | next_req |
+----+--------+----------+
| 1 | 1 | 4 |
| 4 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 1 | 0 |
+----+--------+----------+
Explains:
record1 with id=1 and next_req=4 means: next must be record4 with id=4 and next_req=2
record4 with id=5 and next_req=2 means: next must be record2 with id=2 and next_req=3
record2 with id=2 and next_req=3 means: next must be record3 with id=1 and next_req=0
record3 with id=3 and next_req=0: means that this is a last record
I need to store an order of records in table. It's important fo me.
If you can, change your table format. Rather than naming the next record, mark the records in order so you can use a natural SQL sort:
+----+--------+------+
| id | doc_id | sort |
+----+--------+------+
| 1 | 1 | 1 |
| 4 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 1 | 4 |
+----+--------+------+
Then you can even cluster-index on doc_id,sort for if you need to for performance issues. And honestly, if you need to re-order rows, it is not any more work than a linked-list like you were working with.
Am able to give you a solution in Oracle,
select id,doc_id,next_req from table2
start with id =
(select id from table2 where rowid=(select min(rowid) from table2))
connect by prior next_req=id
fiddle_demo
I'd suggest to modify your table and add another column OrderNumber, so eventually it would be easy to order by this column.
Though there may be problems with this approach:
1) You have existing table and need to set OrderNumber column values. I guess this part is easy. You can simply set initial zero values and add a CURSOR for example moving through your records and incrementing your order number value.
2) When new row appears in your table, you have to modify your OrderNumber, but here it depends on your particular situation. If you only need to add items to the end of the list then you can set your new value as MAX + 1. In another situation you may try writing TRIGGER on inserting new items and calling similar steps to point 1). This may cause very bad hit on performance, so you have to carefully investigate your architecture and maybe modify this unusual construction.