Selecting by two fields in UNION ALL and ordering between rows - mysql

I have a small problem I have a MySQL Table called categories that looks like so:
| id | parent_id | name | position | status | ... |
| 1 | 0 | A | 1 | 1 | ... |
| 2 | 1 | A1 | 2 | 1 | ... |
| 3 | 2 | A2 | 1 | 1 | ... |
| 4 | 1 | A3 | 1 | 1 | ... |
| 5 | 0 | B | 2 | 1 | ... |
Basically a table that holds all my categories with multi level depth, each category that is a sub category has a parent_id > 0. At any given time I am using the following SQL statement to only select the top level categories that have parent_id = 0 and their first level children that have parent_id = id of category with parent_id = 0.
SELECT * FROM categories WHERE parent_id = 0 UNION ALL SELECT c.* FROM categories c
INNER JOIN categories p ON c.parent_id = p.id WHERE p.parent_id
1. Problem
This will always select all categories even if they have status = 0 which is a problem. So I tried adding AND status = 1 to both WHERE statements however I this does not work as all categories get selected regardless of status.
Second thing I tried is since on first select I'm only selecting categories with parent_id = 0 and then making a UNION with all categories where parent_id matches id's of those from the first select I could just add AND status = 0 to only the first SELECT WHERE and then the parent category that has status 0 would not get selected and thus when UNION is made it's children would also not get selected. However if I just add AND status = 1 to the first WHERE then I only get the categories with parent_id & status = 1 and nothing happens in the UNION.
2. Problem
Once I do this select I would have to order the categories and subcategories by the position value this can be done in PHP using the sort functions but this is pretty expansive when you have some 8000 categories.
I tried adding ORDER BY id ASC, position ASC. After my last WHERE statement I would like it so that I would get an output like so:
| id | parent_id | position |
| 1 | 0 | 1 |
| 3 | 1 | 1 |
| 6 | 1 | 2 |
| 2 | 0 | 2 |
| 4 | 2 | 1 |
Or even:
| id | parent_id | position |
| 1 | 0 | 1 |
| 2 | 0 | 2 |
| 3 | 1 | 1 |
| 6 | 1 | 2 |
| 4 | 2 | 1 |
I thot that second example would be pretty trivial and I got it to work when my statement did not include UNION ALL. But with the UNION my statement just like with the status = 1 returns only parent categories.
SELECT * FROM categories WHERE parent_id = 0 UNION ALL SELECT c.* FROM categories c
INNER JOIN categories p ON c.parent_id = p.id WHERE p.parent_id ORDER BY parent_id ASC, position ASC;
Since all parents have parent_id = 0 they would be listed first however ordered by position and then all children would be ordered together by parent_id first and then by position.

If I have understood correctly, I think you want the following:
SELECT DISTINCT c.* FROM categories c, categories p
WHERE c.status<>0 and (c.parent_id = 0 OR (c.parent_id=p.id and p.parent_id=0))
ORDER BY c.id,c.position

Related

Get a row with min(priority) from two tables

