Using SQL query to create new column - sql

In MS SQL Server, how would I create a new column from a data query, e.g. say I had a table with a UNIX timestamp -
timestamp | value
------------------
1468073257 | 15
1468073589 | 22
the following query outputs the SQL date object for each timestamp
Select
dateadd(S, [timestamp], '1970-01-01')
From myTable
how would I use the above query to amend the table such that it looked like:
timestamp | datetime | value
----------------------------------------------
1468073257 | 2016-07-09 14:07:37.000 | 15
1468073589 | 2016-07-09 18:12:56.000 | 22
I suppose it is sort of like a SELECT... INTO but within a table

If your table exists you can use INSERT INTO and SELECT statements.
For example:
INSERT INTO <your_table_name>(timestamp, datetime, value)
SELECT
timestamp AS [timestamp],
dateadd(S, [timestamp], '1970-01-01') AS datetime,
value AS [value]
From myTabl
If you don't have a table but want to create a new one then you can use SELECT ... INTO
For example:
SELECT
timestamp AS [timestamp],
dateadd(S, [timestamp], '1970-01-01') AS datetime,
value AS [value]
INTO <your_new_table>
FROM myTabl

Since you're not adding any new data (only derived/formatted data), I think it's better to add a computed column.
ALTER TABLE [YourTableName]
ADD [FormattedDateColumnName] AS DATEADD(SECOND,[timestamp],'1/1/1970')

Your table is likely being populated by existing applications, a schema change could potentially break these applications.
One solution would be to make a VIEW.
CREATE VIEW vw_myTable
AS
SELECT timestamp, DATEADD(S, timestamp, '1970-01-01') AS datetime, value
FROM myTable
Querying the view.
SELECT *
FROM vw_myTable

Related

Search for closes index on SQL table

