Writing a parent to child group query in SQL Server? - sql-server

Is there a more efficient way to write this query without subqueries?
Table Question_Group:
Question_Group_ID int,
Question_Group_Name nvarchar,
Question_Group_Indent int, // 1=parent,2=child,3=grandchild
Question_Group_Order int,
Question_Parent_ID int
My query:
SELECT
parent.Question_Group_Name, parent.Question_Group_Order,
L2Child.Question_Group_Name, L2Child.Question_Group_Order
FROM
(SELECT
Question_Group_ID, Question_Group_Name, Question_Group_Indent,
Question_Group_Order, Question_Parent_ID
FROM
Question_Groups
WHERE
Question_Group_Indent = 1) parent
LEFT JOIN
(SELECT
Question_Group_ID, Question_Group_Name, Question_Group_Indent,
Question_Group_Order, Question_Parent_ID
FROM
Question_Groups
WHERE
Question_Group_Indent = 2) L2Child ON parent.Question_Group_ID = L2Child.Question_Parent_ID
ORDER BY
parent.question_group_Order
Results:
Pre-Site 1 NULL NULL
Agency Information 2 Contacts 1
Agency Information 2 Contracting Services 2
Agency Information 2 Start-Up Agency 3
Agency Information 2 Hiring 4
Agency Information 2 Budgeted and Actual Sworn Force 5
Agency Information 2 CP Questions 6
Hiring Grants 3 Per Hiring Grant Questions 1
Non-Hiring Grants 4 Per Non-Hiring Grant Questions 1

I'm not sure if it is more efficient but you can write it with CTEs to make it more readable.
WITH parent AS (SELECT Question_Group_ID, Question_Group_Name, Question_Group_Indent,
Question_Group_Order, Question_Parent_ID
FROM Question_Groups
WHERE Question_Group_Indent=1),
L2Child AS (SELECT Question_Group_ID, Question_Group_Name, Question_Group_Indent,
Question_Group_Order, Question_Parent_ID
FROM Question_Groups
WHERE Question_Group_Indent=2)
SELECT parent.Question_Group_Name, parent.Question_Group_Order,
L2Child.Question_Group_Name, L2Child.Question_Group_Order
FROM parent
LEFT JOIN L2Child
ON parent.Question_Group_ID = L2Child.Question_Parent_ID
ORDER BY parent.question_group_Order
The other option is to re-write it this way
SELECT parent.Question_Group_Name, parent.Question_Group_Order,
L2Child.Question_Group_Name, L2Child.Question_Group_Order
FROM Question_Groups AS parent
LEFT JOIN Question_Groups AS L2Child
ON parent.Question_Group_ID = L2Child.Question_Parent_ID
AND L2Child.Question_Group_Indent=2
WHERE parent.Question_Group_Indent=1
ORDER BY parent.question_group_Order
Read this to see why I put the "left" table condition in the WHERE clause and the "right" table condition in the ON clause.

Related

How to find grandparent parent child relationship for View in SQL

With a table that looks like this:
ParentID
AgencyID
CompanyName
NULL
1
ABC Agency
NULL
2
Another Agency
2
3
Agency 3
3
4
Agency 4
The goal here is to develop a database view that shows the parent and grand-parent for a given agency. Some agencies only have a parent (no grand-parent), and still others are stand-alone - they don't have a parent. We want the view to look like this:
- GrandParentAgencyNo
- GrandParentName
- ParentAgencyNo
- ParentName
- AgencyNo
- AgencyName
- NumberOfChildren
- NumberOfGrandChildren
We could use that to find all the children for a given agency:
select * from AgencyView where ParentAgencyNo = "ABC123"
if an agency doesn't have a parent, the above result should look like this:
- GrandParentAgencyNo: 1
- GrandParentName: ABC Agency
- ParentAgencyNo: 1
- ParentName: ABC Agency
- AgencyNo: 1
- AgencyName: ABC Agency
- NumberOfChildren: 0
- NumberOfGrandChildren: 0
I tried writing recursive functions similar to the one below (including other queries that included trying to find the grandparent) with no luck. I am unfamiliar with recursion and always seem to hit the max recursion rate in SQL Server.
with A(Id, ParentId) as
(
select AgencyId, ParentAgencyId from Agency
union all
select e.AgencyId, p.ParentId from Agency e
join A p on e.ParentAgencyId = p.Id
)
select * from A
OPTION (MAXRECURSION 32767)
I don't use recursion unless I have to, usually due to an unknown or large number of levels. I would simply use left joins and case statements to get parents and grandparents and a sub-select for the child counts.
SELECT A.Agencyid, A.CompanyName
, CASE WHEN p.AgencyId is not null then p.AgencyId
else A.AgencyID END AS ParentAgencyId
, CASE WHEN p.AgencyId is not null then p.CompanyName
else A.CompanyNameEND ParentCompanyName
, CASE WHEN gp.AgencyId is not null then gp.AgencyId
WHEN p.AgencyId is not null then p.AgencyId
else A.AgencyID END GrandParentAgencyId
, CASE WHEN gp.AgencyId is not null then gp.CompanyName
WHEN p.AgencyId is not null then p.CompanyName
else A.CompanyNameEND GrandParentCompanyName
, (SELECT count(*) FROM Agency where ParentId = A.AgencyId) Children
, (SELECT count(*) FROM Agency C
JOIN Agency GC ON C.AgencyId = GC.ParentId
where C.ParentId = A.AgencyId) GrandChildren
FROM Agency A
LEFT JOIN Agency P ON P.AgencyId = A.ParentId
LEFT JOIN Agency GP ON GP.AgencyId = P.ParentId
WHERE A.AgencyId = 1

