SQL query for the following tables - sql

I am trying to write a query to get the following results
Expecting the result are in the following table
HouseName Address CurrentResident
------------------------------------------
A 1 Market St Smith
B 2 Market St Einstein
X 3 Market St [Vacant]
D 4 Market St [Vacant]
Please help
The SQL query for the tables
CREATE TABLE House
(
HouseName Varchar(1),
Address Varchar(255)
);
CREATE TABLE Events
(
Id Integer,
HouseName Varchar(1),
EventName Varchar(255),
Date Date,
Family Varchar(255)
);
INSERT House VALUES('A', '1 Market St');
INSERT House VALUES('B', '2 Market St');
INSERT House VALUES('X', '3 Market St');
INSERT House VALUES('D', '4 Market St');
INSERT Events VALUES(1,'A','MoveIn','2005-01-31','Smith');
INSERT Events VALUES(2,'A','Remodel','2005-03-31','Smith');
INSERT Events VALUES(3,'A','Remodel','2008-03-31','Smith');
INSERT Events VALUES(4,'A','CleanUp','2008-05-31','Smith');
INSERT Events VALUES(5,'B','MoveIn','2005-01-31','Newton');
INSERT Events VALUES(6,'B','MoveOut','2008-05-01','Newton');
INSERT Events VALUES(7,'B','MoveIn','2008-12-01','Einstein');
INSERT Events VALUES(8,'D','MoveIn','2007-08-31','Soo');
INSERT Events VALUES(9,'D','MoveOut','2010-08-13','Soo');
The two resulting tables are as follows
HouseName Address
A 1 Market St
B 2 Market St
X 3 Market St
D 4 Market St
Events
Id HouseName EventName Date Family
1 A MoveIn 2005-01-31 Smith
2 A Remodel 2005-03-31 Smith
3 A Remodel 2008-03-31 Smith
4 A CleanUp 2008-05-31 Smith
5 B MoveIn 2005-01-31 Newton
6 B MoveOut 2008-05-01 Newton
7 B MoveIn 2008-12-01 Einstein
8 D MoveIn 2007-08-31 Musk
9 D MoveOut 2010-08-13 Musk
So for house d there is a move in and a move out but no recorded movein after that so it should show vacant
my code
SELECT HouseName, Address, currentresident
FROM (SELECT h.*, COALESCE(e.Family, 'Vacant') AS currentresident,
ROW_NUMBER()OVER(PARTITION BY h.HouseName ORDER BY e.Date desc) AS rn
FROM House AS h left JOIN Events AS e ON h.HouseName=e.HouseName)
AS t WHERE t.rn=1

Here is something to try on your larger database.
SELECT HouseName, Address, currentresident
FROM (SELECT h.*,
Case When e.EventName = 'MoveOut' Then 'Vacant'
Else COALESCE(e.Family, 'Vacant') End AS currentresident,
ROW_NUMBER()OVER(PARTITION BY h.HouseName ORDER BY e.Date desc) AS rn
FROM xyzHouse AS h left JOIN xyzEvents AS e ON h.HouseName=e.HouseName)
AS t WHERE t.rn=1

select H.HouseName, H.Address,
Case When ISNULL(e.EventName,'MoveOut')='MoveOut' then'[Vacant]'
Else e.Family end as CurrentResident
from House h
left join Events e on e.id =
(
select max(e2.id) from events e2
where e2.housename = h.housename and e2.EventName in ('MoveIn','MoveOut')
)

SELECT HouseName, Address, currentresident
FROM (SELECT h.*, COALESCE(case when e.EventName='MoveOut' then null else e.Family end, 'Vacant') AS currentresident,
ROW_NUMBER()OVER(PARTITION BY h.HouseName ORDER BY e.Date desc) AS rn
FROM House AS h left JOIN Events AS e ON h.HouseName=e.HouseName)
AS t WHERE t.rn=1

Related

Update using joins by finding the previous value in sql

