Avoiding Multiple Joins - sql

I have a table (Manufacturers):
Manufacturer ID
------------------
Lagun 1
Hurco 2
Mazak 3
Haas 4
Then another table (inventory):
Shop Lathe DrillPress CNC Mill ID
-------------------------------------------------
ABC Inc 2 1 3 3 1
VECO 4 2 1 2 2
I need to end up with:
Shop Lathe DrillPress CNC Mill
--------------------------------------------
ABC Inc Hurco Lagun Mazak Mazak
VECO Haas Hurco Lagun Hurco
I have this:
SELECT
Shop, M1.Manufacturer AS Lathe, M2.Manufacturer AS DrillPress,
M3.Manufacturer AS CNC, M4.Manufacturer AS Mill
FROM Inventory I
LEFT JOIN Manufacturers M1 ON M1.ID = I.LstFlowMan
LEFT JOIN Manufacturers M2 ON M2.ID = I.LstFiltFlowMan
LEFT JOIN Manufacturers M3 ON M3.ID = I.LstFilterMan
LEFT JOIN Manufacturers M4 ON M4.ID = I.LstEmitMan
I'm probably missing a better way with a PIVOT or CROSS APPLY or something.

Thanks to #LauDec, here is the SQLServer Version:
select * from (
select SHOP, KEYS, MANUFACTURER from
(select SHOP, LATHE,DRILLPRESS,CNC,MILL from inventory) a
unpivot (val for keys in (LATHE,DRILLPRESS,CNC,MILL)) as unpvt
JOIN Manufacturers M ON M.ID=VAL
) a
PIVOT (
MAX(MANUFACTURER)
FOR keys in (LATHE,DRILLPRESS,CNC,MILL)
) as pp

Here is one way to do it.
SELECT i.shop,
lathe = Max(CASE WHEN i.lathe = m.id THEN m.manufacturer END),
drillpress = Max(CASE WHEN i.drillpress = m.id THEN m.manufacturer END),
mill = Max(CASE WHEN i.mill = m.id THEN m.manufacturer END),
cnc = Max(CASE WHEN i.cnc= m.id THEN m.manufacturer END)
FROM manufacturers m
JOIN inventory i
ON m.id IN ( i.lathe, i.drillpress, i.cnc, i.mill )
GROUP BY i.shop
Consider changing the table structure of inventory table.
inventory : Shop,MachineType,ManufacturerID
Then you can use Pivot/Cross tab to get the result

You can UNPIVOT JOIN and RE-PIVOT
SELECT * FROM (
select SHOP, KEYS, MANUFACTURER from
inventory unpivot ( val for keys in ("LATHE","DRILLPRESS","CNC","MILL"))
JOIN Manufacturers M ON M.ID=VAL
) PIVOT (
MAX(MANUFACTURER)
FOR keys in ('LATHE','DRILLPRESS','CNC','MILL')
)

Related

Total of Totals in SQL Server

