SQL Group By Join - sql

I have three tables I need to join in order to tell what documents a product needs. Not all documents are needed on each product.
There is a Document table, a Product table, and a DocTracking table that tracks the documents associated with products
Product Table
ProdID ProdName
1 Ball
2 Wheel
DocTracking Table
ProdID DocID
1 1
1 2
2 2
I want the join to look like this:
ProdID ProdName Needs Word Doc? Needs Excel Doc?
1 Ball Yes Yes
2 Wheel No Yes
Any help would be appreciated, if I need to make this into a Stored Procedure, that is fine.

If you have only those documents and they are fix you can use this query:
SELECT ProdID, ProdName,
[Needs Word Doc] = CASE WHEN EXISTS(
SELECT 1 FROM Document d INNER JOIN DocTracking dt ON d.DocID=dt.DocID
WHERE dt.ProdID = p.ProdID AND d.[Doc Name] = 'Word Document'
) THEN 'Yes' ELSE 'No' END,
[Needs Excel Doc] = CASE WHEN EXISTS(
SELECT 1 FROM Document d INNER JOIN DocTracking dt ON d.DocID=dt.DocID
WHERE dt.ProdID = p.ProdID AND d.[Doc Name] = ' Excel Spreadsheet'
) THEN 'Yes' ELSE 'No' END
FROM dbo.Product p
Of course you could also use the DocID, then the query doesn't depend on the name.

select P.ProdID, P.ProdName,
case
when DW.DocID is null then 'Yes'
else 'No'
end as NeedsWordDoc,
case
when DE.DocID is null then 'Yes'
else 'No'
end as NeedsExcelDoc
from Product P
left join DocTracking DTW on DTW.ProdId = P.ProdId
left join Document DW on DW.DocID = DTW.DocID
and DW.Name = 'Word Document'
left join DocTracking DTE on DTE.ProdId = P.ProdId
left join Document DE on DE.DocID = DTE.DocID
and DE.Name = 'Excel Spreadsheet'

This is a little more complicated than a typical pivot query. But, the only challenging part is determining which documents are included, and then getting 'Yes' or 'No' out.
The following does this with coalesce() and a conditional that checks for the presence of one type of document:
select pt.ProdId, pt.ProdName,
coalesce(MAX(case when dt.DocId = 1 then 'Yes' end), 'No') as "Needs Word Doc?",
coalesce(MAX(case when dt.DocId = 2 then 'Yes' end), 'No') as "Needs Excel Doc?"
from ProductTable pt left outer join
DocTracking dt
on dt.ProdId = dt.ProdId
group by pt.ProdId, pt.ProdName;
Note that SQL queries return a fixed number of columns. So, you cannot have a SQL query that simply returns a different number of columns based on what is present in the documents table. You can create a SQL query in a string and then use a database-specific command to run it.

may be this would help you - using pivot:
select ProdId, ProdName,
case when isnull([Word Document],0)<>0 then 'Yes'
else 'No'
end as [Needs Word Doc?],
case when isnull([Excel Spreadsheet],0)<>0 then 'Yes'
else 'No'
end as [Needs Excel Spreadsheet?]
from
(
select p.ProdId,p.ProdName,d.DocId,d.DocName from
#Prod p left join
#Track t
on p.ProdId=t.ProdId
inner join #Doc d
on t.DocId=d.DocId
)
as temp
pivot
(max(DocID)
For DocName in ([Word Document],[Excel Spreadsheet])
)
as pvt

Related

SQL Combining Two Totally seperate tables to one

