4 table join with count query - sql-server

I'm using a SQL Server database with 4 tables like this:
Product table: Product_id is the primary key of the table:
| Product_id | Product_name | Description |
Project table: Project_id is the primary key of the table, Product_id is the foreign key to the Product table.
| Project_id | Project_name | Project_start_date | Product_id |
Participant table: Participant_id is the primary key of the table, Project_id is the foreign key to the Project table.
| Participant_id | Participant_name | Participant_email | Project_ID |
Response table: Response_id is the primary key of the table, participant_id is a foreign key to the Participant table.
My question is is it possible I can join all 4 tables together and list product name, project name, participant email and get the count of participants for each project and all response for each project?
Edit:
I feel it's better I put my code here. This is what I've tried, so please do not tell me use JOIN only...
This is the code I got total participant:
SELECT
projects.Project_ID,
count(*) total_Participants,
projects.Project_Name,
projects.Product_ID,
products.Product_Name
FROM
Project projects
INNER JOIN Participant participant on
projects.Project_ID = participant.Project_ID
INNER JOIN Products products ON
products.Product_ID = projects.Product_ID
group by
projects.Project_ID,
projects.Project_Name,
projects.Product_ID,
products.Product_Name,
projects.Project_ID
ORDER BY
projects.Project_ID DESC
And this is the code I got total response:
SELECT
projects.Project_ID,
count(*) total_Participants,
projects.Project_Name,
projects.Product_ID,
products.Product_Name
FROM
Project projects
INNER JOIN Participant participant on
projects.Project_ID = participant.Project_ID
INNER JOIN Products products ON
products.Product_ID = projects.Product_ID
INNER JOIN Response Response ON
Response.Participant_ID = participant.Participant_ID
group by
projects.Project_ID,
projects.Project_Name,
projects.Product_ID,
products.Product_Name,
projects.Project_ID
ORDER BY
projects.Project_ID DESC
Is this a way I can use one query statement to get participant_email, total participants and total response all together?

