How to show last data entry of tables are not directly related - sql-server

So I've read the posts about retrieving the last entry added to a table, but I can't get it to work for me since my query is slightly more complex.
I'm using SQL server and I'm a beginner with SQL queries.
I need to make a list with patients of a certain practice and their last consult. For this I have three tables:
Patients.patients (patient data, also contains practice id)
Consults.consults (contains no patient data!)
Consults.Patients (links consult is to patient id)
This is what I have got so far:
SELECT p.DebtorNumber
,p.ContactDetails_LastName
,c.Startdate
,c.ConsultNumber
FROM Patients.patients as p
JOIN consults.patients as pc on p.id=pc.Patient_Id
JOIN consults.Consults as c on pc.Parent_Id=c.id
WHERE p.HealthCare_Id=90
I use regular JOINS because I'm only interested in patients that have consults. This query retrieves all the consults of the patients in practice id 90, but I only need the most recent one.
As I have read other posts about this issue, I know that I should use a subquery and a TOP 1. But in the other examples the table they pulled the TOP 1 from was directly related to the main table.
I think I need to add to my WHERE, but this sure doesn't work:
and c.id in (select top 1 c.id where pc.Patient_Id=p.id order by start DESC)
How do I only get the last consult for each patient? This would be the consult with the highest Consults.Consults.Startdate.
Thanks in advance guys, I've gotten in over my head here.

Use ROW_NUMBER for this. EG
with q as
(
SELECT p.DebtorNumber
,p.ContactDetails_LastName
,c.Startdate
,c.ConsultNumber
,row_number() over (partition by p.id order p c.start desc) rn
FROM Patients.patients as p
JOIN consults.patients as pc on p.id=pc.Patient_Id
JOIN consults.Consults as c on pc.Parent_Id=c.id
WHERE p.HealthCare_Id=90
)
SELECT p.DebtorNumber
,p.ContactDetails_LastName
,c.Startdate
,c.ConsultNumber
FROM q
where rn = 1;

you can use query as below:
SELECT top (1) with ties
p.DebtorNumber
,p.ContactDetails_LastName
,c.Startdate
,c.ConsultNumber
FROM Patients.patients as p
JOIN consults.patients as pc on p.id=pc.Patient_Id
JOIN consults.Consults as c on pc.Parent_Id=c.id
WHERE p.HealthCare_Id=90
Order by row_number() over (partition by p.id order by c.start desc)

Related

SQL Project using a where clause

