I have two tables "main" and "moved". "main" table has records from where I move 3 rows sequentially to "moved" table by executing a stored procedure. So every time I execute the stored procedure it should check to see the next set of 3 rows are sequentially read from "main" table from the row of last move happened and inserted into "moved" table.
select rowid from #main
rowid
-----------------------
1
2
3
4
5
Now when I execute the query, it should take the 3 rows from "main" table and insert into "moved" table. Say if I run the query 5 times, this is how I expect the "moved" table to contain each time I run it.
1,2,3 --"moved" table has these rows 1st time when I run the query
4,5,1 --"moved" table has these rows 2nd time when I run the query
2,3,4 --"moved" table has these rows 3rd time when I run the query
5,1,2 --"moved" table has these rows 4th time when I run the query
3,4,5 --"moved" table has these rows 5th time when I run the query
1,2,3 --"moved" table has these rows 6th time when I run the query
...so the sequential read continues. Read sequentially from next row where it ended from last run.
I asked this already, but the answer works only partially. It doesn't work when the values in the "main" table grows or not in order.
Thanks in advance for your help.
The following example may give you a starting point. Note that there must be an explicit order provided for the values that you want to insert. A table has no natural order and "sequential" is meaningless.
-- Sample data.
declare #Template as Table ( TemplateValue VarChar(10), TemplateRank Int );
-- NB: TemplateRank values are assumed to start at zero and be dense.
insert into #Template ( TemplateValue, TemplateRank ) values
( 'one', 0 ), ( 'two', 1 ), ( 'three', 2 ), ( 'four', 3 );
declare #NumberOfTemplateValues Int = ( select Max( TemplateRank ) from #Template ) + 1;
select * from #Template;
declare #AccumulatedStuff as Table ( Id Int Identity, Value VarChar(10) );
declare #GroupSize as Int = 5;
-- Add a block of GroupSize rows to the AccumulatedStuff .
declare #LastValue as VarChar(10) = ( select top 1 Value from #AccumulatedStuff order by Id desc );
declare #LastRank as Int = ( select TemplateRank from #Template where TemplateValue = #LastValue );
select #LastValue as LastValue, #LastRank as LastRank;
with Numbers as (
select 1 as Number
union all
select Number + 1
from Numbers
where Number < #GroupSize ),
RankedValues as (
select TemplateValue, TemplateRank
from #Template as T inner join
Numbers as N on ( N.Number + Coalesce( #LastRank, #NumberOfTemplateValues - 1 ) ) % #NumberOfTemplateValues = T.TemplateRank )
insert into #AccumulatedStuff
select TemplateValue from RankedValues;
select * from #AccumulatedStuff;
-- Repeat.
set #LastValue = ( select top 1 Value from #AccumulatedStuff order by Id desc );
set #LastRank = ( select TemplateRank from #Template where TemplateValue = #LastValue );
select #LastValue as LastValue, #LastRank as LastRank;
with Numbers as (
select 1 as Number
union all
select Number + 1
from Numbers
where Number < #GroupSize ),
RankedValues as (
select TemplateValue, TemplateRank
from #Template as T inner join
Numbers as N on ( N.Number + Coalesce( #LastRank, #NumberOfTemplateValues - 1 ) ) % #NumberOfTemplateValues = T.TemplateRank )
insert into #AccumulatedStuff
select TemplateValue from RankedValues;
select * from #AccumulatedStuff;
DECLARE #main TABLE
(
RowID INT IDENTITY(1,1),
DataID INT
)
DECLARE #selected TABLE
(
RowID INT IDENTITY(1,1),
DataID INT
)
DECLARE #final TABLE
(
RowID INT IDENTITY(1,1),
DataID INT
)
INSERT INTO #main values (1),(2),(3),(4),(5),(6),(7)
--INSERT INTO #Selected values (1),(2),(3)
--INSERT INTO #Selected values (4),(5),(6)
--INSERT INTO #Selected values (7),(1),(2)
--INSERT INTO #Selected values (3),(4),(5)
INSERT INTO #Selected values (6),(7),(1)
--INSERT INTO #Selected values (2),(3),(4)
--INSERT INTO #Selected values (5),(6),(7)
--INSERT INTO #Selected values (1),(2),(3)
DECLARE
#LastDataID INT,
#FinalCount INT
SELECT
TOP 1
#LastDataID = DataID
FROM
#selected
ORDER BY
RowID
DESC
INSERT INTO #final
SELECT
TOP 3
DataID
FROM
#main
WHERE
DataID > #LastDataID
SELECT #FinalCount = COUNT(1) FROM #final
IF (ISNULL(#FinalCount, 0 ) < 3)
BEGIN
INSERT INTO #final
SELECT
TOP (3 - #FinalCount)
DataID
FROM
#main
END
SELECT * FROM #final
The key was to have reference to the last record moved to the "moved/selected" table. It worked as expected.
Related
i have a table named Scoreboard which contains a field named as score which is an array containing values 27,56,78,12,89,77,34,23,90,87,33,55,30,67,76,87,56and i want to write a PostgreSQL procedure to fetch three categories
category 1 = top 10% values of the total no of values in array
category 2 = top 20% values of the total no of values in array
category 3 = top 30% values of the total no of values in array
and put it in an array in the same format i.e
[category 1 values,category 2 values,category 3 values]
smth like this should do:
t=# with p as (
with ntile as (
with v as (
select unnest('{27,56,78,12,89,77,34,23,90,87,33,55,30,67,76,87,56}'::int[]) a
)
select a,ntile(10) over(order by a desc)
from v
)
select distinct string_agg(a::text,',') over (partition by ntile),ntile
from ntile
where ntile <=3 order by ntile
)
select array_agg(concat('category ',ntile,' ',string_agg))
from p;
array_agg
------------------------------------------------------------
{"category 1 90,89","category 2 87,87","category 3 78,77"}
(1 row)
Time: 0.493 ms
I am assuming , you have a table with one column as id and another one is an array type. Based on assumption
I have created table as below and inserted two values to it.
create table test_array (id int, values int[]);
insert into test_array values(1 ,'{27,56,78,12,89,77,34,23,90,87,33,55,30,67,76,87,56}' );
insert into test_array values(2 ,'{72,65,84,21,98,77,43,32,9,78,41,66,3,76,67,88,56}' );
Below is function which is used to find category as mentioned by you. If you do not have any id column in your table
then you can add number by using window function hint: row_number().
create or replace function find_category() returns table(category text[]) as
$$
BEGIN
return query with unnestColumn as (
select id, unnest(values) as values, ntile(10) over(partition by id order by unnest(values) desc) as ntilenumber
from test_array
) ,groupedCategory as ( select id, ntilenumber, string_agg(values::text,',') as combinedvalues from unnestColumn
where
ntilenumber <= 3
group by id, ntilenumber )
select array_agg(concat('Categoty',ntilenumber, ' ', combinedvalues ))
from groupedCategory
group by id;
END;
$$
language 'plpgsql';
Execute below function to check output.
select * from find_category();
I have table LessonHour with empty Number column.
TABLE [dbo].[LessonHour]
(
[Id] [uniqueidentifier] NOT NULL,
[StartTime] [time](7) NOT NULL,
[EndTime] [time](7) NOT NULL,
[SchoolId] [uniqueidentifier] NOT NULL,
[Number] [int] NULL
)
How can I fill up the table with Number for each LessonHour so it would be the number of lesson hour in order?
The LessonHours cannot cross each other. Every school has defined its own lesson hour schema.
Example set of data
http://pastebin.com/efWCtUbv
What'd I do:
Order by SchoolId and StartTime
Use Cursor to insert into row next number, starting from 1 every time the SchoolId changes.
Edit:
Solution with cursor
select -- top 20
LH.[Id],
[StartTime],
[EndTime],
[SchoolId]
into #LH
from
LessonHour as LH
join RowStatus as RS on LH.RowStatusId = RS.Id
where
RS.IsActive = 1
select * from #LH order by SchoolId, StartTime
declare #id uniqueidentifier, #st time(7), #et time(7), #sid uniqueidentifier
declare #prev_sid uniqueidentifier = NEWID()
declare #i int = 1
declare cur scroll cursor for
select * from #LH order by SchoolId, StartTime
open cur;
fetch next from cur into #id, #st, #et, #sid
while ##FETCH_STATUS = 0
begin
--print #prev_sid
if #sid <> #prev_sid
begin
set #i = 1
end
update LessonHour set Number = #i where Id = #id
print #i
set #i = #i + 1
set #prev_sid = #sid
fetch next from cur into #id, #st, #et, #sid
end;
close cur;
deallocate cur;
drop table #LH
This is the result I was after http://pastebin.com/iZ8cnA6w
Merging the information from the StackOverflow questions SQL Update with row_number() and
How do I use ROW_NUMBER()?:
with cte as (
select number, ROW_NUMBER() OVER(partition by schoolid order by starttime asc) as r from lessonhour
)
update cte
set number = r
Would this work
CREATE TABLE [dbo].[LessonHour]
(
[Id] [uniqueidentifier] NOT NULL,
[StartTime] [time](7) NOT NULL,
[EndTime] [time](7) NOT NULL,
[SchoolId] [uniqueidentifier] NOT NULL,
[Number] AS DATEDIFF(hour,[StartTime],[EndTime])
)
So if I understand the question correctly you require a calculated column which takes in the values of [StartTime] and [EndTime] and returns the number of hours for that lesson as an int. The above table definition should do the trick.
Let us say I have some data I would like to repeat N times. A naive approach would be this:
IF OBJECT_ID('dbo.Data', 'U') IS NOT NULL
DROP TABLE dbo.Data
CREATE TABLE Data
(
DataId INT NOT NULL PRIMARY KEY,
DataValue NVARCHAR(MAX) NOT NULL
)
INSERT INTO Data (DataId, DataValue)
SELECT 1, 'Value1' UNION ALL
SELECT 2, 'Value2' UNION ALL
SELECT 3, 'Value3' UNION ALL
SELECT 4, 'Value4' UNION ALL
SELECT 5, 'Value5'
DECLARE #RowsRequired INT
DECLARE #Counter INT
DECLARE #NumberOfRows INT
SET #RowsRequired = 22
IF OBJECT_ID('tempdb..#TempData') IS NOT NULL DROP TABLE #TempData
CREATE TABLE #TempData
(
Id INT IDENTITY(1,1),
DataValue NVARCHAR(MAX)
)
SELECT #NumberOfRows = COUNT(*) FROM Data
SET #Counter = 1
WHILE #RowsRequired > 0
BEGIN
INSERT INTO #TempData
SELECT DataValue FROM Data WHERE DataId = #Counter
SET #Counter = #Counter + 1
SET #RowsRequired = #RowsRequired - 1
IF(#Counter > #NumberOfRows)
BEGIN
SET #Counter = 1
END
END
SELECT * FROM #TempData
Here #RowsRequired determines how many rows are required. Could this be rephrased in a set based form? Thanks.
Here is a SQLFiddle with the code.
Try this instead:
DECLARE #RowsRequired INT = 22
;WITH CTE AS
(
SELECT DataId, DataValue, ROW_NUMBER() over (PARTITION BY DataId ORDER BY DataId) sort
FROM DATA
CROSS JOIN
(
SELECT TOP (#RowsRequired) 0 d
FROM master..spt_values
) d
)
SELECT TOP (#RowsRequired) ROW_NUMBER() over (order by sort), DataValue
FROM CTE
ORDER BY sort, 1
I tried this and worked for me.
declare #requiredrows int
set #requiredrows = 22;
declare #foreachrow int
select #foreachrow = #requiredrows / Count(*) from Data;
select top (#requiredrows) * from
(
select *, ROW_NUMBER() over(partition by dataId order by number) rno
from Data
Cross Join master..spt_values
) A
where rno <= #foreachrow + 1
Hope it will help.
I've this table:
CREATE TABLE "mytable"
( name text, count integer );
INSERT INTO mytable VALUES ('john', 4),('mark',2),('albert',3);
and I would like "denormlize" the rows in this way:
SELECT name FROM mytable JOIN generate_series(1,4) tmp(a) ON (a<=count)
so I've a number of rows for each name equals to the count column: I've 4 rows with john, 2 with mark and 3 with albert.
But i can't use the generate_series() function if I don't know the highest count (in this case 4). There is a way to do this without knowing the MAX(count) ?
select name,
generate_series(1,count)
from mytable;
Set returning functions can be used in the select list and will do a cross join with the row retrieved from the base table.
I think this is an undocumented behaviour that might go away in the future, but I'm not sure about that (I recall some discussion regarding this on the mailing list)
SQLFiddle example
DROP TABLE ztable ;
CREATE TABLE ztable (zname varchar, zvalue INTEGER NOT NULL);
INSERT INTO ztable(zname, zvalue) VALUES( 'one', 1), ( 'two', 2 ), ( 'three', 3) , ( 'four', 4 );
WITH expand AS (
WITH RECURSIVE zzz AS (
SELECT 1::integer AS rnk , t0.zname
FROM ztable t0
UNION
SELECT 1+rr.rnk , t1.zname
FROM ztable t1
JOIN zzz rr ON rr.rnk < t1.zvalue
)
SELECT zzz.zname
FROM zzz
)
SELECT x.*
FROM expand x
;
I am trying to count the number of records that fall between a given time period, generally 15 minutes, but I'd like to have this interval a variable. My table has a datetime column and I need to get a count of the number of records for every 15 minute interval between two dates and when there aren't any records for the 15-minute window, I need that time period with a zero. I've tried using CTE in different combinations and couldn't get it to work. I can generate the date series using a CTE, but haven't been able to get the actual data in the result. I have a working solution using a stored procedure with a WHILE loop. I was hoping to avoid this if possible in favor or a more elegant solution.
Here is my working loop using a temporary table:
declare #record_count int = 0
declare #end_date_per_query datetime
create table #output (
SessionIdTime datetime,
CallCount int)
while #date_from < #date_to
begin
set #end_date_per_query = DATEADD(minute, #interval, #date_from)
select #record_count = COUNT(*) from tbl WHERE SessionIdTime between #date_from and #end_date_per_query
insert into #output values (#date_from, #record_count)
set #date_from = #end_date_per_query
end
select * from #output order by sessionIdTime
drop table #output
Hopefully someone can help with a more elegant solution. Any help is appreciated.
A CTE works just fine:
-- Parameters.
declare #Start as DateTime = '20120901'
declare #End as DateTime = '20120902'
declare #Interval as Time = '01:00:00.00' -- One hour. Change to 15 minutes.
select #Start as [Start], #End as [End], #Interval as [Interval]
-- Sample data.
declare #Sessions as Table ( SessionId Int Identity, SessionStart DateTime )
insert into #Sessions ( SessionStart ) values
( '20120831 12:15:07' ), ( '20120831 21:51:18' ),
( '20120901 12:15:07' ), ( '20120901 21:51:18' ),
( '20120902 12:15:07' ), ( '20120902 21:51:18' )
select * from #Sessions
-- Summary.
; with SampleWindows as (
select #Start as WindowStart, #Start + #Interval as WindowEnd
union all
select SW.WindowStart + #Interval, SW.WindowEnd + #Interval
from SampleWindows as SW
where SW.WindowEnd < #End
)
select SW.WindowStart, Count( S.SessionStart ) as [Sessions]
from SampleWindows as SW left outer join
#Sessions as S on SW.WindowStart <= S.SessionStart and S.SessionStart < SW.WindowEnd
group by SW.WindowStart
Is this what you're looking for?
-- setup
DECLARE #interval INT, #start DATETIME
SELECT #interval = 15, #start = '1/1/2000 0:00:00'
DECLARE #t TABLE (id INT NOT NULL IDENTITY(1,1) PRIMARY KEY, d DATETIME)
INSERT INTO #t (d) VALUES
(DATEADD(mi, #interval * 0.00, #start)) -- in
,(DATEADD(mi, #interval * 0.75, #start)) -- in
,(DATEADD(mi, #interval * 1.50, #start)) -- out
-- query
DECLARE #result INT
SELECT #result = COUNT(*) FROM #t
WHERE d BETWEEN #start AND DATEADD(mi, #interval, #start)
-- result
PRINT #result