MySQL self join with limited hierarchy - mysql

Hi I have a self joining MySQL table I am using for comments and replies.
CREATE TABLE comments (id INT, parent_id INT, comment VARCHAR(50));
INSERT INTO comments VALUES
(1, 0, 'comment 1' ),
(2, 0, 'comment 2' ),
(3, 0, 'comment 3' ),
(4, 1, 'comment 1 - reply 1' ),
(5, 0, 'comment 4' ),
(6, 3, 'comment 3 - reply 1' ),
(7, 1, 'comment 1 - reply 2' ),
(8, 0, 'comment 5' );
There is only ever one level of replies. That is, a reply can only ever be associated with a top level comment (where parent_id = 0).
I using the following query to show each top level comment (where parent_id = 0) and each of comments associated replies.
SELECT *
FROM comments
ORDER BY IF(parent_id = 0, id, parent_id) desc , parent_id != 0, id desc
Output:
id parent_id comment
-------------------------
8 0 comment 5
5 0 comment 4
3 0 comment 3
6 3 comment 3 - reply 1
2 0 comment 2
1 0 comment 1
7 1 comment 1 - reply 2
4 1 comment 1 - reply 1
The current query is working well for what I need.
My question is how can I limit the number of replies for each comment? eg. Show the latest 50 top level comments with a maximum of 2 replies for each comment.
Here is a SqlFiddle if it helps

