How to perform recursive SQL where statement? - sql

Update
Thank you to #forpas and #trincot for sharing their solutions and ideas below. I got it working with the following code (demo):
with recursive cte_comments as (
select
*
from
comments
where parent_comment_id = 1
union all
select
this_execution.*
from
cte_comments prev_execution
inner join comments this_execution
on this_execution.parent_comment_id = prev_execution.comment_id
)
select * from cte_comments
Original post
I have the following comments table and data in a SQLite database:
Table structure
-----------------------------------------
| Column | Type |
+++++++++++++++++++++++++++++++++++++++++
| comment_id | integer |
+---------------------------------------+
| parent_comment_id | integer |
+---------------------------------------+
| comment_text | text |
-----------------------------------------
Table data
--------------------------------------------------------------------
| comment_id | parent_comment_id | comment_text |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
| 1 | | First comment, level 1 |
--------------------------------------------------------------------
| 2 | 1 | First comment, level 2 |
--------------------------------------------------------------------
| 3 | 2 | First comment, level 3 |
--------------------------------------------------------------------
| 4 | 2 | First comment, level 3 |
--------------------------------------------------------------------
| 5 | | Second comment, level 1 |
--------------------------------------------------------------------
| 6 | 5 | Second comment, level 2 |
--------------------------------------------------------------------
| 7 | 6 | Second comment, level 3 |
--------------------------------------------------------------------
The data is for nested comment section in a website where comment_id is unique and parent_comment_id can be null. Two or more comments can be under one same parent_comment_id. The comment_text column contains random strings.
Question
How to perform SQL search that will return back all children under a parent comment? For example, when I search for all comments under comment 1, I want comment 2, 3 and 4 (all comments that start with First comment) to return back. And when I search for all comments under comment 5, I want comment 6 and 7 (all comments that start with Second comment) to return back.
Do I need to have an intermediary/join table? Do I need to alter my table structure? Or, do I need to use another database engine to make it happen?

With a recursive CTE:
with recursive cte as (
select * from comments
where parent_comment_id = 1
union all
select t.*
from cte c inner join comments t
on t.parent_comment_id = c.comment_id
)
select * from cte
See the demo.
Results:
| comment_id | parent_comment_id | comment_text |
| ---------- | ----------------- | ---------------------- |
| 2 | 1 | First comment, level 2 |
| 3 | 2 | First comment, level 3 |
| 4 | 2 | First comment, level 3 |

If your version of sqlite is 3.8.4 or greater, then you can use the recursive with clause:
with recursive cte (id, name, parent_id) as (
select comment_id,
comment_text,
parent_comment_id
from comments
where parent_comment_id = 1
union all
select c.comment_id,
c.comment_text,
c.parent_comment_id
from comments c
inner join cte
on c.parent_comment_id = cte.comment_id
)
select * from cte;
In the condition parent_comment_id = 1 you would mention the id of the comment of which the descendants should be retrieved.

Related

Oracle SQL: Counting how often an attribute occurs for a given entry and choosing the attribute with the maximum number of occurs

I have a table that has a number column and an attribute column like this:
1.
+-----+-----+
| num | att |
-------------
| 1 | a |
| 1 | b |
| 1 | a |
| 2 | a |
| 2 | b |
| 2 | b |
+------------
I want to make the number unique, and the attribute to be whichever attribute occured most often for that number, like this (This is the end-product im interrested in) :
2.
+-----+-----+
| num | att |
-------------
| 1 | a |
| 2 | b |
+------------
I have been working on this for a while and managed to write myself a query that looks up how many times an attribute occurs for a given number like this:
3.
+-----+-----+-----+
| num | att |count|
------------------+
| 1 | a | 1 |
| 1 | b | 2 |
| 2 | a | 1 |
| 2 | b | 2 |
+-----------------+
But I can't think of a way to only select those rows from the above table where the count is the highest (for each number of course).
So basically what I am asking is given table 3, how do I select only the rows with the highest count for each number (Of course an answer describing providing a way to get from table 1 to table 2 directly also works as an answer :) )
You can use aggregation and window functions:
select num, att
from (
select num, att, row_number() over(partition by num order by count(*) desc, att) rn
from mytable
group by num, att
) t
where rn = 1
For each num, this brings the most frequent att; if there are ties, the smaller att is retained.
Oracle has an aggregation function that does this, stats_mode().:
select num, stats_mode(att)
from t
group by num;
In statistics, the most common value is called the mode -- hence the name of the function.
Here is a db<>fiddle.
You can use group by and count as below
select id, col, count(col) as count
from
df_b_sql
group by id, col

How to order by other table?

I'm working on Stackoverflow-like system for my school project on ASPX. I'm already way beyond what the teacher thought us, so I decided to use the stupid way of counting down and upvotes - Having a spacial table for each vote.
Here are both of my tables:
comments
id | content | by | bestanswer
1 | demo | Vlad | 0
2 | sample | Hagay | 1
3 | chacking| Gil | 0
4 | trying | Teddy | 0
...| ... | ... | ...
votes
postid | username | upvote
1 | Gil | 1
1 | Hagay | 1
1 | Teddy | -1
2 | Gil | -1
3 | Vlad | -1
2 | Gil | 1
... | ... | ...
How can I, using SQL, SELECT all and set the one with bestanswer=1 as the first, and then order by the ammount of upvotes (2, 1, 3)?
You can aggregate the "upvotes" in a subquery, then JOIN to that table for ordering:
SELECT c.*,v.upvotes
FROM comments c
LEFT JOIN (SELECT postid,SUM(upvote) as upvotes
FROM votes
GROUP BY postid
) v
ON c.id = v.postid
ORDER BY c.bestanswer DESC, upvotes DESC
I'm assuming that id and postid relate to each other, and that you want to return all fields from the comments table, and maybe the total votes as well.

