Multiple JOIN on more then 2 tables - sql-server

I have 3 tables:
Cars
-----------------
CarBrand | Amount
Honda | 0
Mitsu | 5
Ford | 7
CarParts
--------------------
CarPartID | CarBrand
001 | Honda
002 | Mitsu
003 | Ford
004 | Ford
005 | Ford
Drivers
-----------------------
DriverName | CarBrand
Tom | Ford
John | Mitsu
Mark | Mitsu
And I need following data in result:
CarBrand | PartsAmount | DriversAmount
Is it possible? Count after full join doesn't work there...

here are a couple of ways to do what you want: SQLFiddle
select c.CarBrand, j.parts, k.drivers
from
Cars c
cross apply
(
select count(*)
from CarParts cp
where cp.CarBrand = c.CarBrand
) j (parts)
cross apply
(
select count(*)
from Drivers d
where d.CarBrand = c.CarBrand
) k (drivers);
or
select
c.CarBrand,
(select count(*) from CarParts cp where cp.CarBrand = c.CarBrand) as Parts,
(select count(*) from Drivers d where d.CarBrand = c.CarBrand) as Drivers
from
Cars C
is this ok for you?

Related

SQL Server - write query that normalizes comma separated values in a poorly constructed schema

Problem: I am querying a poorly normalized database (I have no say in the design of the data and cannot change it)
Given the following database:
------------------------------------------
| Name | Codes |
------------------------------------------
| Josh | A2A-8292 |
-------------------------------------------
| Adam | D2C-1292, B2A-7292 |
-------------------------------------------
| Jery | A2A-1292, F2A-2292, |
| | C2A-2292 |
-------------------------------------------
How can I write a query that returns the normalized version e.g:
------------------------------------------
| Name | Codes |
------------------------------------------
| Josh | A2A-8292 |
-------------------------------------------
| Adam | D2C-1292 |
-------------------------------------------
| Adam | B2A-7292 |
-------------------------------------------
| Jery | A2A-1292 |
-------------------------------------------
| Jery | F2A-2292 |
-------------------------------------------
| Jery | C2A-2292 |
-------------------------------------------
If you can't use a TVF, here's another option
Example
Select A.Name
,B.*
From YourTable A
Cross Apply (
Select RetSeq = row_number() over (order by (Select null))
,RetVal = ltrim(rtrim(B2.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace(Codes,',','</x><x>')+'</x>' as xml).query('.')) as B1
Cross Apply x.nodes('x') AS B2(i)
) B
Returns
Name RetSeq RetVal
Josh 1 A2A-8292
Adam 1 D2C-1292
Adam 2 B2A-7292
Jery 1 A2A-1292
Jery 2 F2A-2292
Jery 3 C2A-2292
Taking string split function from this answer: T-SQL split string
You could run your query as following:
SELECT T.Name, X.*
FROM TABLE AS T
CROSS APPLY dbo.splitstring(Codes) AS X;

Select row satisfying certain condition and rows next to it

Let's say I have a historical table keeping who has modified data
-------------------------------------------------------------
| ID | Last_Modif | User_Modif | Col3, Col4...
-------------------------------------------------------------
| 1 | 2018-04-09 12:12:00 | John
| 2 | 2018-04-09 11:10:00 | Jim
| 3 | 2018-04-09 11:05:00 | Mary
| 4 | 2018-04-09 11:00:00 | John
| 5 | 2018-04-09 10:56:00 | David
| 6 | 2018-04-09 10:53:00 | John
| 7 | 2018-04-08 19:50:00 | Eric
| 8 | 2018-04-08 18:50:00 | Chris
| 9 | 2018-04-08 15:50:00 | John
| 10 | 2018-04-08 12:50:00 | Chris
----------------------------------------------------------
I would like to find the modifs done by John and previous version before he did that, to check what he had modified. For example in this scenario I would like to return row 1,2,4,5,6,7,9,10
I am thinking of ranking first based on Last_modif then do a join to pick up the next row, but somehow the result is not correct. This seems not a LAG/LEAD case since I am not picking a single value from the next row, but instead the whole next row. Any idea ?
-- sample 1000 rows with RowNumber
with TopRows as
(select top 1000 *, ROW_NUMBER() OVER(ORDER BY Last_modif desc) RowNum from [Table])
--Reference rows : Rows modif by John
, ModifByJohn as
(Select * from TopRows where USER_MODIF = 'John')
select * from ModifByJohn
UNION
select ModifByNext.* from ModifByJohn join TopRows ModifbyNext on ModifByJohn.RowNum + 1 = ModifByNext.RowNum
order by RowNum
How will the code look like if we would like to return last 2 modifs before John did instead of 1 ?
Maybe you can take advantage of your current ID:
with x as
(
select t1.*,
(select top 1 id from tbl where id > t1.id) prev_id
from tbl t1
where t1.User_Modif = 'John'
)
select * from x;
GO
ID | Last_Modif | User_Modif | prev_id
-: | :------------------ | :--------- | ------:
1 | 09/04/2018 12:12:00 | John | 2
4 | 09/04/2018 11:00:00 | John | 5
6 | 09/04/2018 10:53:00 | John | 7
9 | 08/04/2018 15:50:00 | John | 10
with x as
(
select t1.*,
(select top 1 id from tbl where id > t1.id) prev_id
from tbl t1
where t1.User_Modif = 'John'
)
select ID, Last_Modif, User_Modif from x
union all
select ID, Last_Modif, User_Modif
from tbl
where ID in (select prev_id from x)
order by ID
GO
ID | Last_Modif | User_Modif
-: | :------------------ | :---------
1 | 09/04/2018 12:12:00 | John
2 | 09/04/2018 11:10:00 | Jim
4 | 09/04/2018 11:00:00 | John
5 | 09/04/2018 10:56:00 | David
6 | 09/04/2018 10:53:00 | John
7 | 08/04/2018 19:50:00 | Eric
9 | 08/04/2018 15:50:00 | John
10 | 08/04/2018 12:50:00 | Chris
dbfiddle here