Try this:
EDIT:
SELECT pc.id,
pc.parent_id,
pc.comment
FROM (
SELECT id,
parent_id,
comment,
#parentRank := #parentRank + 1 AS rank
FROM comments,
(SELECT #parentRank := 0) pcr
WHERE parent_id = 0
ORDER BY id DESC
) pc
WHERE pc.rank <= 5
UNION
SELECT cc.id,
cc.parent_id,
cc.comment
FROM (
SELECT id,
parent_id,
comment,
#childRank := if(#current_parent_id = parent_id, #childRank + 1, 1) AS rank,
#current_parent_id := parent_id
FROM comments,
(SELECT #childRank := 0) cr
WHERE parent_id in (
SELECT id
FROM (
SELECT id,
#parentRank := #parentRank + 1 AS rank
FROM comments,
(SELECT #parentRank := 0) pcr
WHERE parent_id = 0
ORDER BY id DESC
) pc
WHERE pc.rank <= 5
)
ORDER BY parent_id DESC,
id DESC
) cc
WHERE cc.rank <= 1
ORDER BY IF(parent_id = 0, id, parent_id) desc , parent_id != 0, id desc
I did a demo in SQLFiddler

First parameter of Limit (offset) controls the number of replies
SELECT *,
(SELECT COUNT(id) FROM comments r where r.parent_id = c.id) AS number_of_replies
FROM comments c
WHERE IFNULL((SELECT e.id FROM comments e WHERE e.parent_id != 0 AND
e.parent_id = c.parent_id ORDER BY e.id DESC LIMIT 2, 1), 0) < c.id
ORDER BY IF(parent_id = 0, id, parent_id) desc , parent_id != 0, id desc

Related

Count if avg is below/above X

I am trying to get the number of 'critics' and 'promoters' from average of ratings from a joined table on a specific group of questions
SELECT category
, SUM( IF( round(avg(items.value) ) <= 6, 1, 0) ) AS critics
, SUM( IF( round(avg(items.value) ) >= 9, 1, 0) ) AS promoters
FROM reviews
INNER JOIN items
ON reviews.id = items.review_id
AND items.question_id in (1, 2, 4)
GROUP BY category
However I get the error:
General error: 1111 Invalid use of group function
I think you should try with using having with it, something like below:
SELECT
category,
COUNT(items.id) AS critics
FROM reviews
INNER JOIN items ON reviews.id = items.review_id AND
items.question_id IN (1, 2, 4)
GROUP BY category
HAVING ROUND(AVG(items.value)) <= 6
First retrieve category wise rounded average value and then apply condition either it is critics and promoters.
-- MySQL
SELECT t.category
, CASE WHEN t.avg_value <= 6
THEN 1
ELSE 0
END critics
, CASE WHEN t.avg_value >= 9
THEN 1
ELSE 0
END promoters
FROM (SELECT category
, ROUND(AVG(items.value)) avg_value
FROM reviews
INNER JOIN items
ON reviews.id = items.review_id
AND items.question_id IN (1, 2, 4)
GROUP BY category) t
Please check this url for finding out pseudocode https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=2679b2be50c3059c73ab9754c612179c
First retrieve category and review_id wise rounded average value and then apply condition either it is critics and promoters.
SELECT t.category
, SUM(CASE WHEN t.avg_value <= 6
THEN 1
ELSE 0
END) critics
, SUM(CASE WHEN t.avg_value >= 9
THEN 1
ELSE 0
END) promoters
FROM (SELECT category
, items.review_id
, ROUND(AVG(items.value)) avg_value
FROM reviews
INNER JOIN items
ON reviews.id = items.review_id
AND items.question_id IN (1, 2, 4)
GROUP BY category
, items.review_id) t
GROUP BY t.category

Mysql increment column value by group

Would like to increment column between groups of the same parentid. See problem below:
ID Name Parent Pos
================================
1 Alex 1 0
2 Mary 1 0
3 John 1 0
4 Doe 2 0
5 Bob 2 0
6 Kate 2 0
EXPECTED RESULT
ID Name Parent Pos
================================
1 Alex 1 1
2 Mary 1 2
3 John 1 3
4 Doe 2 1
5 Bob 2 2
6 Kate 2 3
I would do this using two queries to select distinct values of the parent, then do a loop and update in sets but I feel there is a more efficient way!!
These problems can be easily solved by ranking function. As mysql doesn't support ranking function we've to go with alternative.
Check this query
-- for dense rank
SELECT
Id,
NAME,
Parent,
Pos
, case when #previousParent = rankTab.Parent THEN #runningGroup := #runningGroup + 1
else #runningGroup := 1 AND #previousParent := rankTab.Parent
END as denseRank
FROM
inc_col_val_by_group AS rankTab,
(SELECT #runningGroup := 0) AS b
, (select #previousParent := 0 ) as prev
ORDER BY rankTab.Parent -- order by Parent
--
-- -- below are the create table & insert the given records script
-- create the table
CREATE TABLE inc_col_val_by_group
(Id INT
, NAME CHAR(10)
, Parent INT
, Pos INT
)
-- insert some records
INSERT INTO inc_col_val_by_group(Id, NAME, Parent, Pos)
VALUES
(1, 'Alex', 1, 0)
, (1, 'Mary', 1, 0)
, (3, 'John', 1, 0)
, (4, 'Doe', 2, 0)
, (5, 'Bob', 2, 0)
, (6, 'Kate', 2, 0)
The most efficient way is to probably use variables:
select t.*,
(#rn := if(#p = parent, #p + 1,
if(#p := parent, 1, 1)
)
) as pos
from table t cross join
(select #p := 0, #rn := 0) init
order by parent, id;
SET #posn:=0;
SET #pid:=0;
SELECT IF(#pid=k.parentid,#posn:=#posn+1,#posn:=1) pos,#pid:=k.parentid pid, k.*
FROM kids k
ORDER BY parentid

Different outputs for each condition.get together group by all values - single mysql query

Here how my tables look like:
CREATE TABLE my_table(id INT,project_id VARCHAR(6),order_id VARCHAR(6),user_id VARCHAR(6),owner_id VARCHAR(6));
INSERT INTO my_table
VALUES
(1, 211541, 8614, 1605, 0),
(2, 211541, 8614, 16079, 1605),
(3, 210446, 0, 12312, 0),
(4, 208216, 0, 16467, 14499),
(5, 208216, 0, 14499, 0),
(6, 208216, 0, 14499, 0),
(7, 208216, 0, 16467, 14499),
(8, 209377, 0, 7556, 0),
(9, 209324, 0, 7556, 0),
(10,201038, 8602, 9390, 101);
I have to check split Multiple condtion:
Query Execution this kind of way.
order_id != 0
Initially goes to project_id,
(i.e)
1.project_id - 211541 then first condition (owner_id = 0) , select user_id
note:
- if not get user_id(empty result) - goes to second condition.
- if get user_id - do not go to second condtion.
2.project_id - 211541 - second condtion (owner_id != 0), select owner_id.
i got
my_user_id
1605
101
order_id = 0
(i.e)
1.project_id - 208216 then first condition (owner_id = 0) , select group by user_id
note:
- if not get user_id(empty result) - goes to second condition.
- if get user_id - do not go to second condtion.
2.project_id - 208216 - second condtion (owner_id != 0), select group by owner_id.
i got
my_user_id
123121449975567556
Finally, i need this answer - group by my_user_id
my_user_id
160510112312144997556
Note:
I need single query.
why not just use an IF?
SELECT
IF (order_id = 0, user_id, owner_id) AS new_val
FROM my_table
GROUP BY new_val
when looking at it more it seems like you need a few more ifs.. something like this?
SELECT
if(order_id <> 0,
if(owner_id = 0, user_id, owner_id),
if(user_id = 0, owner_id, user_id)
) AS new_val
FROM my_table
group by new_val
this is what i understand from your conditions
ill number them and then put them with the if conditions i'll build in a second
if the order_id is not 0, -- 1
check to see if the owner_id is 0,
if owner_id = 0 -- 2
then pull in user_id -- 3
else owner_id is not 0
and you pull in owner_id -- 4
to write this more like code..
if(order_id <> 0, if(owner_id <> 0, owner_id, user_id), some condition for when order_id is 0)
other case
if the order_id is 0
pull in user_id (grouped)
if user_id = 0 -- 5
then pull in owner_id -- 6
else user_id is not 0
and pull in user_id -- 7
to put this with the other part replace the some other condition for when its 0.
-- 1 2 3 4 5 6 7
if(order_id <> 0, if(owner_id = 0, user_id, owner_id), if(user_id = 0, owner_id, user_id))
now to format it so its readable
if(order_id <> 0, -- if its not 0
if(owner_id = 0, user_id, owner_id), -- true condition
if(user_id = 0, owner_id, user_id) -- false condition
)
am I correct?
You can use 'if'.
Eg
#result=if(1!=2,'yes','no');
Which would give result a value of 'yes'.
These can be nested to create complex conditions:
SET #value_a='no';
set #value_b='';
set #value_c=’test’;
SET #value_d=’’;
set #result=
(
select if(#value_a!=’no’,(SELECT column1 FROM table1 WHERE id= #value_a),
if(#value_b!='',#value_b,
if(#value_c!='',(select column2 from table2 where id=#value_a),
if(#value_d!='',(select column3 from table3 where id=#value_a),
‘I am a default value’
)
)
)
)
);
Giving whatever select column2 from table2 where id=#value_a gives.

MySQL Count frequency of records

Table:
laterecords
-----------
studentid - varchar
latetime - datetime
reason - varchar
students
--------
studentid - varchar -- Primary
class - varchar
I would like to do a query to show the following:
Sample Report
Class No of Students late 1 times 2 times 3 times 4 times 5 & more
Class A 3 1 0 2 0 0
Class B 1 0 1 0 0 0
My query below can show the first column results:
SELECT count(Distinct studentid), class FROM laterecords, students
WHERE students.studenid=laterecords.studentid AND
GROUP BY class
I can only think of getting the results for each column and store them into php arrays. Then echo them to table in HTML.
Is there any better SQL way to do the above? How to do up the mysql query ?
Try this:
SELECT
a.class,
COUNT(b.studentid) AS 'No of Students late',
SUM(b.onetime) AS '1 times',
SUM(b.twotime) AS '2 times',
SUM(b.threetime) AS '3 times',
SUM(b.fourtime) AS '4 times',
SUM(b.fiveormore) AS '5 & more'
FROM
students a
LEFT JOIN
(
SELECT
aa.studentid,
IF(COUNT(*) = 1, 1, 0) AS onetime,
IF(COUNT(*) = 2, 1, 0) AS twotime,
IF(COUNT(*) = 3, 1, 0) AS threetime,
IF(COUNT(*) = 4, 1, 0) AS fourtime,
IF(COUNT(*) >= 5, 1, 0) AS fiveormore
FROM
students aa
INNER JOIN
laterecords bb ON aa.studentid = bb.studentid
GROUP BY
aa.studentid
) b ON a.studentid = b.studentid
GROUP BY
a.class
How about :
SELECT numlates, `class`, count(numlates)
FROM
(SELECT count(laterecords.studentid) AS numlates, `class`, laterecords.studentid
FROM laterecords,
students
WHERE students.studentid=laterecords.studentid
GROUP BY laterecords.studentid, `class`) aliastbl
GROUP BY `class`, numlates

Sql Server 2008 Select From table with AND style conditions in related tables

Given a model like this
ProductFacets contains the following data:
ProductId, FacetTypeId
1, 1
1, 2
2, 1
2, 3
3, 4
3, 5
4, 1
4, 2
I'd like to be able to select all Products which have a FacetTypeId of 1 AND 2.
The result set should contain ProductIds 1 and 4
This will return rows for products that have only facet types 1 and 2, and only those facets.
SELECT ProductId,
COUNT(*) AS FacetCountByProduct,
SUM(CASE WHEN FacetTypeId in (1, 2) THEN 1 ELSE 0 END) AS FacetCountSelectedFacets
FROM ProductFacets
GROUP BY ProductId
HAVING COUNT(*) = 2
and SUM(CASE WHEN FacetTypeId in (1, 2) THEN 1 ELSE 0 END) = 2
;
SELECT * FROM Product PROD WHERE PROD.ProductId IN(
SELECT P.ProductId pId FROM ProductFacets AS P
WHERE P.FacetTypeId = 1
AND EXISTS
(
SELECT *
FROM ProductFacets AS P1
WHERE P1.FacetTypeid = 2
AND P1.ProductId = pId
)
AND NOT EXISTS
(
SELECT *
FROM ProductFacets AS P2
WHERE P2.FacetTypeid NOT IN (1,2)
AND P2.ProductId = pId
)
)
There must be a better way to solve this, but it's the only one i can come up with
Just thought of a way to do this:
select distinct ProductId from ProductFacets
where ProductId in (select ProductId from ProductFacets where FacetTypeId = 1)
and ProductId in (select ProductId from ProductFacets where FacetTypeId = 2)