SQL Server: How to select second-highest parentid? - sql

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.

Related

Get the records from a table which id does not exist in another table for a specific User

I've Two tables:
table name column names
----------- -------------
question id | name | description
review_labels id | question_id | user_id | review_label
I have a user_id (example: 9101)
Now I want to extract the questions from question table of which the question_id doesn't exist in review_labels table for user 9101.
example:
Question table
id | name | description
1 | .... | ....
2 | .... | ....
3 | .... | ....
4 | .... | ....
5 | .... | ....
6 | .... | ....
table ReviewLabel
id | question_id | user_id | review_label
1 | 1 | 9432 | 1
2 | 3 | 9442 | 5
3 | 1 | 9101 | 4
4 | 4 | 9101 | 5
5 | 4 | 9432 | 4
6 | 6 | 9432 | 4
The result of the query should be
id | name | description
2 | .... | ....
3 | .... | ....
5 | .... | ....
6 | .... | ....
I tried this following query:
Question.left_outer_joins(:review_labels).where(review_labels: {user_id: 9101, question_id: nil})
It create the following sql:
SELECT `questions`.* FROM `questions` LEFT OUTER JOIN `review_labels` ON `review_labels`.`question_id` = `questions`.`id` WHERE `review_labels`.`user_id` = 9101 AND `review_labels`.`question_id` IS NULL
Unfortunately the result is an empty list.
I can't understand what should I do to solve this problem.
Don't know ruby-on-rails but in SQL NOT EXISTS suits your problem better:
SELECT `questions`.*
FROM `questions` Q
WHERE NOT EXISTS
(SELECT 1
FROM `review_labels` RL
WHERE RL.`question_id` = Q.`id`
AND RL.`user_id` = 9101
)
Can you try?
The query doesn't work since .where(review_labels: {user_id: 9101 ... creates WHERE review_labels.user_id = 9101 AND review_labels.question_id = nil. The rows would have to meet both of those condition - not just the joined rows. If you wanted to do it though a join you would have to add the conditions to the join clause:
SELECT *
FROM "questions"
LEFT OUTER JOINS "review_labels" ON
"review_labels"."user_id" = 9101
AND "review_labels"."question_id" = "questions"."id"
WHERE "review_labels"."id" IS NULL
AR doesn't have a good way to use binds in join clauses so there are other solutions to the problem that are preferable.
The most straight forward way is a WHERE id NOT IN (subquery):
Question.where.not(
id: ReviewLabel.select(:question_id).where(
user_id: 9101
)
)
Another way of doing this is a NOT EXIST subquery like in Tinamzu's answer:
Question.where(
ReviewLabel.where(
user_id: 9101
).where(
ReviewLabel.arel_table[:question_id].eq(Question.arel_table[:id])
).arel.exists.not
)

How do I query a master-detail result having only the last detail row in MS-Access?

