Logic for Order By in SQL - sql-server

I have a table which get result as left one of the photo.However, I would like to get the result which order by:
1. first, compare the number in val field which group field's data must be 'total'.
2. then, all data with same value at item field will get an order based on the order at condition 1
The right part of the photo is what I want the result is. How should I set the logic with ORDER BY? Thank you.

One approach is to join to a subquery which finds the totals for each item, and then to sort using that.
SELECT
t1.item,
t1.[group],
t1.val
FROM yourTable t1
INNER JOIN
(
SELECT item, val AS total
FROM yourTable
WHERE [group] = 'total'
) t2
ON t1.item = t2.item
ORDER BY
t2.total DESC,
t1.item,
CASE WHEN t1.[group] = 'total' THEN 1 ELSE 0 END,
t1.[group];

Related

Top 1 not returning results when joined

I have the below SQL query that is attempting to return the Last Transaction date for a specific part. The subquery that I'm left joining runs fine when I run it by itself (with the part specific criteria)
SELECT TOP 1 S1.*
FROM PartTran S1
WHERE S1.TranDate > '10/10/2016' AND S1.TranType <> 'ADJ-CST' AND S1.PartNum = '0000AAAO' ORDER BY S1.TranDate DESC
However when I join this into my main query, its returning null.
SELECT T1.PartNum, T2.TranDate, T2.TranType
FROM dbo.Part T1
LEFT JOIN (SELECT TOP 1 S1.* FROM PartTran S1 WHERE S1.TranDate > '10/10/2016' AND S1.TranType <> 'ADJ-CST' ORDER BY S1.TranDate DESC) T2 ON T1.Company = T2.Company AND T1.PartNum = T2.PartNum
WHERE T1.PartNum = '0000AAAO'
Am I missing something here?
Can you please check this following query-
SELECT
T1.PartNum,
T2.TranDate,
T2.TranType
FROM dbo.Part T1
LEFT JOIN
(
SELECT TOP 1 S1.*
FROM PartTran S1
WHERE S1.TranDate > '10/10/2016'
AND S1.TranType <> 'ADJ-CST'
AND S1.PartNum = '0000AAAO'
-- I think this above filter (AND S1.PartNum = '0000AAAO') is required
-- other wise top 1 can select records belongs to other PartNum and
-- your left join will return NULL logically
ORDER BY S1.TranDate DESC
) T2 ON T1.Company = T2.Company
AND T1.PartNum = T2.PartNum
WHERE T1.PartNum = '0000AAAO';
The reason why your original query doesn't work has to do with order of operations.
The derived table T2 resulted in 1 and only 1 record; not 1 record per PART number. This has to do with the derived table obtaining it's results BEFORE it can be joined to T1. Since the part numbers didn't match unless you got lucky on the part and day and company... you would get no data. A cross/outer apply allows you to get the TOP record per Join Criteria. and thus will return multiple records; 1 for each part and company; instead of just 1.
I think you're after a cross or outer apply and you can avoid the 2nd filter in the derived table (T2) If you want parts w/o any transactions kept then use the outer apply, if you only want those with part transactions use cross apply.
SELECT T1.PartNum, T2.TranDate, T2.TranType
FROM dbo.Part T1
CROSS APPLY (SELECT TOP 1 S1.*
FROM PartTran S1
WHERE S1.TranDate > '10/10/2016'
AND S1.TranType <> 'ADJ-CST'
ORDER BY S1.TranDate DESC) T2
ON T1.Company = T2.Company
AND T1.PartNum = T2.PartNum
WHERE T1.PartNum = '0000AAAO'
Alternatively you could use a row number instead of top and partition by your company and partNum ordering by transdate and only return row number 1st ordering by your transdate descending.
Here's a MSDN Doc link showing how cross/outer apply
works.
Try removing the 'LEFT' in your join because it is allowing you to select rows in the sub query that don't meet the criteria of your WHERE clause. That seemed to fix the issue in my text environment at least.
I would suggest a simpler query:
SELECT TOP 1 p.PartNum, T.TranDate, T.TranType
FROM dbo.Part p JOIN
PartTran pt
ON pt.Company = p.Company AND
pt.PartNum = t.PartNum AND
pt.TranType <> 'ADJ-CST' AND
pt.TranDate > '2016-10-10'
WHERE p.PartNum = '0000AAAO'
ORDER BY pt.TranDate DESC;

