How to fill column basing on two other columns - tsql

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.

Related

reuse table data in round robin manner

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.

Calculating Running Totals

I am trying to re-calculate a few different columns of data for a particular EmployeeID.
I want the hrs_YTD column to keep a running total. What is a good way of updating this info?
HRS_YTD currently has 0.00 values. I wan't to achieve the results in the table below.
ID | CHEKDATE | CHEKNUMBR | HRS | HRS_YTD
EN344944 | 01/1/2014 | dd1001 | 40.00 | 40.00
EN344944 | 01/8/2014 | dd1002 | 30.00 | 70.00
EN344944 | 1/15/2014 | dd1003 | 32.50 | 102.50
etc.....
DECLARE #k_external_id varchar(32)
SET #k_external_id = 'EN344944'
SELECT * INTO #tmpA
FROM dbo.gp_check_hdr a
WHERE a.EMPLOYID = #k_external_id
SELECT a.ID, a.CHEKNMBR, a.CHEKDATE,
(SELECT CAST(SUM(a.[hours]) as decimal(18,2)) FROM #tmpA b
WHERE (b.CHEKDATE <= a.CHEKDATE and YEAR(b.CHEKDATE) = 2013)) AS hrs_ytd
FROM #tmpA a
WHERE YEAR(a.CHEKDATE) = 2013
I really don't know if I can alias a table like I did with #tmpA b, but it's worked for me in the past. That doesn't mean its a good way of doing things though. Can someone show me a way to achieve the results I need?
havent tested this, but you can give this a try
DECLARE #k_external_id varchar(32)
SET #k_external_id = 'EN344944'
SELECT g1.primarykey, g1.ID,g1.CHEKDATE, g1.CHEKNUMBR, g1.HRS ,(SELECT SUM(g2.HRS)
FROM dbo.gp_check_hdr g2
WHERE g2.ID = #k_external_id AND
(g2.primarykey <= g1.primarykey)) as HRS_YTD
FROM dbo.gp_check_hdr g1
WHERE g1.ID = #k_external_id
ORDER BY g1.primarykey;
http://www.codeproject.com/Articles/300785/Calculating-simple-running-totals-in-SQL-Server
The way I'd do this is a combination of a computed column and a user defined function.
The function allows to aggregate the data. In a computed column, you can only work with fields of the same row, hence calling a function (which is allowed) is necessary.
The computed column allows this to work continuously without any additional queries or temp tables, etc. Once it's set, you don't need to run nightly updates or triggers or anything of the sort to keep the data updated, including when records change or get deleted.
Here's my solution ... and SqlFiddle: http://www.sqlfiddle.com/#!3/cd8d6/1/0
Edit:
I've updated this to reflect your need to calculate the running totals per employee. SqlFiddle also updated.
The function:
Create Function udf_GetRunningTotals (
#CheckDate DateTime,
#EmployeeID int
)
Returns Decimal(18,2)
As
Begin
Declare #Result Decimal(18,2)
Select #Result = Cast(Sum(rt.Hrs) As Decimal(18,2))
From RunningTotals rt
Where rt.CheckDate <= #CheckDate
And Year(rt.CheckDate) = Year(#CheckDate)
And rt.EmployeeID = #EmployeeID
Return #Result
End
The Table Schema:
Create Table [dbo].[RunningTotals](
[ID] [int] Identity(1,1) NOT NULL,
[EmployeeID] [int] NOT NULL,
[CheckDate] [datetime] NOT NULL,
[CheckNumber] [int] NOT NULL,
[Hrs] [decimal](18, 2) NOT NULL,
[Hrs_Ytd] AS ([dbo].[udf_GetRunningTotals]([CheckDate],[EmployeeID])), -- must add after table creation and function creation due to inter-referencing of table and function
Constraint [PK_RunningTotals3] Primary Key Clustered (
[ID] ASC
) With (
PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON
)
) On [PRIMARY]
Result will tally up the YTD hrs for each year.
Note --
You cannot create the function or the table as is since they reference each other.
First, create the table with all but the computed column;
Then, create the function.
Finally, alter the table and add the computed column.
Here's a full running test script:
-- Table schema
Create Table [dbo].[RunningTotals](
[ID] [int] Identity(1,1) NOT NULL,
[EmployeeID] [int] NOT NULL,
[CheckDate] [datetime] NOT NULL,
[CheckNumber] [int] NOT NULL,
[Hrs] [decimal](18, 2) NOT NULL,
Constraint [PK_RunningTotals3] Primary Key Clustered (
[ID] ASC
) With (
PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON
)
) On [PRIMARY]
Go
-- UDF Function to compute totals
Create Function udf_GetRunningTotals (
#CheckDate DateTime,
#EmployeeID int
)
Returns Decimal(18,2)
As
Begin
Declare #Result Decimal(18,2)
Select #Result = Cast(Sum(rt.Hrs) As Decimal(18,2))
From RunningTotals rt
Where rt.CheckDate <= #CheckDate
And Year(rt.CheckDate) = Year(#CheckDate)
And rt.EmployeeID = #EmployeeID
Return #Result
End
Go
-- Add the computed column to the table
Alter Table RunningTotals Add [Hrs_Ytd] As (dbo.udf_GetRunningTotals(CheckDate, EmployeeID))
Go
-- Insert some test data
Insert into RunningTotals Values (334944, '1/1/2014', '1001', 40.00)
Insert into RunningTotals Values (334944, '1/5/2014', '1002', 30.00)
Insert into RunningTotals Values (334944, '1/15/2014', '1003', 32.50)
Insert into RunningTotals Values (334945, '1/5/2014', '1001', 10.00)
Insert into RunningTotals Values (334945, '1/6/2014', '1002', 20.00)
Insert into RunningTotals Values (334945, '1/8/2014', '1003', 12.50)
-- Test the computed column
Select * From RunningTotals
Your sub query should work just fine.
I used a table variable in place of a temp table.
I also limited the results inserted in the temp table to 2013 to simplify the final select statement and limit the results in the temp table to just what you need. The only other thing is joining the sub query to the main query using the ID but what you have should work as you are limiting the result in your temp table to a specific ID.
DECLARE
#k_external_id varchar(32)
,#k_reporting_year int
SET #k_external_id = 'EN344944'
SET #k_reporting_year = 2013
DECLARE #temp TABLE(
ID NVARCHAR(32)
,CheckDate DATE
,CheckNumber NVARCHAR(6)
,HRS DECIMAL(18,2)
)
INSERT INTO #temp (
ID
,CheckDate
,CheckNumber
,HRS
)
SELECT
ID
,CHEKDATE
,CHEKNMBR
,[hours]
FROM
dbo.gp_check_hdr
WHERE
EMPLOYID = #k_external_id
AND YEAR(a.CHEKDATE) = #k_reporting_year
SELECT
ID
,CheckDate
,CheckNumber
,HRS
,(SELECT SUM(HRS) FROM #temp b WHERE a.ID = b.ID AND b.CheckDate <= a.CheckDate) AS hrs_ytd
FROM
#temp a

Take next N rows recursively and sequentially from sql table

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.

INSERT query from trigger inserts NULL

I have the following query in SQL Server 2005:
DECLARE #issueid numeric(10,0)
DECLARE #domain nvarchar (255)
SET #issueid = (SELECT issueid FROM issues WHERE issueid = 4850)
SET #domain = (SELECT fielddata FROM customfielddata WHERE issueid = #issueid AND customfieldid = 99)
PRINT CONVERT(NVARCHAR, #issueid) + ': ' + #domain
It outputs the result as expected:
4850: www.domain.com
When I have the same query in a trigger, it inserts the #issueid value correctly in the first column, but NULL in place of #section_name in the 2nd column:
CREATE TRIGGER trigger_website_sections
ON gemini_issues
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
IF (SELECT issuetypeid FROM inserted) = 58 BEGIN
DECLARE #issueid numeric(10,0)
DECLARE #domain nvarchar (255)
SET #issueid = (SELECT issueid FROM inserted)
SET #domain = (SELECT fielddata FROM customfielddata WHERE issueid = #issueid AND customfieldid = 99)
INSERT INTO website_sections VALUES (#issueid, #domain);
END
END
Can someone help figure out why?
Ok, after some more work I realised that because the row in customfielddata is created only after a row is created in issues (the 2nd is the primary table, and the 1st stores supplementary data for created records), the query on customfielddata will return NULL.
And trying to concatenate a string with a NULL returns a NULL and that's what's inserted into the row.
Mystery solved.

sql missing rows when grouped by DAY, MONTH, YEAR

If I select from a table group by the month, day, year,
it only returns rows with records and leaves out combinations without any records, making it appear at a glance that every day or month has activity, you have to look at the date column actively for gaps. How can I get a row for every day/month/year, even when no data is present, in T-SQL?
Create a calendar table and outer join on that table
My developer got back to me with this code, underscores converted to dashes because StackOverflow was mangling underscores -- no numbers table required. Our example is complicated a bit by a join to another table, but maybe the code example will help someone someday.
declare #career-fair-id int
select #career-fair-id = 125
create table #data ([date] datetime null, [cumulative] int null)
declare #event-date datetime, #current-process-date datetime, #day-count int
select #event-date = (select careerfairdate from tbl-career-fair where careerfairid = #career-fair-id)
select #current-process-date = dateadd(day, -90, #event-date)
while #event-date <> #current-process-date
begin
select #current-process-date = dateadd(day, 1, #current-process-date)
select #day-count = (select count(*) from tbl-career-fair-junction where attendanceregister <= #current-process-date and careerfairid = #career-fair-id)
if #current-process-date <= getdate()
insert into #data ([date], [cumulative]) values(#current-process-date, #day-count)
end
select * from #data
drop table #data
Look into using a numbers table. While it can be hackish, it's the best method I've come by to quickly query missing data, or show all dates, or anything where you want to examine values within a range, regardless of whether all values in that range are used.
Building on what SQLMenace said, you can use a CROSS JOIN to quickly populate the table or efficiently create it in memory.
http://www.sitepoint.com/forums/showthread.php?t=562806
The task calls for a complete set of dates to be left-joined onto your data, such as
DECLARE #StartInt int
DECLARE #Increment int
DECLARE #Iterations int
SET #StartInt = 0
SET #Increment = 1
SET #Iterations = 365
SELECT
tCompleteDateSet.[Date]
,AggregatedMeasure = SUM(ISNULL(t.Data, 0))
FROM
(
SELECT
[Date] = dateadd(dd,GeneratedInt, #StartDate)
FROM
[dbo].[tvfUtilGenerateIntegerList] (
#StartInt,
,#Increment,
,#Iterations
)
) tCompleteDateSet
LEFT JOIN tblData t
ON (t.[Date] = tCompleteDateSet.[Date])
GROUP BY
tCompleteDateSet.[Date]
where the table-valued function tvfUtilGenerateIntegerList is defined as
-- Example Inputs
-- DECLARE #StartInt int
-- DECLARE #Increment int
-- DECLARE #Iterations int
-- SET #StartInt = 56200
-- SET #Increment = 1
-- SET #Iterations = 400
-- DECLARE #tblResults TABLE
-- (
-- IterationId int identity(1,1),
-- GeneratedInt int
-- )
-- =============================================
-- Author: 6eorge Jetson
-- Create date: 11/22/3333
-- Description: Generates and returns the desired list of integers as a table
-- =============================================
CREATE FUNCTION [dbo].[tvfUtilGenerateIntegerList]
(
#StartInt int,
#Increment int,
#Iterations int
)
RETURNS
#tblResults TABLE
(
IterationId int identity(1,1),
GeneratedInt int
)
AS
BEGIN
DECLARE #counter int
SET #counter= 0
WHILE (#counter < #Iterations)
BEGIN
INSERT #tblResults(GeneratedInt) VALUES(#StartInt + #counter*#Increment)
SET #counter = #counter + 1
END
RETURN
END
--Debug
--SELECT * FROM #tblResults