I have 3 tables -
Books -
BookNo BookName BookType
123 ABC 1
555 XYZ 0
Shelf
Shelf ShelfNo BookNo BookQuantity
XB XB01 123 5
XB XB02 555 3
XB XB03 123 8
BooksIssued
ShelfNo BookName IssuedDate QuantityIssued
XB01 ABC 11/21/2022 2
XB02 XYZ 11/20/2022 1
XB03 ABC 11/21/2022 5
My goal is to find out total number of books stock we have. The output should be grouped by book. And I have to combine all shelfNo which contain the same book and sum their Shelf.BookQuantity and then add it to BooksIssued.QuantityIssued for that particular book. Booktype should be displayed as Children for 0 and 1 for adults.
For example,
Output
BookNo BookName BookType Total Stock
123 ABC adults 20 //(5+8+2+5)
555 XYZ children 4 //(3+1)
So far, I have written this. I know I have chosen extra columns in my query than what I have mentioned in my output format. It is so because I was going step by step to understand the flow. I wanted to first group the data by book and sum the quantity but it isn't grouping the data by bookno . It is also not summing the bi.quantityissued.
select s.bookno, b.booktype, s.shelfno, b.bookname, s.bookquantity,
sum(bi.quantityissued), bi.issueddate
from Shelf s
left outer join BooksIssued bi on s.shelfno = bi.shelfno
left outer join Books b on s.bookno=b.bookno
where s.shelf = 'XB'
and bi.issueddate between '11/01/2022' and '11/07/2022'
group by s.bookno, s.shelfno, b.booktype, b.bookname, s.bookquantity, bi.issueddate
Please guide me what do I do next. Thank you.
This should do it:
WITH baseData As
(
SELECT BookNo, BookQty As Qty
FROM Shelf s
WHERE s.shelf = 'XB'
UNION ALL
SELECT b0.BookNo, QtyIssued As Qty
FROM BooksIssued bi
INNER JOIN Books b0 on b0.BookName = bi.BookName
WHERE bi.IssuedDate >= '20221101' AND bi.IssuedDate < '20221108'
),
grouped As
(
SELECT BookNo, Sum(Qty) As [Total Stock]
FROM baseData
GROUP BY BookNo
)
SELECT b.BookNo, b.BookName, b.BookType, g.[Total Stock]
FROM grouped g
INNER JOIN Books b ON b.BookNo = g.BookNo
This also shows one of the reasons (among several) the BooksIssued table should use BookNo instead of BookName: it would save you a join.
We can also write this with nested SELECT queries instead of the common table expressions (CTEs), but I find the CTE much easier to reason about:
SELECT b.BookNo, b.BookName, b.BookType, g.[Total Stock]
FROM (
SELECT BookNo, Sum(Qty) As [Total Stock]
FROM (
SELECT BookNo, BookQty As Qty
FROM Shelf s
WHERE s.shelf = 'XB'
UNION ALL
SELECT b0.BookNo, QtyIssued As Qty
FROM BooksIssued bi
INNER JOIN Books b0 on b0.BookName = bi.BookName
WHERE bi.IssuedDate >= '20221101' AND bi.IssuedDate < '20221108'
) baseData
GROUP BY BookNo
) g
INNER JOIN Books b ON b.BookNo = g.BookNo
This is still missing the adults vs children book type. You can fix this with a CASE expression, or using a Table Value Constructor. I prefer the latter here, because you can add many more types over time in an efficient way and because it sets you up to eventually use a real table, which is what you should have had in the first place:
WITH baseData As
(
SELECT BookNo, BookQty As Qty
FROM Shelf s
WHERE s.shelf = 'XB'
UNION ALL
SELECT b0.BookNo, QtyIssued As Qty
FROM BooksIssued bi
INNER JOIN Books b0 on b0.BookName = bi.BookName
WHERE bi.IssuedDate >= '20221101' AND bi.IssuedDate < '20221108'
),
grouped As
(
SELECT BookNo, Sum(Qty) As [Total Stock]
FROM baseData
GROUP BY BookNo
)
SELECT b.BookNo, b.BookName, bt.Name As BookType, g.[Total Stock]
FROM grouped g
INNER JOIN Books b ON b.BookNo = g.BookNo
INNER JOIN (VALUES (0, 'Childrens'), (1 'Adults') ) AS bt(Type, Name)
ON b.BookType = bt.Type;
Try this:
select bookno BookNo,
bookname BookName,
case booktype
when 1 then 'adults'
when 0 then 'children'
end BookType
SUM(TotalStock) TotalStock
from (
select b.bookno,
b.bookname,
b.booktype,
sum(bookquantity) TotalStock
from ..books b
inner join ..shelf s
ON b.bookno = s.bookno
group by b.bookno, b.bookname, b.booktype
UNION ALL
select b.bookno,
b.bookname,
b.booktype,
sum(QuantityIssued) TotalStock
from ..books b
inner join ..shelf s
ON b.bookno = s.bookno
inner join ..booksissued bi
ON s.shelfno = bi.shelfno
and b.bookname = bi.bookname
group by b.bookno, b.bookname, b.booktype
) s
group by bookno, bookname, booktype