I am VERY new to SQL and self taught. I have two SQL that I stuggled through but got working. Now I need to combine them into one and I'm lost.
SELECT
s.lastfirst,
s.student_number,
SUM(tr.howmany)
FROM
students s
JOIN
truancies tr ON s.id = tr.studentid
WHERE
s.enroll_status = 0 AND
s.schoolid = ~(curschoolid)
GROUP BY
s.lastfirst, s.student_number
HAVING
SUM(tr.howmany) > 0
ORDER BY
s.lastfirst
And this table:
SELECT
S.DCID as DCID,
S.LASTFIRST as LASTFIRST,
S.STUDENT_NUMBER as STUDENT_NUMBER,
S2.FC_SRVC_HRS_DUE as FC_SRVC_HRS_DUE,
CASE
WHEN S2.FC_SRVC_HRS_COMPLETED IS NULL
THEN '0'
ELSE S2.FC_SRVC_HRS_COMPLETED
END AS FC_SRVC_HRS_COMPLETED,
S2.FC_SRVC_HRS_BUYOUT as FC_SRVC_HRS_BUYOUT,
CASE
WHEN S2.FC_SRVC_HRS_COMPLETED IS NULL
THEN S2.FC_SRVC_HRS_DUE * S2.FC_SRVC_HRS_BUYOUT
ELSE ((S2.FC_SRVC_HRS_DUE - S2.FC_SRVC_HRS_COMPLETED) * S2.FC_SRVC_HRS_BUYOUT)
END as Balance_Due
FROM
STUDENTS S, U_STUDENTSUSERFIELDS S2
WHERE
S.DCID = S2.STUDENTSDCID AND
s.enroll_status = 0 AND
s.schoolid = ~(curschoolid) AND
(((S2.FC_SRVC_HRS_DUE - S2.FC_SRVC_HRS_COMPLETED) * S2.FC_SRVC_HRS_BUYOUT) > 0 OR
((S2.FC_SRVC_HRS_DUE - S2.FC_SRVC_HRS_COMPLETED) * S2.FC_SRVC_HRS_BUYOUT) IS NULL) AND
S2.FC_SRVC_HRS_DUE >.1
ORDER BY
s.lastfirst
What I am really looking for are the totals of both of these. I want the SUM(tr.howmany) from the first table and the balance due of the second BUT I need the filters that are in there. This would be sorted by student. I hope I am making sense. Any assistance would be appreciated.
You can join together 2 separate SQL select statements:
Eg:
Select A.id, A.value, B.value
From (select id, count(*) as value from TableA ...) AS A
join (select id, sum(field) as value from TableB ...) AS B
on A.id = B.id
order by A.id
As long as you have a common field to join on this would work. In your case the student_number looks like a good candidate. You will have to do the ordering outside of your subqueries.

TSQL - Multiple Values in INSERT (because of joins)