So this is what I am working with new to sql and still learning been stuck on this for a few days now. Any advice would be appreciated I attached the image of the goal I'm trying to achieve
OrderItem And Product Table
Order And OrderItem Table(https://i.stack.imgur.com/pdbMT.png)
Scenario: Our boss would like to see the OrderNumber, OrderDate, Product Name, UnitPrice and Quantity for products that have TotalAmounts larger than the average
Create a query with a subquery in the WHERE clause. OrderNumber, OrderDate and TotalAmount come from the Order table. ProductName comes from the Product table. UnitPrice and Quantity come from the OrderItem table.
This is the code I came up with but it causes product name to run endlessly and displays wrong info.
USE TestCorp;
SELECT DISTINCT OrderNumber,
OrderDate,
ProductName,
i.UnitPrice,
Quantity,
TotalAmount
FROM [Order], Product
JOIN OrderItem i ON Product.UnitPrice = i.UnitPrice
WHERE TotalAmount < ( SELECT AVG(TotalAmount)
FROM [Order]
)
ORDER BY TotalAmount DESC;
Best guess assuming joins and fields not provided.
SELECT O.OrderNumber, O.orderDate, P.ProductName, OI.UnitPrice, OI.Quantity, O.TotalAmount
FROM [Order] O
INNER JOIN OrderItem OI
on O.ID = OI.orderID
INNER JOIN Product P
on P.ID= OI.ProductID
CROSS JOIN (SELECT avg(TotalAmount) AvgTotalAmount FROM [Order]) z
WHERE O.TotalAmount > z.AvgTotalAmount
Notes:
You're mixing join notations don't use , and inner join together that's mixing something called ANSI Standards.
I'm not sure why you have a cross join to product to begin with
You don't specify how to join Order to order item.
It seems very odd to be joining on Price.... join on order ID or productID maybe?
you could cross join to an "Average" result so it's available on every record. (I aliased this inline view "Z" in my attempt)
so what the above does is include all Orders. and for each order, an order item must be associated for it to be included. And then for each order item, a productid must be included and related to a record in product. If for some reason an order item record doens't have a related entry in product table, it gets excluded.
I use a cross join to get the average as it's executed 1 time and applied/joined to every record.
If we use the query in the where clause it's executed one time for EVERY record (unless the DB Engine optimizer figures it out and generates a better plan)
I Assume
Order.ID relates to OrderItem.OrderID
OrderItem.productID relates to Product.ID
Order.TotalAmount is what we are wanting to "Average" and compare against
Every Order has an Order Item entry
Every Order Item entry has a related product.

How do I properly add this query into my existing query within Query Designer?

I currently have the below query written within Query Designer. I asked a question yesterday and it worked on its own but I would like to incorporate it into my existing report.
SELECT Distinct
i.ProductNumber
,i.ProductType
,i.ProductPurchaseDate
,ih.SalesPersonComputerID
,ih.SalesPerson
,ic2.FlaggedComments
FROM [Products] i
LEFT OUTER JOIN
(SELECT Distinct
MIN(c2.Comments) AS FlaggedComments
,c2.SalesKey
FROM [SalesComment] AS c2
WHERE(c2.Comments like 'Flagged*%')
GROUP BY c2.SalesKey) ic2
ON ic2.SalesKey = i.SalesKey
LEFT JOIN [SalesHistory] AS ih
ON ih.SalesKey = i.SalesKey
WHERE
i.SaleDate between #StartDate and #StopDate
AND ih.Status = 'SOLD'
My question yesterday was that I wanted a way to select only the first comment made for each sale. I have a query for selecting the flagged comments but I want both the first row and the flagged comment. They would both be pulling from the same table. This was the query provided and it worked on its own but I cant figure out how to make it work with my existing query.
SELECT a.DateTimeCommented, a.ProductNumber, a.Comments, a.SalesKey
FROM (
SELECT
DateTimeCommented, ProductNumber, Comments, SalesKey,
ROW_NUMBER() OVER(PARTITION BY ProductNumber ORDER BY DateTimeCommented) as RowN
FROM [SalesComment]
) a
WHERE a.RowN = 1
Thank you so much for your assistance.
You can use a combination of row-numbering and aggregation to get both the Flagged% comments, and the first comment.
You may want to change the PARTITION BY clause to suit.
DISTINCT on the outer query is probably spurious, on the inner query it definitely is, as you have GROUP BY anyway. If you are getting multiple rows, don't just throw DISTINCT at it, instead think about your joins and whether you need aggregation.
The second LEFT JOIN logically becomes an INNER JOIN due to the WHERE predicate. Perhaps that predicate should have been in the ON instead?
SELECT
i.ProductNumber
,i.ProductType
,i.ProductPurchaseDate
,ih.SalesPersonComputerID
,ih.SalesPerson
,ic2.FlaggedComments
,ic2.FirstComments
FROM [Products] i
LEFT OUTER JOIN
(SELECT
MIN(CASE WHEN c2.RowN = 1 THEN c2.Comments) AS FirstComments
,c2.SalesKey
,MIN(CASE WHEN c2.Comments like 'Flagged*%' THEN c2.Comments) AS FlaggedComments
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY ProductNumber ORDER BY DateTimeCommented) as RowN
FROM [SalesComment]
) AS c2
GROUP BY c2.SalesKey
) ic2 ON ic2.SalesKey = i.SalesKey
JOIN [SalesHistory] AS ih
ON ih.SalesKey = i.SalesKey
WHERE
i.SaleDate between #StartDate and #StopDate
AND ih.Status = 'SOLD'

Why is Rank() OVER PARTITION BY returning too many results