Sql Join 2 tables get multiple column data referencing data in another table

I have the 2 tables:
Games tbl Official tbl
Id Ref1 Ref2 Ref3 RefId Name
------------------------ ---------------------
1 1 3 2 1 Jaz
2 2 1 3 2 Rog
3 1 2 3 3 Dan
If a game is selected I need to get Ref Names as separate entries for that game, for example:
Game 1 - Ref1 = Jaz Game 2 - Ref1 = Rog
Ref2 = Dan Ref2 = Jaz
Ref3 = Rog Ref3 = Dan
I have tried the sql below, but it seems to only get Ref1 and not Ref2 or Ref3:
Select a.Ref1, a.Ref2, a.Ref3
From Games a
Inner Join Official b On a.Ref1 = b.RefId
Where a.Id = #Id
You must join Games to 3 copies of Official:
SELECT g.Id,
o1.Name Name1,
o2.Name Name2,
o3.Name Name3
FROM Games g
INNER JOIN Official o1 ON g.Ref1 = o1.RefId
INNER JOIN Official o2 ON g.Ref2 = o2.RefId
INNER JOIN Official o3 ON g.Ref3 = o3.RefId
WHERE g.Id = ?;
See the demo.
Also, you could test correlated subqueries instead of the joins. Sometimes they provide better performance:
SELECT g.Id,
(SELECT o.Name FROM Official o WHERE o.RefId = g.Ref1) Name1,
(SELECT o.Name FROM Official o WHERE o.RefId = g.Ref2) Name2,
(SELECT o.Name FROM Official o WHERE o.RefId = g.Ref3) Name3
FROM Games g
WHERE g.Id = ?;
You should join for every reference from official table in your query
Select Ref1, a.Ref2, a.Ref3 From Games a
Left Join Official
b On a.Ref1 = b.RefId
OR a.Ref2 = b.RefId
OR a.Ref3 = b.RefId
Where a.Id = #Id
Initially from looking at your desired results with the names on individual rows it looked like you wanted to unpivot the data, for which the following query using cross apply would have been applicable:
select g.id GameId, o.refId, o.Name
from games g
cross apply (values (ref1,1), (ref2,2), (ref3,3))r(refId,seq)
join officials o on o.refId=r.RefId
where g.id=1
order by r.seq;
however, it does seem like you are expecting one row per game with three columns, in which case the same is applicable, now with a pivot operation also.
You might think this is long-winded compared to the simpler multiple-join option, however for a specific GameId it requires reading each table only once.
select GameId,
Max(case when seq=1 then name end) Ref1,
Max(case when seq=2 then name end) Ref2,
Max(case when seq=3 then name end) Ref3
from (
select g.id GameId, o.refId, o.Name, r.Seq
from games g
cross apply (values (ref1,1), (ref2,2), (ref3,3))r(refId,seq)
join officials o on o.refId=r.RefId
where g.id=1
)g
group by GameId;
working example Fiddle

How to get two different value for same reference id in same row