SQL - How can I get the number of duplicates in the non-aggregated result?

Suppose I have a table tb such that
select * from tb
returns
ID | City | Country
1 | New York | US
2 | Chicago | US
3 | Boston | US
4 | Beijing | China
5 | Shanghai | China
6 | London | UK
What is the easiest way to write a query that can return the following result?
ID | City | Country | Count
1 | New York | US | 3
2 | Chicago | US | 3
3 | Boston | US | 3
4 | Beijing | China | 2
5 | Shanghai | China | 2
6 | London | UK | 1
The only solution I can think of is
with cte as (select country, count(1) as Count from tb group by country)
select tb.*, cte.Count from tb join cte on tb.Country = cte.Country
But I feel that is not succinct enough. I am wondering if there is anything like Duplicate_Number() over (partition by country) to do this.
Try this:
select *
,COUNT(*) OVER (PARTITION BY Country)
from tb
The OVER clause
Determines the partitioning and ordering of a rowset before the
associated window function is applied.
So, we are basically telling to COUNT the records, but to group the rows per COUNTRY.
Another approach to achieve the result :
select t1.*, t2.Country_Count from tb t1
join
(select country, count(country) Country_Count from tb group by country) t2
on t1.country=t2.country
order by t1.id
SQL HERE

SQL Server parent child (parent see all everything)?

This is my table:
EmployeeID Employee ManagerID
---------------------------------
1 Anna 5
2 John 4
3 Steve 4
4 Lisa 1
5 Adam NULL
6 Per 1
There is no problem for me to get parent and child relationship with a self-join like this:
SELECT
E.EmployeeID,
E.Employee AS Employee,
E.ManagerID,
M.Employee AS Manager
FROM
Employee AS E
LEFT JOIN
Employee AS M ON E.ManagerID = M.EmployeeID
EmployeeID Employee ManagerID Manager
1 Anna 5 Adam
2 John 4 Lisa
3 Steve 4 Lisa
4 Lisa 1 Anna
5 Adam NULL NULL
6 Per 1 Anna
However, How would i go about to make sure that the parent see the whole hierarchy level?
I would like the table to look like this:
EmployeeID Manager Employee EmployeeID
5 Adam Anna 1
5 Adam Per 6
5 Adam Lisa 4
5 Adam John 2
5 Adam Steve 3
1 Anna Per 6
1 Anna Lisa 4
1 Anna John 2
1 Anna Steve 3
4 Lisa John 2
4 Lisa Steve 3
Note: in this example i only have 3 levels of manger but there can be many more
You can try this:
DECLARE #DataSource TABLE
(
[EmployeeID] TINYINT
,[Employee] VARCHAR(12)
,[ManagerID] TINYINT
);
INSERT INTO #DataSource ([EmployeeID], [Employee], [ManagerID])
VALUES (1, 'Anna', 5)
,(2, 'John', 4)
,(3, 'Steve', 4)
,(4, 'Lisa', 1)
,(5, 'Adam', NULL)
,(6, 'Per', 1);
WITH DataSource AS
(
SELECT DISTINCT DS1.*
,0 AS [Level]
,DS1.[EmployeeID] AS Parent
FROM #DataSource DS1
INNER JOIN #DataSource DS2
ON DS1.[EmployeeID] = DS2.[ManagerID]
UNION ALL
SELECT DS2.*
,DS1.[Level] + 1
,DS1.Parent
FROM DataSource DS1
INNER JOIN #DataSource DS2
ON DS1.[EmployeeID] = DS2.[ManagerID]
)
SELECT DS1.[EmployeeID]
,DS1.[Employee] AS [Manager]
,DS.[EmployeeID]
,DS.[Employee]
FROM DataSource DS
INNER JOIN #DataSource DS1
ON DS.[Parent] = DS1.[EmployeeID]
WHERE DS.[Level] <> 0
ORDER BY DS.[Parent] DESC;
We are using recursive CTE and it may look a kind of messy and complicated if you are seeing this syntax for the first time, but it's nothing special.
When are using recursive CTE run some performance tests in order to be sure it is the right technique for solving your issue.
You should use recursive CTE syntax. In the first iteration (before UNION ALL) you get all Parent-Child pairs. In the recursive part (after UNION ALL) you get the next level child for each pair and substitute it into the pair Parent-Child instead of the Child leaving Parent the same.
WITH CTE AS
(
SELECT TP.EmployeeID as ManagerId,
TP.Employee as Manager,
TC.EmployeeID as EmployeeID,
TC.Employee as Employee
FROM TEmployee as TP
JOIN TEmployee as TC on (TP.EmployeeID = TC.ManagerID)
UNION ALL
SELECT TP.ManagerId as ManagerId,
TP.Manager as Manager,
TC.EmployeeID as EmployeeID,
TC.Employee as Employee
FROM CTE as TP
JOIN TEmployee as TC on (TP.EmployeeID = TC.ManagerID)
)
SELECT * FROM CTE Order By ManagerID
result:
+-----------+---------+------------+----------+
| ManagerId | Manager | EmployeeID | Employee |
+-----------+---------+------------+----------+
| 1 | Anna | 4 | Lisa |
| 1 | Anna | 6 | Per |
| 1 | Anna | 2 | John |
| 1 | Anna | 3 | Steve |
| 4 | Lisa | 2 | John |
| 4 | Lisa | 3 | Steve |
| 5 | Adam | 1 | Anna |
| 5 | Adam | 4 | Lisa |
| 5 | Adam | 6 | Per |
| 5 | Adam | 2 | John |
| 5 | Adam | 3 | Steve |
+-----------+---------+------------+----------+

