Multilevel Users in the Database table - mysql

I am having one table users in which I have one field 'id' and another field is 'parent id'. Also I have expected target field in the users table.
I am having list of users till the 8th level hierarchy. Where A is parent of B and B is parent of C and so on.
e.g
A level 0
|
B level 1
|
c level 2
Now when I am looking for user A. I want to get the all the sub users using sql query 'expected target'.
i.e. When I use id = id of A then I can see the expected target of A,B,C etc.
If expected_targets for A, B and C are 1000, 500 , 200 respectively the output should be like :
id parent_id expected_target
A_id 1000
B_id A_id 500
C_id B_id 200

this will do the job - http://sqlfiddle.com/#!2/0de1f/7:
select u1.id, u1.parent_id, u1.expected_target
from users u1
left join users u2 on u1.parent_id = u2.id
left join users u3 on u2.parent_id = u3.id
left join users u4 on u3.parent_id = u4.id
left join users u5 on u4.parent_id = u5.id
left join users u6 on u5.parent_id = u6.id
left join users u7 on u6.parent_id = u7.id
left join users u8 on u7.parent_id = u8.id
where :A_id in (u1.id, u2.id, u3.id, u4.id, u5.id,
u6.id, u7.id, u8.id, u8.parent_id)

SET search_path='tmp';
DROP TABLE targets CASCADE;
CREATE TABLE targets
( id integer not null primary key
, parent_id integer references targets(id)
, expected_target integer
);
INSERT INTO targets(id,parent_id,expected_target) VALUES
(1,NULL, 1000), (2,1, 500), (3,2, 200);
WITH RECURSIVE zzz AS (
SELECT t0.id, t0.parent_id
, 0::integer AS level
, t0.expected_target
FROM targets t0
WHERE t0.parent_id IS NULL
UNION
SELECT t1.id, t1.parent_id
, 1+zzz.level AS level
, t1.expected_target
FROM targets t1
JOIN zzz ON zzz.id = t1.parent_id
)
SELECT * FROM zzz
;
OUTPUT:
SET
DROP TABLE
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "targets_pkey" for table "targets"
CREATE TABLE
INSERT 0 3
id | parent_id | level | expected_target
----+-----------+-------+-----------------
1 | | 0 | 1000
2 | 1 | 1 | 500
3 | 2 | 2 | 200
(3 rows)
UPDATE: if you don't want the whole tree, the true tree and nothing but the tree but only a subtree part of it, you can of course change the conditions a bit:
WITH RECURSIVE zzz AS (
SELECT t0.id, t0.parent_id
, 0::integer AS level
, t0.expected_target
FROM targets t0
-- WHERE t0.parent_id IS NULL
WHERE t0.id = 2
UNION
SELECT t1.id, t1.parent_id
, 1+zzz.level AS level
, t1.expected_target
FROM targets t1
JOIN zzz ON zzz.id = t1.parent_id
)
SELECT * FROM zzz
;

As this is tagged with PostgreSQL:
with recursive users_tree as (
select id,
parent_id,
expected_target,
1 as level
from users
where id = 'A_id'
union all
select c.id,
c.parent_id,
c.expected_target,
p.level + 1
from users c
join users_tree p on c.parent_id = p.id
)
select *
from users_tree
MySQL is not advanced enough to support this. There you'll need to do a self-join for each level.

Related

Unique rows in join result

