SQL nested query in SQL server - sql

i have a library database which wrote a query to display count of loaned books by employee like this:
select Emploee.[Emp-No],count(*) as ecount
from Emploee
inner join Loan on Emploee.[Emp-No]=Loan.[Emp-No]
inner join Book on Loan.ISBN=Book.ISBN group by Emploee.[Emp-No]
the result of above query is something like this:
Emp-No ecount
------------------
1000 4
1001 2
1002 3
now i want to modify the output and make a comparison between ecount column of each row of result with another query which give me count of loaned books based on specific published by that user
in other word the result im looking for is something like this
Emp-No ecount
-----------------
1000 4
assume Employee 1000 loaned all of his book from one publisher. he will be showen in the result.
something like this
"..... my query...." having ecount=
(select count(*) from books where publisher='A')
but i cant use the result ecount in another query :(

After clarification, I understood the question as follows: return those employees which only loaned books from a single publisher.
You can do that by using COUNT(DISTINCT publisher) in your HAVING clause.
Like so:
declare #employee table (id int);
declare #books table (isbn int, title varchar(50), publisher varchar(50));
declare #loan table (employee_id int, book_isbn int);
insert #employee values (1000);
insert #employee values (1001);
insert #employee values (1002);
insert #books values (1, 'Some Book', 'Publisher A');
insert #books values (2, 'Some Book', 'Publisher A');
insert #books values (3, 'Some Book', 'Publisher A');
insert #books values (4, 'Some Book', 'Publisher B');
insert #books values (5, 'Some Book', 'Publisher B');
insert #books values (6, 'Some Book', 'Publisher B');
insert #loan values (1000, 1);
insert #loan values (1000, 2);
insert #loan values (1001, 3);
insert #loan values (1001, 4);
insert #loan values (1001, 5);
-- Show the number of different publishers per employee
select e.id, count(*) as ecount, count(DISTINCT b.publisher) as publishers
from #employee e
inner join #loan l on e.id = l.employee_id
inner join #books b on l.book_isbn = b.isbn
group by e.id
-- yields: id ecount publishers
-- ----------- ----------- -----------
-- 1000 2 1
-- 1001 3 2
-- Filter on the number of different publishers per employee
select e.id, count(*) as ecount
from #employee e
inner join #loan l on e.id = l.employee_id
inner join #books b on l.book_isbn = b.isbn
group by e.id
having count(DISTINCT b.publisher) = 1
-- yields: id ecount
-- ----------- -----------
-- 1000 2

To refer to an aggregate in another query, the aggregate has to have an alias. SQL Server does not allow you to refer to an alias at the same level. So you need a subquery to define the alias, before you can use the alias in another subquery.
For example, the following SQL uses a subquery to define alias bookcount for count(*). Thanks to this first subquery, the second subquery in the where clause can use the bookcount:
declare #books table (title varchar(50), author varchar(50));
declare #author_filter table (name varchar(50), bookcount int);
insert #books values
('The Lord of the Rings', 'J.R.R. Tolkien'),
('The Silmarillion', 'J.R.R. Tolkien'),
('A Song for Arbonne', 'G.G. Kay');
insert #author_filter values
('2_books', 2);
select *
from (
select author
, count(*) as bookcount
from #books
group by
author
) authors
where '2_books_filter' =
(
select filter.name
from #author_filter filter
where authors.bookcount = filter.bookcount
)

Related

Find duplicate names with different dob tsql (T-SQL)

I want to get the only duplicate names with different dob with dob in the result set. I don't want non-duplicate rows.
I tried CTE and combination of Group By and Having.
declare #person table (id int, e_name nvarchar(50), dob datetime)
INSERT #person VALUES (1,'Jack Hughens','1960-11-02')
INSERT #person VALUES (2,'Tom Hughens','1971-01-08')
INSERT #person VALUES (3,'Sam Scallion','1960-11-02')
INSERT #person VALUES (4,'Sam Scallion','1960-11-01')
INSERT #person VALUES (5,'Paul Darty','1994-10-19')
INSERT #person VALUES (6,'Paul Ashley','1983-09-21')
The result should be as below:
--------------------------------
|id|e_name |dob |
--------------------------------
|3|Sam Scallion |1960-11-02 |
--------------------------------
|4|Sam Scallion |1960-11-01 |
--------------------------------
If id is auto then you can do :
select p.*
from #person p
where exists (select 1 from #person p1 where p1.e_name = p.e_name and p.id <> p1.id);
However, you can also use dob instead of id :
select p.*
from #person p
where exists (select 1 from #person p1 where p1.e_name = p.e_name and p.dob <> p1.dob);
My suggestion uses GROUP BY with a HAVING-clause:
The mockup table to simulate your issue
declare #person table (id int, e_name nvarchar(50), dob datetime)
INSERT #person VALUES (1,'Jack Hughens','19601102')
INSERT #person VALUES (2,'Tom Hughens', '19710108')
INSERT #person VALUES (3,'Sam Scallion','19601102')
INSERT #person VALUES (4,'Sam Scallion','19601101')
INSERT #person VALUES (5,'Paul Darty', '19941019')
INSERT #person VALUES (6,'Paul Ashley', '19830921');
--The query
WITH FindNames AS
(
SELECT p.e_name
FROM #person p
GROUP BY p.e_name
HAVING COUNT(DISTINCT p.dob)>1
)
SELECT p.*
FROM #person p
INNER JOIN FindNames n ON p.e_name=n.e_name;
the idea in short:
Using COUNT(DISTINCT p.dob) will count differing values only.
The CTE will return names, where you see more than one, but different DOBs.
The final SELECT will JOIN the CTE's set, hence using it as filter.

How to select rows in a many-to-many relationship? (SQL)

I have a Students table and a Courses table.
They have a many to many relationship between them and the StudentCourses table is the intermediary.
Now, I have a list of Course ids and want to select the Students that follow all Courses in my list.
How??
--CREATE TYPE CourseListType AS TABLE
--(
-- CourseID INT
--)
DECLARE
#CourseList CourseListType
CREATE TABLE #Students
(
ID INT
,Name CHAR(10)
)
CREATE TABLE #Courses
(
ID INT
,Name CHAR(10)
)
CREATE TABLE #StudentCourses
(
StudentID INT
,CourseID INT
)
INSERT INTO #CourseList (CourseID)
VALUES
(1) --English
,(2) --Math
INSERT INTO #Students (ID, Name)
VALUES
(1, 'John')
,(2, 'Jane')
,(3, 'Donald')
INSERT INTO #Courses (ID, Name)
VALUES
(1, 'English')
,(2, 'Math')
,(3, 'Geography')
INSERT INTO #StudentCourses (StudentID, CourseID)
VALUES
(1, 1)
,(1, 2)
,(2, 1)
,(2, 2)
,(3, 1)
,(3, 3)
In this example, I only want the result to be John and Jane, because they both have the two courses in my CourseList.
I dont want Donald, because he only has one of them.
Have tried this JOIN, construction, but it does not eliminate students that only have some of my desired courses.
SELECT
*
FROM
#CourseList CRL
INNER JOIN #Courses CRS ON CRS.ID = CRL.CourseID
INNER JOIN #StudentCourses STC ON STC.CourseID = CRS.ID
INNER JOIN #Students STD ON STD.ID = STC.StudentID
If you want students with all your required courses, you can use aggregation and having:
SELECT sc.StudentId
FROM #StudentCourses sc JOIN
#CourseList cl
ON sc.CourseID = cl.id
GROUP BY sc.StudentId
HAVING COUNT(DISTINCT sc.CourseId) = (SELECT COUNT(*) FROM #DcourseList);
If you want additional information about students, you can join in the Students table (or use a IN or a similar construct).
Note that this only needs the StudentCourses table. It has the matching ids. There is no need to join in the reference tables.

How to count records in SQL

Edit: Schema taken/extrapolated from comment below
create table #employees
(
Emp_ID int,
Name varchar(50),
Dept_ID int,
);
create table #departments
(
Dept_ID int,
Dept_Name varchar(50)
);
How do I count the number of employees from table employees that work in each department in table departments and include all departments that have no employees working in them.
Welcome to SO.
This problem is quite simple to solve. The steps would be as follows:
Join the Departments table to the Employees table on the Dept_ID column.
SELECT the Dept_ID and Count() and GROUP BY the Dept_ID field.
In order to return Departments without employees you need to LEFT JOIN this aggregation to the Departments table on the Dept_ID column.
In order to return the value of 0 for departments without employees, use the ISNULL() function.
Please see the below sample script using your schema. Note that this script is written in T-SQL, as you did not mention your server type.
create table #employees
(
Emp_ID int,
Name varchar(50),
Dept_ID int,
);
create table #departments
(
Dept_ID int,
Dept_Name varchar(50)
);
insert into #employees
select 1, 'Pim', 1
union all
select 2, 'Salma', 2;
insert into #departments
select 1, 'IT'
union all
select 2, 'Marketing'
union all
select 3, 'Design'
select
d1.Dept_Name
,isnull(d2.EmployeeCount, 0) as EmployeeCount
from
#departments d1
left join
(
select
d.Dept_ID
,count(e.Dept_ID) as EmployeeCount
from
#departments d
join
#employees e
on e.Dept_ID = d.Dept_ID
group by
d.Dept_ID
)
d2
on d2.Dept_ID = d1.Dept_ID
drop table #employees
drop table #departments
As you have supplied no data please try below and see if this works for you
Create the tables, I don't know if this is similar to your table structure
CREATE TABLE tbl_EMPLOYEES (Empl_Name nvarchar(20), Dept nvarchar(15))
CREATE TABLE tbl_DEPARTMENT (Dept nvarchar(15))
Populate these tables
INSERT INTO tbl_EMPLOYEES Values ('James', 'Finance')
INSERT INTO tbl_EMPLOYEES Values ('Tim', 'HR')
INSERT INTO tbl_EMPLOYEES Values ('Sally', 'Finance')
INSERT INTO tbl_EMPLOYEES Values ('Bob', 'Sales')
INSERT INTO tbl_EMPLOYEES Values ('Sam', 'HR')
INSERT INTO tbl_EMPLOYEES Values ('James', 'Finance')
INSERT INTO tbl_DEPARTMENT Values ('Finance')
INSERT INTO tbl_DEPARTMENT Values ('HR')
INSERT INTO tbl_DEPARTMENT Values ('Sales')
INSERT INTO tbl_DEPARTMENT Values ('IT')
This query will give you the number of people in each department
SELECT Dept, Count(Dept) AS Count
FROM
(
SELECT Dept
FROM tbl_EMPLOYEES
) AS Blah_Blah
GROUP BY Dept

CTE Recursive Queries

I have a table with records of employees that shows a relationship of employees and who they report to:
From_ID position TO_ID position
----------------------------------------
1 Lowest_employee 3 employee
3 employee 4 employee
4 employee 5 BOSS
2 Lowest_employee 6 employee
6 employee 3 employee
10 Lowest_employee 50 BOSS2
I would like to show results that look like this, with the employee / boss IDs:
EmployeeID BossID
--------------------
1 5
2 5
10 50
This means employees 1 and 2 report to ID 5 and employee 10 reports to another boss with ID 50.
I know I need to use CTE and Recursive Queries, but cannot understand how it can be done, I'm newer to CTE Recursive Queries.
I read this article but it doesn't make any sense to me MS link
Any help with the query required to achieve this would be useful.
This includes setting up test data, however I think this is what you want:
Test Data:
DECLARE #Table TABLE
(
From_ID int,
TO_ID int
)
INSERT INTO #Table VALUES(1,3)
INSERT INTO #Table VALUES(3,4)
INSERT INTO #Table VALUES(4,5)
INSERT INTO #Table VALUES(2,6)
INSERT INTO #Table VALUES(6,3)
INSERT INTO #Table VALUES(10,50)
Query to get answer:
;WITH Hierarchy (Employee, Superior, QueryLevel)
AS
(
--root is all employees that have no subordinates
SELECT E.From_ID, E.TO_ID, 1
FROM #Table E
LEFT
JOIN #Table S
ON S.TO_ID = E.From_ID
WHERE S.TO_ID IS NULL
--recurse up tree to final superior
UNION ALL
SELECT H.Employee, S.TO_ID, H.QueryLevel + 1
FROM Hierarchy H
JOIN #Table S
ON S.From_ID = H.Superior
)
SELECT Employee, Superior
FROM
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY Employee ORDER BY QueryLevel DESC) AS RowNumber
FROM Hierarchy
) H
WHERE RowNumber = 1
Essentially, this works by :
1) get all employees with no reportees (the root)
2) recurses up through the bosses, recording the 'level'
3) use over/partition to select only the 'final' boss
WITH q (employee, boss) AS
(
SELECT fromId, toId
FROM mytable
WHERE fromId NOT IN
(
SELECT toId
FROM mytable
)
UNION ALL
SELECT employee, toId
FROM q
JOIN mytable t
ON t.fromId = boss
)
SELECT *
FROM q
WHERE boss NOT IN
(
SELECT fromId
FROM mytable
)
You could try something like this?
DECLARE #Employees TABLE (
EmployeeId INT,
PositionName VARCHAR(50),
ReportsToId INT);
INSERT INTO #Employees VALUES (1, 'Driver', 3);
INSERT INTO #Employees VALUES (3, 'Head of Driving Pool', 4);
INSERT INTO #Employees VALUES (4, 'Corporate Flunky', 5);
INSERT INTO #Employees VALUES (2, 'Window Cleaner', 6);
INSERT INTO #Employees VALUES (6, 'Head of Office Services', 3);
INSERT INTO #Employees VALUES (10, 'Minion', 50);
INSERT INTO #Employees VALUES (5, 'BOSS', NULL);
INSERT INTO #Employees VALUES (50, 'BOSS2', NULL);
WITH Employees AS (
SELECT
EmployeeId,
1 AS [Level],
EmployeeID AS [Path],
ISNULL(ReportsToId, EmployeeId) AS ReportsToId
FROM
#Employees
WHERE
ReportsToId IS NULL
UNION ALL
SELECT
e.EmployeeID,
x.[Level] + 1 AS [Level],
x.[Path] + e.EmployeeID AS [Path],
x.ReportsToId
FROM
#Employees e
INNER JOIN Employees x ON x.EmployeeID = e.ReportsToId)
SELECT
ec.EmployeeId,
e.PositionName,
ec.[Level],
CASE WHEN ec.ReportsToId = ec.EmployeeId THEN NULL ELSE ec.ReportsToId END AS ReportsToId --Can't really report to yourself
FROM
Employees ec
INNER JOIN #Employees e ON e.EmployeeId = ec.EmployeeId
ORDER BY
ec.[Path];