I need to query data from multiple tables, below are the major tables(simplified).
Project
+-----+-------+-------+
| pid | pname | status| //status: 0 = pending, 1 = complete
+-----+-------+-------+
| 1 | Proj1 | 0 |
| 2 | Proj2 | 1 |
| 3 | Proj3 | 0 |
+-----+-------+-------+
Module
+-----+--------+-------+----------+-----------------+
| mid | pid | status| priority |modulecategoryid |
+-----+--------+-------+----------+-----------------+
| 1 | 1 | 1 | 1 | 1 |
| 2 | 1 | 0 | 2 | 3 |
| 3 | 3 | 1 | 1 | 1 |
| 4 | 3 | 0 | 2 | 3 |
| 5 | 3 | 0 | 3 | 5 |
+-----+--------+-------+----------+-----------------+
Task
+----+--------+-------+----------+-----------------+
| id | mid | status| priority | taskcategoryid |
+----+--------+-------+----------+-----------------+
| 1 | 2 | 1 | 2 | 2 |
| 2 | 2 | 0 | 1 | 1 |
| 3 | 4 | 1 | 1 | 2 |
| 4 | 4 | 1 | 2 | 3 |
| 5 | 4 | 0 | 3 | 4 |
| 6 | 5 | 0 | 1 | 1 |
+----+--------+-------+----------+-----------------+
I am trying to get the pending tasks for all the pending projects that can be started first based on the module priority and task priority. i.e. for Proj3, module with priority 1 is completed so i should get first priority pending task for module 2.
I need to get the most prior task for each pending project with modulecategoryid and taskcategoryid for get its related info like this
+-----+--------+-----+------------------+----------------+
| pid | mid | tid | modulecategoryid | taskcategoryid |
+-----+--------+-----+------------------+----------------+
| 1 | 2 | 2 | 3 | 2 |
| 2 | 4 | 5 | 3 | 4 |
+----+---------+-----+------------------+----------------+
I am new to MySql and I have tried query with multiple joins and group it by projectids and min(priority) to get desired result. But columns that are not in group by are fetched randomly from the aggregate.
I have seen this answer SQL Select only rows with Max Value on a Column but that solves the problem for data in only one table.
Shall I get some help on that?
I can post my query if needed but it is getting wrong data.
SQL Select only rows with Max Value on a Column has the right approach. You just need to do it twice.
First create a subquery a showing the highest priority task for each module.
Then create a subquery b showing the highest priority Module for each project.
Then join your three tables and two subqueries together.
Here's a. It shows the highest priority Task id for each Module mid. (http://sqlfiddle.com/#!9/7eb1f3/4/0)
SELECT Task.id, Task.mid
FROM Task
JOIN (
SELECT MAX(priority) priority,
mid
FROM Task
WHERE status = 0
GROUP BY mid
) q ON q.priority = Task.priority AND q.mid = Task.mid
Here's b. It works the same way as a and shows the highest priority Module mid for each Project pid. (http://sqlfiddle.com/#!9/7eb1f3/3/0)
SELECT Module.mid, Module.pid
FROM Module
JOIN (
SELECT MAX(priority) priority,
pid
FROM Module
WHERE status = 0
GROUP BY pid
) q ON q.priority = Module.priority AND q.pid = Module.pid
Then you need a big JOIN to pull everything together. In outline it looks like this.
SELECT Project.pid, Project.pname,
Module.mid, Task.id tid,
Module.modulecategoryid, Task.taskcategoryid
FROM Project
JOIN ( /* the subquery called b */
) b ON Project.pid = b.pid
JOIN Module ON b.mid = Module.mid
JOIN ( /* the subquery called a */
) a ON Module.mid = a.mid
JOIN Task ON a.id = Task.id
WHERE Task.status = 0
The actual query looks like this, with the subqueries put in. (http://sqlfiddle.com/#!9/7eb1f3/2/0)
SELECT Project.pid, Project.pname,
Module.mid, Task.id tid,
Module.modulecategoryid, Task.taskcategoryid
FROM Project
JOIN (
SELECT Module.mid, Module.pid
FROM Module
JOIN (
SELECT MAX(priority) priority, pid
FROM Module
WHERE status = 0
GROUP BY pid
) q ON q.priority = Module.priority
AND q.pid = Module.pid
) b ON Project.pid = b.pid
JOIN Module ON b.mid = Module.mid
JOIN (
SELECT Task.id, Task.mid
FROM Task
JOIN (
SELECT MAX(priority) priority, mid
FROM Task
WHERE status = 0
GROUP BY mid
) q ON q.priority = Task.priority
AND q.mid = Task.mid
) a ON Module.mid = a.mid
JOIN Task ON a.id = Task.id
WHERE Task.status = 0
The secret to this is understanding that subqueries are virtual tables that you can join to each other or to ordinary tables. The skill you need is sorting out the combination of physical and virtual tables you need, and the join sequence.

Extracting record from table skip record of other table

Having two tables
1. Parent Users
2. Child Users
Child User table also has records of Parent Users.
I want an output of All Childs only.
The better way to take one more column in child User with flag ( 1 for parant user, 0 for child user ) like:
child users
-------------------------------------
| id | name | by_id | flag |
-------------------------------------
| 1 | x | 1 | 1 |
| 2 | a | 1 | 0 |
| 3 | b | 1 | 0 |
| 4 | z | 3 | 1 |
| 5 | c | 3 | 0 |
| 6 | y | 2 | 1 |
| 7 | d | 2 | 0 |
| 8 | e | 1 | 0 |
To get all child entries:
SELECT id, name FROM child_users WHERE flag = 0;
I prefer to solve this using a LEFT JOIN of the child table against the parent table:
SELECT t1.*
FROM ChildUsers t1
LEFT JOIN ParentUsers t2
ON t1.by_id = t2.id AND
t1.name = t2.name
WHERE t2.name IS NULL
The trick here is the WHERE clause WHERE t2.name IS NULL, which will discard any records which correspond to parents.
As #SHAZ pointed out in his comment, the only way to identify a child entry as being a potential parent is by using the name field. In the event that a parent and child who are different happen to have the same, then the child could be incorrectly filtered out from the result set.
Demo Here:
SQLFiddle
This one:
SELECT a.id, a.name,a.by_id from
(select id,name,by_id from tbl_child) as a
left JOIN
(select id, name from tbl_parent)as b
on a.name = b.name where b.name is null
It return an output as you need.

MySQL getting maximum of column with fallback to second column

+------+---------+--------+---------+---------+---------+
| id | user_id | obj_id | created | applied | content |
+------+---------+--------+---------+---------+---------+
| 1 | 1 | 1 | 1 | 1 | ... |
| 2 | 1 | 2 | 1 | 1 | ... |
| 3 | 1 | 1 | 1 | 2 | ... |
| 4 | 1 | 2 | 2 | 2 | ... |
| 5 | 2 | 1 | 1 | 1 | ... |
| 6 | 2 | 2 | 1 | 1 | ... |
+------+---------+--------+---------+---------+---------+
I have a table similar to the one above. id, user_id and obj_id are foreign keys; created and applied are timestamps stored as integers. I need to get the entire row, grouped by user_id and obj_id, with the maximum value of applied. If two rows have the same applied value, I need to favour the maximum value of created. So for the above data, my desired output is:
+------+---------+--------+---------+---------+---------+
| id | user_id | obj_id | created | applied | content |
+------+---------+--------+---------+---------+---------+
| 1 | 1 | 1 | 1 | 1 | ... |
| 4 | 1 | 2 | 2 | 2 | ... |
| 5 | 2 | 1 | 1 | 1 | ... |
| 6 | 2 | 2 | 1 | 1 | ... |
+------+---------+--------+---------+---------+---------+
My current solution is to get everything ordered by applied then created:
select * from data order by applied desc created desc;
and sort things out in the code, but this table gets pretty big and I'd like an SQL solution that just gets the data I need.
select *
from my_table
where id in (
/* inner subquery b */
select max(id)
from my_table where
(user_id, obj_id, applied, created) in (
/* inner subquery A */
select user_id, obj_id, max(applied), max(created)
from my_table
group by user_id, obj_id
)
);
Then inner subquery A return the (distinct) rows having user_id, obj_id, max(applied), max(created). Using these with in clause the subquery B retrive a list of single ID each realated the a row with a proper value of user_id, obj_id, max(applied), max(created). so you have a collection of valid id for getting your result.
The main select use these ID for select the result you need.
Thanks to Mark Heintz in the comments, this answer got me to where I need to be.
SELECT
data.id,
data.user_id,
data.obj_id,
data.created,
data.applied,
data.content
FROM data
LEFT JOIN data next_max_applied ON
next_max_applied.user_id = data.user_id AND
next_max_applied.obj_id = data.obj_id AND (
next_max_applied.applied > data.applied OR (
next_max_applied.applied = data.applied AND
next_max_applied.created > data.created
)
)
WHERE next_max_applied.applied IS NULL
GROUP BY user_id, obj_id;
Go read the answer for details on how it works; the left join tries to find a more recently applied row for the same user and object. If there isn't one, it will find a row applied at the same time, but created more recently.
The above means that any row without a more recent row to replace it will have a next_max_applied.applied value of null. These rows are filtered for by the IS NULL clause.
Finally, the group by clause handles any rows that have identical user, object, applied and created columns.

Count rows with specific value over multiple rows

Its very hard for to set a proper title, because I dont know how I describe my problem.
I have a table like this:
dlID | dl_seID | dlEpisode | dlFlag
___________________________________
1 | 1 | 1 | 0
2 | 1 | 2 | 1
3 | 1 | 3 | 1
4 | 2 | 1 | 1
5 | 2 | 2 | 0
6 | 3 | 1 | 0
What i want is a select query where I get something like this:
dlID | dl_seID | dlEpisode | dlFlag | dlFlagCount
_________________________________________________
1 | 1 | 1 | 0 | 2
2 | 1 | 2 | 1 | 2
3 | 1 | 3 | 1 | 2
4 | 2 | 1 | 1 | 1
5 | 2 | 2 | 0 | 1
6 | 3 | 1 | 0 | 0
dlFlagCount shoud be a counter of dlFlag = 1 where dl_seID = dl_seID.
Second try:
I need a value where I see how many Flags have the value 1 with the same dl_seID.
Is that possible?
I hope you guys know what I want^^
Regards
Try this:
select
a.*,
ifnull(b.ctflags,0)
from
tablea a left join
( select dl_seID, count(dlFlag) ctflags
from tablea
where dlFlag=1
group by dl_seID ) b on (a.dl_seID = b.dl_seID)
The left join is just to get the registry with 0 flags
See the fiddle: http://sqlfiddle.com/#!2/ef9b0/5
EDIT:
As op requested some explanation, here it goes:
What you asked is to count the amount of flags by the dl_seID and to do that you need to do this you separeta your problems, first you get the count for the dl_seID by flags, this is this subquery:
select dl_seID, count(dlFlag) ctflags
from tablea
where dlFlag=1
group by dl_seID
This became a 'separe table' or a new group of data, whatever you wanna call it. Then you have to join this with your original data (from your table) like the query for answer.
The left join part is because maybe there are some data that wont complain with where dlFlag=1 therefore if you want to get then as 0 you have to bring all values from table that exists or not on our created subgroup. And this ifnull(b.ctflags,0) is for theese data data exists on your table but has no flags (for your problem). If you use just b.ctflags it will bring null.
SELECT x.*
, COALESCE(y.flagcount,0) flagcount
FROM my_table x
LEFT
JOIN
( SELECT seID
, COUNT(*) flagcount
FROM my_table
WHERE flag = 1
GROUP
BY seid
) y
ON y.seid = x.seid;

how to write this self join based on three columns

Hello there I have a following table
------------------------------------------
| id | language | parentid | no_daughter |
------------------------------------------
| 1 | 1 | 0 | 2 |
------------------------------------------
| 1 | 1 | 0 | 2 |
------------------------------------------
| 2 | 1 | 1 | 1 |
------------------------------------------
| 2 | 2 | 1 | 1 |
------------------------------------------
| 3 | 1 | 1 | 0 |
------------------------------------------
| 3 | 2 | 1 | 0 |
------------------------------------------
| 4 | 1 | 2 | 0 |
------------------------------------------
| 4 | 2 | 2 | 0 |
------------------------------------------
| 5 | 1 | 2 | 0 |
------------------------------------------
| 5 | 2 | 2 | 1 |
-----------------------------------------
| 5 | 1 | 4 | 1 |
------------------------------------------
| 5 | 2 | 4 | 1 |
------------------------------------------
Scenario
Every record has more than one rows in table with different language ids. parentid tells who is the parent of this record. no_daughter columns tells against each record that how many child one record has. Means in Ideal scenario If no_daughter has value 2 of id = 1 , it means 1 should be parentid of 2 records in same table. But If a record has more than one exitance with respect to language, it will be considered as one record.
My Problem
I need to find out those records where no_daughter value is not correct. It means if no_daughter is 2, there must be two records whoes parentid has that id. In above case record with id = 1 is valid. But record having id = 2 is not valid because the no_daughter = 1 but actual daughter of this record is 2. Same is the case with id=4
Can any body tell me how can I find these faulty records?
Updated after answers
Ken Clark has and shola has given answer which return same result for example shola query is
SELECT DISTINCT
id
FROM
tbl_info t
INNER JOIN
(SELECT
parentid,
COUNT(DISTINCT id) AS childs
FROM
tbl_info
GROUP BY parentid) AS parentchildrelation
ON t.id = parentchildrelation.parentid
AND t.no_daughters != parentchildrelation.childs
This query is returning those ids who have been used as parentid somewhere in table but having wrong no_daughter values. But not returning ids that has value in no_daugter columns but have not been used as parentid any where in table. For exampl id = 5 has no_daughter = 1 but it is not used as parentid in table. So it is also a faulty record. But above query is not capturing such records.
Any help will be much appreciated.
Try this:
SELECT DISTINCT
id
FROM
tbl_info t
Left JOIN
(SELECT
parentid,
COUNT(DISTINCT id) AS childs
FROM
tbl_info
GROUP BY parentid) AS parentchildrelation
ON t.id = parentchildrelation.parentid
Where t.no_daughters != parentchildrelation.childs
Try this:
SELECT id FROM tinfo t inner join
(SELECT parentid, COUNT(distinct language ) as childs FROM tinfo group by parentid) as summary
on t.id=summary.parentid and t.no_daughters!= summary.childs
try this
Select Distinct * From tablename t
Left Join
(
Select COUNT(t1.Id) Doughter,t1.parentid,t1.language From tablename t1 Group By t1.parentid,t1.language
)tbl
On t.id=tbl.parentid And tbl.language=t.language And t.no_daughter<>tbl.Doughter