I have the following master table
.------------------.
| id | parent_name |
.------------------.
| 1 | Mike |
| 2 | Sarah |
| 3 | Danial |
| 4 | Alex |
.------------------.
And have the following child-table details:
.------------------------------------------.
| id | parent_id | child_name | birth year |
.------------------------------------------.
| 1 | 1 | mandy | 2000 |
| 2 | 1 | mark | 2003 |
| 3 | 1 | mathew | 2005 |
| 4 | 2 | sandy | 1998 |
| 5 | 2 | sharon | 2006 |
| 6 | 3 | david | 2001 |
.------------------------------------------.
In the example above, I delibretaly choose names of children with the first letter matching their parents' names just to make it easier to understand the relationship, even though each child is connected to his/her parent using the parent_id.
What I would like to have is a list of all parents (4 rows) and to have a matching 4 rows from the children table, selecting only the last born child of each respectful parent.
.-------------------------------.
| id | parent_name | last_child |
.-------------------------------.
| 1 | Mike | mathew |
| 2 | Sarah | sharon |
| 3 | Danial | david |
| 4 | Alex | (null) |
.-------------------------------.
In oracle, this is easy:
SELECT
p.id,
p.parent_name,
c.child_name last_child
FROM
parents_table p,
children_table c
WHERE
p.id = c.parent_id
AND c.birth_year = (SELECT MAX(birth_year) FROM children_table where parent_id = p.id)
But I am struggling to generate the same result in MS Access.. MS Access does not accept sub-queries (for select the child having the maximum birth year for the same parent).
Is there a better way to get the result in MS Access?
Access certainly does support subqueries, but you're using a crossjoin, so you will never get a null there.
Instead, left join and perform a subquery in the FROM clause.
Your query would fail identically in Oracle, by the way. There are no relevant differences between Access and Oracle here.
SELECT
p.id,
p.parent_name,
c.child_name last_child
FROM
parents_table p
LEFT JOIN
(
SELECT *
FROM children_table c
WHERE c.birth_year = (SELECT MAX(c2.birth_year) FROM children_table c2 WHERE c2.parent_id = c.parent_id)
) c
ON p.id = c.parent_id
Access sometimes performs better with an EXISTS, so a rewrite to that would be:
SELECT
p.id,
p.parent_name,
c.child_name last_child
FROM
parents_table p
LEFT JOIN
(
SELECT *
FROM children_table c
WHERE EXISTS(SELECT 1 FROM children_table c2 WHERE c2.parent_id = c.parent_id HAVING c.birth_year = MAX(c2.birth_year))
) c
ON p.id = c.parent_id
If you just want the child's name, you can use a correlated subquery:
select p.*,
(select top 1 child_name
from children_table as ct
where ct.parent_id = p.id
order by ct.birth_year desc, ct.child_name
) as youngest_child_name
from parents_table as p;
This can take advantage of an index on children_table(parent_id, birth_year desc, child_name). With the index, I would expect this to be quite fast.

How to perform recursive SQL where statement?

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.

Join Lookup from 1 table to multiple columns