Get multiple values in a single column / row in SQL Server

I have a table projects in which I have projectid and projectname.
Each project has multiple users and the table is project_users and the fields are projectuserid, projectid.
Also each project has multiple solutions and the table is project_solutions and the fields are projectsolutionid, projectid.
Again each solution has multiple users these are stored in the table solution_users and the fields are solutionuserid, solutionid, userid
There are master tables here users for users with fields userid, username and solutions for solutions with fields solutionid, solutionname.
I am trying to get the output in single row where the first column will be projectid and second column is the respective users of the project in an array or json format the third is solutions for the project and the fourth is solution users
The tables are as below
projects
projectid projectname
1 abc
2 xyz
users
userid username image(data type image)
1 user1 04949949499994
2 user2 3434jj34kjd3434
3 user3 8934u34kj343434
solutions
solutionid solution_name
1 sol1
2 sol2
3 sol3
project_users
id projectuserid projectid
1 1 1
2 2 1
3 3 2
project_solutions
id solutionid projectid
1 1 1
2 3 1
3 2 2
project_solution_users
id projectsolutionid userid
1 1 1
2 1 2
3 2 1
4 2 2
5 2 3
The output which I am expecting is like below
{
projectid:1
users: [user1, user2],
solutions: [ sol1, sol3 ],
user_images: [04949949499994, 3434jj34kjd3434]
}
{
projectid:2
users: [user3],
solutions: [ sol2 ],
user_images: [04949949499994, 3434jj34kjd3434, 8934u34kj343434]
}
This is what I tried
select p.projectid, ps.solutionid, cast(cast (uu.image as varbinary) as varbinary) as userimage,
from projects as p
left join project_users as pu on pu.projectid = p.projectid
left join users as u on u.userid = su.userid
left join project_solutions as ps on ps.projectid = p.projectid
left join solutions as s on s.solutionid = ps.solutionid
left join project_solution_users as su on su.projectsolutionid = ps.solutionid
left join users as uu on uu.userid = su.userid
group by p.projectid, s.solution_name, cast(cast (uu.image as varbinary) as varbinary)
for json path, without_array_wrapper
But it generates multiple rows.
How can I achieve this using SQL Server? I have tried using inner joins and sub queries but I got stuck and not getting the respective output.
select t1.projectid,
username,
user_image,
solution_name
from dbo.project_solutions t1
left join dbo.project_users t2 on t2.projectid = t1.projectid
left join dbo.users t3 on t3.userid = t2.projectuserid
left join dbo.solutions t4 on t4.solutionid = t1.solutionid
for json auto

T-SQL stored procedure with joins

