find records for user that are not contiguous - sql

I am having trouble figuring out how to even start this query.
I have a table that has the following columns and data:
User BeginMile EndMile
1 1 5
1 5 6
1 6 20
1 20 25
1 25 29
2 1 9
2 15 20
3 1 2
3 6 10
3 10 12
I need to first find where there are gaps for each user from the EndMile of the previous record, to the BeginMile of the next record. I then need to return the record before and after where the gap occurs for each user.
In the previous data example, I would like the following returned:
User PrevBeginMile PrevEndMile AfterBeginMile AfterEndMile Gap
2 1 9 15 20 6
3 1 2 6 10 4
How can this be done?

Considering you're on SQL 2005, this should work:
DECLARE #Runners TABLE (Id INT, BeginMile INT, EndMile INT)
INSERT INTO #Runners VALUES (1,1,5)
INSERT INTO #Runners VALUES (1,5,6)
INSERT INTO #Runners VALUES (1,6,20)
INSERT INTO #Runners VALUES (1,20,25)
INSERT INTO #Runners VALUES (1,25,29)
INSERT INTO #Runners VALUES (2,1,9)
INSERT INTO #Runners VALUES (2,15,20)
INSERT INTO #Runners VALUES (3,1,2)
INSERT INTO #Runners VALUES (3,6,10)
INSERT INTO #Runners VALUES (3,10,12)
WITH OrderedUsers AS (
SELECT *
, ROW_NUMBER() OVER (PARTITION BY Id ORDER BY BeginMile) RowNum
FROM #Runners
)
SELECT a.Id [User]
, a.BeginMile PrevBeginMile
, a.EndMile PrevEndMile
, b.BeginMile AfterBeginMile
, b.EndMile AfterEndMile
, b.BeginMile - a.EndMile Gap
FROM OrderedUsers a
JOIN OrderedUsers b
ON a.Id = b.Id
AND a.EndMile <> b.BeginMile
AND a.RowNum = b.RowNum - 1

Other than using RowNumber() [as in other answers], you could use...
SELECT
[current].User,
[current].BeginMile AS [PrevBeginMile],
[current].EndMile AS [PrevEndMile],
[next].BeginMile AS [AfterBeginMile],
[next].EndMile AS [AfterEndMile],
[next].BeginMile - [current].EndMile AS [Gap]
FROM
myTable AS [current]
CROSS APPLY
(SELECT TOP 1 * FROM myTable WHERE user = [current].User AND BeginMile > [current].BeginMile ORDER BY BeginMile ASC) AS [next]
WHERE
[current].EndMile <> [next].BeginMile
Or possibly...
FROM
myTable AS [current]
INNER JOIN
myTable AS [next]
ON [next].BeginMile != [current].EndMile
AND [next].BeginMile = (
SELECT
MIN(BeginMile)
FROM
myTable
WHERE
user = [current].User
AND BeginMile > [current].BeginMile
)

How about
WITH acte(user,beginmile,endmile) AS
(
SELECT user,start,end
ROW_NUMBER() OVER(PARTITION BY user ORDER BY START ASC) rownum
FROM mytable
)
SELECT base.user,base.beginmile,base.endmile,base.BeginMile - lead.EndMile Gap
FROM acte base
LEFT JOIN acte lead on base.id=lead.id AND base.rownum=lead.rownum-1
WHERE base.BeginMile - lead.EndMile > 0

Related

Add Min Value on Query Output in Separate Column