I have a tables of delas and curencies look like this
curecnies
id,code
pairs (the available pairs of curencies )
id to_sell to_buy
deals
id
user_id
pair_id
amount_to_sell
amount_to_buy
So I need to get all match deals which can execute , but I am can not get the unique matches.
Here is my sql query
select *
from deals as d1
join deals d2
on d1.sell_amount = d2.buy_amount and d1.buy_amount = d2.sell_amount
i am getting result look like this
id | user_id | pair_id | amount_to_buy | amount_to_sell | id | user_id | pair_id | amount_to_buy | amount_to_sell
1|2|1|1000|3000|2|1|2|3000|1000
2|1|2|3000|1000|1|2|1|1000|3000
You may try using a least/greatest trick here:
SELECT t1.*, t2.*
FROM
(
SELECT DISTINCT
LEAST(d1.id, d2.id) AS d1_id,
GREATEST(d1.id, d2.id) AS d2_id
FROM deals AS d1
INNER JOIN deals d2
ON d1.sell_amount = d2.buy_amount AND
d1.buy_amount = d2.sell_amount
) d
INNER JOIN deals t1
ON d.d1_id = t1.id
INNER JOIN deals t2
ON d.d2_id = t2.id;
The basic idea here is that the subquery labelled d finds a single pair of matched deal IDs, using a least/greatest trick. Then, we join twice to the deals table again to bring in the full information for each member of that deal pair.

SQL query to get children of entity into same row as parent entity