I have a problem with a stored procedure. I have 3 tables for a mass mailing service and I want to know how many tasks (table - MMProcessItem) I still need to do...
I have these 3 tables:
Here is my select:
SELECT
MMAddress.AddressID, MMProcess.ProcessID
FROM
MMProcess, MMAddress
LEFT OUTER JOIN
(SELECT *
FROM MMProcessItem) Items ON Items.AddressID = MMAddress.AddressID
WHERE
Items.ResultID IS NULL
ORDER BY
ProcessID, AddressID
And my SQL Code is working fine if there is nothing in MMProcessItem table, this is what I get:
But if I send 1 email, like the one with AddressID = 1 and ProcessID = 1, I don't get anymore the 1 record with AddressID = 1 and ProcessID = 2, I should get a total of 3 records, but what i get is a total of 2 records...
Sorry if this is an amateur mistake, im not used to work with t-sql and do these type of things...
Your join to MMProcessItem requires two predicates, one to join to MMProcess, and one to join to MMAddress. You are currently only joining to MMAddress. That means that when you add a record with AddressID = 1 and ProcessID = 1 it removes both records where AddressID = 1, not just the one record where AddressID is 1 and ProcessID is 1.
You could rewrite your query as:
SELECT a.AddressID, p.ProcessID
FROM MMProcess AS p
CROSS JOIN MMAddress AS a
LEFT OUTER JOIN MMProcessItem AS i
ON i.AddressID = a.AddressID
AND i.ProcessID = p.ProcessID
WHERE i.ResultID IS NULL
ORDER BY p.ProcessID, a.AddressID;
Note the use of explicit join syntax, and also aliases for brevity
Since you are using the LEFT JOIN to MMProcessItem solely for the reason of removing records, then you might find that using NOT EXISTS conveys intention better, but more importantly, it can also perform better.
SELECT a.AddressID, p.ProcessID
FROM MMProcess AS p
CROSS JOIN MMAddress AS a
WHERE NOT EXISTS
( SELECT 1
FROM MMProcessItem AS i
WHERE i.AddressID = a.AddressID
AND i.ProcessID = p.ProcessID
)
ORDER BY p.ProcessID, a.AddressID;

SQL 2005 returning unique results with subquery

I have a database Table a (EMAILS) where EmailID is the Primary Key
EmailID Email_To Email_From Email_Subject Email_Registered Email_Read
If a user creates an email it is registered in this table.
For example, the user "Dave" who has id 3 sends an email to "John" who has id 4
So this would give
EmailID Email_To Email_From Email_Subject Email_Registered Email_Read
10 4 3 TEST 2/23/2016 11:00 False
To return results I do this select (joining the user profile database)
SELECT PROFILE_1.SellerID, PROFILE_1.Seller_UserName, EMAILS.EmailID, EMAILS.Email_From, EMAILS.Email_To, EMAILS.Email_Subject,
EMAILS.Email_Registered, EMAILS.Email_Read,
(SELECT Seller_UserName AS Epr2
FROM PROFILE
WHERE (SellerID = EMAILS.Email_To)) AS Expr2
FROM PROFILE AS PROFILE_1 LEFT OUTER JOIN
EMAILS ON EMAILS.Email_From = PROFILE_1.SellerID
WHERE (EMAILS.Email_From IS NOT NULL) AND (PROFILE_1.Seller_UserName = 'Dave')
ORDER BY EMAILS.Email_Registered DESC
So John Replies to Dave's email and it goes into the EMAILS_THREAD table and is registered as
EmailThreadID EmailID Email_To Email_From Email_Registered Email_Read
1 10 3 4 2/23/2016 11:05 False
What I am trying to do is a select that
SELECTS from EMAILS where Email_From is from Dave and return in the results the top 1 result from EMAIL_THREADS that is sent to Dave (based on Email_Registered) with the same EmailID as the EMAILS.EmailID if there is a entry in EMAIL_THREADS.
So in other words return the result of the EMAIL table and latest corresponding result in the EMAIL_THREADS table if there is one.
I hope this makes sense.
I've tried a ton of combinations and I can't figure this out.
At first I thought it was a subquery or a join or a group by...but i can't seem to nail the select and how it is structured.
Looking for some help.
Here is my last attempt
SELECT PROFILE_1.SellerID, PROFILE_1.Seller_UserName, EMAILS.EmailID, EMAILS.Email_From, EMAILS.Email_To, EMAILS.Email_Subject,
EMAILS.Email_Registered, EMAILS.Email_Read,
(SELECT Seller_UserName AS Epr2
FROM PROFILE
WHERE (SellerID = EMAILS.Email_To)) AS Expr2
FROM PROFILE AS PROFILE_1 LEFT OUTER JOIN
EMAILS ON EMAILS.Email_From = PROFILE_1.SellerID CROSS JOIN
(SELECT TOP (1) EMAILS_THREAD.Email_From, EMAILS_THREAD.Email_To, EMAILS_THREAD.Email_Registered, EMAILS_THREAD.Email_Read
FROM EMAILS_THREAD LEFT OUTER JOIN
EMAILS AS EMAILS_1 ON EMAILS_THREAD.EmailID = EMAILS_1.EmailID) AS derivedtbl_1
WHERE (EMAILS.Email_From IS NOT NULL) AND (PROFILE_1.Seller_UserName = 'Dave')
ORDER BY EMAILS.Email_Registered DESC
But it's not returning anything from EMAILS_THREADS at all.
SELECTS from EMAILS where Email_From is from Dave and return in the results the top 1 result from EMAIL_THREADS that is sent to Dave (based on Email_Registered) with the same EmailID as the EMAILS.EmailID if there is a entry in EMAIL_THREADS.Hope this helps
with cte
as
(select
* from emails
where email_from=3
)
select * from cte mt --only for dave
outer apply
(
select top 1* from
emial_threads st where mt.mail_from=st.mail_to ---send to dave
and mt.emailid=st.emailid) b
Haven't been able to find a solution to this so I am paying a SQL professional to resolve this for me. Thanks for the input from Tab and The Gameiswar. Much appreciated.