I have two tables and I have to update the address field in the emp table by looking up in the emphistory table with previous value i.e USA for employee John
Table emp
EId ename sal Address AccountId
-------------------------------
101 John 100 U X12
102 Peter 500 Null X13
Table emphistory
emphisid EId AccountId Address Date (use row_number to find the second record for that eid and accountid)
-----------------------------------------------------
1 101 X12 U 11-01-2020 09:45:00
2 102 X13 Null 11-01-2020 09:46:00
3. 101 X12 USA 11-01-2020 09:30:00
I have to join the tables with account id and eid.
This works in Postgresql & Sql Server
UPDATE emp
SET Address = hist.Address
FROM (
SELECT h.EId, h.AccountId, h.Address
, ROW_NUMBER() OVER (PARTITION BY h.EId, h.AccountId
ORDER BY h.Date DESC) AS Rn
FROM emphistory h
JOIN emp e
ON e.EId = h.EId
AND e.AccountId = h.AccountId
WHERE h.Address IS NOT NULL
) hist
WHERE emp.EId = hist.EId
AND hist.Rn = 2

An SQL query to pull count of employees absent under each manager on all dates

The objective of the query is get a count of employees absent under each manager.
Attendance (Dates when employees are present)
id date
1 16/05/2020
2 16/05/2020
1 17/05/2020
2 18/05/2020
3 18/05/2020
Employee
id manager_id
1 2
2 3
3 NA
The desired output should be in this format:
Date manager_id Number_of_absent_employees
16/05/2020 NA 1
17/05/2020 3 1
17/05/2020 NA 1
18/05/2020 2 1
I have tried writing code but partially understood it, intuition being calculating total number of actual employees under each manager and subtracting it from number of employees present on given day. Please help me in completing this query, many thanks!
with t1 as /* for counting total employees under each manager */
(
select employee.manager_id,count(*) as totalc
from employee as e
inner join employee on e.employee_id=employee.employee_id
group by employee.manager_id
)
,t2 as /* for counting total employees present each day */
(
select Attendence.date, employee.manager_id,count(*) as present
from employee
Left join Attendence on employee.employee_id=Attendence.employee_id
group by Attendence.date, employee.manager_id
)
select * from t2
Left join t1 on t2.manager_id=t1.manager_id
order by date
Cross join the distinct dates from Attendance to Employee and left join Attendance to filter out the matching rows.
The remaining rows are the absences so then you need to aggregate:
select d.date, e.manager_id,
count(*) Number_of_absent_employees
from (select distinct date from Attendance) d
cross join Employee e
left join Attendance a on a.date = d.date and a.id = e.id
where a.id is null
group by d.date, e.manager_id
See the demo.
Results:
| date | manager_id | Number_of_absent_employees |
| ---------- | ---------- | -------------------------- |
| 16/05/2020 | NA | 1 |
| 17/05/2020 | 3 | 1 |
| 17/05/2020 | NA | 1 |
| 18/05/2020 | 2 | 1 |
Try this query. In first cte just simplify your code. And in the last query calculate absent employees.
--in this CTE just simplify counting
with t1 as /* for counting total employees under each manager */
(
select employee.manager_id,count(*) as totalc
from employee
group by manager_id
)
,t2 as
(
select Attendence.date, employee.manager_id,count(*) as present
from employee
Left join Attendence on employee.employee_id=Attendence.employee_id
group by Attendence.date, employee.manager_id
)
select t2.date,t2.manager_id, (t1.totalc-t2.present) as employees_absent from t2
Left join t1 on t2.manager_id=t1.manager_id
order by date
Select ec.manager_id, date, (total_employees - employee_attended) as employees_absent from
(Select manager_id, count(id) as total_employees
from employee
group by manager_id) ec,
(Select distinct e.manager_id, a.date, count(a.id) over (partition by e.manager_id, a.date) as employee_attended
from Employee e, attendence, a
where e.id = a.id(+)) ea
where ec.manager_id = ea.manager_id (+)
I guess this should work

Line Number and attaching single employee