I have a table that has the following fields:
| entity_id | parent_entity_id | name | status |
|----------------------------------------------|
I'm attempting to write a query that displays every entity that doesn't have a parent and displays their children's name and status inline for a result like this:
| entity_id | name | child_entity_1_name | child_entity_1_status |...| child_entity_4_name | child_entity_4_status |
--------------------------------------------------------------------------------------------------------------------
I know the data is structured so that every entity has at least 3 children, but not every entity has 4 (therefore the ones with 3 will have NULL in the columns for the 4th child name and status). Furthermore, I know that no entity that has a parent is a parent itself.
From the introductory database classes I've taken, this seems like a complicated query. The part that's tripping me up is getting all of the sub-entities into the same row. I can get one sub-entity in the same row as its parent but can't get more than one.
EDIT: The database is basically a set of trees each with a height of 2. There are no grandparents.
PARENT_ENT_1 PARENT_ENT_2
| | | | | | | |
| | | | | | | |
C1 C2 C3 C4 C5 C6 C7 C8
Every row in my result query should represent one of these trees
This works: http://sqlfiddle.com/#!9/e1127f/27/0
But I feel like it should be much, much easier.
I basically had this:
SELECT P.entity_id as Parent_id, P.name as Parent_Name, C1.entity_id, C1.Name,
C2.entity_id, C2.Name, C3.entity_id, C4.Name, C4.entity_id, C4.Name
FROM entity P
JOIN entity C1 on C1.parent_entity_id = P.entity_id
JOIN entity C2 on C2.parent_entity_id = P.entity_id
JOIN entity C3 on C3.parent_entity_id = P.entity_id
LEFT JOIN entity C4 on C4.parent_entity_id = P.entity_id
WHERE P.parent_entity_id IS NULL
AND C1.entity_id < C2.entity_id
AND C2.entity_id < C3.entity_id
AND C3.entity_id < C4.entity_id
But of course that final join won't work as it is there, because the WHERE clause turns it into an INNER join.. Maybe someone will see an easy way to handle that part.
I ended up relenting and using a UNION, one half for parents with 3 children and the other for parents with 4.
Edit: Thank you Paul for making the final join work!
SELECT P.entity_id as Parent_id, P.name as Parent_Name, C1.entity_id, c1.Name,
C2.entity_id, c2.Name, C3.entity_id, c3.Name, C4.entity_id, c4.Name
FROM entity P
JOIN entity C1 on C1.parent_entity_id = P.entity_id
JOIN entity C2 on C2.parent_entity_id = P.entity_id
JOIN entity C3 on C3.parent_entity_id = P.entity_id
LEFT JOIN entity C4 on C4.parent_entity_id = P.entity_id
and c3.entity_id < c4.entity_id
WHERE p.parent_entity_id IS NULL
AND C1.entity_id < C2.entity_id
AND C2.entity_id < C3.entity_id
AND (3 = (SELECT COUNT(1)
FROM entity c
WHERE c.parent_entity_id = p.entity_id)
OR c4.entity_id is not null)
Here is a query for two children:
select
p.entity_id, p.name,
c1.name as child_entity_1_name,
c1.status as child_entity_1_status,
c2.name as child_entity_2_name,
c2.status as child_entity_2_status
from entities p
left join entities c1 on c1.entity_id = (
select c.entity_id
from entities c
where c.parent_entity_id = p.entity_id
order by c.entity_id asc
limit 1
offset 0
)
left join entities c2 on c2.entity_id = (
select c.entity_id
from entities c
where c.parent_entity_id = p.entity_id
order by c.entity_id asc
limit 1
offset 1
)
where p.parent_entity_id is null
For child_entity_3 you will use offset 2 and for child_entity_4 you will use offset 3.
But I would rather just use the following two queries
select p.entity_id, p.name
from entities p
where p.parent_entity_id is null;
select p.entity_id as parent_id, c.name, c.status
from entities p
join entities c on c.parent_entity_id = p.entity_id
where p.parent_entity_id is null
order by p.entity_id, c.entity_id;
and create the desired table in application language with a couple of simple loops.
SET #cNum := 0;
SET #prevParent := 0;
SELECT p.id, p.Name
, GROUP_CONCAT(IF(numberedChildren.childNum = 1, c.Name, NULL)) AS child_entity_1_name
, GROUP_CONCAT(IF(numberedChildren.childNum = 1, c.Status, NULL)) AS child_entity_1_status
, GROUP_CONCAT(IF(numberedChildren.childNum = 2, c.Name, NULL)) AS child_entity_2_name
, GROUP_CONCAT(IF(numberedChildren.childNum = 2, c.Status, NULL)) AS child_entity_2_status
, ...
FROM (
SELECT #cNum := IF(#prevParent <> orderedChildren.parent_id, #cNum + 1, 1) AS childNum
, orderedChildren.id AS child_id
, #prevParent := orderedChildren.parent_id AS parent_id
FROM (
SELECT parent_id, id
FROM sometable
ORDER BY parent_id, id
) AS orderedChildren
) AS numberedChildren
INNER JOIN sometable AS p ON numberedChildren.parent_id = p.id
INNER JOIN sometable AS c ON numberedChildren.child_id = c.id
GROUP BY p.id, p.Name
;
I think this script might work. It relies on GROUP_CONCAT, and pretty much any other aggregate function, ignoring null values.
You can probably also make it a single query (dropping the initial SET statements) by changing this line:
) AS orderedChildren
to
) AS orderedChildren, (SELECT #cNum AS cnInit, #prevParent AS ppInit) As init
but that is not my usual style for session variable init.
Edit: Also, ordered children may not NEED to be a subquery (you might be able to do the ORDER BY and childNum calculation in the same subquery) but such use of session variables can be...delicate.
This is a bit messy, and there's probably a better means of storing this, especially because this is only manually scalable.
Assuming a table:
CREATE TABLE
parents
(
entity_id INT PRIMARY KEY AUTO_INCREMENT,
parent_entity_id INT,
name VARCHAR(15),
`status` VARCHAR(15)
);
EDITED
With some sample data:
INSERT INTO
`parents`
(entity_id, parent_entity_id, name, `status`)
VALUES
(1, NULL, 'Parent1', 'sfsd'),
(2, 1, 'Child1A', 'sfsd'),
(3, 1, 'Child1B', 'sfsd'),
(4, 1, 'Child1C', 'sfsd'),
(5, NULL, 'Parent2', 'sfsd'),
(6, 5, 'Child2A', 'sfsd'),
(7, 5, 'Child2B', 'sfsd');
You can create a view, temporary table or permanent table (depending on your ultimate goal) that stores the following:
SET #row_number = 0;
SET #parent_id = 0;
SELECT
#row_number:=CASE
WHEN #parent_id = parent_entity_id THEN #row_number + 1
ELSE 1
END AS `child_num`,
entity_id,
#parent_id:= parent_entity_id as parent_entity_id,
name,
`status`
FROM
`parents`
WHERE
`parent_entity_id` IS NOT NULL
ORDER BY
parent_entity_id ASC,
entity_id ASC;
The above would be easier with SQL Server and using PARTITION BY and ROW_NUMBER, but this is a way around it.
Gives us:
Then, you could join that table/view 3 times, adding a second JOIN condition for the child number. This is demoed here, using a derived table due to the restrictions in SQL Fiddle with data modification, which I supposed could be done all 3 times, though you'd have to look into the efficiency and benchmarking.
http://sqlfiddle.com/#!9/5ddef3/3
Ultimately, it gives us:

MySQL One to Many remove specific 'many'

I have a database with a one to many relationship (venues, with associated categories) like so:
venue_id | venue_name
---------------------
1 | venue1
2 | venue2
and categories
venue_id | category_id
---------------------
1 | 5
2 | 7
1 | 8
2 | 5
I want to show all venues that HAVE category_id of 5, but dont have category_id of 7 and 8. I tried using a join like so:
SELECT distinct(`venue_to_category`.`venue_id`),`venue_name`
FROM `venue_to_category` INNER JOIN `venues`
ON `venues`.venue_id = `venue_to_category`.venue_id
WHERE `category_id` != 7
AND `category_id` != 8
AND `category_id` = 5
But it is not returning the correct results (in fact I am unsure the results it is returning)
2 Things in your case
Get data where category_id = 5 and not 7 or 8
Get data where category_id = 5 and not both 7 and 8
For the first one you can use
select
v.venue_id,
v.venue_name
from venues v
join categories c on c.venue_id = v.venue_id
where c.category_id = 5
AND NOT EXISTS
(
select 1 from categories c1 where v.venue_id = c1.venue_id
AND c1.category_id in (7,8)
);
For the 2nd one
select
v.venue_id,
v.venue_name
from venues v
join categories c on c.venue_id = v.venue_id
where c.category_id = 5
AND NOT EXISTS
(
select 1 from categories c1 where v.venue_id = c1.venue_id
AND c1.category_id in (7,8) having count(*) = 2
);
DEMO
When doing a search function of your site. Match and Against is better than Like sql statement. The field must be set to FullText match the term on the field:
SELECT vc.venue_id, venue_name FROM venue_to_category vc, venues v WHERE MATCH(category_id) AGAINST('-7 -8 +5');
You can also use the IN BOOLEAN MODE to allow operators in the sql statement. eg. ...
MATCH(category_id) AGAINST('-7 -8 +5' IN BOOLEAN MODE) ...
(-) minus sign that means nothing should match '-7' '-8'
(+) the word must be present in the match.
There are many other operators to be used. Refer to this page for more operator and explanation
Try
SELECT distinct `venue_to_category`.`venue_id` ,`venue_name`
FROM `venue_to_category` INNER JOIN `venues`
ON `venues`.venue_id = `venue_to_category`.venue_id
WHERE `category_id` <> 7
AND `category_id` <> 8
AND `category_id` = 5
or
SELECT distinct `venue_to_category`.`venue_id` ,`venue_name`
FROM `venue_to_category` INNER JOIN `venues`
ON `venues`.venue_id = `venue_to_category`.venue_id
WHERE `category_id` NOT IN (7,8)
AND `category_id` = 5
Note: distinct is not a function. And for your example you could leave it out entirely, because there are no duplicate rows.
SELECT v.id, v.name
FROM venues v
JOIN venue_to_category vc
ON vc.venue_id=v.id
AND vc.category_id=5
WHERE NOT EXISTS (
SELECT 1
FROM venue_to_category vci
WHERE vci.venue_id = v.id
AND vci.category_id IN (7,8)
);

MySql Join - How to get all rows besides related? HowTo SUM within a Joined Table?

I have 3 Tables (with prefix "pindex_")
names photos link
id, name id, filename photo_id, name_id
-------- ------------ -----------------
1 , leo 1 , aa.jpg 1 , 1
2 , liz 2 , bb.jpg
3 , ann
So Leo is connected to Photo aa.jpg
Now I would like to get all names that are not connected to aa.jpg.
Result should be: Liz (2), Ann (3).
I have tried with this but it isn't working so far:
select nm.name from pindex_names nm
join pindex_link pl on pl.name_id = nm.id
join pindex_photos pp on pl.photo_id = pp.id
where pl.name_id !='$id'
And my second question is how i can SUM up a Col within a joined table?
For exaple I would like to Count how many names are connected to a certain photo.
Here is one method:
select n.*
from names n
where not exists (select 1
from link l join
photos p
on l.photo_id = p.id
where l.name_id = n.id and
p.filename = 'aa.jpg'
);
SELECT *
FROM pindex_names
WHERE id NOT IN
(
SELECT name_id
FROM pindex_link pl
INNER JOIN pindex_photos pp
ON pl.photo_id = pp.id
WHERE filename='aa.jpg'
)

How to left join or inner join a table itself

I have this data in a table, for instance,
id name parent parent_id
1 add self 100
2 manage null 100
3 add 10 200
4 manage null 200
5 add 20 300
6 manage null 300
How can I left join or inner join this table itself so I get this result below?
id name parent
2 manage self
4 manage 10
6 manage 20
As you can I that I just want to query the row with the keyword of 'manage' but I want the column parent's data in add's row as the as in manage's row in the result.
Is it possible?
EDIT:
the simplified version of my actual table - system,
system_id parent_id type function_name name main_parent make_accessible sort
31 30 left main Main NULL 0 1
32 31 left page_main_add Add self 0 1
33 31 left page_main_manage Manage NULL 0 2
my actual query and it is quite messy already...
SELECT
a.system_id,
a.main_parent,
b.name,
b.make_accessible,
b.sort
FROM system AS a
INNER JOIN -- self --
(
SELECT system_id, name, make_accessible, sort
FROM system AS s2
LEFT JOIN -- search --
(
SELECT system_id AS parent_id
FROM system AS s1
WHERE s1.function_name = 'page'
) AS s1
ON s1.parent_id = s2.parent_id
WHERE s2.parent_id = s1.parent_id
AND s2.system_id != s1.parent_id
ORDER BY s2.sort ASC
) b
ON b.system_id = a.parent_id
WHERE a.function_name LIKE '%manage%'
ORDER BY b.sort ASC
result I get currently,
system_id main_parent name make_accessible sort
33 NULL Main 0 1
but I am after this,
system_id main_parent name make_accessible sort
33 self Main 0 1
You just need to reference the table twice:
select t1.id, t1.name, t2.id, t2.name
from TableA t1
inner join TableA t2
on t1.parent_id = t2.Id
Replace inner with left join if you want to see roots in the list.
UPDATE:
I misread your question. It seems to me that you always have two rows, manage one and add one. To get to "Add" from manage:
select system.*, (select parent
from system s2
where s2.parent_id = system.parent_id
and s2.name = 'add')
AS parent
from system
where name = 'manage'
Or, you might split the table into two derived tables and join them by parent_id:
select *
from system
inner join
(
select * from system where name = 'add'
) s2
on system.parent_id = s2.parent_id
where system.name = 'manage'
This will allow you to use all the columns from s2.
Your data does not abide to a child-parent hierarchical structure. For example, your column parent holds the value 10, which is not the value of any id, so a child-parent association is not possible.
In other words, there's nothing that relates the record 2,manage,null to the record 1,add,self, or the record 4,manage,null to 3,add,10, as you intend to do in your query.
To represent hierarchical data, you usually need a table that has a foreign key referencing it's own primary key. So your column parent must reference the column id, then you can express a child-parent relationship between manage and add. Currently, that's not possible.
UPDATED: Joining by parent_id, try:
select m.id, m.name, a.parent
from myTable m
join myTable a on m.parent_id = a.parent_id and a.name = 'add'
where m.name = 'manage'
Change the inner join to a left join if there may not be a corresponding add row.