I want the results of my query to be the top 3 newest, distinct Campaign Names for each Campaign Type.
My query at the moment is:
DECLARE #currentRecord varchar(160);
SET #currentRecord = '316827D2-B522-E811-816A-0050569FE3BD';
SELECT DISTINCT
rs.CampaignName,
rs.CampaignType,
rs.receivedon,
rs.Rank
FROM
(SELECT
fs_retentioncontact,
receivedon,
regardingobjectidname AS CampaignName,
fs_campaignresponsetypename AS CampaignType,
RANK() OVER (PARTITION BY fs_campaignresponsetypename, regardingobjectidname
ORDER BY receivedon DESC) AS Rank
FROM
dbo.FilteredCampaignResponse) rs
INNER JOIN
dbo.FilteredContact ON rs.fs_retentioncontact = dbo.FilteredContact.contactid
WHERE
(dbo.FilteredContact.parentcustomerid IN (#currentRecord))
AND Rank <= 3
ORDER BY
CampaignType, receivedon DESC;
There may be multiple results for each campaign name as well as campaign response because they are linked to individual contacts but I only want to see the 3 latest unique campaigns for each campaign type.
My query is not partitioning by each individual campaign response type (there are 6 different ones) as I was expecting. If I remove the regardingobjectidname from the PARTITION BY I only get a single row in the results when I should be getting 18 rows. This particular company has over 700 campaign responses across the 6 campaign types.
My query is returning 102 rows so it seems to be removing duplicates on campaign name which is part of what I need but not the whole story.
I have read quite a few posts regarding rank() on here e.g.
how-to-use-rank-in-sql-server
[ using-sql-rank-for-overall-rank-and-rank-within-a-group]2
but I am not able to work out what I am doing wrong from their examples. Could it be the positioning of the 'receivedon' in the ORDER BY? or something else?
I have finally worked out from reading a post on another site how to get the top 3 of each group. I shall post my answer in case it helps anyone else.
I had to use ROW_NUMBER() OVER (PARTITION BY instead of RANK() OVER (PARTITION BY and I also moved the INNER JOIN and WHERE clause (to filter for the correct company) from the outer query to the inner query.
DECLARE #currentRecord varchar(160)
SET #currentRecord='316827D2-B522-E811-816A-0050569FE3BD'
SELECT distinct rs.CampaignName
,rs.CampaignType
, rs.receivedon
,RowNum
FROM(
SELECT fs_retentioncontact
, receivedon
, regardingobjectidname AS CampaignName
,fs_campaignresponsetypename as CampaignType
,ROW_NUMBER() OVER (PARTITION BY fs_campaignresponsetypename ORDER BY fs_campaignresponsetypename, receivedon DESC) AS RowNum
FROM FilteredCampaignResponse
INNER JOIN dbo.FilteredContact ON fs_retentioncontact = dbo.FilteredContact.contactid
WHERE(dbo.FilteredContact.parentcustomerid IN (#currentRecord)))rs
WHERE RowNum <=3
ORDER BY CampaignType,receivedon DESC;

One to many join to last modified record in the most efficient way

I realise that variations of this question have been asked before but I'd like to know the most efficient solution to my particular issue.
I have two tables...
Event (event_id, customer_email...)
Customer (customer_email, last_modified...)
I'm joining these two tables and only want the customer with the greatest last_modified date. The customer table is absolutely huge so was wondering the best way to go about this.
Setting aside indices, this is the query you could use:
SELECT <columns you want>
FROM Event AS E
JOIN Customer AS C
ON C.Customer_Email = E.Customer_Email
JOIN ( SELECT C1.Customer_Email, MAX(C1.Last_Modified) AS LastModified
FROM Customer AS C1
GROUP BY C1.Customer_Email
) AS C2
ON C2.Customer_Email = C.Customer_Email
AND C2.LastModified = C.Last_Modified
Use row_number
select *
from
(
select *, Row_number() over (partition by Event_ID order by Last_Modified desc) rn
from Event
inner join Customer
on Event.Customer_Email = Customer.Customer_Email
) v
where rn = 1

Is there a way to do FIRST() in SQL Server?

From my old Access days, there was a First() function that allowed you to get the first row as an aggregate function. Is there any equivalent in SQL Server?
SELECT
c.ID
, p.ID
, FIRST(p.ProductName)
, SUM(fee.Amount)
from Fee as f
INNER JOIN Product as p
ON p.ID = f.ProductID
INNER JOIN Customer as c
ON c.ID = p.CustomerID
GROUP BY c.ID, p.ID
Edit:
I just wanted a value from any row, since they are all going to be the same. I was trying to be nice to the database and let it just give me the first one that it finds :)
Well, it depends.
You mean "any single row"? Then you can use MIN or MAX, it should work with most data types.
However, if you mean "the first row you can find", then the answer is no.
That's akin to telling the database engine that "I want you to give me a particular row, that fits these criteria, and one of the criteria is that you can give me any row you want".
The reason for this is that unless you order the rows, there's no concept of first, and you can't order the rows in any meaningful way that would work with a group this way.
You could try:
SELECT c.ID, p.ID,
(SELECT TOP 1 ProductName FROM Product ORDER BY ID) AS ProductName,
SUM(fee.Amount)
FROM Fee as f
INNER JOIN Product as pON p.ID = f.ProductID
INNER JOIN Customer as cON c.ID = p.CustomerIDGROUP BY c.ID, p.ID
This one gets the first product directly from the Product Table as a sub-query. The ORDER BY ID in the sub query should get you the first ProductName in the Product table.
You can do SELECT TOP 1 * FROM ... to get only the first row.
not that I know about, just use MIN()

Resources