SQL Server 2000 so no ROW_NUMBER available ....
I need to attach employees to the free lines.
I have a dataset 1 that tells me the free lines per country and region combo.
Table A – available line numbers to use:
Country Region Line Number Employee
---------------------------------------------------
A 1 1 Null
A 1 2 Null
A 2 1 Null
Table B – what employees are available to fill missing line numbers:
Country Region Employee
----------------------------------------
A 1 Dave Smith
A 1 Johnny Cash
A 1 Peter Seller
A 2 David Donald
So required output is
Table C - attaching a single employee to each combo of country, region, line number:
Country Region Line Number Employee
-------------------------------------------------------------
A 1 1 Dave Smith
A 1 2 Johnny Cash
A 2 1 David Donald
I tried a lot of joins, including self joins, and cross joins in SQL Server 2000, but can't get the desired output.
This is my last attempt:
Select
A.Country, A.Region, A.Line Number,
B.Employee
From
Table_A A
Inner Join
Table_B B On A.Country = B.Country and A.Region = B.Region
You need an additional join key after country and region for the assignment. For this, you can use row_number():
select a.*, b.employee
from (select a.*,
row_number() over (partition by country, region order by linenumber) as seqnum
from table_a a
) a join
(select b.*
row_number() over (partition by country, region order by (select null) ) as seqnum
from b
) b
on b.country = a.country and b.region = a.region and b.seqnum = a.seqnum
Just pulling together all of the suggestions, answers, and comments.
--Setting up the tables as given:
CREATE TABLE #e (
Country char(1),
Region int,
LineNumber int,
Employee varchar(50));
INSERT #e
VALUES ('A', 1, 1,NULL)
,('A',1,2,NULL)
,('A',2,1,NULL);
CREATE TABLE #r (
Country char(1),
Region int,
Employee varchar(50));
INSERT #r
VALUES
('A', 1, 'Dave Smith')
,('A', 1, 'Johnny Cash')
,('A', 1, 'Peter Sellers')
,('A', 2, 'David Donald');
--Creating a temporary table with
--a line number to join on.
CREATE TABLE #T(
LineNumber int,
Country char(1),
Region int,
Employee varchar(50));
--Populate the temporary table
--with the line number data.
INSERT INTO #T
(
LineNumber,
Country,
Region,
Employee
)
SELECT
(SELECT
COUNT(*) AS Line
FROM #r AS R2
WHERE R2.Employee <= #r.Employee
AND R2.Region = #r.Region
) AS LineNumber,
Country,
Region,
Employee
FROM #r;
--Set up the final output.
SELECT
A.Country,
A.Region,
A.LineNumber,
B.Employee
FROM
#e A
INNER JOIN
#T B
ON A.Country = B.Country
AND A.Region = B.Region
AND A.LineNumber = B.LineNumber
ORDER BY
A.Country,
A.Region,
A.LineNumber;
--Clean up.
DROP TABLE #r;
DROP TABLE #T;
DROP TABLE #e;
Results:
+---------+--------+------------+--------------+
| Country | Region | LineNumber | Employee |
+---------+--------+------------+--------------+
| A | 1 | 1 | Dave Smith |
| A | 1 | 2 | Johnny Cash |
| A | 2 | 1 | David Donald |
+---------+--------+------------+--------------+

SQL Server - select min date and id from foreign key

These are my tables:
USER:
id_user name email last_access id_company
1 jhonatan abc#abc.com 2014-12-15 1
2 cesar cef#cef.com 2014-12-31 1
3 john 123#123.com 2015-01-09 2
4 steven 897#asdd.cpom 2015-01-02 2
5 greg sd#touch.com 2014-12-07 1
6 kyle fb#fb.com 2014-11-20 1
COMPANY:
id_company company
1 Facebook
2 Appslovers
I need to know, what are the users which has the MIN last_access per company (just one). It could be like this:
id_user name last_access company
6 kyle 2014-11-20 Facebook
4 steven 2015-01-02 Appslovers
Is it possible ?
Use window function
SELECT id_user,
NAME,
last_access,
company
FROM (SELECT id_user,
NAME,
last_access,
company,
Row_number()OVER(partition BY company ORDER BY last_access) rn
FROM users u
JOIN company c
ON u.id_company = c.id_company) a
WHERE rn = 1
or join both the tables find the min last_access date per company then join the result back to the users table to get the result
SELECT id_user,
NAME,
a.last_access,
a.company
FROM users u
JOIN(SELECT u.id_company,
Min(last_access) last_access,
company
FROM users u
JOIN company c
ON u.id_company = c.id_company
GROUP BY u.id_company,
company) a
ON a.id_company = u.id_company
AND u.last_access = a.last_access
This can be done in many ways, for example by using a window function like row_number to partition the data and then selecting the top rows from each group like this:
;with cte (id_user, name, last_access, company, seq) as (
select
id_user,
name,
last_access,
company,
seq = row_number() over (partition by u.id_company order by last_access)
from [user] u
inner join [company] c on u.id_company = c.id_company
)
select id_user, name, last_access, company
from cte where seq = 1

