I have a table "Items" with records that shows a progress through time of a single item. One of the columns is numeric value (DeltaLimit) showing price change compared to the starting point of the records.
I also have a defined #Limit variable. I need to select the record where DeltaLimit exceeds #Limit for the first time, and if the DeltaLimit exceeds multiples of the #Limit, I need to do the same.
Basically, I need the first row where DeltaLimit exceeds #Limit, the fist row where DeltaLimit exceeds 2*#Limit, the first row where DeltaLimit exceeds 3*#Limit etc.
Source data - #Limit = 0.5
Name | DeltaLimit
Ex1 | 0.4
Ex2 | 0.6
Ex3 | 0.9
Ex4 | 1.1
Ex5 | 1.3
Desired output
Name | DeltaLimit
Ex2 | 0.6
Ex4 | 1.1
The only thing I managed to do was to get the first row that exceeds the #Limit itself with the following select, but I have no idea how to get the rows the exceeds the following multiples of #Limit. Any help would be greatly aprreciated.
select * from Items
where DeltaLimit = (select top 1 DeltaLimit from Items where DeltaLimit < #Limit);
You can divide the DeltaLimit by the Limit and get your Row_Number using the result.
SELECT * FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY convert(int, DeltaLimit / #Limit) ORDER BY DeltaLimit) Rn
FROM Table1
WHERE DeltaLimit > #Limit
) t
WHERE t.Rn = 1
Rextester.com demo
You need to create a projection relation for the delta limit multipliers... basically a temporary table with values {.5, 1.0, ,1.5, 2.0} etc, up the highest needed by the data. I'll leave that as an exercise and just call it #ScaledLimits in the below code. Once you have it, you can use it like this:
SELECT results.*
FROM #ScaledLimits sl
CROSS APPLY (
SELECT TOP 1 *
FROM Items
WHERE Items.DeltaLimit > sl.DeltaLimit
ORDER BY Items.DeltaLimit
) results
See it in action here:
http://rextester.com/QIP53078
Related
Is there a way to use the results from a multiple rows on a formula, divided by each group.
I have the followin formula:
result = (1st vfg ) / (1 + (1st vfg / 2nd vfg) + (1st vfg / 3rd vfg) + ... + (1st vfg / *nth* vfg) )
vfg = value from group
For example, the table bellow:
Group | Value
---------------
1 | 1000
1 | 280
1 | 280
2 | 1000
Note: I guarantee that there will be no 0 (zero) or NULLs in the value for the first table
Should give me the following result:
Group | Result
---------------
1 | 122.85
2 | 1000 -> If there is only one value on the group, the result will be the value itself
You need a column that indicates the row order within a group (timestamps, the sequence number, identity column, etc.). Rows in a database table have no implicit order. Once you have that, you can use a CTE and window functions to solve the problem:
;WITH
cte AS
(
SELECT [Group]
, [Value]
, FIRST_VALUE([Value]) OVER (PARTITION BY [Group] ORDER BY RowOrder) AS FirstValue
, FIRST_VALUE([Value]) OVER (PARTITION BY [Group] ORDER BY RowOrder) / [Value] AS Subtotal
FROM MyTable
)
SELECT [Group]
, AVG(FirstValue) / SUM(Subtotal) AS Result
FROM cte
GROUP BY [Group]
I'm not sure why I'm getting the following error:
Incorrect syntax near the keyword 'UNION'.
My table looks like the following:
+-------+--------+---------------+
| label | Name | Budget |
+-------+--------+---------------+
| 1 | ABC | Allocated |
| 1 | DEF | NotAllocated |
| 0 | XYZ | Allocated |
| 0 | LMN | Allocated |
| 1 | QRS | NotAllocated |
+-------+--------+---------------+
I have a column called Label consisting of 1's and 0's.
Number of records where label is 1 = 10540
Number of records where label is 0 = 1546
I have many records for "1" so I want to undersample them to the "0" level
I'm trying to get 1600 records where label is 1 and 1546 records where label is 0.
I have tried the following but I'm getting an error. How to solve this issue?
SELECT TOP 1600 *
FROM myTable
ORDER BY label ASC
UNION ALL
SELECT TOP 1546 *
FROM myTable
ORDER BY label DESC
You can use the following solution:
SELECT * FROM (SELECT TOP 1600 * FROM myTable ORDER BY label ASC) t1
UNION ALL
SELECT * FROM (SELECT TOP 1546 * FROM myTable ORDER BY label DESC) t2
You get the error message on the ORDER BY with UNION. You can place the ORDER BY only after the SELECT statements of UNION. In your case this would not work because you are using different ORDER BY conditions. So you can solve this by "re-select" the results of your queries. You can find more information about this topic on the Transact-SQL documentation.
As #Zorkolot already mentioned in his answer you don't need a ORDER BY in case you only want to ORDER BY column label to get the rows with 0 or 1. So you can use the following too:
SELECT TOP 1600 * FROM myTable WHERE label = 0
UNION ALL
SELECT TOP 1546 * FROM myTable WHERE label = 1
demo (for both solutions): http://sqlfiddle.com/#!6/d9c21/4/1
Another thought:
If you want to get a maximum amout of rows per group (max. 1600 rows with label = 1 and max. 1600 rows with label = 0, so in sum max. 3200 rows). You should use the following:
SELECT TOP 1600 * FROM myTable WHERE label = 0
UNION ALL
SELECT TOP 1600 * FROM myTable WHERE label = 1
I am trying to get 1600 records where label is 1 and 1546 records
where label is 0.
You could just say WHERE label = # instead of using order by.
SELECT TOP 1600 *
FROM myTable
WHERE label = 1
UNION ALL
SELECT TOP 1546 * --you might not even need TOP here (there are only 1546)
FROM myTable
WHERE label = 0
I want to find the rows which are similar to each other, and replace them with a new row. My table looks like this:
OrderID | Price | Minimum Number | Maximum Number | Volume
1 45 2 10 250
2 46 2 10 250
3 60 2 10 250
"Similar" in this context means that the rows that have same Maximum Number, Minimum Number, and Volume. Prices can be different, but the difference can be at most 2.
In this example, orders with OrderID of 1 and 2 are similar, but 3 is not (since even if it has same Minimum Number, Maximum Number, and Volume, its price is not within 2 units from orders 1 and 2).
Then, I want orders 1 and 2 be replaced by a new order, let's say OrderID 4, which has same Minimum Number and Maximum Number. Its Volume hass to be sum of volumes of the orders it is replacing. Its price can be the Price of any of the previous orders that will be deleted in the output table (45 or 46 in this example). So, the output for the example above would be:
OrderID | Price | Minimum Number | Maximum Number | Volume
4 45 2 10 500
3 60 2 10 250
Here is a way to do this in SQL Server 2012 or Oracle. The idea is to use lag() to find where groups should begin and end and then aggregate.
select min(id) as id, min(price) as price, MinimumNumber, MaximumNumber, sum(Volume)
from (select t.*,
sum(case when prev_price < price - 2 then 1 else 0 end) over
(partition by MinimumNumber, MaximumNumber, Volume order by price) as grp
from (select t.*,
lag(price) over (partition by MinimumNumber, MaximumNumber, Volume
order by price
) as prev_price
from table t
) t
) t
group by grp, price, MinimumNumber, MaximumNumber;
The only issue is the setting of the id. I'm not sure what the exact rule is for that.
I have a table using SQL Server 2008 it has a table with two sortable columns on it one is manually set and the other is calculated by a system procedure (this procedure sorts everything as a whole and assigns a sort starting at 10 until the highest row number times 10)
ID Manual System
------------------------
1 null 300
2 2 380
3 null 500
4 null 200
And I am trying to get it to sort the ids to be 4,2,1,3
I would like the output to take the Manual Sort over the System when it has been applied. to further complicate things if another row is added and it has a manual sort that also needs to be considered.
ID Manual System
-----------------------
1 null 300
2 2 380
3 null 500
4 null 200
5 5 100
so the new sort would be 4,2,1,3,5
ID Manual System
-----------------------
4 null 200
2 2 380
1 null 300
3 null 200
5 5 100
Any ideas? and I have tried Rank, Dense_Rank, Row_Number etc.
The solutions that have been given seem correct for my example. I forgot to mention there is a third column personID that is also a factor here.
ID Manual System PersonID
-------------------------------------
4 null 200 22
2 2 380 22
1 null 300 22
3 null 200 22
5 5 100 22
8 1 210 25
6 1 480 25
7 null 600 25
9 4 800 25
10 null 990 25
So I first have to order them by person then, order them by Manual then by sort. which still seems to give me an issue.
Here is my solution: http://sqlfiddle.com/#!3/a32a0/1/0
SELECT *
FROM
(
SELECT
ID
, ROW_NUMBER() OVER (PARTITION BY PersonID ORDER BY System)-.1 AS rn
, Manual
, System
, PersonID
FROM YourTable
) t0
ORDER BY PersonID
, COALESCE(Manual, RN)
Here is the explanation:
We are taking the row number as the base row number. But since we first order by a higher-order index of PersonID, I PARTITION BY... before I ORDER BY... this resets the index for each grouping of MANUAL
In the case of a tie between the natural ordering of the ROW_NUMBER and the MANUAL sorting, I subtract .1 (arbitrary amount between (0,1)). This gives preference to the MANUAL value in case of a tie
When it comes to ordering the final result, I ORDER BY the PARTITION BY value first, ensuring the proper grouping first, then I order by the first non-null value of MANUAL and RN
Give it a try. +points to the starting points of the previous two answers. I used one of them as a starting point and re-wrote from there.
EDIT: Removed the subtraction of .1 and added a new ranking function which "tricks" the optimizer into preferring manual over rank. I have no idea if this holds up in all cases or if the optimizer will fail to give the results in this order under other circumstances, but I wanted to include the findings just in case they're helpful.
My updated query is as follows:
SELECT *
FROM
(
SELECT
ID
, ROW_NUMBER() OVER (PARTITION BY PersonID ORDER BY System) AS rn
, ROW_NUMBER() OVER (PARTITION BY PersonID ORDER BY Manual) AS rn_throwaway
, Manual
, System
, PersonID
FROM YourTable
) t0
ORDER BY PersonID
, COALESCE(Manual, RN)
And examples of it in use are at http://sqlfiddle.com/#!3/1831d/55/0 and http://sqlfiddle.com/#!3/a32a0/9/0
If I'm understanding your requirements, you want to sort by the System column, unless the Manual column is supplied, and in which case, use that as the sort position instead? If so, then this should work for you using CASE and ROW_NUMBER:
SELECT Id, Manual, System
FROM (
SELECT Id,
Manual,
System,
ROW_NUMBER() OVER (ORDER BY Manual, System) rn
FROM YourTable) t
ORDER BY CASE WHEN Manual IS NULL THEN RN ELSE Manual END, COALESCE(Manual,RN+1)
SQL Fiddle Demo
I think this is what you need. It is bit difficult to explain.
Basically inserting not null manual values as row index (or row number) to the record list ordered by system.
FIDDLE DEMO
;with cte as (
select id, manual,system,
convert(decimal(10,1),row_number() over(order by system)) rn
from t
where manual is null
union all
select id, manual,system, convert(decimal(10,1),manual-0.5) rn
from t
where manual is not null
)
select id,manual,system
from cte
order by rn
| ID | MANUAL | SYSTEM |
------------------------
| 4 | (null) | 200 |
| 2 | 2 | 380 |
| 1 | (null) | 300 |
| 3 | (null) | 500 |
| 5 | 5 | 100 |
I have a need to SELECT all the rows from a table where the selected rows are greater than the datetime of the previously selected row by a given constant number of minutes. An example probably speaks best.
The following represents the table of data - we will call it myTable.
guid fkGuid myDate
------- ------- ---------------------
1 100 2013-01-10 11:00:00.0
2 100 2013-01-10 11:05:00.0
3 100 2013-01-10 11:10:00.0
4 100 2013-01-10 11:15:00.0
5 100 2013-01-10 11:20:00.0
6 100 2013-01-10 11:25:00.0
7 100 2013-01-10 11:30:00.0
8 100 2013-01-10 11:35:00.0
9 100 2013-01-10 11:40:00.0
10 100 2013-01-10 11:50:00.0
11 100 2013-01-10 11:55:00.0
What I want to do is provide a constant increment (say 10 minutes) and get back all the rows from the first that are 10 minutes or more from the previous row. So, with 10 minutes the result set should look like this:
guid myDate
------- ---------------------
1 2013-01-10 11:00:00.0
3 2013-01-10 11:10:00.0
5 2013-01-10 11:20:00.0
7 2013-01-10 11:30:00.0
9 2013-01-10 11:40:00.0
11 2013-01-10 11:55:00.0
The constant is passed in as a variable so it could be anything. Let's say it was 23 minutes, then the result set should look like this:
guid myDate
------- ---------------------
1 2013-01-10 11:00:00.0
6 2013-01-10 11:25:00.0
10 2013-01-10 11:50:00.0
The last example shows that I start at row 0's time (11:00:00) add 23 minutes and get the next >= row which is 11:25:00, add 23 minutes to the new row's time and then get the next (11:50:00) and so on.
I have tried doing this with a CTE but although I can quite easily get back all my times or none of them, I can't seem to figure how to get the rows I need. My current test code using 23 minutes hard coded into the WHERE clause:
WITH myCTE AS
(
SELECT guid,
myDate,
ROW_NUMBER() OVER (PARTITION BY guid ORDER BY myDate ASC) AS rowNum
FROM myTable
WHERE fkGuid = 100
)
SELECT currentRow.guid, currentRow.myDate
FROM myCTE AS currentRow
LEFT OUTER JOIN
myCTE AS previousRow
ON currentRow.guid = previousRow.guid
AND currentRow.rowNum = previousRow.rowNum + 1
WHERE
currentRow.myDate > DATEADD(minute, 23, previousRow.myDate)
ORDER BY
currentRow.myDate ASC
This returns nothing. If I omit the WHERE clause I get all rows back (obviously because I'm not filtering).
What am I missing?
Any and all help would be very much appreciated as it always is!
#gilly3, hardly SQL voodoo
WITH CTE
AS
(
SELECT TOP 1
guid
,fkGuid
,myDate
,ROW_NUMBER() OVER (ORDER BY myDate) RowNum
FROM MyTable
UNION ALL
SELECT mt.guid
,mt.fkGuid
,mt.myDate
,ROW_NUMBER() OVER (ORDER BY mt.myDate)
FROM MyTable mt
INNER JOIN
CTE ON mt.myDate>=DATEADD(minute,23,CTE.myDate)
WHERE RowNum=1
)
SELECT guid
,fkGuid
,myDate
FROM CTE
WHERE RowNum=1
The SQL Fiddle is here
First, your join will never return any rows, regardless of the where clause. Guid and rowNum are both unique keys per row, so if the guid is the same, so will be the rowNum. You can see that the join always fails by adding a field from previousRow to your select list and running your query without the where clause.
Next, joining on rowNum + 1 prevents skipping rows. You will only select adjacent rows that satisfy the date filter.
There may be some SQL voodoo with recursive queries that will make this work, but there will be a huge performance hit. Filter the data in your application code. Eg, in C#:
List<DataRow> FilterByInterval(IEnumerable<DataRow> rows, string dateColumn, int minutes)
{
List<DataRow> filteredRows = new List<DataRow>();
DateTime lastDate = DateTime.MinValue;
foreach (DataRow row in rows)
{
DateTime dt = row.Field<DateTime>(dateColumn);
TimeSpan diff = dt - lastDate;
if (diff.TotalMinutes >= minutes)
{
filteredRows.Add(row);
lastDate = dt;
}
}
return rows;
}