Joining two tables with special case on the right table? - sql

I have two tables: Candidate: {Id, Name} and Candidate_Education: {Id, CandidateId, Education, GraduationDate}
I want to show the candidate name and his last education , I made that query:
SELECT c.Name, ce.Education AS 'Last Education'
FROM Candidate c
LEFT JOIN Candidate_Education ce
ON c.Id = (SELECT TOP 1 CandidateID FROM Candidate_Education
ORDER BY GraduationDate DESC)
But the results is not correct, there are Candidates who assigned Educations they don't have relation with Candidate_Education

More typical ways to do what you want use ROW_NUMBER() or OUTER APPLY:
SELECT c.Name, ce.Education
FROM Candidate c OUTER APPLY
(SELECT TOP 1 ce.*
FROM Candidate_Education ce
WHERE ce.CandidateID = c.CandidateID
ORDER BY ce.GraduationDate DESC
) ce;
Your query is missing a join condition between the two tables in the FROM clause. However, there are alternatives that are more appropriate for SQL Server.

other solution
with OneCandidate as (
select * from (
select ce.*, ROW_NUMBER() over(partition by ce.CandidateID order by ce.CandidateID desc) rang
from Candidate_Education ce
) tmp
where tmp.rang=1
)
SELECT c.Name, ce.Education
FROM Candidate c left outer OneCandidate ce on ce.CandidateID = c.CandidateID

WITH maxGraduationCTE AS
(SELECT Id, MAX(GraduationDate) AS 'Last Education' FROM Candidate_Education
GROUP BY Id),
lastEducationCTE AS
(SELECT [Last Education], ce.Id, ce.CandidateId, ce.Education FROM maxGraduationCTE
INNER JOIN Candidate_Education ce ON maxGraduationCTE.Id = ce.Id);
SELECT c.Name,
CASE WHEN [Last Education] IS NULL THEN 'No Education'
ELSE CAST([Last Education] As Varchar)
END As [Last Education]
FROM Candidate c
LEFT JOIN lastEducationCTE ce
ON c.Id = ce.CandidateId
You could use two CTE expressions and a left join with a CASE statement to handle null values. I used a case because I wasn't sure about the data type for the GraduationDate...may have to convert it because if it is a date, you can't mix varchar and date data types

Related

Table Joining Issues