Sum worked hours

I have a issues table where users can log worked hours and estimate hours that looks like this
id | assignee | task | timespent | original_estimate | date
--------------------------------------------------------------------------
1 | john | design | 2 | 3 | 2013-01-01
2 | john | mockup | 2 | 3 | 2013-01-02
3 | john | design | 2 | 3 | 2013-01-01
4 | rick | mockup | 5 | 4 | 2013-01-04
And I need to sum and group the worked and estimated hours by task and date to get this
assignee | task | total_spent | total_estimate | date
------------------------------------------------------------------
john | design | 4 | 6 | 2013-01-01
john | mockup | 2 | 3 | 2013-01-02
rick | design | 5 | 4 | 2013-01-04
Ok, this is easy, I've already got this:
SELECT assignee, task, SUM(timespent) as total_spent, SUM(original_estimate) AS total_estimate, date FROM issues GROUP BY assignee, task, date
My problem is I need to also show the assignees that did not logged hours on any task that day, I mean:
assignee | task | total_spent | total_estimate | date
------------------------------------------------------------------
john | design | 4 | 6 | 2013-01-01
john | mockup | 2 | 3 | 2013-01-02
rick | design | 5 | 4 | 2013-01-04
pete | design | 0 | 0 | 2013-01-01
pete | mockup | 0 | 0 | 2013-01-02
liz | design | 0 | 0 | 2013-01-04
liz | mockup | 0 | 0 | 2013-01-04
The goal is to draw a chart like this http://jsfiddle.net/uUjst/embedded/result/
You need the Assignees in their own separate table to join from.
SELECT tblAssignee.Name, task, SUM(timespent) as total_spent, SUM(original_estimate) AS total_estimate, date
FROM tblAssignee
LEFT JOIN issue ON issues.assignee = tblAssignee.Name
GROUP BY tblAssignee.Name, task, date
Assuming that you have a user table, but not a tasks or dates table... meaning that we have to derive these values from the values present in issues:
;WITH dates AS (
SELECT DISTINCT date
FROM issues
), tasks AS (
SELECT DISTINCT task
FROM issues
)
SELECT
u.user as assignee,
t.task,
SUM(i.timespent) as total_spent,
SUM(i.original_estimate) AS total_estimate,
d.date
FROM
users u CROSS JOIN
dates d CROSS JOIN
tasks t LEFT OUTER JOIN
issues i ON
i.assignee = u.user
AND i.task = t.task
AND i.date = d.date
GROUP BY u.user, t.task, d.date
SELECT
A.name,
task,
ISNULL(SUM(timespent), 0) as total_spent,
ISNULL(SUM(original_estimate), 0) AS total_estimate,
date
FROM Assignee A
LEFT JOIN issue
ON issues.assignee = A.Name
GROUP BY A.name, task, date

Resources