SQL Join tables - detecting presence of some tuples but not others

I've got two primary tables: codes and categories.
I've also got a join table code_mappings which associates codes with categories.
I need to be able to determine which codes are mapped to one group of categories, but not mapped to another. Been banging my head against this for a while, but am completely stuck.
Here's the schema:
create table codes(
id int,
name varchar(256));
create table code_mappings(
id int,
code_id int,
category_id int);
create table categories(
id int,
name varchar(256));
And some seed data:
INSERT INTO categories VALUES(1, 'Dental');
INSERT INTO categories VALUES(2, 'Weight');
INSERT INTO categories VALUES(3, 'Other');
INSERT INTO categories VALUES(4, 'Acme Co');
INSERT INTO categories VALUES(5, 'No Name');
INSERT INTO codes VALUES(100, "big bag of cat food");
INSERT INTO codes VALUES(200, "healthy doggie treatz");
INSERT INTO code_mappings VALUES(50, 200, 1);
INSERT INTO code_mappings VALUES(51, 100, 4);
INSERT INTO code_mappings VALUES(52, 100, 3);
How would I write a query that will give me the codes that are mapped to one of categories (1,2,3) but not to one of categories (4,5)?
This is an example of a set-within-sets query. I like to approach these using group by and having, because I find that the most flexible approach:
select cm.code_id
from code_mappings cm
group by cm.code_id
having sum(case when cm.category_id in (1, 2, 3) then 1 else 0 end) = 1 and
sum(case when cm.category_id in (4, 5) then 1 else 0 end) = 0;
Each condition in the having clause implements exactly one of the conditions. You said one code of 1, 2, or 3, hence the = 1 (if you wanted at least one of these three, it would be > 0). You said no 4 or 5, hence = 0.
SELECT *
FROM codes co
WHERE EXISTS (
SELECT *
FROM code_mappings ex
WHERE ex.code_id = co.id
AND ex.category_id IN (1,2,3)
)
AND NOT EXISTS (
SELECT *
FROM code_mappings nx
WHERE nx.code_id = co.id
AND nx.category_id IN (4,5)
)
;