SQL Server joining two tables, order by and display one record - sql

I am having trouble with a SQL Server statement. The perfect scenario is the order and another table (jobs) by date created then display the contact information in descending order. Currently I can get the script to show all records, however if the user has more than one job then they are displayed more than once.
SELECT
c.*,
p.date_created
FROM
[db].[dbo].[Contact] AS c
JOIN
[db].[dbo].[job] AS p ON p.contact_id = c.contact_id
UNION
SELECT
*,
0 as date_created
FROM
[db].[dbo].[Contact]
ORDER BY
p.date_created DESC
The output
contact_id| date_created | contact_name
1 | 8/29/2016 1:07:18 PM | sam
1 | 8/26/2016 1:04:01 PM | sam
14 | 8/24/2016 5:07:22 PM | steve
The final output should just show the newest date created and for one user. Help is much appreciated.

The column in union select must match for number and type so convert 0 in a proper date
SELECT
c.contact_id
,max(p.date_created)
,c. contact_name
FROM [db].[dbo].[Contact] AS c
JOIN [db].[dbo].[job] AS p
ON p.contact_id = c.contact_id
GROUP BY c.contact_id,c. contact_name
union
select
c.contact_id
, convert(datetime, '01/01/1070', 101) as date_created
, c. contact_name
from [db].[dbo].[Contact]
ORDER BY p.date_created desc`
The result you need anyway should be obtainable with only
SELECT
c.contact_id
,max(p.date_created) as max_date_created
,c. contact_name
FROM [db].[dbo].[Contact] AS c
LEFT JOIN [db].[dbo].[job] AS p
ON p.contact_id = c.contact_id
GROUP BY c.contact_id,c. contact_name
ORDER BY c.contact_id,c. contact_name, max_date_created

Related

SQL MAX aggregate function not bringing the latest date

Purpose: I am trying to find the max date of when the teachers made a purchase and type.
Orders table
ID
Ordertype
Status
TeacherID
PurchaseDate
SchoolID
TeacherassistantID
1
Pencils
Completed
1
1/1/2021
1
1
2
Paper
Completed
1
3/5/2021
1
1
3
Notebooks
Completed
1
4/1/2021
1
1
4
Erasers
Completed
2
2/1/2021
2
2
Teachers table
TeacherID
Teachername
1
Mary Smith
2
Jason Crane
School table
ID
schoolname
1
ABC school
2
PS1
3
PS2
Here is my attempted code:
SELECT o.ordertype, o.status, t.Teachername, s.schoolname
,MAX(o.Purchasedate) OVER (PARTITION by t.ID) last_purchase
FROM orders o
INNER JOIN teachers t ON t.ID=o.TeacherID
INNER JOIN schools s ON s.ID=o.schoolID
WHERE o.status in ('Completed','In-progress')
AND o.ordertype not like 'notebook'
It should look like this:
Ordertype
Status
teachername
last_purchase
schoolname
Paper
Completed
Mary Smith
3/5/2021
ABC School
Erasers
Completed
PS1
2/1/2021
ABC school
It is bringing multiple rows instead of just the latest purchase date and its associated rows. I think i need a subquery.
Aggregation functions are not appropriate for what you are trying to do. Their purpose is to summarize values in multiple rows, not to choose a particular row.
Just a window function does not filter any rows.
You want to use window functions with filtering:
SELECT ordertype, status, Teachername, schoolname, Purchasedate
FROM (SELECT o.ordertype, o.status, t.Teachername, s.schoolname,
o.Purchasedate,
ROW_NUMBER() OVER (PARTITION by t.ID ORDER BY o.PurchaseDate DESC) as seqnum
FROM orders o JOIN
teachers t
ON t.ID = o.TeacherID
schools s
ON s.ID = o.schoolID
WHERE o.status in ('Completed', 'In-progress') AND
o.ordertype not like 'notebook'
) o
WHERE seqnum = 1;
You can use it in different way. it's better to use Group By for grouping the other columns and after that use Order by for reorder all records just like bellow.
SELECT top 1 o.ordertype, o.status, t.Teachername, s.schoolname
,o.Purchasedate
FROM orders o
INNER JOIN teachers t ON t.ID=o.TeacherID
INNER JOIN schools s ON s.ID=o.schoolID
having o.status in ('Completed','In-progress')
AND o.ordertype not like 'notebook'
group by o.ordertype, o.status, t.Teachername, s.schoolname
order by o.Purchasedate Desc

Sub query for two tables base on status using SQL

I have two tables Patient and PatientStatusLog using SQL tables
1.Patient
sno patientid status createdby Reegion
1 481910 D 1222 India
2 476795 D 1222 India
2.PatientStatusLog
sno patientid status comments createdby CreatedDate(dd/mm/yyyy)
1 481910 A mycommnet 1222 01/01/2000
2 481910 A mycommnet 1222 02/01/2000
3 481910 B mycommnet 1222 01/01/2000
4 481910 C mycommnet 1222 01/01/2000
I need output like below who have status A and createddate should recent pass date from PatientStatusLog table using patientid of both tables
Region status CreatedDate
India A 02/01/2000
You can use window functions:
select . . . -- whatever columns you want
from Patient p join
(select psl.*,
rank() over (partition by patientid order by createddate desc) as seqnum
from PatientStatusLog psl
) psl
on p.patientid = psl.patientid
where psl.seqnum = 1 and psl.status = 'A';
Note that this uses rank() because the creation date appears to have duplicates.
Try this:
SELECT TOP 1
p.Region,
psl.Status,
psl.CreatedDate
FROM
Patient [p]
JOIN PatientStatusLog [psl] ON psl.patientid = p.patientid
AND psl.status = 'A'
ORDER BY
psl.CreatedDate DESC
For the subquery request:
SELECT
p.Region,
x.status,
x.CreatedDate
FROM
(
SELECT TOP 1
PatientId,
Status,
CreatedDate
FROM
PatientStatusLog
WHERE
status = 'A'
ORDER BY
CreatedDate DESC
) AS x
JOIN Patient [p] ON p.PatientId = x.PatientId
Note that this is definitely not the best way to handle this, both from a code readability and optimization perspective.

Select based on max date from another table

I'm trying to do a simple Select query by getting the country based on the MAX Last update from the other table.
Order#
1
2
3
4
The other table contains the country and the last update:
Order# Cntry Last Update
1 12/21/2019 9:19 PM
1 US 1/10/2020 1:07 AM
2 JP 7/29/2020 12:15 PM
3 CA 4/12/1992 2:04 PM
3 GB 11/6/2001 9:26 AM
3 DK 2/1/2005 3:04 AM
4 CN 8/20/2013 12:04 AM
4 10/1/2015 4:04 PM
My desired result:
Order# Country
1 US
2 JP
3 DK
4
Not sure the right solution for this. So far i'm stuck with this:
SELECT Main.[Order#], tempTable.Cntry
FROM Main
LEFT JOIN (
SELECT [Order#], Cntry, Max([Last Update]) as LatestDate FROM Country
GROUP BY [Order#], Cntry
) as tempTable ON Main.[Order#] = tempTable.[Order#];
Thanks in advance!
If needs only number of order and country,maybe don't need two tables:
SELECT distinct order, country
FROM
(
SELECT order, LAST_VALUE (country) OVER (PARTITION by [order] order by last_update) country FROM Country
) X
In SQL Server, you can use a correlated subquery:
update main
set country = (select top (1) s.country
from secondtable s
where s.order# = main.order#
order by s.lastupdate desc
);
EDIT:
A select would look quite simimilar:
select m.*,
(select top (1) country
from secondtable s
where s.order# = main.order#
order by s.lastupdate desc
)
from main m
I don't have time to try it with sample data, but is that what you are looking for?
select order orde, cntry
from table
where last_update =
(select max(last_update) from table where order = orde)

How to remove duplicate columns from join in SQL

I have the following code
SELECT *
FROM customer
INNER JOIN
(SELECT
customerid, newspapername, enddate, n.publishedby
FROM
newspapersubscription ns, newspaper n
WHERE
publishedby IN (SELECT publishedby
FROM newspaper
WHERE ns.newspapername = n.NewspaperName)
UNION
SELECT
customerid, Magazinename, enddate, m.publishedby
FROM
magazinesubscription ms, magazine m
WHERE
publishedby IN (SELECT publishedby
FROM magazine
WHERE ms.Magazinename = m.MagazineName)) ON customer.customerid = customerid
ORDER BY
customer.customerid;
The customer table has the following:
customerid | customername | customersaddress
This query returns the following result:
customerid | customername | customersaddress | customerid | newspapername | enddate| publishedby
What I actually want is
customerid | customername | customersaddress | newspapername | magazinename | enddate| publishedby
Here, the newspapername field should be blank if the magazinename is present and vice versa. Also, the duplicate field of customerid from the union operations should not be present, while in my result, the value of both the newspapername and the magazinename are put under newspapername title.
How can I do that?
Since you are querying the table with '*', you will always get all the columns in both tables. In order to omit this column, you will have to manually name all columns you DO want to query. To address your other need, you need to simply insert a dummy column to each clause in the union query. Below is an example that should work to allow for what you want -
SELECT customer.customerid, customer.customername, customer.customeraddress, newspapername, magazinename, enddate, publishedby
FROM customer
INNER JOIN
(select customerid, newspapername, null Magazinename, enddate, n.publishedby
from newspapersubscription ns, newspaper n
where publishedby in(select publishedby
from newspaper
where ns.newspapername = n.NewspaperName)
UNION
select customerid, null newspapername, Magazinename, enddate, m.publishedby
from magazinesubscription ms, magazine m
where publishedby in(select publishedby
from magazine
where ms.Magazinename = m.MagazineName))
on customer.customerid = customerid
ORDER BY customer.customerid;
To get the projection you want, build sub-queries of the right shape and UNION them to get the result set. UNION ALL is better than UNION because it avoids a sort: you know you'll get a distinct set because you're joining on two different tables.
select * from (
select customer.*
, n.newspapername
, null as magazinename
, ns.enddate
, n.publishedby
from customer
join newspapersubscription ns
on ns.customerid = customer.customerid
join newspaper n
on n.newspapername = ns.newspapername
union all
select customer.*
, null as newspapername
, m.magazinename
, ms.enddate
, m.publishedby
from customer
join magazinesubscription ms
on ms.customerid = customer.customerid
join magazine m
on m.magazinename = ms.magazinename
)
order by customerid, newspapername nulls last, magazinename ;
Here is the output from my toy data set (which lacks publishedby columns:
CUSTOMERID CUSTOMERNAME NEWSPAPERNAME MAGAZINENAME ENDDATE
---------- -------------------- ---------------------- ---------------------- ---------
10 DAISY-HEAD MAISIE THE DAILY BUGLE 30-SEP-17
30 FOX-IN-SOCKS THE DAILY BUGLE 30-SEP-17
30 FOX-IN-SOCKS THE WHOVILLE TIMES 30-SEP-16
30 FOX-IN-SOCKS GREEN NEWS 31-DEC-17
30 FOX-IN-SOCKS TWEETLE BEETLE MONTHLY 31-DEC-16
40 THE LORAX GREEN NEWS 31-DEC-18
6 rows selected.
SQL>

Build customers report of last years with T-SQL

I've 3 tables (simplified):
-----------Orders--------------------
Id | Total_Price | Customer_Id | Date
--------Order Details---------------------
Id | Order_Id | Product Name | Qty | Value
----Customers------
Id | Name | Address
I take a total order value of single customer with this query:
SELECT C.ID, C.NAME , SUM(O.TOTAL_PRICE)
FROM CUSTOMERS C
JOIN ORDERS O ON O.CUSTOMER_ID = C.ID
GROUP BY C.ID, C.NAME
Now, I want to build a report with total order value filtered by a range of dates:
SELECT C.ID, C.NAME , SUM(O.TOTAL_PRICE)
FROM CUSTOMERS C
JOIN ORDERS O ON O.CUSTOMER_ID = C.ID
WHERE O.DATE BETWEEN #value1 AND #value2
GROUP BY C.ID, C.NAME
this works OK, but I want to select last 3 year sums of total orders value grouped by customer, this is the results that I want:
1Year | 2Year | 3Year | Customer_Name
-------------------------------------------------
XXX | YYY | ZZZZ | Customer1
XYX | YYZ | ZZTZ | Customer2
....
I've this cardinality:
Customer table with 22.000 rows
Orders table with 87.000 rows
Orders details with 600.000
It is possible without temptable,vartable or stored procedure with long execution time?
In my report I want also to calculate total Qty of last 3 years grouped by customer of a product, but this is the next step.
Any ideas?
Thanks
You can use a case statement to get the result you want. Since there is some ambiguity in your post about how the year ranges are defined, I've left out any calculations to get those year end/starts and just put variables in. You can revise to suit your need.
SELECT C.ID
,C.NAME
,SUM(CASE
WHEN o.DATE BETWEEN #year1start
AND #year1end
THEN O.TOTAL_PRICE
ELSE 0
END) Year1
,SUM(CASE
WHEN o.DATE BETWEEN #year2start
AND #year2end
THEN O.TOTAL_PRICE
ELSE 0
END) Year2
,SUM(CASE
WHEN o.DATE BETWEEN #year3start
AND #year3end
THEN O.TOTAL_PRICE
ELSE 0
END) Year3
FROM CUSTOMERS C
INNER JOIN ORDERS O ON O.CUSTOMER_ID = C.ID
GROUP BY C.ID
,C.NAME
Another option is to use pivot statement. I assume every your date range equals to one year (e.g. 2013, 2014 and so on).
If these years are strongly determined pivot isn't very beautiful option (look at full sqlfiddle example, it has possible solution for your additional question):
select
c.Id, c.Name, c.Address, CostByYear.[2013], CostByYear.[2014], CostByYear.[2015]
from Customers c
left join (
select
pt.Customer_Id, isnull(pt.[2013], 0) as [2013],
isnull(pt.[2014], 0) as [2014], isnull(pt.[2015], 0) as [2015]
from (
select
o.Customer_Id, year(o.Date) [Year], sum(o.Total_Price) [TotalCost]
from Orders o
group by
o.Customer_Id, year(o.Date)
) src
pivot (
sum(TotalCost) for [Year] in ([2013], [2014], [2015])
) pt
) CostByYear on
c.Id = CostByYear.Customer_Id
order by
c.Name
Also you can do both approaches (mine and prev answer) with dynamically created queries if year ranges aren't known and strongly defined.