I have two table:
1) Product
2) Item
Product table:
PId Name value1 value2
1 abc 1233 4567
2 xyz 9099 9099
Item Table:
itemId itemname item_start item_end
1 idc 1 2
item_start & item_end are the reference of product table on column PId.
Now when i write below query:
select * from item left join product on item_start = PId and item_end = PId
then it gives only value1 data but i want data like :
itemId itemname item_start item_start_value1 item_start_value2 item_end item_end_value1 item_end_value2
1 idc 1 1233 4567 2 9099 9099
How can i get the above output?
Join the product table twice.
SELECT item.*
, productStart.Value1 AS product_start_value1
, productStart.Value2 AS product_start_value2
, productEnd.Value1 AS product_end_value1
, productEnd.Value2 AS product_end_value2
FROM item
LEFT OUTER
JOIN product productStart
ON productStart.PId = item.item_start
LEFT OUTER
JOIN product productEnd
ON productEnd.PId = item.item_end
As you have 2 FKs on item table, you'd need 2 joins on product table.
SELECT I.itemId, I.itemname, I.item_start, P1.value1 as item_start_value1, P1.value2 as item_start_value2, I.item_end, P2.value1 as item_end_value1, P2.value2 as item_end_value2
FROM item
LEFT JOIN product P1 ON item_start = PId
LEFT JOIN product P2 ON item_end = PId
Example of what can be done:
select * from #Item i inner join #Product p on i.item_Start = p.Pid
inner join #Product p2 on i.Item_end = p2.PId
SELECT item.itemId
, item.itemname
, item.item_start
, productStart.Value1 AS item_start_value1
, productStart.Value2 AS item_start_value2
, item.item_end
, productEnd.Value1 AS item_end_value1
, productEnd.Value2 AS item_end_value2
FROM item
Inner JOIN product productStart ON productStart.PId = item.item_start
Inner JOIN product productEnd ON productEnd.PId = item.item_end

SELECT common entities only based on different corresponding entities

3 Tables
Client -
CID Name
1 Ana
2 Bana
3 Cana
ClientProgram (Bridge Table) -
CID PID
1 4
1 5
1 8
2 10
Program -
PID Program
4 X
5 Y
8 Z
10 G
Desired Output:
Name Program
Ana X
Ana Y
I want to extract only those Clients which are common/exist in different Programs I choose (say X and Y in this case)
Query attempt:
SELECT
C.Name
,P.Program
FROM ClientProgram CP
INNER JOIN Client C
ON CP.CID=C.CID
INNER JOIN Program P
ON CP.PID=P.PID
INNER JOIN ClientProgram CP1
ON CP.CID=CP1.CID
WHERE P.Program = 'X' OR P.Program = 'Y'
AND CP.CID = CP1.CID
This however doesn't pulls in all clients and not only those which exist in multiple programs.
;WITH cte AS (
SELECT
c.Name
,p.Program
,COUNT(*) OVER (PARTITION BY c.CID) as ProgramCount
FROM
Program p
INNER JOIN ClientProgram cp
ON p.PID = cp.PID
INNER JOIN Client c
On cp.CID = c.CID
WHERE
p.Program IN ('X','Y')
)
SELECT Name, Program
FROM
cte
WHERE
ProgramCount > 1
The use of COUNT(*) over will be a problem if PID is not unique in Programs or if the combination of CID to PID in ClientProgram is not unique. However I would assume uniqueness based on what I see.
If not you can go a route like this:
;WITH cte AS (
SELECT
cp.CID
FROM
Program p
INNER JOIN ClientProgram cp
ON p.PID = cp.PID
WHERE
p.Program IN ('X','Y')
GROUP BY
cp.CID
HAVING
COUNT(DISTINCT p.PID) > 1
)
SELECT
c.Name
,p.Program
FROM
cte t
INNER JOIN Client c
ON t.CID = c.CID
INNER JOIN ClientProgram cp
ON t.CID = cp.CID
INNER JOIN Program p
ON cp.PID = p.PID
AND p.Program IN ('X','Y')
This is kind of a round about way of doing it. Probably a better way but this will do it. I through in scripts for temp table in case someone else wants to improve. Could do a temp table for example instead of CTE.
create table #client(cid int,name varchar(20))
create table #clientprogram (cid int, pid int)
create table #program( pid int, program varchar(20))
insert into #client
values(1,'Ana')
,(2,'Bana')
,(3,'Cana')
insert into #clientprogram
values (1,4)
,(1,5)
,(1,8)
,(2,10)
,(2,4)
insert into #program
values (4,'x')
,(5,'y')
,(8,'z')
,(10,'g')
WITH CHECKPLEASE AS(
Select c.Name,ISNULL(p.Program,p2.PRogram) Program
from #client c
inner join #clientprogram cp
on c.CID = cp.CID
left join #program p
on cp.PID = p.PID
and p.PRogram = 'X'
left join #program p2
on cp.PID = p2.PID
and p2.Program = 'Y'
where ISNULL(p.Program,p2.PRogram) is not null
)
Select *
From CHECKPLEASE
where Name in (
SELECT Name
From CHECKPLEASE
group by Name
having COUNT(*) > 1)