I have a hypothetical SQL table "EVENTS", with two columns, a UUID index column, and a DateTime column,
The table is populated with values ranging from 1900-01-01 to today, it is not ordered, there are numerous dates missing.
The query that I have to run is basically 'retrieve all events that happened at the requested date (start to the end of the day) or the closest previous date'
If I were looking for all events in a day that I know that exists in the database it would be something as simple as:
SELECT * FROM Events e
WHERE
e.date BETWEEN $START_OF_DAY AND $END_OF_DAY;
But if that date doesn't exist I must retrieve the latest date up to the requested date.
Grab current day, but if no records found, will return all records from the nearest previous day with records.
So in my sample data, Jan 2 returns 3 events dated Jan 1
SQL Server Solution
DECLARE #Input DATE = '2022-01-02' /*Try Jan 1,2,3, or 4*/
DROP TABLE IF EXISTS #Event
CREATE TABLE #Event (ID INT IDENTITY(1,1),EventDateTime DATETIME)
INSERT INTO #Event
VALUES
('2022-01-01 08:00')
,('2022-01-01 09:00')
,('2022-01-01 10:00')
,('2022-01-03 12:00')
SELECT TOP (1) WITH TIES *
FROM #Event AS A
CROSS APPLY (SELECT EventDate = CAST(EventDateTime AS DATE)) AS B
WHERE B.EventDate <= #Input
ORDER BY B.EventDate DESC
SQL Fiddle wasn't letting me create a variable, but here's a the code conceptually for a more efficient version for MySQL. It grabs the desired date range in the first query, then uses it to filter in the second query. I think it should perform far better than the accepted answer assuming you have an index on EventDateTime
CREATE TABLE Event (
ID MEDIUMINT NOT NULL AUTO_INCREMENT
,EventDateTime DATETIME
,PRIMARY KEY (ID));
INSERT INTO Event (EventDateTime)
VALUES
('2022-01-01 08:00')
,('2022-01-01 09:00')
,('2022-01-01 10:00')
,('2022-01-03 12:00');
/*Need to save these off to variables to use in later query*/
SELECT TIMESTAMP(CAST(EventDateTime AS DATE)) AS StartRange
,TIMESTAMP(CAST(EventDateTime AS DATE)) + INTERVAL 1 DAY AS EndRange
FROM Event
WHERE EventDateTime < DATE_ADD('2022-01-04' /*Input*/,INTERVAL 1 DAY)
ORDER BY EventDateTime DESC
LIMIT 1;
SELECT *
FROM Event
WHERE EventDateTime >= StartRange
AND EventDateTime < EndRange
Calculate the most recent date, and do a self join. Although I'm using MYSQL, I believe this is the most generic workaround
CREATE TABLE d0207Event (ID INT ,EventDateTime DATETIME)
INSERT INTO d0207Event
VALUES
(1,'2022-01-01 08:00')
,(2,'2022-01-01 09:00')
,(3,'2022-01-01 10:00')
,(4,'2022-01-03 12:00')
INSERT INTO d0207Event
VALUES
(5, '2021-12-12 08:00');
select t1.*
from d0207Event t1,
(
select min(t1.dat) mindat
from (
select t1.*,
DATEDIFF('2022-01-02', cast(t1.EventDateTime as date)) dat
from d0207Event t1
) t1
where t1.dat >= 0
) t2
where DATEDIFF('2022-01-02', cast(t1.EventDateTime as date)) = t2.mindat
;
There are also many advanced syntaxes that can solve this problem better, depending on which DB you use and your specific application scenario
It seems that you can also choose a database with more syntax, then using an analytic function usually solves the efficiency problem well, since the EVENT table only needs to be queried once.
CREATE TABLE Event (
ID MEDIUMINT NOT NULL AUTO_INCREMENT
,EventDateTime DATETIME
,PRIMARY KEY (ID));
INSERT INTO Event (EventDateTime)
VALUES
('2022-01-01 08:00')
,('2022-01-01 09:00')
,('2022-01-01 10:00')
,('2022-01-03 12:00');
select *
from (
select t1.*,
first_value(cast(t1.EventDateTime as date))
over(order by cast(t1.EventDateTime as date) desc) fv
from event t1
where cast(t1.EventDateTime as date) <= '2022-01-03'
) t1
where cast(t1.EventDateTime as date) = fv
Creating a functional index cast(t1.EventDateTime as date), or creating a virtual column directly can make the query easier, otherwise using date_add() is a good way

Optimize this query without using not exist repeatably, is there a better way to write this query?

For example I have three table where say DataTable1, DataTable2 and DataTable3
and need to filter it from DataRange table, every time I have used NOT exist as shown below,
Is there a better way to write this.
Temp table to hold some daterange which is used for fiter:
Declare #DateRangeTable as Table(
StartDate datetime,
EndDate datetime
)
Some temp table which will hold data on which we need to apply date range filter
INSERT INTO #DateRangeTable values
('07/01/2020','07/04/2020'),
('07/06/2020','07/08/2020');
/*Table 1 which will hold some data*/
Declare #DataTable1 as Table(
Id numeric,
Date datetime
)
INSERT INTO #DataTable1 values
(1,'07/09/2020'),
(2,'07/06/2020');
Declare #DataTable2 as Table(
Id numeric,
Date datetime
)
INSERT INTO #DataTable2 values
(1,'07/10/2020'),
(2,'07/06/2020');
Declare #DataTable3 as Table(
Id numeric,
Date datetime
)
INSERT INTO #DataTable3 values
(1,'07/11/2020'),
(2,'07/06/2020');
Now I want to filter data based on DateRange table, here I need some optimized way so that i don't have to use not exists mutiple times, In real senario, I have mutiple tables where I have to filter based on the daterange table.
Select * from #DataTable1
where NOT EXISTS(
Select 1 from #DateRangeTable
where [Date] between StartDate and EndDate
)
Select * from #DataTable2
where NOT EXISTS(
Select 1 from #DateRangeTable
where [Date] between StartDate and EndDate
)
Select * from #DataTable3
where NOT EXISTS(
Select 1 from #DateRangeTable
where [Date] between StartDate and EndDate
)
Instead of using NOT EXISTS you could join the date range table:
SELECT dt.*
FROM #DataTable1 dt
LEFT JOIN #DateRangeTable dr ON dt.[Date] BETWEEN dr.StartDate and dr.EndDate
WHERE dr.StartDate IS NULL
It may perform better on large tables but you would have to compare the execution plans and make sure you have indexes on the date columns.
I would write the same query... but if you can change table structure I would try to improve performance adding two columns to specify the month as an integer (I suppose is the first couple of figures).
Obviously you have to test with your data and compare the timings.
Declare #DateRangeTable as Table(
StartDate datetime,
EndDate datetime,
StartMonth tinyint,
EndMonth tinyint
)
INSERT INTO #DateRangeTable values
('07/01/2020','07/04/2020', 7, 7),
('07/06/2020','07/08/2020', 7, 7),
('07/25/2020','08/02/2020', 7, 8); // (another record with different months)
Now your queries can use the new column to try to reduce comparisons (is a tinyint, sql server can partition records if you define a secondary index for StartMonth and EndMonth):
Select * from #DataTable1
where NOT EXISTS(
Select 1 from #DateRangeTable
where (DATEPART('month', [Date]) between StartMonth and EndMonth)
and ([Date] between StartDate and EndDate)
)