I have difficulties joining the tables below for the desired query as stated.
Theatre (Theatre#, Name, Address, MainTel);
Production (P#, Title, ProductionDirector, PlayAuthor);
Performance (Per#, P#, Theatre#, pDate, pHour, pMinute, Comments);
Client (Client#, Title, Name, Street, Town, County, telNo, e-mail);
Ticket Purchase (Purchase#, Client#, Per#, PaymentMethod, DeliveryMethod, TotalAmount)
Required Query
The theater name (for each theater) and the names of clients who have the highest spending in that theater
SELECT T.NAME, C.NAME, SUM(TOTALAMOUNT)
FROM TICKETPURCHASE TP,
THEATRE T,
CLIENT C,
PERFORMANCE PER
WHERE TOTALAMOUNT = (SELECT MAX (TOTALAMOUNT)
FROM TICKETPURCHASE TP2,
THEATRE T2,
PERFORMANCE PER2,
CLIENT C2,
PRODUCTION P2
WHERE T2.NAME = T.NAME
AND T2.THEATRE# = PER2.THEATRE#
AND TP2.CLIENT# = C2.CLIENT#
AND TP2.PER# =PER2.PER#
AND PER2.P# = P2.P# )
AND C.CLIENT# = TP.CLIENT#
AND T.THEATRE# = PER.THEATRE#
AND TP.PER# = PER.PER#
AND PER.P# = P.P#
GROUP BY T.NAME, C.NAME, TOTALAMOUNT
From Oracle 12, you can do it all in a single query if you aggregate by the primary keys for the theatre and the client to get the total spent and then rank the spending in the ORDER BY clause and use the row limiting clause to get the first ranked values:
SELECT MAX(T.NAME) AS theatre_name,
MAX(C.NAME) AS client_name,
SUM(TP.TOTALAMOUNT) AS amount_spent
FROM TICKETPURCHASE TP
INNER JOIN PERFORMANCE PER
ON (TP.PER# = PER.PER#)
INNER JOIN THEATRE T
ON (T.THEATRE# = PER.THEATRE#)
INNER JOIN CLIENT C
ON (C.CLIENT# = TP.CLIENT#)
GROUP BY
T.THEATRE#,
C.CLIENT#
ORDER BY
DENSE_RANK() OVER (
PARTITION BY T.THEATRE#
ORDER BY SUM(TP.TOTALAMOUNT) DESC
)
FETCH FIRST ROW WITH TIES;
In earlier versions, you can use the same technique and filter in an outer query:
SELECT theatre_name,
client_name,
amount_spent
FROM (
SELECT MAX(T.NAME) AS theatre_name,
MAX(C.NAME) AS client_name,
SUM(TP.TOTALAMOUNT) AS amount_spent,
DENSE_RANK() OVER (
PARTITION BY T.THEATRE#
ORDER BY SUM(TP.TOTALAMOUNT) DESC
) As rnk
FROM TICKETPURCHASE TP
INNER JOIN PERFORMANCE PER
ON (TP.PER# = PER.PER#)
INNER JOIN THEATRE T
ON (T.THEATRE# = PER.THEATRE#)
INNER JOIN CLIENT C
ON (C.CLIENT# = TP.CLIENT#)
GROUP BY
T.THEATRE#,
C.CLIENT#
)
WHERE rnk = 1;

Get Min date as condition

I have a table that contains invoices for all phone numbers, and each number has several invoices, I want to display only the first invoice for precise number but i don't really know how get only first invoice , this is my query
SELECT
b.contrno
a.AR_INVDATE
FROM P_STG_TABS.IVM_INVOICE_RECORD a
INNER JOIN P_EDW_TMP.invoice b
ON b.contrno=a.contrno
WHERE a.AR_INVDATE< (SELECT AR_INVDATE FROM P_STG_TABS.IVM_INVOICE_RECORD WHERE contrno=b.contrno )
Teradata supports a QUALIFY clause to filter the result of an OLAP-function (similar to HAVING after GROUP BY), which greatly simplifies Tim Biegeleisens's answer:
SELECT *
FROM P_STG_TABS.IVM_INVOICE_RECORD a
INNER JOIN P_EDW_TMP.invoice b
ON b.contrno = a.contrno
QUALIFY
ROW_NUMBER()
OVER (PARTITION BY b.contrno
ORDER BY a.AR_INVDATE) = 1
Additionally you can apply the ROW_NUMBER before the join (might be more efficient depending on additional conditions):
SELECT *
FROM
( SELECT *
FROM P_STG_TABS.IVM_INVOICE_RECORD a
QUALIFY
ROW_NUMBER()
OVER (PARTITION BY b.contrno
ORDER BY a.AR_INVDATE) = 1
) AS a
INNER JOIN P_EDW_TMP.invoice b
ON b.contrno = a.contrno
Use ROW_NUMBER():
SELECT
t.contrno,
t.AR_INVDATE
FROM
(
SELECT
b.contrno,
a.AR_INVDATE,
ROW_NUMBER() OVER (PARTITION BY b.contrno ORDER BY a.AR_INVDATE) rn
FROM P_STG_TABS.IVM_INVOICE_RECORD a
INNER JOIN P_EDW_TMP.invoice b
ON b.contrno = a.contrno
) t
WHERE t.rn = 1;
If you are worried about ties, and you want to display all ties, then you can replace ROW_NUMBER with either RANK or DENSE_RANK.
If I correctly understand, then one way is to use group by with min(a.AR_INVDATE):
SELECT
b.contrno,
min(a.AR_INVDATE)
FROM P_STG_TABS.IVM_INVOICE_RECORD a
INNER JOIN P_EDW_TMP.invoice b
ON b.contrno=a.contrno
group by b.contrno

How to make multiple aggregate column in pivot table using one column in SQL Server?

I wants to show the cost and sales by year.
Error msg:
The column name "2016" specified in the PIVOT operator conflicts with the existing column name in the PIVOT argument.
The column name "2017" specified in the PIVOT operator conflicts with the existing column name in the PIVOT argument.
The column '2016' was specified multiple times for 'pivSales'.
Note
I can understand why it shows error, But I don't know the way to get an output in my scenario.
No need for the PIVOT. Just apply a conditional aggregation
select
StoreID,
Department.Name Department,
Category.Name Category,
Sum(case when Year(Time)=2016 then ExtendedCost end) [Cost(2016)],
Sum(case when Year(Time)=2017 then ExtendedCost end) [Cost(2017)],
Sum(case when Year(Time)=2016 then ExtendedPrice end) [Sales(2016)],
Sum(case when Year(Time)=2017 then ExtendedPrice end) [Sales(2017)],
from F_itemDailySalesParent
Inner join item with(Nolock) on item.id = F_itemDailySalesParent.ItemID
Left join Department with(Nolock) on Department.ID = item.DepartmentID
Left join Category with(Nolock) on Category.ID =item.CategoryID
where DATEPART(yyyy,Time) in (2016,2017)
group by StoreID,Department.Name,Category.Name
order by StoreID
Edit - Using your original query and applying a PIVOT
Select *
From (
Select StoreID
,Department
,Category
,B.*
From (
select
DATEPART(yyyy,Time) Years,
StoreID,
Department.Name Department,
Category.Name Category,
Sum(ExtendedCost) Cost,
sum(ExtendedPrice) Sales
from F_itemDailySalesParent
Inner join item with(Nolock) on item.id = F_itemDailySalesParent.ItemID
Left join Department with(Nolock) on Department.ID = item.DepartmentID
Left join Category with(Nolock) on Category.ID =item.CategoryID
where DATEPART(yyyy,Time) in (2016,2017)
group by DATEPART(yyyy,Time),StoreID,Department.Name,Category.Name
) A
Cross Apply ( values (concat('cost(',Years,')'),Cost)
,(concat('sales(',Years,')'),Sales)
) B (Item,Value)
) src
Pivot (sum[Value]) For [Item] in ([cost(2016)],[cost(2017)],[sales(2016)],[sales(2017)] ) p

DB2 - Invalid use of an aggregate function SQLCODE=-120

Can anyone help me to understand what is wrong with this Db2 query syntax,it is failing with SQLCODE -120 because i am trying to use row_number()?
SELECT COUNT(ORDER_ID) OVER() TOTAL_NO_OF_RECORDS,
ROW_NUMBER() OVER (ORDER BY CREATED_DATE DESC) AS ROW_NUM
FROM (
SELECT DISTINCT A.ORDER_ID ORDER_ID,
B.AgencyName AS AGENCY_NAME,
C.FirstName FIRST_NAME,
C.LastName LAST_NAME,
DEMOGRAPHIC.State STATE,
A.CreatedTS CREATED_DATE,
E.WritingTIN WRITING_TIN,
E.ParentTIN PARENT_TIN
FROM DBO.TABLE1 A
INNER JOIN
DBO.TABLE2 TABLE2 ON TABLE2.ORDER_ID=A.ORDER_ID
INNER JOIN
DBO.TABLE3 TABLE3 ON TABLE3.QuoteId=TABLE2.QuoteId
INNER JOIN
DBO.Demographic DEMOGRAPHIC ON
DEMOGRAPHIC.DemographicId=TABLE3 .DemographicId
INNER JOIN
DBO.Agent E ON E.AgentId=DEMOGRAPHIC.AgentId
INNER JOIN
DBO.User USER ON USER.WebAcctID=AGENT.WebAcctId
INNER JOIN
DBO.Shopper SHOPPER ON SHOPPER.ShopperId=DEMOGRAPHIC.ShopperId
LEFT OUTER JOIN
DBO.Subsidy D ON D.demographicId=DEMOGRAPHIC.demographicId
LEFT OUTER JOIN
DBO.Employer EMPLOYER ON DEMOGRAPHIC.demographicId=EMPLOYER.demographicId
WHERE E.WritingTIN = 'XYZ' AND E.ParentTIN = 'XYZ'
AND DEMOGRAPHIC.State='OH'
AND A.Status IN ('X','Y','Z')
)AS ORDER_DETAILS
where ROW_NUMBER() OVER (ORDER BY CREATED_DATE DESC) BETWEEN ((1*50)-50)+1 AND 1*50 ORDER BY CREATED_DATE DESC
ERROR SHOWN:Invalid use of an aggregate function or OLAP function.. SQLCODE=-120, SQLSTATE=42903, DRIVER=4.18.60
You have multiple errors:
X is not defined in the subquery.
B.CREATED_DATE is referenced in the outer query, but it is not defined.
ROW_NUMBER() is used in the WHERE clause.
This would seem to be the intention of the query you write:
SELECT COUNT(*) OVER () NO_OF_RECORDS, ROW_NUM
FROM (SELECT A.ID, B.FirstName as FIRST_NAME, B.LastName as LAST_NAME,
MAX(B.CREATED_DATE) as CREATED_DATE,
ROW_NUMBER() OVER (ORDER BY MAX(CREATED_DATE) DESC) AS ROW_NUM
FROM SCHEMANAME.A A INNER JOIN
SCHEMANAME.B B
ON B.ShopperId = A.ShopperId LEFT OUTER JOIN
SCHEMANAME.C C
ON C.demographicId = B.demographicId
WHERE A.WritingTIN = 'XYZ' AND A.ParentTIN = 'XYZ' AND
B.State = 'OH' AND C.Status IN ('X', 'Y', 'Z')
GROUP BY A.ID, B.FirstName, B.LastName
) ORDER_DETAILS
WHERE ROW_NUM BETWEEN ((1*50)-50)+1 AND 1*50
ORDER BY CREATED_DATE DESC;
I'm not sure that this result makes sense, but it should fix your error.

Problems with SQL Inner join

Having some problems while trying to optimize my SQL.
I got 2 tables like this:
Names
id, analyseid, name
Analyses
id, date, analyseid.
I want to get the newest analyse from Analyses (ordered by date) for every name (they are unique) in Names. I can't really see how to do this without using 2 x nested selects.
My try (Dont get confused about the names. It's the same principle):
SELECT
B.id,
B.chosendatetime,
vStockNames.name
FROM
vStockNames
INNER JOIN
(
SELECT TOP 1
vAnalysesHistory.id,
vAnalysesHistory.chosendatetime,
vAnalysesHistory.companyid
FROM
vAnalysesHistory
ORDER BY
vAnalysesHistory.chosendatetime DESC
) AS B
ON
B.companyid = vStockNames.stockid
In my example the problem is that i only get 1 row returned (because of top 1). But if I exclude this, I can get multiple analyses of the same name.
Can you help me ? - THanks in advance.
SQL Server 2000+:
SELECT (SELECT TOP 1
a.id
FROM vAnalysesHistory AS a
WHERE a.companyid = n.stockid
ORDER BY a.chosendatetime DESC) AS id,
n.name,
(SELECT TOP 1
a.chosendatetime
FROM vAnalysesHistory AS a
WHERE a.companyid = n.stockid
ORDER BY a.chosendatetime DESC) AS chosendatetime
FROM vStockNames AS n
SQL Server 2005+, using CTE:
WITH cte AS (
SELECT a.id,
a.date,
a.analyseid,
ROW_NUMBER() OVER(PARTITION BY a.analyseid
ORDER BY a.date DESC) AS rk
FROM ANALYSES a)
SELECT n.id,
n.name,
c.date
FROM NAMES n
JOIN cte c ON c.analyseid = n.analyseid
AND c.rk = 1
...without CTE:
SELECT n.id,
n.name,
c.date
FROM NAMES n
JOIN (SELECT a.id,
a.date,
a.analyseid,
ROW_NUMBER() OVER(PARTITION BY a.analyseid
ORDER BY a.date DESC) AS rk
FROM ANALYSES a) c ON c.analyseid = n.analyseid
AND c.rk = 1
You're only asking for the TOP 1, so that's all you're getting. If you want one per companyId, you need to specify that in the SELECT on vAnalysesHistory. Of course, JOINs must be constant and do not allow this. Fortunately, CROSS APPLY comes to the rescue in cases like this.
SELECT
B.id,
B.chosendatetime,
vStockNames.name
FROM
vStockNames
CROSS APPLY
(
SELECT TOP 1
vAnalysesHistory.id,
vAnalysesHistory.chosendatetime,
vAnalysesHistory.companyid
FROM
vAnalysesHistory
WHERE companyid = vStockNames.stockid
ORDER BY
vAnalysesHistory.chosendatetime DESC
) AS B
You could also use ROW_NUMBER() to do the same:
SELECT
B.id,
B.chosendatetime,
vStockNames.name
FROM
vStockNames
INNER JOIN
(
SELECT
vAnalysesHistory.id,
vAnalysesHistory.chosendatetime,
vAnalysesHistory.companyid,
ROW_NUMBER() OVER (PARTITION BY companyid ORDER BY chosendatetime DESC) AS row
FROM
vAnalysesHistory
) AS B
ON
B.companyid = vStockNames.stockid AND b.row = 1
Personally I'm a fan of the first approach. It will likely be faster and is easier to read IMO.
Will something like this work for you?
;with RankedAnalysesHistory as
(
SELECT
vah.id,
vah.chosendatetime,
vah.companyid
,rank() over (partition by vah.companyid order by vah.chosendatetime desc) rnk
FROM
vAnalysesHistory vah
)
SELECT
B.id,
B.chosendatetime,
vsn.name
FROM
vStockNames vsn
join RankedAnalysesHistory as rah on rah.companyid = vsn.stockid and vah.rnk = 1
It seems to me that you only need SQL-92 for this. Of course, explicit documentation of the joining columns between the tables would help.
Simple names
SELECT B.ID, C.ChosenDate, N.Name
FROM (SELECT A.AnalyseID, MAX(A.Date) AS ChosenDate
FROM Analyses AS A
GROUP BY A.AnalyseID) AS C
JOIN Analyses AS B ON C.AnalyseID = B.AnalyseID AND C.ChosenDate = B.Date
JOIN Names AS N ON N.AnalyseID = C.AnalyseID
The sub-select generates the latest analysis for each company; the join with Analyses picks up the Analyse.ID value corresponding to that latest analysis, and the join with Names picks up the company name. (The C.ChosenDate in the select-list could be replaced by B.Date AS ChosenDate, of course.)
Complicated names
SELECT B.ID, C.ChosenDateTime, N.Name
FROM (SELECT A.CompanyID, MAX(A.ChosenDateTime) AS ChosenDateTime
FROM vAnalysesHistory AS A
GROUP BY A.CompanyID) AS C
JOIN vAnalysesHistory AS B ON C.CompanyID = B.CompanyID
AND C.ChosenDateTime = B.ChosenDateTime
JOIN vStockNames AS N ON N.AnalyseID = C.AnalyseID
Same query with systematic renaming (and slightly different layout to avoid horizontal scrollbars).