I have the following table:
No Item Value
----------------------------
1 A 5
2 B 8
3 C 9
If I use Min function on Value field, then I'll get 5.
My question is, how can I put the MIN value into a new column? Like the following result:
No Item Value newCol
----------------------------
1 A 5 5
2 B 8 5
3 C 9 5
Is it possible to do that?
Thank you.
Something like:
select No, Item, Value, (select min(value) from table)
from table
should do it.
I'd prefer to do the subquery in a join, you'll have to name the field. Something like this;
Sample Data
CREATE TABLE #TestData (No int, item nvarchar(1), value int)
INSERT INTO #TestData (No, item, value)
VALUES
(1,'A',5)
,(2,'B',8)
,(3,'C',9)
Query
SELECT
td.No
,td.item
,td.value
,a.Min_Value
FROM #TestData td
CROSS JOIN
(
SELECT
MIN(Value) Min_Value
FROM #TestData
) a
Result
No item value Min_Value
1 A 5 5
2 B 8 5
3 C 9 5
You could do that even simpler by using an appropriate OVER() clause.
SELECT *
, MIN(Value) OVER () AS [newCol]
FROM Table
This would be simpler and less resource consuming than a (SELECT MIN(Value) FROM TABLE) in the top level SELECT.
Sample code:
DECLARE #tbl TABLE (No int, Item char(1), Value int)
INSERT #tbl VALUES (1, 'A', 5), (2, 'B', 8), (3, 'C', 9)
SELECT *
, MIN(Value) OVER () AS [newCol]
FROM #tbl
Using cross join with min value from table :
SELECT * FROM #Tbl1 CROSS JOIN (SELECT MIN(Value1) Value1 FROM #Tbl1) A

Sum the number of occurrence by id