SQL Server : join only one table

Let's say that I have those four tables.
PEOPOLE
ID NAME SURNAME COMPANY UNIT GROUPS
--------------------------------------------
1 Michael Backer 1 1 1
2 Travis Morgan 2 2 2
3 George Marshall 3 3 3
COMPANY
ID NAME
------------
1 Coca Cola
2 Pepsi
3 Sprite
WORKUNIT
ID NAME
-------------
1 Finances
2 Marketing
3 Sales
GROUPS (both values can be null)
ID NAME FLOOR
-------------------------
1 Risks 5
2 NULL NULL
3 Secretariat NULL
Expected results
NAME SURNAME COMPANYNAME WORKUNIT GROUPS FLOOR
-----------------------------------------------
Michael Backer Coca Cola Finances Risks 5
Travis Morgan Pepsi Marketing NULL NULL
George Marshall Sprite Sales Secretariat NULL
So far I write this query with no success:
SELECT
people.NAME, people.SURNAME, company.NAME,
workunit.NAME, groups.NAME, groups.FLOOR
FROM
company, workunit, groups, people
LEFT JOIN
groups on people.GROUP = GROUPS.id
WHERE
company.id = people.company AND
workunit.id = people.unit AND
groups.id = people.group AND
groups.floor = 'something from textbox';
I am not familiar with combining join statements in more than one table, so please help me out because I'm stuck.
Just write like this:
SELECT
people.NAME,
people.SURNAME,
company.NAME,
workunit.NAME,
groups.NAME,
groups.FLOOR
FROM
people
INNER JOIN GROUP
ON people.GROUP=GROUPS.id
INNER JOIN company
ON people.company=company.id
INNER JOIN UNIT
ON people.UNIT =UNIT.id
WHERE
GROUPS.floor='something from textbox';
I verified your query in SQL Server. You need to modify it a little bit, at least, to see it executing, in this way:
SELECT
people.NAME, people.SURNAME, company.NAME,
workunit.NAME, groups.NAME, groups.FLOOR
FROM
company, workunit, people
LEFT JOIN
groups on people.GROUP = GROUPS.id
WHERE
company.id = people.company AND
workunit.id = people.unit AND
groups.id = people.group AND
groups.floor = 'something from textbox';
Anyway, your query doesn't work because, even if you want the results in the way you described using a LEFT JOIN, you are setting the condition:
AND groups.floor = 'something from textbox';
In this way, you won't be able to see the records where there isn't a match with the GROUPS table, because you're saying explicitly that you want the records where the value of the column floor must be equal to 'something from textbox'.
If you want also the non-matches you can replace the condition with:
OR groups.floor = 'something from textbox';
but you will have some duplicates and a group by statement won't work anyway.
Then, you have to check if you really want those expected results.
I didn't understand why you want also the non-matches, even if you are looking for the precise result, using groups.floor = 'something from textbox'. If you want to display the results in a GUI, the non-matches would be useless information for a user, in my opinion.
SELECT people.NAME, people.SURNAME, company.NAME, workunit.NAME, groups.NAME, groups.FLOOR
FROM company, workunit, groups LEFT JOIN people on people.GROUP=GROUPS.id
WHERE
company.id=people.company AND
workunit.id=people.unit AND
groups.id=people.group AND
groups.floor='something from textbox';

Resources