Without an example of your expected result, this is the best I could come up with.
Some sample data:
declare #product table
(
product_id int,
product_name nvarchar(20)
);
insert into #product (product_id, product_name) values
(1, 'Product ABC'),
(2, 'Product DEF'),
(3, 'Product GHI');
declare #project table
(
project_id int,
project_name nvarchar(20),
product_id int
);
insert into #project (project_id, project_name, product_id) values
(1, 'Project 001', 1),
(2, 'Project 002', 1),
(3, 'Project 003', 2),
(4, 'Project 004', 3);
declare #participant table
(
participant_id int,
participant_name nvarchar(20),
participant_email nvarchar(20),
project_id int
);
insert into #participant (participant_id, participant_name, participant_email, project_id) values
(1, 'Andy', 'andy#host.org', 1),
(2, 'Beatrice', 'beatrice#host.org', 1),
(3, 'Charles', 'charles#host.org', 2),
(4, 'Charles', 'charles#host.org', 4),
(5, 'David', 'david#host.org', 3),
(6, 'Eve', 'eve#host.org', 2);
declare #response table
(
response_id int,
response_content nvarchar(100),
participant_id int
);
insert into #response (response_id, response_content, participant_id) values
(1, 'please', 1),
(2, 'provide', 2),
(3, 'sample', 2),
(4, 'data', 3),
(5, 'next', 5),
(6, 'time', 4),
(7, 'thank', 6),
(8, 'you', 6),
(9, '>.<', 6);
Possible solution, using Common Table Expressions (CTE's) to isolate the count subqueries:
with cte_part as
(
select proj.project_id, count(1) as 'participant_count'
from #project proj
join #participant part on part.project_id = proj.project_id
group by proj.project_id
),
cte_resp as
(
select proj.project_id, count(1) as 'response_count'
from #project proj
join #participant part on part.project_id = proj.project_id
join #response resp on resp.participant_id = part.participant_id
group by proj.project_id
)
select prod.product_name,
proj.project_name,
part.participant_email,
cp.participant_count,
cr.response_count
from #product prod
join #project proj on proj.product_id = prod.product_id
join #participant part on part.project_id = proj.project_id
join cte_part cp on cp.project_id = proj.project_id
join cte_resp cr on cr.project_id = proj.project_id
order by prod.product_name;
This gives me:
product_name project_name participant_email participant_count response_count
-------------------- -------------------- -------------------- ----------------- --------------
Product ABC Project 001 andy#host.org 2 3
Product ABC Project 001 beatrice#host.org 2 3
Product ABC Project 002 charles#host.org 2 4
Product ABC Project 002 eve#host.org 2 4
Product DEF Project 003 david#host.org 1 1
Product GHI Project 004 charles#host.org 1 1

Related

What would be the query for Employee who work for all the department?

What would be the query for the employee who worked for all the department. here department and employee have many to many cardinality.
The tables are:
CREATE TABLE employees
(
employee_id int NOT NULL CONSTRAINT pk_employees PRIMARY KEY,
employee_name nvarchar(128) NOT NULL CONSTRAINT uk_employees_employee_name UNIQUE
);
CREATE TABLE departments
(
department_id int NOT NULL PRIMARY KEY,
department_name nvarchar(128) NOT NULL CONSTRAINT uk_departments_department_name UNIQUE
);
CREATE TABLE department_employees
(
department_id int NOT NULL CONSTRAINT fk_department_employees_departments REFERENCES departments(department_id),
employee_id int NOT NULL CONSTRAINT fk_departement_employees_employees REFERENCES employees(employee_id),
CONSTRAINT pk_deparment_employees PRIMARY KEY (department_id, employee_id)
)
Sample data:
INSERT INTO employees
VALUES (1, 'John Doe'), (2, 'Jane Doe'), (3, 'William Doe'), (4, 'Margaret Doe')
INSERT INTO departments
VALUES (1, 'Accounting'), (2, 'Humman Resources'), (3, 'Marketing')
INSERT INTO department_employees
VALUES
(1, 1), (2, 1), (3, 1),
(2, 2), (2, 3),
(3, 3), (3, 4)
Expected results:
+-------------+---------------+
| employee_id | employee_name |
+-------------+---------------+
| 1 | John Doe |
+-------------+---------------+
This operation is called Relation Division: on Relational algebra.
It can be implemented in sql with a query like the following
SELECT *
FROM dbo.employees e
WHERE
NOT EXISTS (
SELECT *
FROM departments d
WHERE d.department_id NOT IN (
SELECT dp.department_id
FROM department_employees dp
WHERE dp.department_id = d.department_id AND dp.employee_id = e.employee_id
)
)
Notice that the query: "Give me the employees that work for all departments" is equivalent to "Give me all employees that there is no department that the employee is not working for"
You can try this below query.
Here in a variable all distinct count department has been taken from department master table. After that only those employee has been selected where count match with distinct linked department count in relation table.
declare #distinctDeptCount int
SET #DistinctDeptCount = (SELECT Count(Distinct department_id) FROM departments)
--SELECT #DistinctDeptCount
SELECT Distinct employees.employee_id, employee_name
from employees
where employees.employee_id in (
select employee_id from department_employees GROUP BY employee_id HAVING COUNT(department_id) = #distinctDeptCount
)
OR
SELECT Distinct employees.employee_id, employee_name
from employees
where employees.employee_id in (
select employee_id from department_employees GROUP BY employee_id HAVING COUNT(department_id) =
(SELECT Count(Distinct department_id) FROM departments)
)
The output is as shown below
employee_id employee_name
1 John Doe
Here is the live demo Emp. with all departments
You can try this
SELECT
emp.name
FROM
Employee emp
JOIN
EmpDept empd
ON
emp.EmployeeID = empd.EmpId
GROUP BY
emp.EmployeeID
HAVING
count(emp.EmployeeID) = (select count(1) from Department)

how to select and join same table in mssql [duplicate]

I have a simple categories table as with the following columns:
Id
Name
ParentId
So, an infinite amount of Categories can be the child of a category. Take for example the following hierarchy:
I want, in a simple query that returns the category "Business Laptops" to also return a column with all it's parents, comma separator or something:
Or take the following example:
Recursive cte to the rescue....
Create and populate sample table (Please save us this step in your future questions):
DECLARE #T as table
(
id int,
name varchar(100),
parent_id int
)
INSERT INTO #T VALUES
(1, 'A', NULL),
(2, 'A.1', 1),
(3, 'A.2', 1),
(4, 'A.1.1', 2),
(5, 'B', NULL),
(6, 'B.1', 5),
(7, 'B.1.1', 6),
(8, 'B.2', 5),
(9, 'A.1.1.1', 4),
(10, 'A.1.1.2', 4)
The cte:
;WITH CTE AS
(
SELECT id, name, name as path, parent_id
FROM #T
WHERE parent_id IS NULL
UNION ALL
SELECT t.id, t.name, cast(cte.path +','+ t.name as varchar(100)), t.parent_id
FROM #T t
INNER JOIN CTE ON t.parent_id = CTE.id
)
The query:
SELECT id, name, path
FROM CTE
Results:
id name path
1 A A
5 B B
6 B.1 B,B.1
8 B.2 B,B.2
7 B.1.1 B,B.1,B.1.1
2 A.1 A,A.1
3 A.2 A,A.2
4 A.1.1 A,A.1,A.1.1
9 A.1.1.1 A,A.1,A.1.1,A.1.1.1
10 A.1.1.2 A,A.1,A.1.1,A.1.1.2
See online demo on rextester

SQL Server - Conditional DELETE based on SUM from two tables

I want to delete a specified student from the Studies table, for a specified term, if their score reaches a value of above x sPoints. I did attempt to do this and it works if I only specify which score they must be below.
If I start specifying student ID and term number in the query, it deletes all students from Studies where the student ID and term matches the one in the query without bothering to check the score.
My attempt: Student S2 has a total of 45 points in term 2 (5 in HasStudied and 40 in Studies) so he shouldn't be deleted with this query. Yet he still gets deleted when I run this query.
I'm on MS SQL server 2014.
DELETE FROM Studies
FROM Studies a1
INNER JOIN (SELECT a.stID AS ha
FROM Studies a
INNER JOIN HasStudied wq ON wq.stID = a.stID
WHERE a.stID = 'S2' AND a.termNbr = 2
GROUP BY a.stID
HAVING (SUM(wq.sPoints) + (SUM(a.sPoints))) > 50) a2 ON a1.stID = a2.ha
Studies table
CREATE TABLE Studies
(
cID VARCHAR (5) NOT NULL,
stID VARCHAR (5) NOT NULL,
sPoints int,
termNbr int
CONSTRAINT STUDIES_PK PRIMARY KEY (cID, stID),
CONSTRAINT STUDIES_CID_FK
FOREIGN KEY (cID) REFERENCES Course (cID),
CONSTRAINT STUDIES_STID_FK
FOREIGN KEY (stID) REFERENCES Student (stID)
)
HasStudied table
CREATE TABLE HasStudied
(
cID VARCHAR (5) NOT NULL,
stID VARCHAR (5) NOT NULL,
grade VARCHAR (5),
sPoints int,
termNbr int
CONSTRAINT HASSTUDIED_PK PRIMARY KEY (cID, stID)
CONSTRAINT HASSTUDIED_CID_FK
FOREIGN KEY (cID) REFERENCES Course (cID),
CONSTRAINT HASSTUDIED_STID_FK
FOREIGN KEY (stID) REFERENCES Student (stID)
)
Data in HasStudies and Studies.
INSERT INTO HasStudied (cID, stID, grade, sPoints, termNbr) VALUES
('K1', 'S2', 'D', (SELECT cPoints FROM COURSE WHERE cID = 'K1'), 1),
('K2', 'S2', 'A', (SELECT cPoints FROM COURSE WHERE cID = 'K2'), 1),
('K3', 'S2', 'C', (SELECT cPoints FROM COURSE WHERE cID = 'K3'), 1),
('K1', 'S3', 'C', (SELECT cPoints FROM COURSE WHERE cID = 'K1'), 1),
('K2', 'S3', 'E', (SELECT cPoints FROM COURSE WHERE cID = 'K2'), 1),
('K3', 'S3', 'B', (SELECT cPoints FROM COURSE WHERE cID = 'K3'), 1),
('K4', 'S3', 'E', (SELECT cPoints FROM COURSE WHERE cID = 'K4'), 2),
('K5', 'S3', 'D', (SELECT cPoints FROM COURSE WHERE cID = 'K5'), 2)
INSERT INTO Studies (cID, stID, sPoints, termNbr) VALUES
('K1', 'S1', (SELECT cPoints FROM COURSE WHERE cID = 'K1'), 1),
('K2', 'S1', (SELECT cPoints FROM COURSE WHERE cID = 'K2'), 1),
('K3', 'S1', (SELECT cPoints FROM COURSE WHERE cID = 'K3'), 1),
('K4', 'S2', (SELECT cPoints FROM COURSE WHERE cID = 'K4'), 2),
('K5', 'S2', (SELECT cPoints FROM COURSE WHERE cID = 'K5'), 2),
('K6', 'S3', (SELECT cPoints FROM COURSE WHERE cID = 'K6'), 3)
EDIT 2
I did yet another test with the student S2 that has 45 points in total. If I do HAVING (SUM(wq.sPoints) + (SUM(a.sPoints))) > x) and set x to a number below 50, it deletes the student. If I set it to 50 and above, it doesn't delete the student. So even a number of 49 will delete the student.
Your query looks OK and seems to work like a charm... maybe there is an error in your data?
DECLARE #Studies TABLE(
cID VARCHAR (5) NOT NULL,
stID VARCHAR (5) NOT NULL,
sPoints int,
termNbr int
)
DECLARE #HasStudied TABLE(
cID VARCHAR (5) NOT NULL,
stID VARCHAR (5) NOT NULL,
grade VARCHAR (5),
sPoints int,
termNbr int
)
INSERT INTO #Studies VALUES ('AAA', 'SSS1', 7, 11), ('AAA', 'SSS1', 13, 11), ('AAA', 'SSS1', 30, 11)
INSERT INTO #Studies VALUES ('BBB', 'SSS2', 7, 11), ('BBB', 'SSS2', 13, 11)
INSERT INTO #HasStudied VALUES ('AAA', 'SSS1', 'A', 20, 11), ('BBB', 'SSS2', 'F', 0, 11)
DELETE FROM #Studies
FROM #Studies a1
INNER JOIN (SELECT a.stID as ha
FROM #Studies a
INNER JOIN #HasStudied wq ON wq.stID = a.stID
WHERE a.stID = 'SSS1' AND a.termNbr = 11
GROUP BY a.stID
HAVING SUM(wq.sPoints) + SUM(a.sPoints) > 50
) a2
ON a1.stID = a2.ha
SELECT *
FROM #Studies
Result:
cID stID sPoints termNbr
BBB SSS2 7 11
BBB SSS2 13 11
UPDATE
Using Union, you can get the combined set to then test for the total:
WITH a AS(
SELECT sPoints
FROM Studies
WHERE termNbr = 2 AND stID = 'S2'
UNION
SELECT sPoints
FROM HasStudied
WHERE termNbr = 2 AND stID = 'S2'
)
DELETE FROM Studies
WHERE termNbr = 2 AND stID = 'S2' AND (SELECT SUM(sPoints) FROM a) > 59;
Original
You could try organizing your query more like this, which might make it easier to read and input the parameters you are looking for:
DELETE FROM Studies
WHERE termNbr = 2 AND stID = 'Bob' AND stID IN(
SELECT s.stID FROM Studies s
JOIN HasStudied hs ON s.stID = hs.stID AND s.termNbr = hs.termNbr
WHERE s.termNbr = 2
GROUP BY s.stID
HAVING (SUM(s.sPoints) + SUM(hs.sPoints)) > 50
);
http://sqlfiddle.com/#!17/6ffb8/18