Convert varchar to date - SQL

I have a column of date values that are currently of type varchar. Could someone please explain the easiest way to convert these to actual dates. Below is a list of example values in the aforementioned column
- Expiration Date
- 00/00/00
- 11/06/10
- 00/00/00
- 29/02/08
- 01/04/11
NOTE: 00/00/00 means there is no expiration date, but they still need to be included somehow as I store other data on them.
Your data appears to be in dd/mm/yy format, so use convert() with format code 3:
select try_convert(date, str, 3)
Try this:
select case yourVarcharValue when '00/00/00' then NULL else Try_Convert(Date, yourVarcharValue)
In that case, need to select from converted results:
declare #myTable table(ExpirationDate varchar(max))
insert into #myTable (ExpirationDate ) values ('00/00/00')
insert into #myTable (ExpirationDate) values ('11/06/10')
insert into #myTable (ExpirationDate) values ('29/02/08')
insert into #myTable (ExpirationDate) values ('01/04/11')
select ExpirationDate from
(
select ISNULL(try_convert(date, ExpirationDate, 3), '9999-12-31') as ExpirationDate
from #myTable) d
order by d.ExpirationDate asc
Results:
ExpirationDate
2008-02-29
2010-06-11
2011-04-01
9999-12-31
I highly recommend you to not store Date fields as NVARCHAR/VARCHAR:
select ISNULL(try_convert(date, ExpirationDate, 3), '9999-12-31') from myTable
SQL Fiddle
MS SQL Server 2017 Schema Setup:
create table myTable (ExpirationDate varchar(max))
insert into myTable (ExpirationDate ) values ('00/00/00')
insert into myTable (ExpirationDate) values ('11/06/10')
insert into myTable (ExpirationDate) values ('29/02/08')
insert into myTable (ExpirationDate) values ('01/04/11')
Query 1:
select ISNULL(try_convert(date, ExpirationDate, 3), '9999-12-31') from myTable
Results:
| |
|------------|
| 9999-12-31 |
| 2010-06-11 |
| 2008-02-29 |
| 2011-04-01 |

Search By Time (only) in DateTime SQL column

I am working with SQL Server & PHP and using stored procedures.
I have a table called myTable. I has a column start_time (DateTime Format).
start_time
-----------------------
2019-05-23 12:20:22.000
2019-08-02 01:21:02.000
2019-02-10 22:32:17.000
2019-08-14 04:56:24.000
I want to filter results by time only.
For-example: BETWEEN 22:20:10.000 AND 04:56:24.000
But, It's not Working.
Simple casting to time datatype will work:
select * from myTable
where cast(start_time as time) >= '22:00:00.000'
or cast(start_time as time) <= '04:00:00.000'
Note that applying a CAST function to the start_time column in the WHERE clause predicate will prevent an index that column from being used efficiently. A full table scan will be required unless other criteria are specified.
this code will work please check
create table #temp
(
[Date] datetime
)
insert into #temp values ('2019-05-23 12:20:22.000')
insert into #temp values ('2019-08-02 01:21:02.000')
insert into #temp values ('2019-02-10 22:32:17.000')
insert into #temp values ('2019-08-14 04:56:24.000')
select cast([Date] as date) as [Date],convert(char(15), [Date], 108) [Time]
from #temp
where convert(char(15), [Date], 108) between '04:56:24' and '22:32:17'
Drop table #temp

SQL Assign datediff to table column in insert trigger

Table
CREATE TABLE CurrentApplication
(
StartDate datetime NOT NULL,
EndDate datetime NOT NULL,
NoOfDays integer,
StaffID integer NOT NULL,
AppStatus varchar(30) NOT NULL DEFAULT 'PENDING'
)
Insert Trigger
CREATE TRIGGER InsertNoOfDays ON CurrentApplication
AFTER INSERT
BEGIN
INSERT INTO CurrentApplication
NoOfDays AS Datediff(day, StartDate, EndDate)
END
I have NoOfDays column which should hold the DateDiff between StartDate and EndDate and the value should be inserted whenever a new record is inserted into the table. How do I write the trigger and do it? I've tried but my trigger doesn't work. Thanks!
One method is to use an INSTEAD OF trigger:
CREATE TRIGGER InsertNoOfDays ON CurrentApplication
INSTEAD OF INSERT AS
BEGIN
INSERT INTO CurrentApplication( . . ., NoOfDays)
SELECT . . .,
Datediff(day, StartDate, EndDate)
FROM inserted;
END;
One thing you can do is add a computed column instead of actual column as NoOfdays.
There are benefits and drawbacks in adding a computed column.
You don't have have a trigger and calculate it. Saves on writes.
This also means sometimes you cannot index on that column and reads may have an impact.
Here is the documentation from Microsoft on computed columns (documentation says it is only on SQL 2016 onwards):
Specify Computed Columns in Tables
But, here is sample in SQL 2014 that worked for me (I think it works in SQL 2012 also)
use tempdb
GO
/*
Wors in version:
Microsoft SQL Server 2014 - 12.0.2000.8 (X64)
Standard Edition (64-bit
*/
IF OBJECT_ID('tempdb.dbo.CurrentApplication') IS NULL
CREATE TABLE dbo.CurrentApplication
(
StartDate datetime NOT NULL,
EndDate datetime NOT NULL,
NoOfDays AS (datediff(dd, StartDate, EndDate)) PERSISTED,
StaffID integer NOT NULL,
)
INSERT INTO dbo.CurrentApplication(StartDate, EndDate, StaffId)
SELECT TOP 10
StartDate = DATEADD(dd, object_id, '1/1/2017')
,EndDate = DATEADD(dd, object_id*object_id, '1/1/2017')
,StaffId = object_id
FROM sys.objects
WHERE object_id < 300
order by Object_id
SELECT * FROM CurrentApplication
IF OBJECT_ID('tempdb.dbo.CurrentApplication') IS NOT NULL
DROP TABLE dbo.CurrentApplication
Since not eligible to add comments, adding as a new answer.
Adding to #Gordon Linoff's answer, just substituted column names instead of periods to see if that caused the error you getting. I didnt get that error. Not sure if there is anything with SQL 2012.
But, this works (in SQL 2014):
CREATE TRIGGER InsertNoOfDays ON dbo.CurrentApplication
INSTEAD OF INSERT AS
BEGIN
INSERT INTO CurrentApplication(StartDate, EndDate, NoOfDays, StaffID)
SELECT StartDate
,EndDate
,Datediff(dd, StartDate, EndDate)
,StaffId
FROM inserted as i;
END;