SQL Server Group By - Aggregate NULL or empty values into all other values

I am trying to group by a column. The problem is that the NULL values of the column are grouped as a separate group.
I want the NULL values to be added to each of the other group values instead.
Example of a table:
The results I want to get from group by with sum aggregation over the 'val' column:
Can anyone help me?
Thanks!
You can precalculate the value to spread through the rows and then just do arithmetic:
select t.id,
sum(t.val) + (null_sum / cnt_id)
from t cross join
(select count(distinct id) as cnt_id,
sum(case when id is null then val else 0 end) as null_sum
from t
) tt
group by t.id;
Note some databases do integer division, so you might need null_sum * 1.0 / cnt_id.
A GROUP BY operation can't really generate values for each group on the fly, so logically you need records which are missing to really be present.
One approach is to use a calendar table to generate a table containing one NULL record for each id group:
WITH ids AS (
SELECT DISTINCT id FROM yourTable
WHERE id IS NOT NULL
),
cte AS (
SELECT t1.id, t2.val
FROM ids t1
CROSS JOIN yourTable t2
WHERE t2.id IS NULL
)
SELECT t.id, SUM(t.val) AS val
FROM
(
SELECT id, val FROM yourTable WHERE id IS NOT NULL
UNION ALL
SELECT id, val FROM cte
) t
GROUP BY
id;
Demo

Using sub-queries and filter in WHERE clause while joining tables

SELECT DISTINCT(t1.Ticker),t2.SecurityID,t2.ClosePrice,t2.QuoteDateTime FROM [Hub].[SecurityMaster].[SecurityMasterDetails] as t1
INNER JOIN [Hub].[SecurityMaster].[SecurityPrices] as t2
ON t2.SecurityID =t1.SecurityID
WHERE t2.QuoteDateTime IN (SELECT max(QuoteDateTime) FROM [Hub].[SecurityMaster].[SecurityPrices]) AND t1.SecurityTypeName = 'REIT'
I get an output with no data. The subquery doesn't run along with the other filter in the WHERE clause. I am not sure what I am doing wrong. Can somebody please help!
If you are trying to get the lastest row from SecurityPrices for each Ticker, one option is to use cross apply():
select --distinct /* distinct not needed if `Ticker` is unique on `smd`
smd.Ticker
, sp.SecurityID
, sp.ClosePrice
, sp.QuoteDateTime
from [Hub].[SecurityMaster].[SecurityMasterDetails] as smd
cross apply (
select top 1
i.SecurityID
, i.ClosePrice
, i.QuoteDateTime
from [Hub].[SecurityMaster].[SecurityPrices] i
where i.SecurityID = smd.SecurityID
order by i.QuoteDateTime desc
) as sp
where SecurityTypeName = 'REIT' /* which table does this column belong to? */
I think your query would be
SELECT DISTINCT TOP 1 WITH TIES
t1.Ticker,
t2.SecurityID,
t2.ClosePrice,
t2.QuoteDateTime
FROM [Hub].[SecurityMaster].[SecurityMasterDetails] as t1
INNER JOIN [Hub].[SecurityMaster].[SecurityPrices] as t2 ON t2.SecurityID =t1.SecurityID
WHERE SecurityTypeName = 'REIT'
ORDER BY t2.QuoteDateTime DESC
You aren't getting results because the max(QuoteDateTime) record doesn't have SecurityTypeName = 'REIT'. I think you want the max(QuoteDateTime) for this SecurityTypeName, so this can be done with an INNER JOIN.
SELECT DISTINCT
(t1.Ticker),
t2.SecurityID,
t2.ClosePrice,
t2.QuoteDateTime
FROM [Hub].[SecurityMaster].[SecurityMasterDetails] as t1
INNER JOIN [Hub].[SecurityMaster].[SecurityPrices] as t2
ON t2.SecurityID =t1.SecurityID
INNER JOIN
(SELECT max(QuoteDateTime) DT FROM [Hub].[SecurityMaster].[SecurityPrices]) P on P.DT = t2.QuoteDateTime
WHERE SecurityTypeName = 'REIT'
EDIT
Your data doesn't have what you think it does, I suspect. Here is how you can check...
--Find the SecurityID that matches the max date
SELECT
SecurityID ,
max(QuoteDateTime) DT
FROM [Hub].[SecurityMaster].[SecurityPrices]
GROUP BY SecurityID
--I'm betting this ID isn't in your SecurityMasterDetails where the Type is REIT
SELECT DISTINCT
SecurityID
FROM SecurityMasterDetails
WHERE SecurityTypeName = 'REIT'
Since the SecurityID returned in the first query isn't in the second query result set, you are going to get NULL results.