SQL: How to create new columns depending on other column's value in the same table

I have 2 tables, Main and Units.
Main table:
Todate Unit CategoryID Quantity
1/7/2012 1 S 300
1/7/2012 1 U 350
2/7/2012 2 S 220
3/7/2012 2 S 50
3/7/2012 2 U 330
4/7/2012 1 S 200
4/7/2012 1 U 180
S = Sales, U = Upgrades
Units table:
UnitNum UnitName
1 Measures
2 Performance
I need to get this result:
Todate UnitNum UnitName Sales Upgrades
1/7/2012 1 Measures 300 350
2/7/2012 2 Performance 220
3/7/2012 2 Performance 50 330
4/7/2012 1 Measures 200 180
Meaning i need to create 2 columns - sales and upgrades, depending on the value in CategoryID, and i need them to be in the same row.
What i have so far is this
select Todate, Main.Unit, UnitName,
case when CategoryID = 'S' then Quantity end as Sales,
case when CategoryID = 'U' then Quantity end as Upgrades
from Main join Units on Main.UnitNum = Units.UnitNum
group by Todate, Main.Unit, UnitName
It gives me 2 new columns but they are in two separate rows..
I would really appreciate any help resolving this!
Thank you
You just need an aggregate query around the case statements.
select m.todate
, m.unit
, u.unitname
, sum(case when m.categoryid = 'S' then quantity end ) as sales
, sum(case when m.categoryid = 'U' then quantity end ) as upgrages
from main m
join units u
on m.unit = u.unitnum
group by m.todate, m.unit, u.unitname
You need something like this:
SELECT
Todate, m.Unit, UnitName,
Sales = (SELECT SUM(Quantity)
FROM dbo.Main m2
WHERE m.Todate = m2.Todate AND CategoryID = 'S'),
Updates = (SELECT SUM(Quantity)
FROM dbo.Main m2
WHERE m.Todate = m2.Todate AND CategoryID = 'U')
FROM
dbo.Main m
INNER join
dbo.Units u on m.Unit = u.UnitNum
GROUP BY
Todate, m.Unit, UnitName
ORDER BY
Todate, m.Unit, UnitName
This seems to be returning the output you're looking for:
You need to do a self-join of your main table with itself:
SELECT m1.todate, m1.unit AS unitnum, u.unitname,
SUM(m1.quantity) AS sales, SUM(m2.quantity) AS upgrades
FROM (SELECT todate, unit, quantity FROM Main WHERE category = 'S') AS m1
FULL OUTER JOIN
(SELECT todate, unit, quantity FROM Main WHERE category = 'U') AS m2
ON m1.todate = m2.todate AND m1.unit = m2.unit
JOIN units AS u
ON m1.unit = u.unitnum
GROUP BY m1.todate, m1.unit, u.unitname
ORDER BY m1.todate, m1.unit, u.unitname;
There are many other equivalent ways of writing the same query; this one preserves the symmetry of the problem fairly well, though. (Updated to use a FULL OUTER JOIN between the m1 and m2 sub-queries, to deal with cases where there are sales with no upgrades or upgrades with no sales.)
You could use PIVOT for this too:
WITH data AS (
SELECT
m.Todate,
m.UnitNum,
u.UnitName,
Category = CASE m.CategoryID
WHEN 'S' THEN 'Sales'
WHEN 'U' THEN 'Upgrades'
END,
Quantity
FROM Main m
INNER JOIN Units u
ON m.UnitNum = u.UnitNum
)
SELECT
Todate,
UnitNum,
UnitName,
Sales,
Upgrades
FROM data
PIVOT (
SUM(Quantity) FOR Category IN (Sales, Upgrades)
) p
;