Is it possible to COUNT the number of times a value occurs in a table, however, use the count of 1 if the value appears more than once for each id.
Take the below table as an example. We want to see if either {5,6} occurred for p_id. If more than 1 occurrence of {5,6} is found, treat it as 1. For eg. p_id 1, the total count is 1.
p_id status
1 5
1 6
1 2
2 5
2 5
3 4
3 2
4 6
4 2
4 5
..transforms to..
p_id count
1 1
2 1
3 0
4 1
COUNT(CASE status IN (5,6) THEN 1 END) does an overall count.
Use the CASE...WHEN... as follows:
SELECT a.id, ISNULL(b.cnt, 0)
FROM
(
SELECT DISTINCT id FROM tab
) a
LEFT JOIN
(
SELECT id, CASE COUNT(*) WHEN 1 THEN 0 ELSE 1 END 'cnt'
FROM tab WHERE val in (5, 6) GROUP BY id
) b
ON a.id = b.id
SQLFiddle
This solution provides a quick setup and a simple two-step explanation of how I do this, using your example. The second query provides the desired result:
CREATE TABLE #temp (p_id INT, [status] INT);
INSERT #temp VALUES (1,5);
INSERT #temp VALUES (1,6);
INSERT #temp VALUES (1,2);
INSERT #temp VALUES (2,5);
INSERT #temp VALUES (2,5);
INSERT #temp VALUES (3,4);
INSERT #temp VALUES (3,2);
INSERT #temp VALUES (4,6);
INSERT #temp VALUES (4,2);
INSERT #temp VALUES (4,5);
-- Simple two-step tutorial
-- First, group by p_id so that all p_id's will be shown
-- run this to see...
SELECT A.p_id
FROM #temp A
GROUP BY A.p_id;
-- Now expand your query
-- Next, for each p_id row found, perform sub-query to see if 1 or more exist with status=5 or 6
SELECT A.p_id
,CASE WHEN EXISTS(SELECT 1 FROM #temp B WHERE B.p_id=A.p_id AND [status] IN (5,6)) THEN 1 ELSE 0 END AS [Count]
FROM #temp A
GROUP BY A.p_id;
Use the SIGN() function. It is exactly what you are looking for.
SELECT
[p_id],
SIGN(COUNT(CASE WHEN [status] IN (5,6) THEN 1 END)) AS [count]
FROM #temp
GROUP BY p_id
You can translate 5,6 = 1 and rest to 0 then do max()
with cte as (
select p_id, case when status in (5,6) then 1 else 0 end status
from FROM #tem)
select p_id, max(status) status
from cte
group by p_id

How to make SQL query return exact number of rows

Suppose there is a table with 3 rows for employee A and 2 rows for employee B. I need to write a query which will return exactly 3 rows for both the employee (i.e. a dummy row should be present for employee B)
Your question is not completely clear to me (for instance, can "3 rows" be "8 rows" too), but some starting points:
Set up
create table emprec
( emp_id number
, val number
)
insert into emprec values ( 1, 15)
insert into emprec values ( 1, 16)
insert into emprec values ( 1, 17)
insert into emprec values ( 2, 18)
insert into emprec values ( 2, 19)
Query
select driving_x_axis.counter
, driving_y_axis.emp_id
, emprec.val
from ( select level counter
from dual
connect by level <= 3
) driving_x_axis
join ( select distinct emp_id
from emprec
) driving_y_axis
on 1=1 /* Carthesian. */
left
outer
join ( select emp_id
, val
, row_number() over (partition by emp_id order by val) rownumber
from emprec
) emprec
on emprec.rownumber = driving_x_axis.counter
and emprec.emp_id = driving_y_axis.emp_id
Result
1 1 15
2 1 16
3 1 17
1 2 18
2 2 19
3 2 <null>
Simpler alternatives may be possible; please elaborate on your question.

SQL if statement with two tables

Given the following tables:
table objects
id Name rating
1 Megan 9
2 Irina 10
3 Vanessa 7
4 Samantha 9
5 Roxanne 1
6 Sonia 8
swap table
id swap_proposalid counterpartyid
1 4 2
2 3 2
Everyone wants the ten. I would like to make a list for Irina of possible swaps where id 4 and 3 don't appear because the propositions are already there.
output1
id Name rating
1 Megan 9
5 Roxanne 1
6 Sonia 8
Thanks
This should do the trick:
SELECT o.id, o.Name, o.rating
FROM objects o
LEFT JOIN swap s on o.id = s.swap_proposalid
WHERE s.id IS NULL
AND o.Name != 'Irina'
This works
SELECT mt2.ID, mt2.Name, mt2.Rating
FROM [MyTable] mt2 -- Other Candidates
, [MyTable] mt1 -- Candidate / Subject (Irina)
WHERE mt2.ID NOT IN
(
SELECT st.swap_proposalid
FROM SwapTable st
WHERE
st.counterpartyid = mt1.ID
)
AND mt1.ID <> mt2.ID -- Don't match Irina with Irina
AND mt1.Name = 'Irina' -- Find other swaps for Irina
-- Test Data
CREATE TABLE MyTable
(
ID INT,
Name VARCHAR(100),
Rating INT
)
GO
CREATE TABLE SwapTable
(
ID INT,
swap_proposalid INT,
counterpartyid INT
)
GO
INSERT INTO MyTable VALUES(1 ,'Megan', 9)
INSERT INTO MyTable VALUES(2 ,'Irina', 10)
INSERT INTO MyTable VALUES(3 ,'Vanessa', 7)
INSERT INTO MyTable VALUES(4 ,'Samantha', 9)
INSERT INTO MyTable VALUES(5 ,'Roxanne', 1)
INSERT INTO MyTable VALUES(6 ,'Sonia', 8)
INSERT INTO SwapTable(ID, swap_proposalid, counterpartyid)
VALUES (1, 4, 2)
INSERT INTO SwapTable(ID, swap_proposalid, counterpartyid)
VALUES (1, 3, 2)
Guessing that the logic involves identifying the objects EXCEPT the highest rated object EXCEPT propositions with the highest rated object e.g. (using sample DDL and data kindly posted by #nonnb):
WITH ObjectHighestRated
AS
(
SELECT ID
FROM MyTable
WHERE Rating = (
SELECT MAX(T.Rating)
FROM MyTable T
)
),
PropositionsForHighestRated
AS
(
SELECT swap_proposalid AS ID
FROM SwapTable
WHERE counterpartyid IN (SELECT ID FROM ObjectHighestRated)
),
CandidateSwappersForHighestRated
AS
(
SELECT ID
FROM MyTable
EXCEPT
SELECT ID
FROM ObjectHighestRated
EXCEPT
SELECT ID
FROM PropositionsForHighestRated
)
SELECT *
FROM MyTable
WHERE ID IN (SELECT ID FROM CandidateSwappersForHighestRated);

How to increment in a select query

I've got a query I'm working on and I want to increment one of the fields and restart the counter when a key value is different.
I know this code doesn't work. Programmatically this is what I want...
declare #counter int, #id
set #counter = 0
set #id = 0
select distinct
id,
counter = when id = #id
then #counter += 1
else #id = id
#counter = 1
...with the end result looking something like this:
ID Counter
3 1
3 2
3 3
3 4
6 1
6 2
6 3
7 1
And yes, I am stuck with SQL2k. Otherwise that row_number() would work.
Assuming a table:
CREATE TABLE [SomeTable] (
[id] INTEGER,
[order] INTEGER,
PRIMARY KEY ([id], [order])
);
One way to get this in Microsoft SQL Server 2000 is to use a subquery to count the rows with the same id and a lower ordering.
SELECT *, (SELECT COUNT(*) FROM [SomeTable] counter
WHERE t.id = counter.id AND t.order < counter.order) AS row_num
FROM [SomeTable] t
Tip: It's 2010. Soon your SQL Server will be old enough to drive.
If you use SQL Server 2005 or later, you get wonderful new functions like ROW_NUMBER() OVER (PARTITION...).
Yes you want ROW_NUMBER().
I would try:
SELECT id, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY ID) AS Counter
One way to do this is to throw the data into a temp table with an identity column that is used as a row number. Then make the counter column a count of the other rows with the same Id and a lower row number + 1.
CREATE TABLE #MyData(
Id INT
);
INSERT INTO #MyData VALUES(3);
INSERT INTO #MyData VALUES(3);
INSERT INTO #MyData VALUES(3);
INSERT INTO #MyData VALUES(3);
INSERT INTO #MyData VALUES(6);
INSERT INTO #MyData VALUES(6);
INSERT INTO #MyData VALUES(6);
INSERT INTO #MyData VALUES(7);
CREATE TABLE #MyTempTable(
RowNum INT IDENTITY(1,1),
Id INT,
Counter INT
);
INSERT INTO #MyTempTable
SELECT Id, 0
FROM #MyData
ORDER BY Id;
SELECT Id, (SELECT COUNT(*) + 1 FROM #MyTempTable WHERE Id = t1.Id AND RowNum < t1.RowNum) AS 'Counter'
FROM #MyTempTable t1;
You should get the following output based on your example:
Id Counter
3 1
3 2
3 3
3 4
6 1
6 2
6 3
7 1
Having row_number() means you have to deal with far, far fewer correlated subqueries. #Bill Karwin's solution works (+1); here's another version that does the same thing but that might be a bit easier to follow. (I used datetimes to determine ordering.)
-- Test table
CREATE TABLE Test
( Id int not null
,Loaded datetime not null
)
-- Load dummy data with made-up distinct datetimes
INSERT Test values (3, 'Jan 1, 2010')
INSERT Test values (3, 'Jan 2, 2010')
INSERT Test values (3, 'Jan 5, 2010')
INSERT Test values (3, 'Jan 7, 2010')
INSERT Test values (6, 'Feb 1, 2010')
INSERT Test values (6, 'Feb 11, 2010')
INSERT Test values (7, 'Mar 31, 2010')
-- The query
SELECT t1.Id, count(*) Counter
from Test t1
inner join Test t2
on t2.Id = t1.Id
and t2.Loaded <= t1.Loaded
group by t1.Id, t1.Loaded
-- Clean up when done
DROP TABLE Test
It is important to note that, without good indexes (and perhaps even with them), these kinds of queries can perform very poorly, particularly on large tables. Check and optimize carefully!
For MySql, I was able to make it with this query.
SELECT (SELECT COUNT(id) +1 FROM sku s WHERE t.item_id = s.item AND s.id < t.sku_id) AS rowNumber, t.*
FROM
(select item.Name as itemName ,item.id as item_id , sku.Name as skuName ,sku.id as sku_id from item
INNER JOIN sku ON item.id = sku.item
WHERE item.active = 'Y'
) t
1 Roasted Pistachios (Salted, In Shell) 84 1 Pound Bags 84
3 Roasted Pistachios (Salted, In Shell) 84 25 Pound Cases 1174
5 Roasted Pistachios (Salted, In Shell) 84 12 x 2.6 Ounce Bags 5807
2 Roasted Pistachios (Salted, In Shell) 84 5 Pound Bags 814
4 Roasted Pistachios (Salted, In Shell) 84 Samples 4724
6 Roasted Pistachios (Salted, In Shell) 84 12 x 3.2 Ounce Bags 18145
4 Star Fruit 981 5 Pound Bags 17462
1 Star Fruit 981 1 Pound Bags 2125
3 Star Fruit 981 11 Pound Bags 2226
2 Star Fruit 981 44 Pound Cases 2156