How can I recursively calculate a value

I have this table.
Bundles
id | parent_id | quantity
1 | 0 | 1
2 | 1 | 4
3 | 2 | 5
I want to get the total quantity of a bundle with id 3, which is 1 * 4 * 5 = 20 items
Can this be done with a single query?
Here's a solution using CTE:
Setup:
CREATE TABLE Table1
(id int, parent_id int, quantity int)
;
INSERT INTO Table1
(id, parent_id, quantity)
VALUES
(1, 0, 1),
(2, 1, 4),
(3, 2, 5),
(4, 0, 7),
(5, 4, 10)
;
CTE to return total of id=3 and it's parent items:
;WITH myCTE AS
(
SELECT id, parent_id, quantity
FROM Table1
WHERE id = 3
UNION ALL
SELECT T.id, T.parent_id, T.quantity
FROM Table1 T
JOIN myCTE C ON T.id = C.parent_id
)
SELECT EXP(sum(log(quantity)))
FROM myCTE
Demo SQL Fiddle
Multiplication method for values in a column, SELECT EXP(sum(log(quantity))), taken from here.

COUNT number of rows in a GROUP on higher aggregate level

I am trying to find out how many rows of a certain item exist in the table, e.g. in the following example for itemID 1 I need the result 5 (not 3, which is what I currently get). I am tempted to add TransactionID into the PARTITION BY clause, but that results in Msg 8120 since the query does not GROUP by TransactionID. Well, if it did then getting that count would be easy, but I do not want to group on Transaction Level. What can I do to get that ItemCount right? It must be so easy but I am banging my head.
DECLARE #t TABLE (TransactionID INT PRIMARY KEY IDENTITY, CustomerID INT, ItemID INT);
INSERT INTO #t (CustomerID, ItemID)
VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 2),
(1, 1),
(2, 2),
(3, 3),
(4, 4),
(1, 1);
SELECT
CustomerID,
ItemID,
Rows = COUNT(*),
ItemRowCount = COUNT(*) OVER (PARTITION BY ItemID)
FROM
#t
GROUP BY
CustomerID,
ItemID
ORDER BY
ItemID,
CustomerID;
EDIT: I was overaggregating, I guess. Sebastian Meine got me on the track and his answer is right so I accepted it. However, this subquery works for my:
SELECT
CustomerID,
ItemID,
Rows = COUNT(*),
ItemRowCount = (SELECT COUNT(*) FROM #t x WHERE t.ItemID = x.ItemID)
FROM
#t t
GROUP BY
CustomerID,
ItemID
ORDER BY
ItemID,
CustomerID;
You need to pull your outer group count out of the actual group by query. The easiest way to do that is like this:
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE dbo.tbl (TransactionID INT PRIMARY KEY IDENTITY, CustomerID INT, ItemID INT);
INSERT INTO dbo.tbl (CustomerID, ItemID)
VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 2),
(1, 1),
(2, 2),
(3, 3),
(4, 4),
(1, 1);
Query 1:
SELECT *,SUM(Rows)OVER(PARTITION BY ItemId) ItemCnt
FROM(
SELECT
CustomerID,
ItemID,
Rows = COUNT(*)
FROM
dbo.tbl
GROUP BY
CustomerID,
ItemID
)X
ORDER BY
ItemID,
CustomerID
Results:
| CUSTOMERID | ITEMID | ROWS | ITEMCNT |
----------------------------------------
| 1 | 1 | 3 | 5 |
| 2 | 1 | 1 | 5 |
| 3 | 1 | 1 | 5 |
| 2 | 2 | 1 | 2 |
| 4 | 2 | 1 | 2 |
| 3 | 3 | 1 | 1 |
| 4 | 4 | 1 | 1 |
Notice that I add the inner counts together instead of recounting from scratch.
You can use simple count and group by:
SELECT
ItemID,
ItemRowCount = COUNT(1)
FROM
#t
GROUP BY
ItemID
ORDER BY
ItemID
or if you need attach total rows count and item row count to every row:
SELECT
CustomerID,
ItemID,
Rows = COUNT(1) over (),
ItemRowCount = COUNT(1) OVER (PARTITION BY ItemID)
FROM
#t
ORDER BY
ItemID,
CustomerID;
To find out how many rows of a certain item exist in the table, we may not need CustomerId. Use following query -
DECLARE #t TABLE (TransactionID INT PRIMARY KEY IDENTITY, CustomerID INT, ItemID INT);
INSERT INTO #t (CustomerID, ItemID)
VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 2),
(1, 1),
(2, 2),
(3, 3),
(4, 4),
(1, 1);
;WITH cte AS(
SELECT itemId, ROW_NUMBER() OVER (PARTITION BY itemId ORDER BY itemId DESC) AS row_cnt FROM #t
)
SELECT itemId, MAX(row_cnt) row_count FROM cte GROUP BY itemId
It will return -
itemId row_count
1 5
2 2
3 1
4 1
And if is case you need customerId, the use -
;WITH cte AS(
SELECT customerId, itemId, ROW_NUMBER() OVER (PARTITION BY itemId ORDER BY itemId DESC) AS row_cnt FROM #t
)
SELECT customerId, itemId, MAX(row_cnt) item_count FROM cte GROUP BY CustomerID, itemId
It will return -
customerId itemId item_count
1 1 5
2 1 2
3 1 3
2 2 1
4 2 2
3 3 1
4 4 1
You can use a CTE or join to a sub-table (like this)
SELECT
tbl.CustomerID,
tbl.ItemID,
Rows = COUNT(*),
ItemRowCount
FROM tbl
JOIN (SELECT ItemID, Count(*) as ItemRowCount
FROM tbl
GROUP BY ItemID) t ON tbl.ItemID = t.ItemID
GROUP BY
tbl.CustomerID,
tbl.ItemID,
ItemRowCount
ORDER BY
tbl.ItemID,
CustomerID;
or this
SELECT
tbl.CustomerID,
tbl.ItemID,
Rows = COUNT(*),
MAX(ItemRowCount)
FROM tbl
JOIN (SELECT ItemID, Count(*) as ItemRowCount
FROM tbl
GROUP BY ItemID) t ON tbl.ItemID = t.ItemID
GROUP BY
tbl.CustomerID,
tbl.ItemID
ORDER BY
tbl.ItemID,
CustomerID;

Resources