Order By Asc and Desc both in single query - sql-server

I have a query which retrieves latest data from table with pagination which is working fine.
But once the data is older from current time it should also appear but after the latest one.
SELECT A.UserId,A.FirstName,A.LastName,A.PostDate
(SELECT ROW_NUMBER() OVER(ORDER BY CAST(M.PostDate AS DATETIMEOFFSET) DESC) AS 'RowNumber'
M.UserId,
M.FirstName,
M.LastName,
M.PostDate
FROM Messages AS M
Where M.PostDate >= GetDate()
) A
WHERE A.RowNumber BETWEEN #RowStart AND #RowEnd
ORDER BY CAST(A.PostDate AS DATETIMEOFFSET) DESC

I'm not sure if I'm exactly getting what you're looking for, but this should get you something close. I created a CTE with two computed columns:
BeforeAfter, which determines if the date happens before or after the passed in date
AbsDiff, which gives the absolute value of the date diff.
Then, I just sort using those two fields:
DECLARE #current datetime = GETDATE()
;WITH cteMessages AS
(
SELECT UserId, FirstName, LastName, PostDate,
CASE WHEN PostDate < #current THEN 1 ELSE 0 END AS BeforeAfter,
ABS(DATEDIFF(SECOND, #current, PostDate)) AS AbsDiff
FROM Messages
)
SELECT * FROM cteMessages
ORDER BY BeforeAfter, AbsDiff
From those results, you can see how they are sorted first by messages newer than the passed in date, then in reverse order from older messages. You can substitute that order by into your Row_Number function.

You cant do Order By ASC and DESC on the Same column of a Table if you do you will get the following Error
Msg 169, Level 15, State 1, Line 1 A column has been specified more
than once in the order by list. Columns in the order by list must be
unique.
If you want it you may do using Join of the same Table and order by the same column like
SELECT A.UserId,A.FirstName,A.LastName,A.PostDate
(SELECT ROW_NUMBER() OVER(ORDER BY CAST(M.PostDate AS DATETIMEOFFSET) DESC) AS 'RowNumber'
M.UserId,
M.FirstName,
M.LastName,
M.PostDate
FROM Messages AS M
Where M.PostDate >= GetDate()
) A
Inner Join Messages AS Msg on Msg.UserId=A.UserId
WHERE A.RowNumber BETWEEN #RowStart AND #RowEnd
ORDER BY CAST(A.PostDate AS DATETIMEOFFSET) DESC,
CAST(Msg.PostDate AS DATETIMEOFFSET) ASC

I think you should remove the following line
Where M.PostDate >= GetDate()

Related

How can I use COUNT() result in RANK

I have a SQL query for 'SQL Server' which 'count()' based on column 'id'.
And I also want to 'RANK' based on the value of COUNT(). But when I try the below query,
I get error sayign 'Invalid column names 'IdCount''
SELECT
[id],
COUNT(*) AS IdCount,
RANK() OVER (
ORDER BY IdCount
) CountRank
FROM myTable
where DATEDIFF(day,[Time],GETDATE()) < 30
GROUP BY [id]
Can you please tell me how can I reference the COUNT() result?
Thank you.
You can't reference an expression's alias in a window function at the same scope. Also, I think you want the RANK() to be applied from the highest count, not the lowest. Finally, you should never apply calculations like DATEDIFF against a column. How about:
DECLARE #start date = DATEADD(DAY, -30, GETDATE());
SELECT id, IdCount,
CountRank = RANK() OVER (ORDER BY IdCount DESC)
FROM
(
SELECT id, COUNT(*)
FROM dbo.myTable
WHERE [Time] >= #start
GROUP BY id
) AS x;

SQL Query returning multiple values

I am trying to write a query that returns the time taken by an Order from start to completion.
My table looks like below.
Order No. Action DateTime
111 Start 3/23/2018 8:18
111 Complete 3/23/2018 9:18
112 Start 3/24/2018 6:00
112 Complete 3/24/2018 11:10
Now I am trying to calculate the date difference between start and completion of multiple orders and below is my query:
Declare #StartDate VARCHAR(100), #EndDate VARCHAR(100), #Operation VARCHAR(100)
declare #ORDERTable table
(
order varchar(1000)
)
insert into #ORDERTable values ('111')
insert into #ORDERTable values ('112')
Select #Operation='Boiling'
set #EndDate = (SELECT DATE_TIME from PROCESS WHERE ACTION='COMPLETE' AND ORDER in (select order from #ORDERTable) AND OPERATION=#Operation)
---SELECT #EndDate
set #StartDate = (SELECT DATE_TIME from PROCESS WHERE ACTION='START' AND ORDER in (select order from #ORDERTable) AND OPERATION=#Operation)
---SELECT #StartDate
SELECT DATEDIFF(minute, #StartDate, #EndDate) AS Transaction_Time
So, I am able to input multiple orders but I want to get multiple output as well.
And my second question is if I am able to achieve multiple records as output, how am I gonna make sure which datediff is for which Order?
Awaiting for your answers. Thanks in advance.
I am using MSSQL.
You can aggregate by order number and use MAX or MIN with CASE WHEN to get start or end time:
select
order_no,
max(case when action = 'Start' then date_time end) as start_time,
max(case when action = 'Completed' then date_time end) as end_time,
datediff(
minute,
max(case when action = 'Start' then date_time end),
max(case when action = 'Completed' then date_time end)
) as transaction_time
from process
group by order_no
order by order_no;
You can split up your table into two temp tables, cte's, whatever, and then join them together to find the minutes it took to complete
DECLARE #table1 TABLE (OrderNO INT, Action VARCHAR(100), datetime datetime)
INSERT INTO #table1 (OrderNO, Action, datetime)
VALUES
(111 ,'Start' ,'3/23/2018 8:18'),
(111 ,'Complete' ,'3/23/2018 9:18'),
(112 ,'Start' ,'3/24/2018 6:00'),
(112 ,'Complete' ,'3/24/2018 11:10')
;with cte_start AS (
SELECT orderno, Action, datetime
FROM #table1
WHERE Action = 'Start')
, cte_complete AS (
SELECT orderno, Action, datetime
FROM #table1
WHERE Action = 'Complete')
SELECT
start.OrderNO, DATEDIFF(minute, start.datetime, complete.datetime) AS duration
FROM cte_start start
INNER JOIN cte_complete complete
ON start.OrderNO = complete.OrderNO
Why don't you attempt to approach this problem with a set-based solution? After all, that's what a RDBMS is for. With an assumption that you'd have orders that are of interest to you in a table variable like you described, #ORDERTable(Order), it would go something along the lines of:
SELECT DISTINCT
[Order No.]
, DATEDIFF(
minute,
FIRST_VALUE([DateTime]) OVER (PARTITION BY [Order No.] ORDER BY [DateTime] ASC),
FIRST_VALUE([DateTime]) OVER (PARTITION BY [Order No.] ORDER BY [DateTime] DESC)
) AS Transaction_Time
FROM tableName
WHERE [Order No.] IN (SELECT Order FROM #ORDERTable);
This query works if all the values in the Action attribute are either Start or Complete, but also if there are others in between them.
To read up more on the FIRST_VALUE() window function, check out the documentation.
NOTE: works in SQL Server 2012 or newer versions.

In SQL Server how can I fetch all rows with a date greater than some set date or at least 20 rows

I'm looking for a way to fetch at least 20 rows, regardless of the date or all rows with a date greater than a set date (whichever of the 2 fetches the most rows).
For example:
SELECT *
FROM table1
WHERE the_date >= '2014-11-01'
ORDER BY the_date DESC
will give me what I want, but if this returns less than 20 rows then I want to keep going before that date until I get 20 (or until there are no more rows - whichever comes first).
I could just select all and take the ones I need programatically, but I'm trying to avoid that as that would be a major change to the actual code in many places.
Can this be done?
Note: I am using SQL Server 2008.
Here's an approach:
select *
from
(
select row_number() over (order by the_date desc) num, t.*
from table1 t
) i
where i.the_date >= '2014-11-01'
or i.num < 21
Very strange requirement but something like this should work.
DECLARE #Date date = '2014-11-01';
with MyResults as
(
SELECT *, 1 as RowNum
FROM table1
WHERE (the_date >= #Date )
UNION ALL
SELECT top 10 *, ROW_NUMBER() over(order by the_date desc)
FROM table1
order by the_date desc
)
Select *
from MyResults
where RowNum <= 20
The right answer was almost there, but the user has removed it, so here it is again:
SELECT *
FROM table1
WHERE the_date >= '2014-11-01'
union
SELECT top 20 *
FROM table1
ORDER BY the_date desc
The removed answer had union all
This answer has a problem with duplicate dates , but with the data I have that is not an issue

Include Previous and Next Record in Query

I need to return a query for a line chart that has user selected bounds based on a datetime where the user selects the start and end date. However, the data is often sparse and throws off the line chart's display when there is no values close to the start and end of the selected bounds.
For example, if I want to select all the data for 2012 but the first record doesn't appear until March, then my line chart starts its axis at March.
So basically I need to add the immediately previous and following records to the return set sorted by datetime.
Right now, I'm just using UNION with subselects to make it work, but it seems like this would be a common thing that may be built into SQL?
Here's an example of the working query, how can I improve on it?
-- SQL Server example
-- Compare between start and end dates
-- Union with previous and next records for bounds
SELECT
change_date as event_date,
value_column
FROM
testing.dbo.change_log
WHERE change_date BETWEEN '2012-01-01 00:00:00:000' AND '2013-01-01 00:00:00:000'
UNION ALL
SELECT * FROM (
SELECT TOP 1 '2013-01-01 00:00:00:000' as event_date, value_column FROM testing.dbo.change_log WHERE change_date > '2013-01-01 00:00:00:000' ORDER BY change_date ASC
) AS next_rec
UNION ALL
SELECT * FROM (
SELECT TOP 1 '2012-01-01 00:00:00:000' as event_date, value_column FROM testing.dbo.change_log WHERE change_date < '2012-01-01 00:00:00:000' ORDER BY change_date DESC
) AS prev_rec
ORDER BY change_date;
How's about this? I assume you are using this as a stored procedure and are currently feeding it two input parameters. It's not especially different from how you're doing it but might be a little faster.
declare #lowerbound as datetime
declare #upperbound as datetime
;with ordered as (
select *
, row_num = row_number() over (order by change_date)
from change_log
)
select *
from ordered
where row_num >= (select row_num from ordered where change_date = #lowerbound) -1
and row_num <= (select row_num from ordered where change_date = #upperbound) + 1

SQL Server - Get customers with nth order in specific date range

I'm tasked with the following:
Select a list of all customers who had their nth order during a certain date range (usually a specific month).
This list needs to contain: customer id, sum of first n orders
My tables are something like this:
[dbo.customers]: customerID
[dbo.orders]: orderID, customerID,
orderDate, orderTotal
Here is what I've tried so far:
-- Let's assume our threshold (n) is 10
-- Let's assume our date range is April 2013
-- Get customers that already had n orders before the beginning of the given date range.
DECLARE #tmpcustomers TABLE (tmpcustomerID varchar(8))
INSERT INTO
#tmpcustomers
SELECT
c.customerID
FROM
orders o
INNER JOIN customers c ON o.customerID = c.customerID
WHERE
o.orderDate < '2013-04-01'
GROUP BY c.customerID
HAVING (COUNT(o.orderID) >= 10)
-- Now get all customers that have n orders sometime within the given date range
-- but did not have n orders before the beginning of the given date range.
SELECT
a.customerID, SUM(orderTotal) AS firstTenOrderTotal
SELECT
o.customerID, o.orderID, o.orderTotal
FROM
orders o
INNER JOIN customers c ON c.customerID = o.customerID
WHERE
a.customerID NOT IN ( SELECT tmpcustomerID FROM #tmpcustomers )
AND
o.orderDate > '2013-04-01'
AND
o.orderDate < '2013-05-01'
GROUP BY c.customerID
HAVING COUNT(o.orderID) >= 10
This seems to work but it's clunky and slow. Another big problem is that the firstTenOrderTotal is actually the SUM of the total amount of orders by the end of the given date range and not necessarily the first 10.
Any suggestions for a better approach would be much appreciated.
In the insert to #tmpcustomers, why are you joining back to the customer table? The order table already has the customerID that you want. Also, why are you looking for orders where the order date is before your date range? Don't you just want customers with more than n orders between a date range? This will make the second query easier.
By only having the customers with n or more orders in the table variable #tmpcustomers, you should just be able to join it and the orders table in the second query to get the sum of all the orders for those customers where you would once again limit order table records to your date range (so you do not get orders outside of that range). This will remove the having statement and the join to the customers table in your final result query.
Give this a try. Depending on your order distribution it may perform better. In this query im assembling the list of orders in the range, and then looking back to count the number of prior orders (also grabbing the orderTotal).
note: I am assuming the orderID increments as orders are placed.
If this isnt the case just use a row_number over the date to project the sequence into the query.
declare #orders table (orderID int primary key identity(1,1), customerID int, orderDate datetime, orderTotal int)
insert into #orders (customerID, orderDate, orderTotal)
select 1, '2013-01-01', 1 union all
select 1, '2013-01-02', 2 union all
select 1, '2013-02-01', 3 union all
select 2, '2013-01-25', 5 union all
select 2, '2013-01-26', 5 union all
select 2, '2013-02-02', 10 union all
select 2, '2013-02-02', 10 union all
select 2, '2013-02-04', 20
declare #N int, #StartDate datetime, #EndDate datetime
select #N = 3,
#StartDate = '2013-02-01',
#EndDate = '2013-02-20'
select o.customerID,
[total] = o.orderTotal + p.total --the nth order + total prior
from #orders o
cross
apply ( select count(*)+1, sum(orderTotal)
from #orders
where customerId = o.customerID and
orderID < o.orderID and
orderDate <= o.orderDate
) p(n, total)
where orderDate between #StartDate and #EndDate and p.n = #N
Here is my suggestion:
Use Northwind
GO
select ords.OrderID , ords.OrderDate , '<-->' as Sep1 , derived1.* from
dbo.Orders ords
join
(
select CustomerID, OrderID, ROW_NUMBER() OVER(PARTITION BY CustomerID ORDER BY OrderId DESC) AS ThisCustomerCardinalOrderNumber from dbo.Orders
) as derived1
on ords.OrderID = derived1.OrderID
where
derived1.ThisCustomerCardinalOrderNumber = 3
and ords.OrderDate between '06/01/1997' and '07/01/1997'
EDIT:::::::::
I took my CTE example, and reworked it for multiple Customers (seen below).
Give it the college try.
Use Northwind
GO
declare #BeginDate datetime
declare #EndDate datetime
select #BeginDate = '01/01/1900'
select #EndDate = '12/31/2010'
;
WITH
MyCTE /* http://technet.microsoft.com/en-us/library/ms175972.aspx */
( ShipName,ShipAddress,ShipCity,ShipRegion,ShipPostalCode,ShipCountry,CustomerID,CustomerName,[Address],
City,Region,PostalCode,Country,Salesperson,OrderID,OrderDate,RequiredDate,ShippedDate,ShipperName,
ProductID,ProductName,UnitPrice,Quantity,Discount,ExtendedPrice,Freight,ROWID) AS
(
SELECT
ShipName ,ShipAddress,ShipCity,ShipRegion,ShipPostalCode,ShipCountry,CustomerID,CustomerName,[Address]
,City ,Region,PostalCode,Country,Salesperson,OrderID,OrderDate,RequiredDate,ShippedDate,ShipperName
,ProductID ,ProductName,UnitPrice,Quantity,Discount,ExtendedPrice,Freight
, ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY OrderDate , ProductName ASC ) as ROWID /* Note that the ORDER BY (here) is directly related to the ORDER BY (near the very end of the query) */
FROM
dbo.Invoices inv /* “Invoices” is a VIEW, FYI */
where
(inv.OrderDate between #BeginDate and #EndDate)
)
SELECT
/*
ShipName,ShipAddress,ShipCity,ShipRegion,ShipPostalCode,ShipCountry,CustomerID,CustomerName,[Address],
City,Region,PostalCode,Country,Salesperson,OrderID,OrderDate,RequiredDate,ShippedDate,ShipperName,
ProductID,ProductName,UnitPrice,Quantity,Discount,ExtendedPrice,Freight,
*/
/*trim the list down a little for the final output */
CustomerID ,OrderID , OrderDate, (ExtendedPrice + Freight) as ComputedTotal
/*The below line is the “trick”. I reference the above CTE, but only get data that is less than or equal to the row that I am on (outerAlias.ROWID)*/
, (Select SUM (ExtendedPrice + Freight) from MyCTE innerAlias where innerAlias.ROWID <= outerAlias.ROWID and innerAlias.CustomerID = outerAlias.CustomerID) as RunningTotal
, ROWID as ROWID_SHOWN_FOR_KICKS , OrderDate as OrderDate
FROM
MyCTE outerAlias
GROUP BY CustomerID ,OrderID, OrderDate, ProductName,(ExtendedPrice + Freight) ,ROWID,OrderDate
/*Two Order By Options*/
ORDER BY outerAlias.CustomerID , outerAlias.OrderDate , ProductName
/* << Whatever the ORDER BY is here, should match the “ROW_NUMBER() OVER ( ORDER BY ________ ASC )” statement inside the CTE */
/*ORDER BY outerAlias.ROWID */ /* << Or, to keep is more “trim”, ORDER BY the ROWID, which will of course be the same as the “ROW_NUMBER() OVER ( ORDER BY” inside the CTE */

Resources