I need to fetch a number of rows between two column values, using a defined step value. For example, if the table looks like this:
Id Name
-----------------------
1 Maria Anders
2 Christina Berglund
3 Francisco Chang
4 Roland Mendel
5 Diego Roel
6 Eduardo Saavedra
7 Helen Bennett
8 Philip Cramer
and First = 3, Last = 7, Step = 2, query should return:
Id Name
-----------------------
3 Francisco Chang
5 Diego Roel
7 Helen Bennett
I was thinking of using a modulo to specify which columns should be returned, with something like:
SELECT *
FROM Table
WHERE (i-3) % 2 = 0
This approach will result in SQL Server iterating through the entire table and calculating the expression for each item. Since I expect to have relatively large step values, I would like to know if there is a strategy which would avoid this (possible using an index to "skip" items).
Is there a better (read: faster) way of doing this? (I am using MS SQL Server 2008 R2)
DECLARE #start int = 3, #step int = 2, #stop int = 7;
;WITH cte AS
(
SELECT #start AS ID
UNION ALL
SELECT ID + #step FROM cte WHERE ID + #step <= #stop
)
SELECT *
FROM cte JOIN MyTable M ON cte.ID = MyTable.ID
select * from table
where (id >= #start)
AND (id<=#end)
AND ((id-#start)%#step) = 0
Test case:
declare #start int =3,
#end int = 7,
#step int =2
;with t(id)
as
(
select 1
union select 2
union select 3
union select 4
union select 5
union select 6
union select 7
union select 8
)
select * from t
where (id >= #start) AND (id<=#end) and ((id-#start)%#step) = 0
output:
3
5
7
Related
I am trying to create a loop that when given a part id, it will search a table of assembly parts and explode all the parts into a large list.
It needs to be recursive because Part 123 may have parts 1, 2, 3, 4, 5 and parts 4 and 5 are also assembly items.
I thought I had come up with something pretty good and easily returns the part id and the part level for each item. Then I find out that I can't use temp tables, so it shoots my loop down.
What can I use in place of the temp table to give me the same function here?
CREATE FUNCTION [dbo].[fn_getParts] (
#source_part_id int
, #level int
)
RETURNS #parts_list TABLE (
[part] int NOT NULL,
[level] int NOT NULL
)
AS
BEGIN
DECLARE
#max int = 0,
#cnt int = 0,
#PID int = 0,
#Plvl int = 0,
#id int = 0
INSERT INTO #parts_list VALUES (#source_part_id, #level)
SET #level += 1
SELECT [Comp_Part_ID] AS [PID], #level AS [level]
INTO #chkParts
FROM [assemblies]
WHERE [Assy_PID] = #source_part_id
SELECT #max = COUNT(*) FROM #chkParts
WHILE #cnt <= #max
BEGIN
SELECT #PID = [PID], #Plvl = [level] FROM #chkParts
INSERT INTO #parts_list
SELECT * FROM [fn_getParts](#PID, #Plvl)
SET #cnt += 1
END
RETURN
END
Here's some sample data:
CREATE TABLE [Assemblies] (
[PartID] int,
[Comp_PartID] int
)
INSERT INTO [Assemblies] VALUES
(1,2),
(1,3),
(1,4),
(1,5),
(1,6),
(3,9),
(3,10),
(10,11),
(10,23),
(10,24),
(10,31),
(11,24),
(11,23)
If I enter SELECT * FROM [fn_getParts](1,0) I would expect the following:
part,level
1,0
2,1
3,1
4,1
9,2
10,2
11,3
23,3
24,3
The code can be simplified somewhat by wrapping an Inline Table-Valued Function around a Recursive CTE, e.g.:
create function dbo.fn_getParts (
#source_part_id int
)
returns table as return (
with PartsHierarchy as (
select #source_part_id as part, 0 as level
union all
select Comp_PartID, 1 + level
from Assemblies
join PartsHierarchy on part = PartID
)
select part, level
from PartsHierarchy
);
And then, invoking it for different part numbers...
select * from dbo.fn_getParts(1);
part level
---- ----
1 0
2 1
3 1
4 1
5 1
6 1
9 2
10 2
11 3
23 3
24 3
31 3
24 4
23 4
select * from dbo.fn_getParts(10);
part level
---- -----
10 0
11 1
23 1
24 1
31 1
24 2
23 2
select * from dbo.fn_getParts(11);
part level
---- -----
11 0
24 1
23 1
My problem
Let's say I have a query that returns the following data:
id date
-- ------
1 2015-01-12
1 ... // here I might have X more rows
1 2015-06-30
2 2015-01-12
2 ... // here I might have Y more rows
2 2015-05-20
...
Given that X, Y >= 120 and X != Y and the order of the query is id, date ASC I want a way to retrieve the record number 120 for id 1 and the 120 for the id 2 (and so on for each different ID), something like:
id date
-- --------
1 2015-03-24 // this is the record 120 for id = 1
2 2015-04-26 // this is the record 120 for id = 2
...
Notice that the dates don't follow a sequential order (you may have a gap between one row and the next one).
Is there a direct SQL solution for my problem? (I know I can use vba to achieve my goal but I rather stay with SQL)
As a clarification note, see this example. Given the following resultset:
id date
-- ------
1 2015-01-12 // this is record 1 for id = 1
1 2015-01-13 // this is record 2 for id = 1
1 2015-01-20 // this is record 3 for id = 1
1 2015-01-21 // this is record 4 for id = 1
...
1 2015-03-22 // this is record 118 for id = 1
1 2015-03-23 // this is record 119 for id = 1
1 2015-03-24 // this is record 120 for id = 1
1 2015-03-25 // this is record 121 for id = 1
...
1 2015-06-30 // this is the last row for id = 1
2 2015-01-12 // this is record 1 for id = 2
2 2015-01-13 // this is record 2 for id = 2
...
2 2015-04-25 // this is record 120 for id = 2
...
2 2015-05-20 // this is the last record for id = 2
The result should be:
id date
-- --------
1 2015-03-24
2 2015-04-26
Remember, I have at least 120 records for each ID, this is a fact (I have a query that gives only the IDs with more than 119 records)
Attempted solution
I've tried to play with the SELECT TOP directive, but I fail to achieve the results I want as I cannot apply it direcly: I don't want the top 120 and then get the last row as I want the last one of the TOP 120 for each ID.
Edited for (a second) clarification
My goal would be to have something like:
SELECT id, 120thRow(date)
FROM table
GROUP BY id;
unfortunatelly I don't know how to implement the 120thRow function in access.
Sorry for my previous answer, I misunderstood you. I have another approach but in SQL. I am almost sure that it does not work in Access but might give you an idea.
-- start: this is just preparation of some sample data
declare #t table (id int, date datetime)
declare #id int, #d datetime, #c int
set #c = 0
set #id = 1
set #d = '2015-01-01'
while #c <= 125
begin
insert into #t values (#id, #d)
set #d = dateadd(day, 1, #d)
set #c = #c + 1
end
set #c = 0
set #id = 2
set #d = '2015-01-02'
while #c <= 125
begin
insert into #t values (#id, #d)
set #d = dateadd(day, 1, #d)
set #c = #c + 1
end
-- end: this is just preparation of some sample data
-- this is somewhat like what you need:
select id, date from
(select id, date, row_number() over (partition by id order by date) as rc from #t) as mytable
where rc = 120
Does this work in Access?
select t.*
from table as t
where t.date = (select top 1 date
from (select top 120 date
from table t2
where t2.id = t.id
order by date
) as tt
order by date desc
);
EDIT:
I guess MS Access doesn't allow nesting in the correlation clause. You can do this more painfully as:
select t.*
from table as t join
(select t.id, max(t.date) as maxdate
from table as t
where t.date = (select top 120 date
from table as t2
where t2.id = t.id
order by date
)
) tt
on t.id = tt.id and t.date = tt.maxdate;
In the end, I've managed a way to put a row counter for each day and ID like so:
select id, date,
(
select count(date)
from table As t1
where t1.id = t.id
and t1.date <= t.date
) As rowNum
from table As t
From here on it's just a matter of selecting from this resultset the rows which has rownum = 120 and game over.
Hi I think this must work for you.
with Records AS
(select row_number() over(order by sort_column_name) as 'row', *
from table_name) select * from records where row=n
sort_column_name – mention the name of the column which is sorting the table.
n – row number
table_name – mention the name of the table table name
I'm selecting a range of numbers 1-6 with a CTE. I have another table that has two columns. One with any number between 1-6 and a column called code.
I want to always return 1-6 but join on my other table that has the code.
An Example would be:
Table 1
Number
------
1
2
3
4
5
6
Table 2
Number | Code
------ -----
1 B
3 A
5 C
I want the Select to return:
CodeNumber | Code
------ -----
1 B
2 NULL
3 A
4 NULL
5 C
6 NULL
If I join on the number it doesn't return all the values.
DECLARE #Start INT
DECLARE #End INT
DECLARE #Priority CHAR(2)
SELECT #Start = -3
, #End = 3
, #Code = ''
WITH Numbers (Number, Code)
AS ( SELECT #Start AS Number, #Code
UNION ALL
SELECT Number + 1, #Code
FROM Numbers
WHERE Number < #End
)
Use a left join:
WITH Numbers(Number) as (
SELECT #Start AS Number
UNION ALL
SELECT Number + 1
FROM Numbers
WHERE Number < #End
)
select n.number, t.code
from numbers n left join
table2 t
on t.number = n.number;
Having this selection:
id IDSLOT N_UM
------------------------
1 1 6
2 6 2
3 2 1
4 4 1
5 5 1
6 8 1
7 3 1
8 7 1
9 9 1
10 10 0
I would like to get the row (only one) which has the minimun value of N_UM, in this case the row with id=10 (10 0).
select * from TABLE_NAME order by COLUMN_NAME limit 1
I'd try this:
SELECT TOP 1 *
FROM TABLE1
ORDER BY N_UM
(using SQL Server)
Try this -
select top 1 * from table where N_UM = (select min(N_UM) from table);
Use this sql query:
select id,IDSLOT,N_UM from table where N_UM = (select min(N_UM) from table));
Method 1:
SELECT top 1 *
FROM table
WHERE N_UM = (SELECT min(N_UM) FROM table);
Method 2:
SELECT *
FROM table
ORDER BY N_UM
LIMIT 1
A more general solution to this class of problem is as follows.
Method 3:
SELECT *
FROM table
WHERE N_UM IN (SELECT MIN(N_UM) FROM table);
Here is one approach
Create table #t (
id int,
IDSLOT int,
N_UM int
)
insert into #t ( id, idslot, n_um )
VALUES (1, 1, 6),
(2,6,2),
(3,2,1),
(4,4,1),
(5,5,1),
(6,8,1),
(7,3,1),
(8,7,1),
(9,9,1),
(10, 10, 0)
select Top 1 *
from #t
Where N_UM = ( select MIN(n_um) from #t )
select TOP 1 Col , COUNT(Col) as minCol from employee GROUP by Col
order by mindep asc
I have a table of items, each of which has a date associated with it. If I have the date associated with one item, how do I query the database with SQL to get the 'previous' and 'subsequent' items in the table?
It is not possible to simply add (or subtract) a value, as the dates do not have a regular gap between them.
One possible application would be 'previous/next' links in a photo album or blog web application, where the underlying data is in a SQL table.
I think there are two possible cases:
Firstly where each date is unique:
Sample data:
1,3,8,19,67,45
What query (or queries) would give 3 and 19 when supplied 8 as the parameter? (or the rows 3,8,19). Note that there are not always three rows to be returned - at the ends of the sequence one would be missing.
Secondly, if there is a separate unique key to order the elements by, what is the query to return the set 'surrounding' a date? The order expected is by date then key.
Sample data:
(key:date) 1:1,2:3,3:8,4:8,5:19,10:19,11:67,15:45,16:8
What query for '8' returns the set:
2:3,3:8,4:8,16:8,5:19
or what query generates the table:
key date prev-key next-key
1 1 null 2
2 3 1 3
3 8 2 4
4 8 3 16
5 19 16 10
10 19 5 11
11 67 10 15
15 45 11 null
16 8 4 5
The table order is not important - just the next-key and prev-key fields.
Both TheSoftwareJedi and Cade Roux have solutions that work for the data sets I posted last night. For the second question, both seem to fail for this dataset:
(key:date) 1:1,2:3,3:8,4:8,5:19,10:19,11:67,15:45,16:8
The order expected is by date then key, so one expected result might be:
2:3,3:8,4:8,16:8,5:19
and another:
key date prev-key next-key
1 1 null 2
2 3 1 3
3 8 2 4
4 8 3 16
5 19 16 10
10 19 5 11
11 67 10 15
15 45 11 null
16 8 4 5
The table order is not important - just the next-key and prev-key fields.
Select max(element) From Data Where Element < 8
Union
Select min(element) From Data Where Element > 8
But generally it is more usefull to think of sql for set oriented operations rather than iterative operation.
Self-joins.
For the table:
/*
CREATE TABLE [dbo].[stackoverflow_203302](
[val] [int] NOT NULL
) ON [PRIMARY]
*/
With parameter #val
SELECT cur.val, MAX(prv.val) AS prv_val, MIN(nxt.val) AS nxt_val
FROM stackoverflow_203302 AS cur
LEFT JOIN stackoverflow_203302 AS prv
ON cur.val > prv.val
LEFT JOIN stackoverflow_203302 AS nxt
ON cur.val < nxt.val
WHERE cur.val = #val
GROUP BY cur.val
You could make this a stored procedure with output parameters or just join this as a correlated subquery to the data you are pulling.
Without the parameter, for your data the result would be:
val prv_val nxt_val
----------- ----------- -----------
1 NULL 3
3 1 8
8 3 19
19 8 45
45 19 67
67 45 NULL
For the modified example, you use this as a correlated subquery:
/*
CREATE TABLE [dbo].[stackoverflow_203302](
[ky] [int] NOT NULL,
[val] [int] NOT NULL,
CONSTRAINT [PK_stackoverflow_203302] PRIMARY KEY CLUSTERED (
[ky] ASC
)
)
*/
SELECT cur.ky AS cur_ky
,cur.val AS cur_val
,prv.ky AS prv_ky
,prv.val AS prv_val
,nxt.ky AS nxt_ky
,nxt.val as nxt_val
FROM (
SELECT cur.ky, MAX(prv.ky) AS prv_ky, MIN(nxt.ky) AS nxt_ky
FROM stackoverflow_203302 AS cur
LEFT JOIN stackoverflow_203302 AS prv
ON cur.ky > prv.ky
LEFT JOIN stackoverflow_203302 AS nxt
ON cur.ky < nxt.ky
GROUP BY cur.ky
) AS ordering
INNER JOIN stackoverflow_203302 as cur
ON cur.ky = ordering.ky
LEFT JOIN stackoverflow_203302 as prv
ON prv.ky = ordering.prv_ky
LEFT JOIN stackoverflow_203302 as nxt
ON nxt.ky = ordering.nxt_ky
With the output as expected:
cur_ky cur_val prv_ky prv_val nxt_ky nxt_val
----------- ----------- ----------- ----------- ----------- -----------
1 1 NULL NULL 2 3
2 3 1 1 3 8
3 8 2 3 4 19
4 19 3 8 5 67
5 67 4 19 6 45
6 45 5 67 NULL NULL
In SQL Server, I prefer to make the subquery a Common table Expression. This makes the code seem more linear, less nested and easier to follow if there are a lot of nestings (also, less repetition is required on some re-joins).
Firstly, this should work (the ORDER BY is important):
select min(a)
from theTable
where a > 8
select max(a)
from theTable
where a < 8
For the second question that I begged you to ask...:
select *
from theTable
where date = 8
union all
select *
from theTable
where key = (select min(key)
from theTable
where key > (select max(key)
from theTable
where date = 8)
)
union all
select *
from theTable
where key = (select max(key)
from theTable
where key < (select min(key)
from theTable
where date = 8)
)
order by key
SELECT 'next' AS direction, MIN(date_field) AS date_key
FROM table_name
WHERE date_field > current_date
GROUP BY 1 -- necessity for group by varies from DBMS to DBMS in this context
UNION
SELECT 'prev' AS direction, MAX(date_field) AS date_key
FROM table_name
WHERE date_field < current_date
GROUP BY 1
ORDER BY 1 DESC;
Produces:
direction date_key
--------- --------
prev 3
next 19
My own attempt at the set solution, based on TheSoftwareJedi.
First question:
select date from test where date = 8
union all
select max(date) from test where date < 8
union all
select min(date) from test where date > 8
order by date;
Second question:
While debugging this, I used the data set:
(key:date) 1:1,2:3,3:8,4:8,5:19,10:19,11:67,15:45,16:8,17:3,18:1
to give this result:
select * from test2 where date = 8
union all
select * from (select * from test2
where date = (select max(date) from test2
where date < 8))
where key = (select max(key) from test2
where date = (select max(date) from test2
where date < 8))
union all
select * from (select * from test2
where date = (select min(date) from test2
where date > 8))
where key = (select min(key) from test2
where date = (select min(date) from test2
where date > 8))
order by date,key;
In both cases the final order by clause is strictly speaking optional.
If your RDBMS supports LAG and LEAD, this is straightforward (Oracle, PostgreSQL, SQL Server 2012)
These allow to choose the row either side of any given row in a single query
Try this...
SELECT TOP 3 * FROM YourTable
WHERE Col >= (SELECT MAX(Col) FROM YourTable b WHERE Col < #Parameter)
ORDER BY Col