Cross-multiplying table

I have this SQL code and I want to show the sum of each item on its charge slip and on their receipt:
select item_description, sum(receipt_qty) as Samp1, sum(chargeSlip_qty) as Samp2
from Items inner join Receipt_Detail on (Receipt_Detail.item_number =
Items.item_number)
inner join ChargeSlip_Detail on (ChargeSlip_Detail.item_number =
Items.item_number)
group by item_description
It produces this output:
Acetazolamide 2681 1730
Ascorbic Acid 1512 651
Paracetamol 1370 742
Silk 576 952
But it should be:
Acetazolamide 383 173
Ascorbic Acid 216 93
Paracetamol 274 106
Silk 96 238
What's wrong with my code?
Since you are joining tables, you might have a one-to-many relationship that is causing the problem when you then get the sum(). So you can use subqueries to get the result. This will get the sum() for the receipt and chargeslip for each item_number and then you join that back to your items table to get the final result:
select i.item_description,
r.Samp1,
c.Samp2
from Items i
inner join
(
select sum(receipt_qty) Samp1,
item_number
from Receipt_Detail
group by item_number
) r
on r.item_number = i.item_number
inner join
(
select sum(chargeSlip_qty) Samp2,
item_number
from ChargeSlip_Detail
group by item_number
) c
on c.item_number = i.item_number
Do the GROUP BYs first, per Item_Number, so you don't multiply out rows from Receipt_Detail and ChargeSlip_Detail. That is, you generate the SUM values per Item_Number before JOINing back to Items
select
I.item_description,
R.Samp1,
C.Samp2
from
Items I
inner join
(SELECT item_number, sum(receipt_qty) as Samp1
FROM Receipt_Detail
GROUP BY item_number
) R
on (R.item_number = I.item_number)
inner join
(SELECT item_number, sum(chargeSlip_qty) as Samp2
FROM ChargeSlip_Detail
GROUP BY item_number
) C
on (C.item_number = I.item_number)
A left join returns rows from the left table, and for each row in the left table, all matching rows in the right table.
So for example:
create table Customers (name varchar(50));
insert Customers values
('Tim'),
('John'),
('Spike');
create table Orders (customer_name varchar(50), product varchar(50));
insert Orders values (
('Tim', 'Guitar'),
('John', 'Drums'),
('John', 'Trumpet');
create table Addresses (customer_name varchar(50), address varchar(50));
insert Addresses values (
('Tim', 'Penny Lane 1'),
('John', 'Abbey Road 1'),
('John', 'Abbey Road 2');
Then if you run:
select c.name
, count(o.product) as Products
, count(a.address) as Addresses
from Customers c
left join Orders o on o.customer_name = c.name
left join Addresses a on a.customer_name = c.name
group by name
You get:
name Products Addresses
Tim 1 1
John 4 4
Spike 0 0
But John doesn't have 4 products!
If you run without the group by, you can see why the counts are off:
select *
from Customers c
left join Orders o on o.customer_name = c.name
left join Addresses a on a.customer_name = c.name
You get:
name customer_name product customer_name address
Tim Tim Guitar Tim Penny Lane 1
John John Drums John Abbey Road 1
John John Drums John Abbey Road 2
John John Trumpet John Abbey Road 1
John John Trumpet John Abbey Road 2
Spike NULL NULL NULL NULL
As you can see, the joins end up repeating each other. For each product, the list of addresses is repeated. That gives you the wrong counts. To solve this problem, use one of the excellent other answers:
select c.name
, o.order_count
, a.address_count
from Customers c
left join
(
select customer_name
, count(*) as order_count
from Orders
group by
customer_name
) o
on o.customer_name = c.name
left join
(
select customer_name
, count(*) as address_count
from Addresses
group by
customer_name
) a
on a.customer_name = c.name
The subqueries ensure only one row is joined per customer. The result is much better:
name order_count address_count
Tim 1 1
John 2 2
Spike NULL NULL