SQL Server : return 3 items

Short version: I need to return a query with 3 items from another table and adding it to the existing table.
Long version:
Table A contains the following information:
| ID | Name | Date | Comment |
--------------------------------
| 1 | AJ | 9/11 | Howdy |
| 2 | AW | 9/13 | Hi |
| 3 | AK | 9/15 | Aloha |
| 4 | AW | 9/15 | Hello |
| 5 | AJ | 9/18 | Greetings |
I need Table B to resemble:
| ID | Comment | Comment2 | Comment3 |
--------------------------------------------
| 1 | Howdy | Aloha | Greetings |
I am running
SELECT TOP 3 *
FROM a
WHERE Name IN ('AJ','AK')
but that makes Table B appear like:
| ID | Name | Date | Comment |
--------------------------------
| 1 | AJ | 9/11 | Howdy |
| 3 | AK | 9/15 | Aloha |
| 5 | AJ | 9/18 | Greetings |
Is it even possible to get what I want?
Not entirely sure what you are after as you have id's for each comment, then your output has a single row with an id (where does this id come from for your output?) but this may be able to be expanded upon:
SELECT
[1] AS COMMENT1,
[2] AS COMMENT2,
[3] AS COMMENT3
FROM
TABLE_A
PIVOT (MAX(COMMENT) FOR id IN ([1],[2],[3])) AS PVT
Please try this , it helpful to you
select b.id, b.comment as comment
, (select comment from ##temp1 where id = b.id+2 ) as comment1
, (select comment from ##temp1 where id = b.id+4 ) as comment2
from ##temp1 b where b.id=1
I don't know if this will work, but i'm just throwing the idea here. Let me know if it works!
Insert into B Values (
( SELECT TOP 3 comment FROM a WHERE Name IN ('AJ','AK') limit 1 ),
( SELECT TOP 3 comment FROM a WHERE Name IN ('AJ','AK') limit 1,1 ),
( SELECT TOP 3 comment FROM a WHERE Name IN ('AJ','AK') limit 2,1 )
)
This is for MySQL. please change accordingly for MSSQL Server

Select rows appearing after a row with a given ID when sorted by criteria unrelated to the ID

Given the data in the table "people":
+----+-------+
| id | name |
+----+-------+
| 1 | Jane |
| 2 | Joe |
| 4 | John |
| 5 | Alice |
| 6 | Bob |
+----+-------+
And the order:
SELECT * FROM people ORDER BY name
... which would return:
+----+-------+
| id | name |
+----+-------+
| 5 | Alice |
| 6 | Bob |
| 1 | Jane |
| 2 | Joe |
| 4 | John |
+----+-------+
How could one write a query--including the order above--which would return only rows after the one with a given id, e.g., if given an id of 1, it would return:
+----+-------+
| id | name |
+----+-------+
| 2 | Joe |
| 4 | John |
+----+-------+
To be clear, the id is variable and not known before hand.
An approach using commonly supported SQL would be great, but I'm using PostgreSQL 9.2 and ActiveRecord 3.2 if they have anything additional of use, e.g., OVER() and ROW_NUMBER().
[Edit] I'd previously showed the wrong desired result set, including the row with the given id. But, the result set, as described in the question, should only include rows after the given ID.
select *
from people
where
name >= (
select name
from people
where id = 1
)
and id != 1
order by name
So far the simplest approach I've found for a situation where precision is needed, e.g., no missing or duplicate results across multiple calls with varying values for ID is to combine window functions and CTEs, as in:
WITH ordered_people AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY name) AS n
FROM people
ORDER BY name
)
SELECT *
FROM ordered_people
WHERE n > (SELECT n FROM ordered_people WHERE id = 1)
ORDER BY name
;

SQL Server: How to select second-highest parentid?

I have a SQL Server database with these pages:
+------------+--------------+-------------------------------+
| pageid | parentid | title |
+------------+--------------+-------------------------------+
| 1 | null | Home |
+------------+--------------+-------------------------------+
| 2 | 1 | News |
+------------+--------------+-------------------------------+
| 3 | 1 | User |
+------------+--------------+-------------------------------+
| 4 | 3 | Edit profile |
+------------+--------------+-------------------------------+
| 5 | 3 | Messages |
+------------+--------------+-------------------------------+
| 6 | 5 | View all |
+------------+--------------+-------------------------------+
How do I select the second-highest (in level) parentid for any row? So for pageid=6 (View all) it should return parentid->3 (User).
For a fixed and known number of steps up the parent hierachy, use explicit joins:
select l2.*
from table t
join table l1 on t.parent_id = l1.pageid
join table l2 on l1.parent_id = l2.pageid
where t.pageid = 6;
For an unknow number of steps in the hierachy, use a recursive cte, but you need a stop criteria, see Recursive Queries Using Common Table Expressions.
Try:
select max(thing) from table where thing < (select max(thing) from table)
I couldn't pick from your question and your sample whether you want pageid or parentid.