let's say I have a table like this:
CREATE TABLE [dbo].[Scheduler](
[DayOfWeek] [tinyint] NOT NULL,
[Time] [time](0) NOT NULL,
[Action] [varchar](255) NOT NULL
)
And some data, like this:
INSERT INTO Scheduler VALUES (1, '11:00:00', 'Sunday')
INSERT INTO Scheduler VALUES (2, '12:00:00', 'Monday')
INSERT INTO Scheduler VALUES (4, '13:00:00', 'Tuesday')
INSERT INTO Scheduler VALUES (8, '14:00:00', 'Wednesday')
INSERT INTO Scheduler VALUES (16, '15:00:00', 'Thursday')
INSERT INTO Scheduler VALUES (32, '16:00:00', 'Friday')
INSERT INTO Scheduler VALUES (64, '17:00:00', 'Saturday')
INSERT INTO Scheduler VALUES (62, '06:00:00', 'Every business day')
INSERT INTO Scheduler VALUES (127, '08:00:00', 'Every day')
How can I produce multiple rows in a SELECT statement if DayOfWeek has more than one flag?
For example, this row:
INSERT INTO Scheduler VALUES (62, '06:00:00', 'Every business day')
It will be represented in 5 rows in a SELECT statement (one for each day/flag set)
DayOfWeek Time Message
--------- ---------------- ---------------------------
2 06:00:00 Every business day
4 06:00:00 Every business day
8 06:00:00 Every business day
16 06:00:00 Every business day
32 06:00:00 Every business day
Running the same query with all the data will give me 19 rows.
7 rows - one row for each individual day (1, 2, 4, 8, 16, 32, 64)
5 rows - business days (62)
7 rows - every day (127)
I'm not sure how can I do this.
I think I can use a cursor to do this, but it is the best option here?
Thanks.
You need SQL Server's Bitwise Operators. This example shows how you can determine which flags are contained in the current value.
DECLARE #Mon INT = 1;
DECLARE #Tue INT = 2;
DECLARE #Wed INT = 4;
DECLARE #MonAndTue INT = 3; -- Mon (1) and Tue (2) = 3.
SELECT
#MonAndTue & #Mon, -- Contains Monday, returns Monday.
#MonAndTue & #Tue, -- Contains Tuesday, returns Tuesday.
#MonAndTue & #Wed -- Does not contain Wednesday, returns 0.
Where possible I'd recommend avoiding bit masking based solutions in SQL Server. These work great in other languages (C springs to mind). But SQL works best when each column holds a single value, describing a single item. Of course you could combined these approaches. This table design allows you to retain the base 2 keys (great for the frontend) and includes simple bit fields that make filtering in the backend a straightforward task.
CREATE TABLE [Day]
(
DayId INT PRIMARY KEY,
[DayName] VARCHAR(9),
IsMonday BIT,
IsTuesday BIT,
...
IsSunday BIT
)
;
EDIT
Sorry! I didn't actually answer your question. To use the bitwise operations in a join your need syntax long these lines:
WITH Scheduler AS
(
/* Sample data.
*/
SELECT
*
FROM
(
VALUES
(1, 'Sunday'),
(2, 'Monday'),
(4, 'Tuesday'),
(8, 'Wednesday'),
(16, 'Thursday'),
(32, 'Friday'),
(64, 'Saturday'),
(62, 'Every business day'),
(127, 'Every day')
) AS r(DayId, [DayName])
)
/* This join returns every day from s2 that
* is contained within the s1 record Every business day.
*/
SELECT
*
FROM
Scheduler AS s1
INNER JOIN Scheduler AS s2 ON (s1.DayId & s2.DayId) = s2.DayId
WHERE
s1.DayId = 62
;
Here S1 is filtered to return Every business day. S2 joins on S1 where a match is found. This returns Mon, Tue, Wed, etc without returning Sat and Sun.
Related
I have a constraint that I have two date ranges. One of them will never change it is a measurement period (Date Range A). I need to try and find if the second Date range (Date Range B) Overlaps with A for a length of at least 6 months. What would be the best way to go about this?
I have considered trying to compare the start and end dates of the two ranges in different ways depending on how the two intersect, but have yet to hit upon a solid methodology.
You didn't say what's the database so I worked an example in PostgreSQL:
create table a (
since date,
upto date
);
insert into a (since, upto) values ('2017-01-01', '2017-10-25');
create table b (
since date,
upto date
);
insert into b (since, upto) values ('2017-04-24', '2018-01-15');
insert into b (since, upto) values ('2017-06-01', '2018-01-15');
insert into b (since, upto) values ('2016-08-15', '2017-10-04');
select a.*, b.*
from a, b
where a.upto > b.since + interval '6 month' and a.since < b.since
or b.upto > a.since + interval '6 month' and b.since < a.since
Result:
since upto since upto
------------------------------------------------------------
2017-01-01 2017-10-25 2017-04-24 2018-01-15
2017-01-01 2017-10-25 2016-08-15 2017-10-04
I'm calculating average start times from events that run late at night and may not start until the next morning.
2018-01-09 00:01:38.000
2018-01-09 23:43:22.000
currently all I can produce is an average of 11:52:30.0000000
I would like the result to be ~ 23:52
the times averaged will not remain static as this event runs daily and I will have new data daily. I will likely take the most recent 10 records and average them.
Would be nice to have SQL you're running, but probably you just need to format properly your output, it should be something like this:
FORMAT(cast(<your column> as time), N'hh\:mm(24h)')
The following will both compute the average across the datetime field and also return the result as a 24hr time notation only.
SELECT CAST(CAST(AVG(CAST(<YourDateTimeField_Here> AS FLOAT)) AS DATETIME) AS TIME) [AvgTime] FROM <YourTableContaining_DateTime>
The following will calculate the average time of day, regardless of what day that is.
--SAMPLE DATA
create table #tmp_sec_dif
(
sample_date_time datetime
)
insert into #tmp_sec_dif
values ('2018-01-09 00:01:38.000')
, ('2018-01-09 23:43:22.000')
--ANSWER
declare #avg_sec_dif int
set #avg_sec_dif =
(select avg(a.sec_dif) as avg_sec_dif
from (
--put the value in terms of seconds away from 00:00:00
--where 23:59:00 would be -60 and 00:01:00 would be 60
select iif(
datepart(hh, sample_date_time) < 12 --is it morning?
, datediff(s, '00:00:00', cast(sample_date_time as time)) --if morning
, datediff(s, '00:00:00', cast(sample_date_time as time)) - 86400 --if evening
) as sec_dif
from #tmp_sec_dif
) as a
)
select cast(dateadd(s, #avg_sec_dif, '00:00:00') as time) as avg_time_of_day
The output would be an answer of 23:52:30.0000000
This code allows you to define a date division point. e.g. 18 identifies 6pm. The time calculation would then be based on seconds after 6pm.
-- Defines the hour of the day when a new day starts
DECLARE #DayDivision INT = 18
IF OBJECT_ID(N'tempdb..#StartTimes') IS NOT NULL DROP TABLE #StartTimes
CREATE TABLE #StartTimes(
start DATETIME NOT NULL
)
INSERT INTO #StartTimes
VALUES
('2018-01-09 00:01:38.000')
,('2018-01-09 23:43:22.000')
SELECT
-- 3. Add the number of seconds to a day starting at the
-- day division hour, then extract the time portion
CAST(DATEADD(SECOND,
-- 2. Average number of seconds
AVG(
-- 1. Get the number of seconds from the day division point (#DayDivision)
DATEDIFF(SECOND,
CASE WHEN DATEPART(HOUR,start) < #DayDivision THEN
SMALLDATETIMEFROMPARTS(YEAR(DATEADD(DAY,-1,start)),MONTH(DATEADD(DAY,-1,start)),DAY(DATEADD(DAY,-1,start)),#DayDivision,0)
ELSE
SMALLDATETIMEFROMPARTS(YEAR(start),MONTH(start),DAY(start),#DayDivision,0)
END
,start)
)
,'01 jan 1900 ' + CAST(#DayDivision AS VARCHAR(2)) + ':00') AS TIME) AS AverageStartTime
FROM #StartTimes
I have a task where I need to generate report once in every month let's say 1st of every month. I tried in many ways but every time I need to modify the query in order to get the data for tht particular month.
I used the following query to fetch the data of November:
Select * from user
where m_termination_dt>(sysdate-30) and m_termination_dt<(sysdate).
It is giving proper output.( I ran this query on 1st of Dec so subtracted 30days). Here the concern is, as November has 30 days it worked. For Dec I have to minus 31 days instead 30 which is again a human involvement. So is there any way I can write a query without changing the query.
Can anyone please help me with a generic query so that I can automate that query in my system.
Sai.
Add_months(trunc(sysdate, 'month'), -1) gives first day of previous month. Trunc(sysdate, 'month') gives first day of current month.
So if you run this query you get data from previous month, it does not matter how many days it had:
select * from users
where add_months(trunc(sysdate, 'month'), -1) <= m_termination_dt
and m_termination_dt < trunc(sysdate, 'month')
Example:
create table users (id number(5), m_termination_dt date);
insert into users values (1, date '2015-11-01');
insert into users values (2, date '2015-11-18');
insert into users values (3, date '2015-11-30');
insert into users values (4, date '2015-12-01');
commit;
Output of query:
ID M_TERMINATION_DT
------ ----------------
1 2015-11-01
2 2015-11-18
3 2015-11-30
I'm fairly new to SQL and still trying to get my head around a few concepts...
I have created a simple table and i'm trying to update one of the columns in the table using a CASE statement. I understand why the statment won't work but i'm confused as to how to fix it....
This is my code:
CREATE TABLE q5
(
UserID INT,
Month INT,
Score INT
)
INSERT INTO q5(UserID, Month, Score)
VALUES (1,1,10), (1,2,5), (1,1,6), (2,8,6), (3,1,9), (3,4,11), (3,6,9), (4,9,10), (5,1,2);
UPDATE q5
SET
Month = CASE WHEN Month IN (1, 2, 3) THEN 'First Quarter'
WHEN Month IN (4, 5, 6) THEN 'Second Quarter'
WHEN Month IN (7, 8, 9) THEN 'Third Quarter'
ELSE 'Fourth Quarter'
END
I'm getting this error:
Msg 245, Level 16, State 1, Line 1
Conversion failed when converting the varchar value 'First Quarter' to data type int.
Month is defined as an int, yet you are trying to update the table and set Month to a varchar, like 'First Quarter'. You can either change Month to store a varchar, and then modify your insert statement and WHEN Month IN pieces, or add an additional column that can store the quarter.
The query below shows the second option (adding an additional column).
CREATE TABLE q5
(
UserID INT,
Month INT,
Score INT,
Quarter varchar(20)
)
INSERT INTO q5(UserID, Month, Score)
VALUES (1,1,10), (1,2,5), (1,1,6), (2,8,6), (3,1,9), (3,4,11), (3,6,9), (4,9,10), (5,1,2);
UPDATE q5
SET
Quarter = CASE WHEN Month IN (1, 2, 3) THEN 'First Quarter'
WHEN Month IN (4, 5, 6) THEN 'Second Quarter'
WHEN Month IN (7, 8, 9) THEN 'Third Quarter'
ELSE 'Fourth Quarter'
END
Depending on how this is being utilized, you might be better off adding a View which would calculate the Quarter. This way, you wouldn't have to maintain that additional column, which is entirely dependant on the Month column.
Your column MONTH can only store data of type INT. What you've got now is similar to the following procedural code:
int month = 2;
if (month == 1 || month == 2 || month == 3)
{
month = "First Quarter"; // compiler error
}
As you can see, that conditionally executed line is fairly nonsensical, since month is of type int.
You're probably looking to add another column, say Quarter.
ALTER TABLE q5 ADD Quarter NVARCHAR(100)
UPDATE q5
SET
Quarter = CASE WHEN Month IN (1, 2, 3) THEN 'First Quarter'
WHEN Month IN (4, 5, 6) THEN 'Second Quarter'
WHEN Month IN (7, 8, 9) THEN 'Third Quarter'
ELSE 'Fourth Quarter'
END
Although realistically, listing the value names should be done by the UI. Best practice would be to use a computed column or separate VIEW that has a Quarter INT column. This code would add a computed column with the appropriate datatype.
ALTER TABLE q5 ADD
Quarter AS CASE WHEN Month IN (1, 2, 3) THEN 1
WHEN Month IN (4, 5, 6) THEN 2
WHEN Month IN (7, 8, 9) THEN 3
ELSE 4
END
I have a table called Jobs that keeps track of jobs and their next run time. One particular scheduling options allows a job to run several times per week. I use bitwise comparisons to discern which day comes next (Well... I'm trying to anyway.) So for example. I have a table like this..
JobID NextRunTime DaysOfWeek
1 12-26-2011 21
My bitwise enumeration is like this..
Monday = 1
Tuesday = 2
Wednesday = 4
Thursday = 8
Friday = 16
Saturday = 32
Sunday = 64.
So we know that this job should run on Monday, Wednesday, Friday. (12-26-2011) is a Monday, so when it updates, it should run again on 12-28-2011 but I am unable to come up with an algorithm that allows me to do programmatically set the new NextRunTime.
This is the method I'm currently trying to get to work with some pseudo-code for what I'm having problems with..
IF OBJECT_ID('tempdb..#DaysSchedule') IS NOT NULL DROP TABLE #DaysSchedule
CREATE TABLE #DaysSchedule
(
Monday int, Tuesday Int, Wednesday Int, Thursday INT, Friday INT, Saturday INT, Sunday INT
)
INSERT INTO #DaysSchedule (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)
Values(21 & 1,21 & 2,21 & 4,21 & 8,21 & 16 ,21 & 32,21 & 64)
This gives us a table that looks like this:
Monday Tuesday Wednesday Thursday Friday Saturday Sunday
1 0 4 0 16 0 0
From here the (half) pseudo-code is easy.
for (int i=1; i<7, i++)
{
thisDay = DATENAME(dw, DATEADD(day, i, nextRunTime)) -- we add one day
if (column named thisDay contains a value > 0) -- if that days value > 0
begin
We add the difference of thisDay to NextRunTime to NextRunTime and we're done.
end
}
NOTE: I'm not going to comment on the idea of representing multiple items of data in a single field. It may or may not be appropriate in this case, I'm just commenting on how to make this type of idea work.
The problem that you are facing is that the information does not actually closely match its use.
At present...
- Extract the DAY from NextRunTime
- Identify the BIT representing that day
- SEARCH for the next bit set to one, cycling around to the start if necessary
- Identify the distance traveled in that search
- Add that distance to NextRunTime
It's just not efficient or simple.
I would recommend instead recording the number of days to add to reach the next planned date.
Examples:
-----15 = Saturday and Sunday Only
1111111 = Every Day
11113-- = Every Weekday
2-2-3-- = Monday, Wednesday, Friday
This changes the algorithm to...
- Extract the DAY from NextRunTime
- Identify the character in that position
- Cast it to an INT
- Add that many days to NextRunTime
This avoids a search and count section, replacing it with a straight look-up.
It does allow 'dead-ends', or more complex plans. This may be an advantage or dis-advantage depending on your situation...
1111100 = Every weekday for a week, then stop
2222222 = Every other day, on a two week cycle
Would it be so bad to use three rows to model the three days? e.g.
INSERT INTO Jobs (JobID, NextRunTime, RepeatOption)
VALUES (1, '2011-12-26', 'Y');
INSERT INTO RepeatJobs (JobID, RepeatOption, DaysOffset)
VALUES (1, 'Y', 2),
(1, 'Y', 4);
If you must go with bitwise, how about creating a lookup table e.g.
VALUES (1, 'Monday'),
(2, 'Tuesday'),
(3, 'Monday'),
(3, 'Tuesday'),
(4, 'Wednesday'),
(5, 'Monday'),
(5, 'Wednesday'),
(6, 'Tuesday'),
(6, 'Wednesday'),
(7, 'Monday'),
(7, 'Tuesday'),
(7, 'Wednesday'),
(8, 'Thursday'),
(9, 'Monday'),
(9, 'Thursday'),
(10, 'Tuesday'),
(10, 'Thursday'),
(11, ...
...but rather than 'Monday', 'Tuesday', 'Wednesday' etc store the offset in days from a set day of the week, say Sunday, then round down your NextRunTime to the Sunday then add the offset etc.