SQL Server - Looking for a way to shorten code - sql

I'm basically very new to SQL Server, so please bare with me. Here is my problem:
I have a table with (let's say) 10 columns and 80k rows. I have 1 column called Date in the format of YYYY-MM-DD type varchar(50) (can't convert it to date or datetime type I tried, the initial source of data is not good).
**Example :
Table [dbo].[TestDates]
Code
SellDate
XS4158
2019-11-26
DE7845
2020-02-06
What I need to do is to turn the YYYY-MM-DD format to DD/MM/YYYY format. After a lot of tries (I tried the functions (DATE_FORMAT, CONVERT, TO_DATE etc) and this is solution :
1- I added a primary key for join purpose later (ID)
2- I split my date column in 3 columns in a whole new table
3- I merged the 3 columns in the order I need with the delimiter of my choice (/) in the same new table
4- I copied the good column to my initial table using the primary key ID I created before
alter table [dbo].[TestDates]
add ID int not null IDENTITY primary key;
SELECT ID,
FORMAT(DATEPART(month, [SellDate]),'00') AS Month,
FORMAT(DATEPART(day, [SellDate]),'00') AS Day,
FORMAT(DATEPART(year, [SellDate]),'0000') AS Year
INTO [dbo].[TestDates_SPLIT]
FROM [dbo].[TestDates]
GO
ALTER TABLE [dbo].[TestDates_SPLIT]
ADD SellDate_OK varchar(50)
UPDATE [dbo].[TestDates_SPLIT]
SET SellDate_OK = [Day] + '/' + [Month] + '/' + [Year]
ALTER TABLE [dbo].[TestDates_SPLIT]
DROP COLUMN Month, Day, Year
ALTER TABLE [dbo].[TestDates]
ADD SellDate_GOOD varchar(50)
UPDATE [dbo].[TestDates]
SET [TestDates].SellDate_GOOD = [TestDates_SPLIT].SellDate_OK
FROM [dbo].[TestDates]
INNER JOIN [dbo].[TestDates_SPLIT]
ON [TestDates].ID = [TestDates_SPLIT].ID
This code works but i find too heavy and long, considering I have 6 more dates columns to work on. Is there a way to make it shorter or more efficient? Maybe with SET SellDate = SELECT (some query of sorts that doesn't require to create and delete table)
Thank you for your help
I tried the usual SQL functions but since my column is a varchar type, the converting was impossible

You should not be storing dates as text. But, that being said, we can try doing a rountrip conversion from text YYYY-MM-DD to date to text DD/MM/YYYY:
WITH cte AS (
SELECT '2022-11-08' AS dt
)
SELECT dt, -- 2022-11-08
CONVERT(varchar(10), CONVERT(datetime, dt, 121), 103) -- 08/11/2022
FROM cte;
Demo

Related

How to convert two types of dates format into one date format in SQL?

I have three table in the database two of them contain dates however the dates are in two format, first 20-02-2011 and second is 25/09/2018. Let say each table have 10000 records and mixed with these two types of dates format. This why I why I create the column like --- (Transaction_Date, Varchar(10) Not Null)
I tried convert (Varchar(10),Transaction_Date,105)
and also tried replace(convert(varchar(10),Transaction_Date,105),'/','-')
However date and year functions are still not working.
Please suggest a possible way.
How about this?
select replace(date, replace(Transaction_Date, '/', '-'), 105)
That is: (1) convert to a date and (2) replace the slash before converting.
You need to remember about your culture. Saved format vs server culture. But this is very possible
select Cast('2-22-2011' as datetime) f1,
Cast('2/22/2011' as datetime) f2
I other words just use Cast
select cast(Transaction_Date as datetime) . . .
But you should as soon as possible get rid of columns that saves date as string and create new date/time column, and insert your date values there
alter table tbl add column temp datetime
update tbl set temp = Cast(Transaction_Date as datetime)
alter table tbl drop column Transaction_Date
alter table tbl add column Transaction_Date datetime
update tbl set Transaction_Date = temp
alter table tbl drop column temp

SQL Computed Column , Coallesce , Two DateTime Fields , Index

I have in my database following two Columns :
Delivery_Date (DateTime)
Delivery_Override (DateTime)
( I must preserve the Original Delivery Date for other things I do )
I created following Computed Column
Delivery_Packdate
formula : (coalesce(nullif([Delivery_Override],''),[Delivery_Date]))
and now I can do a select like this :
select * from Deliveries where Delivery_PackDate = '2019-09-24'
and this automatically selects the Overriden Date if filled or else the original date.
the Problem is I would like to index this , but SQL will not allow this because it is non-deterministic .
I suppose I am doing something wrong here , how can I solve this correctly ? Any tips?
Thank you.
The problem is that you are converting to a string. The following works fine:
create table t (
Delivery_Override datetime,
Delivery_Date datetime
);
alter table t add dt as (coalesce(Delivery_Override, Delivery_Date)) persisted;
create index idx_t_dt on t(dt);
Here is a db<>fiddle.
There is no such thing as a date/time with the value ''.

Insert current date time in a column of and keep it constant

i am using sql server in making my project with javafx. Their i have a table of purchase and sale. One of the column of both of them is date having current date and time to store them as a record that this transaction has been saved in this time.
Now i am using the that date column with varchar datatype and have using computed column specification with following function:
(CONVERT([varchar](25),getdate(),(120)))
but when i select records from that table using query
SELECT pr.Date, p.Name, pr.Quantity, s.Name, p.Pur_Price
FROM (([Product] AS p
INNER JOIN [Purchase] AS pr ON pr.Product_id=p.Product_id)
INNER JOIN [Supplier] AS s ON s.Supplier_Id=p.Supplier_Id)
WHERE pr.Date>= dateadd(dd, 0, datediff(dd, 0, getdate()-30))
but it selects all the records keeping all date records to current date and time. Thanks in advance.
Looking forward for your good replies.
The problem is that your Date column is computed on the fly and not actually stored in the table. So each time you SELECT from that table, the expression of the computed column is calculated (CONVERT([varchar](25),getdate(),(120))) thus resulting in the same value for all rows.
A fix would be using a PERSISTED computed column so that values are actually stored with the table when inserting or updating:
CREATE TABLE Product (
OtherColumns INT,
[Date] AS (CONVERT([varchar](25), getdate(), 120)) PERSISTED)
The problem with this is that non-deterministic expressions can't be persisted, as this error message pops up:
Msg 4936, Level 16, State 1, Line 1 Computed column 'Date' in table
'Product' cannot be persisted because the column is non-deterministic.
You have several other options for this. Please use DATE or DATETIME columns to store and handle dates and avoid using VARCHAR for this as it brings many problems. The following examples use DATETIME:
Use a DEFAULT constraint linked to the column with the expression you want:
CREATE TABLE Product (
OtherColumns INT,
[Date] DATETIME DEFAULT GETDATE())
INSERT INTO Product (
OtherColumns) -- Skip the Date column on the INSERT
VALUES
(1)
SELECT * FROM Product
OtherColumns Date
1 2018-12-14 08:49:08.347
INSERT INTO Product (
OtherColumns,
Date)
VALUES
(2,
DEFAULT) -- Or use the keyword DEFAULT to use the default value
SELECT * FROM Product
OtherColumns Date
1 2018-12-14 08:49:08.347
2 2018-12-14 08:50:10.070
Use a trigger to set the value. This will override any inserted or updated value that the original operation set (as it will execute after the operation, as stated in it's definition).
CREATE TRIGGER utrProductSetDate ON Product
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON
UPDATE P SET
Date = GETDATE()
FROM
inserted AS I
INNER JOIN Product AS P ON I.OtherColumns = P.OtherColumns -- Assuming PK or Unique columns join
END
Thanks all of you. But i solved my problem by putting my date Column of that table into datetime data type and i my query i have entered the date using getdate() method. It worked for me to save current date and time in my purchase and sale table.

Generate sequence number in SQL on every insert

I need to enter primary key in table as in below format
YYYY/MMM/NNNNNN
Where, YYYY is the current year, MMM is Month , and NNNNNN is a sequence no from 000001, 000002, 000003, .... 999999.
So my primary key will look like 2012/Oct/000001 or 2012/Oct/000010 ....
How can I generate this type of code..
I can get Year and Month from Getdate() function. But how can I manage sequence number on every insert. can you please give me logic for that?
By far the easiest way would be to let SQL Server handle the dishing out of consecutive numbers using an INT IDENTITY column - and then use a trigger to create that specific format that you need, in a separate column.
So given this table:
CREATE TABLE SampleTable
(ID INT IDENTITY, SaleDate DATE, ComputedPK VARCHAR(25) )
you could use a trigger like this to compute the ComputedPK from the ID (autonumber, handled by SQL Server) and the SaleDate as the date column:
CREATE TRIGGER trgSampleTableInsert
ON dbo.SampleTable FOR INSERT
AS
UPDATE dbo.SampleTable
SET ComputedPK = CAST(YEAR(i.SaleDate) AS CHAR(4)) + '/' + DATENAME(MONTH, i.SaleDate) + '/' + RIGHT('000000' + CAST(i.ID AS VARCHAR(6)), 6)
FROM dbo.SampleTable s
INNER JOIN INSERTED i ON s.ID = i.ID
This approach will not start at 1 for each month, however - but do you really need that? Isn't a sequential number (even across months) good enough?
Update: of course, if you're using SQL Server 2012 (you didn't specify which version of SQL Server you're using...) - you could use a SEQUENCE object to handle the consecutive numbering - and you could even reset that sequence to 1 again every start of a month ....
SELECT
CONCAT(
DATEPART(YEAR, GETDATE()),
'/',
DATENAME(MONTH,GETDATE()),
'/',
REPLACE(STR(((SELECT COUNT(*) FROM yourtable WHERE monthname = DATENAME(MONTH,GETDATE()) GROUP BY monthname) + 1),6,0),' ','0')
)
)
This is untested now tested. You would have to add a monthname column (there is a way of doing this without adding a column, but this is the most convenient)
You can also cast and use addition if you don't want to rely on concat. http://sqlfiddle.com/#!6/3e43d/6
Why don't you just consider a compound key with an identity/numeric column and a date column (or if the day is not needed, the year/month)?
This would give you the same behaviour and would probably be a bit simpler to implement/maintain
I still would like to know the reasoning behind this - just so we can make a better educated guess
Set your Primary Key on two columns. One of them will represent the Daten and the other one the Serial number. Set a Sequence on the Serial column which increases automatically. This will make it easier for you filter the Date part of the Key.

Recursive SQL query to speed up non-indexed query

This question is largely driven by curiosity, as I do have a working query (it just takes a little longer than I would like).
I have a table with 4 million rows. The only index on this table is an auto-increment BigInt ID. The query is looking for distinct values in one of the columns, but only going back 1 day. Unfortunately, the ReportDate column that is evaluated is not of the DateTime type, or even a BigInt, but is char(8) in the format of YYYYMMDD. So the query is a bit slow.
SELECT Category
FROM Reports
where ReportDate = CONVERT(VARCHAR(8), GETDATE(), 112)
GROUP BY Category
Note that the date converstion in the above statement is simply converting it to a YYYYMMDD format for comparison.
I was wondering if there was a way to optimize this query based on the fact that I know that the only data I am interested in is at the "bottom" of the table. I was thinking of some sort of recursive SELECT function which gradually grew a temporary table that could be used for the final query.
For example, in psuedo-sql:
N = 128
TemporaryTable = SELECT TOP {N} *
FROM Reports
ORDER BY ID DESC
/* Once we hit a date < Today, we can stop */
if(TemporaryTable does not contain ReportDate < Today)
N = N**2
Repeat Select
/* We now have a smallish table to do our query */
SELECT Category
FROM TemproaryTable
where ReportDate = CONVERT(VARCHAR(8), GETDATE(), 112)
GROUP BY Category
Does that make sense? Is something like that possible?
This is on MS SQL Server 2008.
I might suggest you do not need to convert the Date that is stored as char data in YYYYMMDD format; That format is inherently sortable all by itself. I would instead convert your date to output in that format.
Also, the way you have the conversion written, it is converting the current DateTime for every individual row, so even storing that value for the whole query could speed things up... but I think just converting the date you are searching for to that format of char would help.
I would also suggest getting the index(es) you need created, of course... but that's not the question you asked :P
Why not just create the index you need?
create index idx_Reports_ReportDate
on Reports(ReportDate, Category)
No, that doesn't make sense. The only way to optimize this query is to have a covering index for it:
CREATE INDEX ndxReportDateCategory ON Reports (ReportDate, Category);
Update
Considering your comment that you cannot modify the schema, then you should modify the schema. If you still can't, then the answer still applies: the solution is to have an index.
And finally, to answer more directly your question, if you have a strong correlation between ID and ReportData: the ID you seek is the biggest one that has a ReportDate smaller than the date you're after:
SELECT MAX(Id)
FROM Reports
WHERE ReportDate < 'YYYYMMDD';
This will do a reverse scan on the ID index and stop at the first ID that is previous to your desired date (ie. will not scan the entire table). You can then filter your reports base don this found max Id.
I think you will find the discussion on SARGability, on Rob Farley's Blog to be very interesting reading in relation to your post topic.
http://blogs.lobsterpot.com.au/2010/01/22/sargable-functions-in-sql-server/
An interesting alternative approach that does not require you to modify the existing column data type would be to leverage computed columns.
alter table REPORTS
add castAsDate as CAST(ReportDate as date)
create index rf_so2 on REPORTS(castAsDate) include (ReportDate)
One of the query patterns I occasionally use to get into a log table with similiar indexing to yours is to limit by subquery:
DECLARE #ReportDate varchar(8)
SET #ReportDate = Convert(varchar(8), GetDate(), 112)
SELECT *
FROM
(
SELECT top 20000 *
FROM Reports
ORDER BY ID desc
) sub
WHERE sub.ReportDate = #ReportDate
20k/4M = 0.5% of the table is read.
Here's a loop solution. Note: might want to make ID primary key and Reportdate indexed in the temp table.
DECLARE #ReportDate varchar(8)
SET #ReportDate = Convert(varchar(8), GetDate(), 112)
DECLARE #CurrentDate varchar(8), MinKey bigint
SELECT top 2000 * INTO #MyTable
FROM Reports ORDER BY ID desc
SELECT #CurrentDate = MIN(ReportDate), #MinKey = MIN(ID)
FROM #MyTable
WHILE #ReportDate <= #CurrentDate
BEGIN
SELECT top 2000 * INTO #MyTable
FROM Reports WHERE ID < #MinKey ORDER BY ID desc
SELECT #CurrentDate = MIN(ReportDate), #MinKey = MIN(ID)
FROM #MyTable
END
SELECT * FROM #MyTable
WHERE ReportDate = #ReportDate
DROP TABLE #MyTable