Aggregate query grouping

The query below seems to work despite being wrong.
The Color column doesn't belong in an aggregate function but I don't want to group by Color. I want to return the color for the minimum priority grouped by Vehicle.
I hope the below is enough to work with - I was hoping for a quick answer but will go into more detail if necessary.
SELECT alert.VehicleID,
min(hotcol.[Priority]) as [Priority]
min(hotcol.Color) as Color,
FROM [ALERTS] alert
INNER JOIN [HOTLISTS] hotlist ON alert.[HotlistID] = hotlist.[HotlistID]
INNER JOIN [HOTLIST_COLORS] hotcol ON hotlist.ColorID = hotcol.ColorID
WHERE VehicleID = 17513851
GROUP BY alert.VehicleID
You can use the ranking function ROW_NUMBER to do this. Something like this:
WITH CTE
AS
(
SELECT
alert.VehicleID,
hotcol.Color,
hotcol.[Priority],
ROW_NUMBER() OVER(PARTITION BY alert.VehicleID
ORDER BY hotcol.[Priority]) AS RN
FROM [ALERTS] alert
INNER JOIN [HOTLISTS] hotlist ON alert.[HotlistID] = hotlist.[HotlistID]
INNER JOIN [HOTLIST_COLORS] hotcol ON hotlist.ColorID = hotcol.ColorID
WHERE VehicleID = 17513851
)
SELECT
VehicleID,
Color,
[Priority]
FROM CTE
WHERE rn = 1;
The ROW_NUMBER function will give a ranking number for each alert.VehicleID and each group will be ordered by priority. Then WHERE rn = 1 will filter out all the rows except the minimum one which has rn = 1.

Top 1 for each joined record

In this SQL Server query, I want to return at most 1 lostReason for each booking. However, the sub-query seems to be returning the first record from the lostBusiness table for every booking. Let me know if I need to clarify.
SELECT
bookings.bookingNumber, lost.lostReason
FROM
bookings
LEFT OUTER JOIN(SELECT TOP (1)
bookingNumber,
lostReason
FROM
lostBusiness) AS lost ON bookings.bookingNumber = lost.bookingNumber
if you need more than one column
select
bookings.bookingNumber, lost.*
from bookings
outer apply
(
select top 1
lost.bookingNumber,
lost.lostReason,
--other columns
from lostBusiness as lost
where bookings.bookingNumber = lost.bookingNumber
order by -- put you order by here
) as lost
or
;with cte as (
select
*,
row_number() over (partition by bookings.bookingNumber order by /* ??? */) as row_num
from bookings
left outer join lostBusiness as lost on bookings.bookingNumber = lost.bookingNumber
)
select * from cte where row_num = 1
if you need more than one column
select
bookings.bookingNumber, max(lost.lostReason) as lostReason
from bookings
left outer join lostBusiness as lost on bookings.bookingNumber = lost.bookingNumber
group by bookings.bookingNumber
If you just want any lost reason, MAX or MIN would do:
SELECT
Bookings.BookingNumber,
MAX(LostBusiness.LostReason) as SomeLostReason
FROM
Bookings
LEFT JOIN LostBusiness ON bookings.BookingNumber= lostBusiness.BookingNumber
GROUP BY
Bookings.BookingNumber
Your query fails because you are joining to a single record, not to a record for each booking.
Try this
select *,
(
select top 1
lostreason
from lostbusiness
where lostbusiness.bookingnumber = bookings.bookingnumber
-- order by goes here.
)
from bookings
You should understand that data doesn't have any inherent order, so you should define what you mean by the "first" reason, by means of an order by clause in the subquery.

Resources