Im trying to insert a data from one database to another. This is what i have so far, on the select side:
USE [db2]
SELECT
sP.pers_FirstName
,sp.pers_LastName
,sPH.Phon_Number
,CASE WHEN LEFT(sPH.Phon_Number, 2) = '04' THEN sPH.Phon_number ELSE NULL END
,CASE WHEN sp.pers_gender = 1 THEN 'M' WHEN sp.pers_gender = 2 THEN 'F' ELSE 'U' END
,CASE
WHEN sP.pers_salutation = '10' THEN 8
WHEN sp.pers_salutation = '6' THEN 2
WHEN sp.pers_salutation = '7' THEN 1
WHEN sp.pers_salutation = '8' THEN 4
WHEN sp.pers_salutation = '9' THEN 5
WHEN sp.pers_salutation = 'APROF' THEN 6
WHEN sp.pers_salutation = 'Ms.' THEN 4
WHEN sp.pers_salutation = 'PROF' THEN 6
END
,sp.pers_dob
,sp.pers_CreatedDate
,sp.pers_UpdatedDate
,'Candidate'
,1
,e.Emai_EmailAddress
,sP.pers_personID
FROM [db1].dbo.person sP
LEFT JOIN [db1].dbo.PhoneLink sPL ON sp.pers_personID = sPL.PLink_recordID
LEFT JOIN [db1].dbo.Phone sPH ON sPL.PLink_PhoneId = sPH.Phon_PhoneID
LEFT JOIN [db1].dbo.EmailLink eL ON sP.pers_personID = eL.ELink_RecordID
LEFT JOIN [db1].dbo.Email e ON eL.Elink_EmailID = e.Emai_EmailID
WHERE
(
sP.pers_employedby NOT IN (
'Aspen'
,'ACH'
)
)
OR
(
sP.pers_employedby IN (
'Aspen'
,'ACH'
)
AND sP.pers_personID NOT IN (
SELECT c.oppo_PrimaryPersonID FROM [SageCRM].dbo.Opportunity c
WHERE (c.oppo_contractcompleted <= '2016-01-01' OR c.oppo_contractterminated <= '2016-01-01') and c.Oppo_Deleted is null)
AND
sp.pers_isanemployee != 'ECHO'
AND sP.pers_personID IN (
SELECT c.oppo_PrimaryPersonID FROM [SageCRM].dbo.Opportunity c
WHERE c.oppo_Status != 'In Progress' OR c.oppo_Status = 'Completed')
AND sP.pers_dod IS NULL
AND sP.pers_FirstName NOT LIKE '%test%'
AND sP.pers_LastName NOT LIKE '%test%'
AND sp.pers_isanemployee != 'SalesContact'
)
Due to the fact that each person record can have multiple phone numbers linked to them, i end up with multiple records for each person, which obviously wont work as i will end up with duplicates when i actually insert the data.
The problem is, that i need to have all of the phone numbers for each record, just displayed in a different field (home phone, work phone, mobile phone).
Any Ideas, other than doing this in a separate insert statement for each phone / email link?
-------- EDIT: -----------------------------------------------------------------
Ok so, my bad for not giving you enough information. Both of your answers were good answers so thanks for that (#Horaciux, #John Wu).
However, there is no phoneType column, just a phone number. That being said, since every mobile starts with 04 and every home phone with anything else, i can pretty easily distinguish between the two phone types.
There are duplicates in the phone table though, so i will have to delete these, most likely via CTE, shouldn't be too hard.
So, i will end up with something like this for the two phone numbers:
SELECT (phon_number FROM phone p INNER JOIN PhoneLink p1 on p1.PhoneLinkID = p.PhoneLink WHERE LEFT(p.Phon_Number, 2) = '04')
SELECT (phon_number FROM phone p INNER JOIN PhoneLink p1 on p1.PhoneLinkID = p.PhoneLink WHERE LEFT(p.Phon_Number, 2) != '04')
My duplicate removal will be something like this:
WITH CTE AS
(
SELECT phon_linkID, phon_phonNumber, ROW_NUMBER() OVER (PARTITION BY phon_phonNumber ORDER BY phon_linkID) AS RN
FROM phone
)
DELETE FROM CTE WHERE RN<>1
Two easy steps.
Get rid of the joins to the phone number table.
Lookup the phone numbers per record by using a subquery in the select clause, one for each type of phone. Example
SELECT sP.pers_FirstName,
sP.pers_LastName,
(SELECT Phon_Number FROM Phone p JOIN PhoneLink pl ON pl.PhoneLinkID = p.PhoneLinkID WHERE pl.Person_ID = sP.pers_personID AND pl.Type = 'WORK') WorkPhone,
(SELECT Phon_Number FROM Phone p JOIN PhoneLink pl ON pl.PhoneLinkID = p.PhoneLinkID WHERE pl.Person_ID = sP.pers_personID AND pl.Type = 'HOME') HomePhone
FROM person
Without knowing your table's structure, I'll do some example.
select person.id,
max(case when phone.type='home' then phone.vlaue else 0 end) 'home',
max(case when phone.type='work' then phone.vlaue else 0 end) 'work'
from person,phone where...
group by person.id
Then use this query to join all other tables needed

SQL Server / T-SQL : query optimization assistance

I have this QA logic that looks for errors into every AuditID within a RoomID to see if their AuditType were never marked Complete or if they have two complete statuses. Finally, it picks only the maximum AuditDate of the RoomIDs with errors to avoid showing multiple instances of the same RoomID, since there are many audits per room.
The issue is that the AUDIT table is very large and takes a long time to run. I was wondering if there is anyway to reach the same result faster.
Thank you in advance !
IF object_ID('tempdb..#AUDIT') is not null drop table #AUDIT
IF object_ID('tempdb..#ROOMS') is not null drop table #ROOMS
IF object_ID('tempdb..#COMPLETE') is not null drop table #COMPLETE
IF object_ID('tempdb..#FINALE') is not null drop table #FINALE
SELECT distinct
oc.HotelID, o.RoomID
INTO #ROOMS
FROM dbo.[rooms] o
LEFT OUTER JOIN dbo.[hotels] oc on o.HotelID = oc.HotelID
WHERE
o.[status] = '2'
AND o.orderType = '2'
SELECT
t.AuditID, t.RoomID, t.AuditDate, t.AuditType
INTO
#AUDIT
FROM
[dbo].[AUDIT] t
WHERE
t.RoomID IN (SELECT RoomID FROM #ROOMS)
SELECT
t1.RoomID, t3.AuditType, t3.AuditDate, t3.AuditID, t1.CompleteStatus
INTO
#COMPLETE
FROM
(SELECT
RoomID,
SUM(CASE WHEN AuditType = 'Complete' THEN 1 ELSE 0 END) AS CompleteStatus
FROM
#AUDIT
GROUP BY
RoomID) t1
INNER JOIN
#AUDIT t3 ON t1.RoomID = t3.RoomID
WHERE
t1.CompleteStatus = 0
OR t1.CompleteStatus > 1
SELECT
o.HotelID, o.RoomID,
a.AuditID, a.RoomID, a.AuditDate, a.AuditType, a.CompleteStatus,
c.ClientNum
INTO
#FINALE
FROM
#ROOMS O
LEFT OUTER JOIN
#COMPLETE a on o.RoomID = a.RoomID
LEFT OUTER JOIN
[dbo].[clients] c on o.clientNum = c.clientNum
SELECT
t.*,
Complete_Error_Status = CASE WHEN t.CompleteStatus = 0
THEN 'Not Complete'
WHEN t.CompleteStatus > 1
THEN 'Complete More Than Once'
END
FROM
#FINALE t
INNER JOIN
(SELECT
RoomID, MAX(AuditDate) AS MaxDate
FROM
#FINALE
GROUP BY
RoomID) tm ON t.RoomID = tm.RoomID AND t.AuditDate = tm.MaxDate
One section you could improve would be this one. See the inline comments.
SELECT
t1.RoomID, t3.AuditType, t3.AuditDate, t3.AuditID, t1.CompleteStatus
INTO
#COMPLETE
FROM
(SELECT
RoomID,
COUNT(1) AS CompleteStatus
-- Use the above along with the WHERE clause below
-- so that you are aggregating fewer records and
-- avoiding a CASE statement. Remove this next line.
--SUM(CASE WHEN AuditType = 'Complete' THEN 1 ELSE 0 END) AS CompleteStatus
FROM
#AUDIT
WHERE
AuditType = 'Complete'
GROUP BY
RoomID) t1
INNER JOIN
#AUDIT t3 ON t1.RoomID = t3.RoomID
WHERE
t1.CompleteStatus = 0
OR t1.CompleteStatus > 1
Just a thought. Streamline your code and your solution. you are not effectively filtering your datasets smaller so you continue to query the entire tables which is taking a lot of your resources and your temp tables are becoming full copies of those columns without the indexes (PK, FK, ++??) on the original table to take advantage of. This by no means is a perfect solution but it is an idea of how you can consolidate your logic and reduce your overall data set. Give it a try and see if it performs better for you.
Note this will return the last audit record for any room that has either not had an audit completed or completed more than once.
;WITH cte AS (
SELECT
o.RoomId
,o.clientNum
,a.AuditId
,a.AuditDate
,a.AuditType
,NumOfAuditsComplete = SUM(CASE WHEN a.AuditType = 'Complete' THEN 1 ELSE 0 END) OVER (PARTITION BY o.RoomId)
,RowNum = ROW_NUMBER() OVER (PARTITION BY o.RoomId ORDER BY a.AuditDate DESC)
FROm
dbo.Rooms o
LEFT JOIN dbo.Audit a
ON o.RoomId = a.RoomId
WHERE
o.[Status] = 2
AND o.OrderType = 2
)
SELECT
oc.HotelId
,cte.RoomId
,cte.AuditId
,cte.AuditDate
,cte.AuditType
,cte.NumOfAuditsComplete
,cte.clientNum
,Complete_Error_Status = CASE WHEN cte.NumOfAuditsComplete > 1 THEN 'Complete More Than Once' ELSE 'Not Complete' END
FROM
cte
LEFT JOIN dbo.Hotels oc
ON cte.HotelId = oc.HotelId
LEFT JOIN dbo.clients c
ON cte.clientNum = c.clientNum
WHERE
cte.RowNum = 1
AND cte.NumOfAuditsComplete != 1
Also note I changed your
WHERE
o.[status] = '2'
AND o.orderType = '2'
TO
WHERE
o.[status] = 2
AND o.orderType = 2
to be numeric without the single quotes. If the data type is truely varchar add them back but when you query a numeric column as a varchar it will do data conversion and may not take advantage of indexes that you have built on the table.

SQL Joining tables with 'constants'

I have a table of orders,
Invoice Location Customer Code SalesPersonEmail
------------------------------------------------------
300001 001 CUS001 ?
300002 006 CUS002 ?
And a table of email groups,
Role Email
-----------------------------------------------------
Filtered_Group Management#gmail.com;john#gmail.com
When Location = 001, SalesPersonEmail must be the Email field from Filtered_Group
SalesPersonEmail for all other locations must be "Orders#gmail.com;" + the Email for Role No_Filter_Group.
I'm currently using the following to achieve this,
SELECT i.Invoice, i.Location, i.[Customer Code],
CASE WHEN i.Location = 001
THEN f.Email
ELSE N'Orders#gmail.com;' + nf.Email as SalesPersonEmail
END
FROM Invoice i, RoleCodes f, RoleCodes nf
WHERE f.Role = N'Filtered_Group' AND nf.Role = N'No_Filter_Group'
My problem is the Role No_Filter_Group may not exist in the Role table at times, which causes the above query to return nothing.
How do I join these tables properly so if No_Filter_Group does not exist in the table, rows that have a SalesPersonEmail of Filtered_Group are still returned from the query?
Thanks
A relatively simple way is to use LEFT JOIN and put the special number 001 for your location and special role names Filtered_Group and No_Filter_Group in the join condition.
In this SQL Fiddle you can comment/uncomment one line in the schema definition to see how it works when RoleCodes has a row with No_Filter_Group and when it doesn't.
In any case, the query would return all rows from Invoice table.
SELECT
Invoice.Invoice
,Invoice.Location
,Invoice.[Customer Code]
,CASE WHEN Invoice.Location = '001'
THEN RoleCodes.Email
ELSE 'Orders#gmail.com;' + ISNULL(RoleCodes.Email, '')
END AS SalesPersonEmail
FROM
Invoice
LEFT JOIN RoleCodes ON
(Invoice.Location = '001'
AND RoleCodes.Role = 'Filtered_Group')
OR
(Invoice.Location <> '001'
AND RoleCodes.Role = 'No_Filter_Group')
Try something like this.
Note: This is just a example am not sure about the tables and column of your schema. Replace with the respective tables and columns
SELECT CASE
WHEN location = '001' THEN (SELECT TOP 1 email
FROM email_table
WHERE [role] = 'Filtered_Group')
ELSE 'Orders#gmail.com;'
END
FROM orders
If email_table table will have only one row for [role] = 'Filtered_Group' then you can remove the TOP 1 from the sub-query
Left join or an easier, albeit less efficient method would be to do a subquery in the select statement itself.
SELECT i.Invoice, i.Location, i.[Customer Code],
CASE WHEN i.Location = 001
THEN (SELECT TOP 1 f.Email FROM RoleCodes f WHERE f.Role = N'Filtered_Group')
ELSE N'Orders#gmail.com;' + ISNULL( (SELECT nf.Email as SalesPersonEmail FROM RoleCodes nf WHERE nf.Role = N'No_Filter_Group'), '')
END
FROM Invoice i
Normally you would want to join these in on each other but I'm not certain how you would do that with the schema provided.
Nested select will be run for each row, instead, you could try this :-
Select i.Invoice
,i.Location
,i.CustomerCode
,Isnull(r.Email,'Orders#gmail.com') As SalesPersonEmail
From Invoice As i With (Nolock)
Left Join
(
Select rc.Email
,'001' As Location
From RoleCodes As rc With (Nolock)
Where rc.Role = 'Filtered_Group'
) As r On i.Location = r.Location
use the following Query:
select t.Invoice,t.Location,t.[Customer Code],
case t.Location
when '001' then
t2.Email
else
'Orders#gmail.com'
end
as
Salespersonemail
from orders t
join email_groups t2 on t2.Role='Filtered_Group'

Case Statement in SQL Server

I am learning SQL case statements and have the following stored procedure.
Select PT.[ID] 'TransactionID', PT.BatchNumber, PT.SequenceNumber, PT.TransactionDate,
PT.TerminalID, PT.TotalAmount, PT.TransactionTypeID, TT.TransactionType,
PT.PAN 'EmbossLine',PT.PreBalanceAmount, PT.PostBalanceAmount, RefTxnID, SettlementDate,PaidCash, CreditAmount, DiscountAmount,
RefPAN, Remarks, PT.Product,
case PT.Product when 1 then 'Taxi' end 'ProductName'
case PT.Product when 2 then 'Airport Lounge' end 'ProductName'
into #Temp
from POS_Transactions PT inner join TransactionType TT on TT.TransactionTypeID = PT.TransactionTypeID
where
PT.[ID] not in (Select distinct isnull(TransactionID,0) from Testcards)
and (PT.TransactionDate >= #DateFrom)
and (PT.TransactionDate < #DateTo)
and (PT.TransactionTypeID = #TransactionTypeID or #TransactionTypeID = -999)
select T.*, C.EmbossLine+' ' as 'EmbossLine', C.EmbossLine as 'EmbossLine1',
C.EmbossName, PM.MerchantID, PM.MerchantName1, C.AccountNumber, C.VehicleNumber
from #Temp T
inner join Card C on C.EmbossLine= T.EmbossLine
inner join Terminal on Terminal.TerminalID = T.TerminalID
inner join Merchant PM on PM.MerchantID = Terminal.MerchantID
where C.Status <>'E3'
and C.CardID not in (Select distinct isnull(CardID,0) from Testcards)
and (PM.MerchantID =#MerchantID or #MerchantID='-999')
and (C.EmbossLine like '%'+#EmbossLine+'%' or #EmbossLine like '-999')
and (C.EmbossName like '%'+#EmbossName+'%' or #EmbossName like '-999')
order by T.TransactionDate, MerchantName1, T.BatchNumber, T.SequenceNumber
drop table #Temp
When I create it, command is executed succesfully. However when I call it, it throws following error
Column names in each table must be unique. Column name 'ProductName'
in table '#Temp' is specified more than once.
I think I have problem in the syntax in these lines
case PT.Product when 1 then 'Taxi' end 'ProductName'
case PT.Product when 2 then 'Airport Lounge' end 'ProductName'
Can someone identify?
Your line is indeed a problem.
I suspect you're after something like:
case PT.Product
when 1 then 'Taxi'
when 2 then 'Airport Lounge'
end 'ProductName'
In your syntax you make two different cases, resulting in two columns being selected, both called the same.
Above the case can return two different values into the one row.