TSQL to combine a date field and a time field - tsql

Using logparser to import IIS logs to a db results in one column that has the date value and a second field for time:
2010-05-25 00:00:00.000
and
2010-01-01 11:11:58.000
I'd like to code an after insert trigger that combines the 2 fields.

You can just add the two values after casting them to DATE and TIME datatypes, if you're using SQL Server 2008 or later. Here's an example.
declare #datet datetime;
set #datet = GETDATE();
select
#datet,
cast(#datet as date),
cast(#datet as time);
select
cast(cast(#datet as date) as datetime),
cast(cast(#datet as time) as datetime),
cast(cast(#datet as date) as datetime) + cast(cast(#datet as time) as datetime);

In case somebody else stumbles on this thread (or in case the original poster still can use this answer), look into the TO_TIMESTAMP ( date , time ) function in LogParser which lets you combine a date-only timestamp with a time-only timestamp into a full timestamp value... and saves you from having to convert in the db...

Try this:
DECLARE #Date varchar(23)
,#Time varchar(23)
,#Both datetime
SELECT #Date='2010-05-25 00:00:00.000'
,#Time='2010-01-01 11:11:58.000'
SELECT #Both=LEFT(#Date,10)+' '+RIGHT(#Time,12)
SELECT #Both
OUTPUT:
-----------------------
2010-05-25 11:11:58.000
(1 row(s) affected)
Set based:
DECLARE #INSERTED table(RowID int, DateOf varchar(23), TimeOf varchar(23), DateTimeOf datetime)
INSERT #INSERTED VALUES (1,'2010-05-25 00:00:00.000','2010-01-01 11:11:58.000',null)
INSERT #INSERTED VALUES (2,'2010-04-05 00:00:00.000','2010-01-01 12:34:56.789',null)
INSERT #INSERTED VALUES (3,'2010-03-15 00:00:00.000','2010-01-01 01:01:01.000',null)
UPDATE #INSERTED
SET DateTimeOf=LEFT(DateOf,10)+' '+RIGHT(TimeOf,12)
SELECT * FROM #INSERTED
OUTPUT:
RowID DateOf TimeOf DateTimeOf
------- ----------------------- ----------------------- -----------------------
1 2010-05-25 00:00:00.000 2010-01-01 11:11:58.000 2010-05-25 11:11:58.000
2 2010-04-05 00:00:00.000 2010-01-01 12:34:56.789 2010-04-05 12:34:56.790
3 2010-03-15 00:00:00.000 2010-01-01 01:01:01.000 2010-03-15 01:01:01.000
(3 row(s) affected)

If you have a choice to add the output into a new column, you can also make that new column as a computed column and define it to combine those 2 columns (at a design time).
Dev

Related

opening hours table design postgresql: how to query this?

I want to store opening hours of a store in a PostgreSQL database. I have a few question about this. First of all, my table design:
Table: opening_hours
--------------------
* id
* store_id
* start_day (0->6)
* start_time (HH:mm:ss)
* duration (HH:mm:ss)
* timezone (default: Europe/Brussels)
Example:
--------
1 1 0 08:00:00 12:00:00 Europe/Brussels
// store ID 1 is open on sundays from 08AM to 08PM (12 hours in total) local time
2 2 6 20:00:00 04:00:00 Europe/Brussels
// store ID 2 is open on saturday from 08PM till 04AM (on sunday) (this is like a pub or something with opening hours at night) local time (so 20:00:00 in Belgium open = 19:00:00 in London open)
I think this is a good design, because now I can make opening hours spanning more than 1 day (like when a pub is open at night). Before that, I was storing opening hours in each day separately so I had to enter 'saturday 20:00:00 -> 23:59:59' + 'sunday 00:00:00 -> 04:00:00' for the opening hours for store ID 2.
How can I query against these rows?
I want to check if a time already exists in the database before adding a new one, and I want to check if a row exists based on the current time of the user (2 different queries).
CURRENT ROWS:
-------------
1 1 1 08:00:00 08:00:00 Europe/Brussels
// open on monday from 08AM until 04PM
2 1 6 20:00:00 12:00:00 Europe/Brussels
// open on saturday from 08PM until sunday 08AM
NEW ROWS:
---------
3 1 1 14:00:00 04:00:00 Europe/Brussels (should not insert this row because it interferes with row ID 1)
// open on monday from 02PM until 06PM
4 1 1 17:00:00 04:00:00 Europe/Brussels (can insert because not interference with row 1)
// open on monday from 05PM until 09PM
5 1 0 07:00:00 05:00:00 Europe/Brussels (can't insert this row because it interferes with row 2).
// open on sunday from 07AM until 12PM
I hope my question is clear. If not, please correct me, I'll try to ask it differently then.
With your new table design actually, it is now a bit tricky, because of durations that can possibly span over the end of a week cycle. Consider for example:
start_day | start_time | duration
-----------+------------+----------
6 | 14:00:00 | 18:00:00
It actually extends into day=0, so it would need to match day=0, t=07:00:00.
When looking for matches against a specific time, you need to check two possibilities (either day, time or day + 7, time fall into one of your intervals). Same thing for overlaps (3 possibilities).
You can define some helper functions:
-- oh: opening hours helper functions
-- oh_tst: convert dow, time into a timestamp after the Epoch
-- (note: Epoch was not a Monday (dow 0), but it doesn't matter,
-- we could use any arbitrary date)
create or replace function oh_tst(dow int, t time) returns timestamp as $$
select '1970-01-01'::timestamp + $1 * interval'1 day' + $2;
$$
language sql immutable;
-- oh_single_matches: internal function (no handling of wrap around)
create or replace function oh_single_matches(start_day int, start_time time, d interval, day int, t time) returns boolean as $$
select oh_tst($4, $5) between oh_tst($1, $2) and oh_tst($1, $2) + $3 - interval'1 millisecond';
$$
language sql immutable;
-- oh_matches: tests if a (day, time) is within an oh interval
-- handle wrap around at end of week
create or replace function oh_matches(start_day int, start_time time, d interval, day int, t time) returns boolean as $$
select oh_single_matches($1, $2, $3, $4, $5)
or oh_single_matches($1, $2, $3, $4, $5 + 7);
$$
language sql immutable;
-- oh_overlaps: test for oh defs overlap (incl wrap-around)
create or replace function oh_overlaps(adow int, astart_time time, aduration interval,
bdow int, bstart_time time, bduration interval) returns boolean as $$
select (oh_tst($1, $2), $3) overlaps (oh_tst($4, $5), $6)
or (oh_tst($1, $2), $3) overlaps (oh_tst($4 + 7, $5), $6)
or (oh_tst($1, $2), $3) overlaps (oh_tst($4 - 7, $5), $6);
$$
language sql immutable;
Examples:
Single match (one day,time against an opening hours definition):
-- intervals are left-close:
select oh_matches(6, '14:00:00'::time, interval'2 hours', 6, '14:00:00'::time);
oh_matches
------------
t
-- ...and right-open (as Nature intended):
select oh_matches(6, '14:00:00'::time, interval'2 hours', 6, '15:59:59'::time);
t
select oh_matches(6, '14:00:00'::time, interval'2 hours', 6, '16:00:00'::time);
f
-- wrap around the end of week
select oh_matches(6, '14:00:00'::time, interval'18 hours', 0, '02:00:00'::time);
t
Interval overlaps:
select oh_overlaps(2, '14:00:00'::time, interval'8 hours',
2, '04:00:00'::time, interval'8 hours');
f
select oh_overlaps(2, '14:00:00'::time, interval'8 hours',
2, '08:00:00'::time, interval'8 hours');
t
-- wraparoud ok
select oh_overlaps(0, '01:00:00'::time, interval'8 hours',
6, '22:00:00'::time, interval'18 hours');
t
Test against a table:
Single timestamp (now()):
select * from mytable
where store_id=5
and oh_matches(start_day, start_time, duration, now()::timestamp);
Check if prospective rows in a candidates table would overlap existing definitions in mytable:
select * from candidates a inner join mytable b using (store_id)
where oh_overlaps(b.start_day, b.start_time, b.duration,
a.start_day, a.start_time, a.duration);

confusion in using select command in postgresql with timestamp column

I have table which has structure like this.
CREATE TABLE users (
id serial NOT NULL,
created_at timestamp NOT NULL
)
I have more than 1000 records in this table.
This is my first query.
1 query
select id,created_at from users where id in (1051,1052)
This returns two rows which is correct as as expected. However when I use
2nd Query
select id,created_at from users where created_at = '2020-06-28'
or
select id,created_at from users where created_at = date '2020-06-28'
It returns nothing, this is not expected result as it should return two rows against it.
Similarly if I use this
3rd Query
select id, created_at from users where created_at between date '2020-06-28' and date '2020-06-28'
It returns nothing however I think this should also return two rows.
While this
4th Query
select id, created_at from users where created_at between date '2020-06-28' and date '2020-06-29'
returns two rows.
Show timezone returns correct timezong in which currently i am
I did`t understand this, why the results are different in 2nd, 3rd and 4th query. How can i get same result as of query 1 using 3rd query.
One single reason for all your queries is that you are comparing timestamp with date
in Query 2
You are comparing 2020-06-28 13:02:53 = 2020-06-28 00:00:00 which will not match so returning no records.
in Query 3
You are using between i.e. 2020-06-28 13:02:53 between 2020-06-28 00:00:00 and 2020-06-28 00:00:00 which will not match so returning no records.
in Query 4
You are using between i.e. 2020-06-28 13:02:53 between 2020-06-28 00:00:00 and 2020-06-29 00:00:00. Here both records are falling in those timestamps and you are getting the records
So you have to compare date values. As right operand is a date type value, you have to convert the left operand to date. try this
for 2nd Query
select id,created_at from users where date(created_at) = '2020-06-28'
for 3rd Query
select id, created_at from users where date(created_at) between date '2020-06-28' and date '2020-06-28'
You should opt 3rd method if you want to compare a date range. For one day only you should use 2nd query.
Because what you are doing is:
test(5432)=# select '2020-06-28'::timestamp;
timestamp
---------------------
06/28/2020 00:00:00
You are selecting for created_at that is exactly at midnight and there is none. The same thing when you do:
select id, created_at from users where created_at between date '2020-06-28' and date '2020-06-28'
You already corrected the mistake in your 3rd query in the 4th query:
select id, created_at from users where created_at between date '2020-06-28' and date '2020-06-29'
where you span the time from midnight of 06/28/2020 to midnight 06/29/2020
An alternate solution is:
create table dt_test(id integer, ts_fld timestamp);
insert into dt_test values (1, '07/04/2020 8:00'), (2, '07/05/2020 1:00'), (3, '07/05/2020 8:15');
select * from dt_test ;
id | ts_fld
----+---------------------
1 | 07/04/2020 08:00:00
2 | 07/05/2020 01:00:00
3 | 07/05/2020 08:15:00
select * from dt_test where date_trunc('days', ts_fld) = '07/05/2020'::date;
id | ts_fld
----+---------------------
2 | 07/05/2020 01:00:00
3 | 07/05/2020 08:15:00
In your case:
select id, created_at from users where date_trunc('days', created_at) = '2020-06-28'::date;

PostgreSql - Adding YEar and Month to Table

I am creating a Customer table and i want one of the attributes to be Expiry Date of credit card.I want the format to be 'Month Year'. What data type should i use? i want to use date but the format is year/month/day. Is there any other way to restrict format to only Month and year?
You can constrain the date to the first day of the month:
create table customer (
cc_expire date check (cc_expire = date_trunc('month', cc_expire))
);
Now this fails:
insert into customer (cc_expire) values ('2014-12-02');
ERROR: new row for relation "customer" violates check constraint "customer_cc_expire_check"
DETAIL: Failing row contains (2014-12-02).
And this works:
insert into customer (cc_expire) values ('2014-12-01');
INSERT 0 1
But it does not matter what day is entered. You will only check the month:
select
date_trunc('month', cc_expire) > current_date as valid
from customer;
valid
-------
t
Extract year and month separately:
select extract(year from cc_expire) "year", extract(month from cc_expire) "month"
from customer
;
year | month
------+-------
2014 | 12
Or concatenated:
select to_char(cc_expire, 'YYYYMM') "month"
from customer
;
month
--------
201412
Use either
char(5) for two-digit years, or
char(7) for four-digit years.
Code below assumes two-digit years, which is the form that matches all my credit cards. First, let's create a table of valid expiration dates.
create table valid_expiration_dates (
exp_date char(5) primary key
);
Now let's populate it. This code is just for 2013. You can easily adjust the range by changing the starting date (currently '2013-01-01'), and the "number" of months (currently 11, which lets you get all of 2013 by adding from 0 to 11 months to the starting date).
with all_months as (
select '2013-01-01'::date + (n || ' months')::interval months
from generate_series(0, 11) n
)
insert into valid_expiration_dates
select to_char(months, 'MM') || '/' || to_char(months, 'YY') exp_date
from all_months;
Now, in your data table, create a char(5) column, and set a foreign key reference from it to valid_expiration_dates.exp_date.
While you're busy with this, think hard about whether "exp_month" might be a better name for that column than "exp_date". (I think it would.)
As another idea you could essentially create some brief utilities to do this for you using int[]:
CREATE OR REPLACE FUNCTION exp_valid(int[]) returns bool LANGUAGE SQL IMMUTABLE as
$$
SELECT $1[1] <= 12 AND (select count(*) = 2 FROM unnest($1));
$$;
CREATE OR REPLACE FUNCTION first_invalid_day(int[]) RETURNS date LANGUAGE SQL IMMUTABLE AS
$$
SELECT (to_date($1[2]::text || $1[1]::text, CASE WHEN $1[2] < 100 THEN 'YYMM' ELSE 'YYYYMM' END) + '1 month'::interval)::date;
$$;
These work:
postgres=# select exp_valid('{04,13}');
exp_valid
-----------
t
(1 row)
postgres=# select exp_valid('{13,04}');
exp_valid
-----------
f
(1 row)
postgres=# select exp_valid('{04,13,12}');
exp_valid
-----------
f
(1 row)
Then we can convert these into a date:
postgres=# select first_invalid_day('{04,13}');
first_invalid_day
-------------------
2013-05-01
(1 row)
This use of arrays does not violate any normalization rules because the array as a whole represents a single value in its domain. We are storing two integers representing a single date. '{12,2}' is December of 2002, while '{2,12}' is Feb of 2012. Each represents a single value of the domain and is therefore perfectly atomic.

How to select total time from three time columns (SQL Server 2008)

I have table with columns of time(0) datatypes:
Name TimeOne TimeTwo TimeThree
------ ---------------- ---------------- ----------------
Sarah 06:45:00 03:30:00 NULL
John 06:45:00 NULL NULL
How to make SELECT statement so that the "CalculatedTotal" column is total from TimeOne, TimeTwo and TimeThree per row?
What I'd like to have is select query like this:
SELECT
Name,
TimeOne,
TimeTwo,
TimeThree,
TimeOne + TimeTwo + TimeThree As CalculatedTotal
FROM
MyTable
I'd like to get back resultset like this:
Name TimeOne TimeTwo TimeThree CalculatedTotal
------ ---------------- ---------------- ---------------- ---------------
Sarah 06:45:00 03:30:00 NULL 10:15:00
John 06:45:00 NULL NULL 06:45:00
Just using plus operator in select statement gives you an error:
Operand data type time is invalid for add operator.
You could determine the number of seconds for each time value, add them together and convert back to time:
select TimeOne, TimeTwo, TimeThree,
cast(dateadd(s, isnull(datediff(s, 0, TimeOne), 0) + isnull(datediff(s, 0, TimeTwo), 0) + isnull(datediff(s, 0, TimeThree), 0), 0) as time(0)) as CalculatedTotal
from MyTable
Try the below script.
select convert(time, CONVERT(datetime, '00:08:00.000') + CONVERT(datetime, '00:07:00.000'))
select convert(time,cast('00:08:00.000'as datetime)+cast('00:07:00.000' as datetime) ) as 'T'
I belive you can use the SUM() function
SELECT
Name,
TimeOne,
TimeTwo,
TimeThree,
SUM(TimeOne+TimeTwo+TimeThree) As CalculatedTotal
FROM
MyTable
But equally you might need to convert each to seconds first using the SECOND() function, then use SEC_TO_TIME() on the result.
Edit: I've just had a look at the manual:
The SUM() and AVG() aggregate functions do not work with temporal
values. (They convert the values to numbers, which loses the part
after the first nonnumeric character.) To work around this problem,
you can convert to numeric units, perform the aggregate operation, and
convert back to a temporal value. Examples:
SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(time_col))) FROM tbl_name;
SELECT FROM_DAYS(SUM(TO_DAYS(date_col))) FROM tbl_name;

Is it possible to set start of week for T-SQL DATEDIFF function?

I use DATEDIFF function to filter records added this week only:
DATEDIFF(week, DateCreated, GETDATE()) = 0
and I noticed what it's assumed what week starts on Sunday. But in my case I would prefer to set start of week on Monday. Is it possible somehow in T-SQL?
Thanks!
Update:
Below is an example showing what DATEDIFF doesn't check ##DATEFIRST variable so I need another solution.
SET DATEFIRST 1;
SELECT
DateCreated,
DATEDIFF(week, DateCreated, CAST('20090725' AS DATETIME)) AS D25,
DATEDIFF(week, DateCreated, CAST('20090726' AS DATETIME)) AS D26
FROM
(
SELECT CAST('20090724' AS DATETIME) AS DateCreated
UNION
SELECT CAST('20090725' AS DATETIME) AS DateCreated
) AS T
Output:
DateCreated D25 D26
----------------------- ----------- -----------
2009-07-24 00:00:00.000 0 1
2009-07-25 00:00:00.000 0 1
(2 row(s) affected)
26 Jul 2009 is Sunday, and I want DATEDIFF returns 0 in third column too.
Yes it possible
SET DATEFIRST 1; -- Monday
from http://msdn.microsoft.com/en-us/library/ms181598.aspx
It appears datediff doesn't respect the Datefirst, so make it do so run it like this
create table #testDates (id int identity(1,1), dateAdded datetime)
insert into #testDates values ('2009-07-09 15:41:39.510') -- thu
insert into #testDates values ('2009-07-06 15:41:39.510') -- mon
insert into #testDates values ('2009-07-05 15:41:39.510') -- sun
insert into #testDates values ('2009-07-04 15:41:39.510') -- sat
SET DATEFIRST 7 -- Sunday (Default
select * from #testdates where datediff(ww, DATEADD(dd,-##datefirst,dateadded), DATEADD(dd,-##datefirst,getdate())) = 0
SET DATEFIRST 1 -- Monday
select * from #testdates where datediff(ww, DATEADD(dd,-##datefirst,dateadded), DATEADD(dd,-##datefirst,getdate())) = 0
Stolen from
http://social.msdn.microsoft.com/Forums/en-US/transactsql/thread/8cc3493a-7ae5-4759-ab2a-e7683165320b
I have another solution.
This should be easier to understand, correct me if I am wrong
SET DATEFIRST 1
select DATEDIFF(week, 0, DATEADD(day, -##DATEFIRST, '2018-04-15 00:00:00.000'))
We subtract '-1' from date and Sunday will become Saturday (which is 7nth day of week)
and Mondфy(2) will first day of week
So if i'm getting this correctly,
the only thing we need to do is remove 1 day from both dates on our datediff as following :
DATEDIFF(week,dateadd(day,-1,cast(GETDATE() as date)),
dateadd(day,-1,cast([Date] as date))) as RollingWeek