T-SQL Order by most relevant and most recent on top - sql-server

I am using ContainsTable in my T-SQL statement to get most relevant content as per some given keywords in below query.
Select #Values = '"Keyword 1","Keyword 2,"Keyword 3"',
Select #FirstLevelValues = 'Isabout ('+#Values+' Weight(0.7))'
Select #SecondLevelValues = 'Isabout ('+#Values+' Weight(0.3))'
;with cte as (
(Select [Key], [Rank] from ContainsTable(PostsTable, Title, #FirstLevelValues))
union
(Select [Key], [Rank] from ContainsTable(PostsTable, Description, #SecondLevelValues))
)
Select Top 100 P.* from PostsTable P
Left Join cte on cte.[Key] = P.ID where
P.PostDate between DATEADD(DAY, -5, GETUTCDATE()) and GETUTCDATE()
Order by cte.[Rank] DESC, PostDate DESC
Now my problem is that I have [Rank] column, which rates a Post from PostsTable on the basis of most relevant (most keyword matches has higher ranking). But, this should work in conjunction with PostDate column. Means the most relevant and the most recent Post should be rated higher than only the most relevant post. I do understand the concept of Order by, where one resultset is ordered basis one column first followed by the second column and so on. But in my case, I want that the PostDate should also be considered a criteria, probably with a Weight as in IsAbout clause, so it can also decide the ranking of the post.

You can just flip the order of the ORDER BY columns to sort by PostDate first. But you need to change to a JOIN otherwise you will just get all Posts sorted by date.
SET #Values = '"Keyword 1","Keyword 2,"Keyword 3"';
SET #FirstLevelValues = 'Isabout ('+#Values+' Weight(0.7))';
SET #SecondLevelValues = 'Isabout ('+#Values+' Weight(0.3))';
with cte as
(
Select [Key], [Rank]
from ContainsTable(PostsTable, Title, #FirstLevelValues)
union
Select [Key], [Rank]
from ContainsTable(PostsTable, Description, #SecondLevelValues)
)
Select Top (100) P.*
from PostsTable P
Join cte on cte.[Key] = P.ID
where P.PostDate between DATEADD(DAY, -5, GETUTCDATE()) and GETUTCDATE()
Order by
p.PostDate DESC,
cte.[Rank] DESC;
I note though that you may get duplicate results if a Key is returned in both CONTAINSTABLE functions. You then need to also to take into account the higher of the ranks.
Instead you need either a FULL JOIN:
with cte as
(
Select
[Key] = ISNULL(t.[Key], d.[Key]),
Rank = CASE WHEN t.Rank < d.Rank THEN d.Rank ELSE t.Rank END -- be careful of NULL
from ContainsTable(PostsTable, Title, #FirstLevelValues) t
full join ContainsTable(PostsTable, Description, #SecondLevelValues) d
on d.[Key] = t.[Key]
)
Select Top (100) P.*
from PostsTable P
Join cte on cte.[Key] = P.ID
where P.PostDate between DATEADD(DAY, -5, GETUTCDATE()) and GETUTCDATE()
Order by
p.PostDate DESC,
cte.[Rank] DESC;
Or you can use two LEFT JOINs.
Select Top (100) P.*
from PostsTable P
Left Join ContainsTable(PostsTable, Title, #FirstLevelValues) t on t.[Key] = P.ID
left join ContainsTable(PostsTable, Description, #SecondLevelValues) d on d.[Key] = P.ID
where P.PostDate between DATEADD(DAY, -5, GETUTCDATE()) and GETUTCDATE()
and (d.Rank is not null or t.Rank is not null)
Order by
p.PostDate DESC,
ISNULL(d.Rank, t.Rank) DESC;
The best option is probably to just use multiple columns in the CONTAINSTABLE, although this won't work if you want multiple weightings.
Select Top (100) P.*
from PostsTable P
Join ContainsTable(PostsTable, (Title, Description), #FirstLevelValues) ct on t.[Key] = P.ID
where P.PostDate between DATEADD(DAY, -5, GETUTCDATE()) and GETUTCDATE()
Order by
p.PostDate DESC,
ct.Rank DESC;

Related

Print results side by side SQL Server

I have following result set,
Now with above results i want to print the records via select query as below attached image
Please note, I will have only two types of columns in output Present Employee & Absent Employees.
I tried using pivot tables, temporary table but cant achieve what I want.
One method would be to ROW_NUMBER each the the "statuses" and then use a FULL OUTER JOIN to get the 2 datasets into the appropriate columns. I use a FULL OUTER JOIN as I assume you could have a different amount of employees who were present/absent.
CREATE TABLE dbo.YourTable (Name varchar(10), --Using a name that doesn't require delimit identification
Status varchar(7), --Using a name that doesn't require delimit identification
Days int);
GO
INSERT INTO dbo.YourTable(Name, Status, Days)
VALUES('Mal','Present',30),
('Jess','Present',20),
('Rick','Absent',30),
('Jerry','Absent',10);
GO
WITH RNs AS(
SELECT Name,
Status,
Days,
ROW_NUMBER() OVER (PARTITION BY Status ORDER BY Days DESC) AS RN
FROM dbo.YourTable)
SELECT P.Name AS PresentName,
P.Days AS PresentDays,
A.Name AS AbsentName,
A.Days AS AbsentDays
FROM (SELECT R.Name,
R.Days,
R.Status,
R.RN
FROM RNs R
WHERE R.Status = 'Present') P
FULL OUTER JOIN (SELECT R.Name,
R.Days,
R.Status,
R.RN
FROM RNs R
WHERE R.Status = 'Absent') A ON P.RN = A.RN;
GO
DROP TABLE dbo.YourTable;
db<>fiddle
2 CTE's is actually far neater:
WITH Absents AS(
SELECT Name,
Status,
Days,
ROW_NUMBER() OVER (ORDER BY Days DESC) AS RN
FROM dbo.YourTable
WHERE Status = 'Absent'),
Presents AS(
SELECT Name,
Status,
Days,
ROW_NUMBER() OVER (ORDER BY Days DESC) AS RN
FROM dbo.YourTable
WHERE Status = 'Present')
SELECT P.Name AS PresentName,
P.Days AS PresentDays,
A.Name AS AbsentName,
A.Days AS AbsentDays
FROM Absents A
FULL OUTER JOIN Presents P ON A.RN = P.RN;

CASE Statement with condition

I think this will be easier to show an example first and then explain:
SELECT P.ID,
(CASE WHEN PC.NewCostPrice IS NULL
THEN P.Cost ELSE MAX(PC.Date) PC.NewCostPrice
END)
FROM price AS P
LEFT OUTER JOIN priceChange as PC
ON P.ID = PC.ID
So in the example, if the NewCostPrice IS NULL, meaning there wasn't a price change, then I want the normal cost (P.Cost). However, if there was a price change, I want the most recent (MAX(Date)) price change. I am not sure how to incorporate that into the CASE statement.
I feel like it can be done with a subquery and having clause but that didn't really work out when I tried. Any suggestions?
Thanks!
There are 2 approaches you might consider - I would test both to see which performs better for your situation.
Use ROW_NUMBER() in subquery to find most recent price change of all price changes, then join that to prices to get correct price.
Use correlated subquery (many ways of this, either in SELECT as in other answer or with OUTER APPLY) to get only most recent price change for each row of prices
If your price table is very large and you are getting a large number of prices at once, method #1 will likely be better so the correlated subquery doesn't run for every single row of the result set.
If your final query pulls back a relatively small number of records instead of huge result sets for your server, then the correlated subquery could be better for you.
1. The ROW_NUMBER() approach
SELECT
P.ID,
COALESCE(PC.NewCostPrice, P.Cost) AS LatestPrice
FROM Price AS P
LEFT OUTER JOIN (
SELECT
ID,
ROW_NUMBER() OVER (PARTITION BY ID
ORDER BY [Date] DESC) AS RowId,
NewCostPrice
FROM PriceChange
) PC
ON P.ID = PC.ID
AND PC.RowId = 1 -- Only most recent
2a. Correlated subquery (SELECT)
SELECT
P.ID,
COALESCE((
SELECT TOP 1
NewCostPrice
FROM PriceChange PC
WHERE PC.ID = P.ID
ORDER BY PC.[Date] DESC
), P.Cost) AS LatestPrice
FROM Price AS P
2b. Correlated subquery with OUTER APPLY
SELECT
P.ID,
COALESCE(PC.NewCostPrice, P.Cost) AS LatestPrice
FROM Price AS P
OUTER APPLY (
SELECT TOP 1
NewCostPrice
FROM PriceChange PC
WHERE PC.ID = P.ID
ORDER BY PC.[Date] DESC
) PC
Whether you use 2a or 2b is more likely a preference in how you want to maintain the query going forward.
Easy way
SELECT distinct P.ID,
ISNULL((SELECT TOP 1 PC1.NewCostPrice FROM priceChange as PC1 WHERE PC1.ID = p.id ORDER BY PC1.Date DESC), p.cost)
FROM price AS P
Here I assume PC.ID is not a primary key, or it makes no sense to join with ID while there could be different price on the same item.
From your query I assume you just want to fetch the latest NewCostPrice sorted by Date, by joining priceChange
SELECT
P.ID,
CASE
WHEN PC.NewCostPrice IS NULL THEN P.Cost
ELSE PC.NewCostPrice
END AS NewPrice
FROM
price AS P
LEFT JOIN
(SELECT *, RANK() OVER (PARTITION BY ID ORDER BY [Date] DESC) as rk FROM priceChange) PC ON P.ID = PC.ID AND PC.rk = 1
SELECT P.ID
,(CASE
WHEN PC.NewCostPrice IS NULL
THEN P.Cost
ELSE (SELECT TOP 1 PC1.NewCostPrice
FROM priceChange PC1
WHERE PC1.ID = PC.ID
GROUP BY PC1.NewCostPrice, PC1.Date
ORDER BY PC1.Date DESC
)
END
)
FROM price AS P
LEFT OUTER JOIN priceChange as PC
ON P.ID = PC.ID

Applying window function in one query without CTE

I have been searched in the net and trying to create a query that contains a window function without a CTE but I couldn't get the result and I need some help
this my query that I create with a CTE what I need is doing that in one query
;with cte(Gid, id, prod, orderdate, shipdate, ranking) as
(
select
p1.Gid Gid,
p1.id id,
p1.prod prod,
p1.orderdate orderdate,
p2.shipdate shipdate,
rank() over (partition by p1.prod order by p1.id desc) ranking
from
shpro p1
inner join
shpro p2 on p1.id = p2.id
where
cast(p1.orderdate as DATE) > GETDATE()
and cast(p1.shipdate as DATE) < GETDATE() - 1
)
select *
from cte
where ranking = 1
Demo with some test data on how this works
select top (1) with ties
p1.Gid Gid,
p1.id id,
p1.prod prod,
p1.orderdate orderdate,
p2.shipdate shipdate
from shpro p1 inner join shpro p2
on p1.id=p2.id
where cast(p1.orderdate as DATE)>GETDATE() and cast(p1.shipdate as DATE)<GETDATE()-1
order by
Rank() over (partition by p1.prod order by p1.id desc)
This works like below..
1.order by arranges all the rows based on their rank
2.Top 1 with ties options fetches all the rows which are tied
References:
T-SQL Querying (Developer Reference)
Below is some sample data to play with:
CREATE TABLE #TEST
(
Id INT,
Name VARCHAR(10)
)
Insert Into #Test
select 1,'A'
Union All
Select 1,'B'
union all
Select 1,'C'
union all
Select 2,'D'
select top 1 with ties id,name
from #test
order by row_number() over (partition by id order by id)
CTE can be translated directly into subquery. Yours query should look similar to following:
SELECT * FROM
(
select
p1.Gid Gid,
p1.id id,
p1.prod prod,
p1.orderdate orderdate,
p2.shipdate shipdate,
rank() over (partition by p1.prod order by p1.id desc) ranking
from
shpro p1
inner join
shpro p2 on p1.id = p2.id
where
cast(p1.orderdate as DATE) > GETDATE()
and cast(p1.shipdate as DATE) < GETDATE() - 1
) CteAsSubquery
WHERE ranking = 1
Note alias CteAsSubquery is there (most common error).

How do I get a max record for each week in SQL Server 2012?

I'm looking to get a query that returns the info for the largest order, week by week.
My very basic concept is something like this:
select
datepart(ww, order_date), order_id, order_revenue, order_margin
from
orders
where
order_date >= 1/1/2016
and order_id in (select max order for each week)
Basically I just want the week #, order id, order revenue, and order margin for the largest order margin record for each week.
You need to use a "Windowing Function"
https://www.simple-talk.com/sql/learn-sql-server/window-functions-in-sql-server/
select *
from
( select datepart(ww, order_date), order_date, order_id, order_revenue, order_margin
, row_number() over (partition by datepart(ww, order_date) order by order_margin desc) as rn
from orders
where order_date >= 1/1/2016
) tt
where tt.rn = 1
order by order_date desc
And I think you need to single quote the date
This is a slightly different method; it has a lot more details, but that's because I designed it to run in AdventureWorks (don't know how to tell which version) so I could test the functionality.
USE AdventureWorks
;WITH OrderDetails
AS (
SELECT soh.SalesOrderID, CAST(soh.OrderDate AS DATE) AS OrderDate,
SUM(sod.OrderQty * (sod.UnitPrice - pch.StandardCost)) AS OrderMargin
FROM Sales.SalesOrderHeader soh
LEFT JOIN Sales.SalesOrderDetail sod
ON soh.SalesOrderID = sod.SalesOrderID
LEFT JOIN Production.ProductCostHistory pch
ON sod.ProductID = pch.ProductID
WHERE soh.OrderDate >= pch.StartDate AND soh.OrderDate <= pch.EndDate
GROUP BY soh.SalesOrderID, soh.OrderDate
),
MaxOrder
AS (
SELECT DATEPART(yy,OrderDate) AS Year, DATEPART(ww,OrderDate) AS Week,
MAX(OrderMargin) AS MaxOrderMargin
FROM OrderDetails
GROUP BY DATEPART(yy,OrderDate), DATEPART(ww,OrderDate)
)
SELECT mo.Year, mo.Week, od.SalesOrderID, '$'+FORMAT(mo.MaxOrderMargin,'#,0.00') AS OrderMargin
FROM MaxOrder mo
INNER JOIN OrderDetails od
ON mo.MaxOrderMargin = od.OrderMargin
WHERE DATEPART(yy,od.OrderDate) = mo.Year AND DATEPART(ww,od.OrderDate) = mo.Week
ORDER BY Year, Week
This has slightly improved functionality over the previous example in one regard only, and that is because it will include ALL the orders with that margin, even if there are multiple. I would note, however, that this can be achieved using RANK() instead of ROW_NUMBER(). Show here using a CTE:
USE AdventureWorks
;WITH OrderDetails
AS (
SELECT soh.SalesOrderID, CAST(soh.OrderDate AS DATE) AS OrderDate,
SUM(sod.OrderQty * (sod.UnitPrice - pch.StandardCost)) AS OrderMargin
FROM Sales.SalesOrderHeader soh
LEFT JOIN Sales.SalesOrderDetail sod
ON soh.SalesOrderID = sod.SalesOrderID
LEFT JOIN Production.ProductCostHistory pch
ON sod.ProductID = pch.ProductID
WHERE soh.OrderDate >= pch.StartDate AND soh.OrderDate <= pch.EndDate
GROUP BY soh.SalesOrderID, soh.OrderDate
),
OrderRank
AS (
SELECT DATEPART(yy,OrderDate) AS Year, DATEPART(ww,OrderDate) AS Week,
SalesOrderID, '$'+FORMAT(OrderMargin,'#,0.00') AS OrderMargin,
RANK() OVER (PARTITION BY DATEPART(yy,OrderDate), DATEPART(ww,OrderDate) ORDER BY OrderMargin DESC) AS MRank
FROM OrderDetails
),
MRank
AS (
SELECT Year, Week, SalesOrderID, OrderMargin
FROM OrderRank
WHERE MRank = 1
)
SELECT * FROM MRank

RANK() Over Partition BY not working

When I run the code below the ROWID is always 1.
I need to the ID to start at 1 for each item with the same Credit Value.
;WITH CTETotal AS (SELECT
TranRegion
,TranCustomer
,TranDocNo
,SUM(TranSale) 'CreditValue'
FROM dbo.Transactions
LEFT JOIN customers AS C
ON custregion = tranregion
AND custnumber = trancustomer
LEFT JOIN products AS P
ON prodcode = tranprodcode
GROUP BY
TranRegion
,TranCustomer
,TranDocNo)
SELECT
r.RegionDesc
,suppcodedesc
,t.tranreason as [Reason]
,t.trandocno as [Document Number]
,sum(tranqty) as Qty
,sum(tranmass) as Mass
,sum(transale) as Sale
,cte.CreditValue AS 'Credit Value'
,RANK() OVER (PARTITION BY cte.CreditValue ORDER BY cte.CreditValue)AS ROWID
FROM transactions t
LEFT JOIN dbo.Regions AS r
ON r.RegionCode = TranRegion
LEFT JOIN CTETotal AS cte
ON cte.TranRegion = t.TranRegion
AND cte.TranCustomer = t.TranCustomer
AND cte.TranDocNo = t.TranDocNo
GROUP BY
r.RegionDesc
,suppcodedesc
,t.tranreason
,t.trandocno
,cte.CreditValue
ORDER BY CreditValue ASC
EDIT
All the credit values with 400 must have the ROWID set to 1. And all the credit values with 200 must have the ROWID set to 2. And so on and so on.
Do you need something like this?
with cte (item,CreditValue)
as
(
select 'a',8 as CreditValue union all
select 'b',18 union all
select 'a',8 union all
select 'b',18 union all
select 'a',8
)
select CreditValue,dense_rank() OVER (ORDER BY item)AS ROWID from cte
Result
CreditValue ROWID
----------- --------------------
8 1
8 1
8 1
18 2
18 2
In your code replace
,RANK() OVER (PARTITION BY cte.CreditValue ORDER BY cte.CreditValue)AS ROWID
by
,DENSE_RANK() OVER (ORDER BY cte.CreditValue)AS ROWID
You just don't have to use PARTITION, just DENSE_RANK() OVER (ORDER BY cte.CreditValue)
I think the problem is with the RANK() OVER (PARTITION BY clause
you have to partition it by item not by CreditValue
Try this
RANK() OVER (PARTITION BY cte.CreditValue ORDER BY cte.RegionDesc)AS ROWID
Edit: The issue here isn't actually the nesting of the subquery, it's potentially based on partition by having columns that truly make each row unique (or 1)
Rather than ranking within your complex query like this
select
rank() over(partition by...),
*
from
data_source
join table1
join table2
join table3
join table4
order by
some_column
Try rank() or row_number() on the resulting data set, not within it.
For example, using the query above, remove rank() and implement it this way:
select
rank() over(partition by...),
results.*
from (
select
*
from
data_source
join table1
join table2
join table3
join table4
order by
some_column
) as results

Resources