How do I link 1 table with multiple columns in another table without using mutiple JOIN query?
Below is my scenario:
I have table User with ID and Name
User
+---------+------------+
| Id | Name |
+---------+------------+
| 1 | John |
| 2 | Mike |
| 3 | Charles |
+---------+------------+
And table Product with multiple columns, but just focus on 2 columns CreateBy And ModifiedBy
+------------+-----------+-------------+
| product_id | CreateBy | ModifiedBy |
+------------+-----------+-------------+
| 1 | 1 | 3 |
| 2 | 1 | 3 |
| 3 | 2 | 3 |
| 4 | 2 | 1 |
| 5 | 2 | 3 |
+------------+-----------+-------------+
With normal JOIN, i will need to do 2 JOIN:
SELECT p.Product_id,
u1.Name AS CreateByName,
u2.Name AS ModifiedByName
FROM Product p
JOIN USER user u1 ON p.CreateBy = u1.Id,
JOIN USER user u2 ON p.ModifiedBy = u2.Id
to come out result
+------------+---------------+-----------------+
| product_id | CreateByName | ModifiedByName |
+------------+---------------+-----------------+
| 1 | John | Charles |
| 2 | John | Charles |
| 3 | Mike | Charles |
| 4 | Mike | John |
| 5 | Mike | Charles |
+------------+---------------+-----------------+
How do i avoid that 2 times JOIN?
I'm using MS-SQL , but open to all SQL query for my own learning curious
Your current design/approach is acceptable, I think, and the need for two joins is a function of there being two user ID columns. Each of the two columns requires a separate join.
For fun, here is a table design which you may consider if you really want to have to perform only one join:
+------------+-----------+-------------+
| product_id | user_id | type |
+------------+-----------+-------------+
| 1 | 1 | created |
| 2 | 1 | created |
| 3 | 2 | created |
| 4 | 2 | created |
| 5 | 2 | created |
| 1 | 3 | modified |
| 2 | 3 | modified |
| 3 | 3 | modified |
| 4 | 1 | modified |
| 5 | 3 | modified |
+------------+-----------+-------------+
Now, you can get away with a just a single join followed by an aggregation:
SELECT
p.product_id,
MAX(CASE WHEN t.type = 'created' THEN u.Name END) AS CreateByName,
MAX(CASE WHEN t.type = 'modified' THEN u.Name END) AS ModifiedByName
FROM Product p
INNER JOIN user u
ON p.user_id = u.Id
GROUP BY
p.product_id;
Note that I don't recommend this approach at all. It is much cleaner to use your current approach and use two joins. Joins can fairly easily be optimized using one or more indices. The above aggregation approach would probably not perform as well as what you already have.
If you use natural keys instead of surrogates, you won't need to join at all.
I don't know how you tell your products apart in the real world, but for the example I will assume you have a UPC
CREATE TABLE User
(Name VARCHAR(20) PRIMARY KEY);
CREATE TABLE Product
(UPC CHAR(12) PRIMARY KEY,
CreatedBy VARCHAR(20) REFERENCES User(Name),
ModifiedBy VARCHAR(20) REFERENCES User(Name)
);
Now your query is a simple select, and you also enforce uniqueness of your user names as a bonus, and don't need additional indexes.
Try it...
HTH
Join is the best Approach, but if looking for alternate approach you can use Inline Query.
SELECT P.PRODUCT_ID,
(SELECT [NAME] FROM #USER WHERE ID = CREATED_BY) AS CREATED_BY,
(SELECT [NAME] FROM #USER WHERE ID = MODIFIED_BY) AS MODIFIED_BY
FROM #PRODUCT P
DEMO

Determine whether a specific entry has any children in a hierarchical database

I'm working on a recursive query for a hierarchal table in psql. While I'm able to produce an ordered, hierarchal list, I cannot figure out how to determine whether a parent has any children.
My code at the moment:
WITH RECURSIVE q AS (
SELECT h, 1 AS level, ARRAY[ordering] AS ordered_path, ARRAY[id] AS breadcrumb
FROM report h
WHERE parent IS NULL
UNION ALL
SELECT hi, q.level + 1 AS level, ordered_path || ordering, breadcrumb || id FROM q
JOIN report hi ON hi.parent = (q.h).id )
SELECT (q.h).id, (q.h).parent, (q.h).name, array_to_json(breadcrumb) AS breadcrumbs,
row_number() OVER (order by ordered_path) AS flat_order
FROM q
ORDER BY ordered_path
Which produces the following table:
id | parent | name | ordering | trail | rownum
----+--------+-----------------------+----------+--------------+--------
1 | | Entry 1 | 1 | [1] | 1
2 | 1 | Entry 2 | 1 | [1,2] | 2
15 | 2 | Entry 3 | 1 | [1,2,15] | 3
159 | 2 | Entry 4 | 2 | [1,2,159] | 4
16 | 2 | Entry 5 | 3 | [1,2,16] | 5
Essentially, I'd like a column that shows if a specific entry has any children. In this example, Entry 5 has no children.
The format of the original table is:
id | name | type | parent | ordering
-----+-----------------------------------------+---------+--------+----------
186 | Entry 1 | page | 172 | 23
154 | Entry 2 | page | 63 | 3
169 | Entry 3 | page | 163 | 3
Thanks!
You could use a correlated subquery as an extra field:
exists (select 1 from report where parent = q.id) as has_children
It's not necessarily the most efficient — though tbh, given the query, I'm can't think of anything better off the top of my head. But it'll work.
sql below fill find the child and will count you can change how you want the output by using case statements I tested the code, seems to be working
select x.Col1, count(y.Col1) as child from Table1 x
inner join Table2 y on x.Col1 = y.Col1
group by x.Col1