Restrict specific date format Data into Table - sql

I Have a table called Sur_Data and the data looks like:
ID SV_Date
258 13/01/2010
569 15/02/2011
695 26/05/2010
745 12/06/2010
Now I want to select the ID's from that table and insert into another table so we are using something like:
Insert into Surdate(ID)
Select ID from Sur_Data
where ISDATE(SV_Date) = 1
Since the format in SV_Date is different it is not inserting any records into Surdate table.
So I am trying to see is there a way that we could restrict the data in Sur_Data table to have only date's that are in MM/DD/YYYY format.So whenever they try to insert records of different format it should throw an error.
Can anyone help on this?

Edit: for example 2 & 3, ANSI WARNINGS must be off.
IS_DATE function is influenced by DATEFORMAT setting for current SQL Server session/connection.
Example 1:
DECLARE #d1 VARCHAR(25) = '26/05/2010'
,#d2 VARCHAR(25) = '15/02/2011';
PRINT '*****Test 1*****'
SET DATEFORMAT DMY;
SELECT ISDATE(#d1), ISDATE(#d2);
PRINT '*****Test 2*****'
SET DATEFORMAT MDY;
SELECT ISDATE(#d1), ISDATE(#d2);
Results:
*****Test 1*****
----------- -----------
1 1
(1 row(s) affected)
*****Test 2*****
----------- -----------
0 0
(1 row(s) affected)
Now, you can see how DATEFORMAT influences ISDATE function.
Instead of ISDATE function you can use CONVERT function with different date/time styles. If a [n][var]char value doesn't have the selected style then CONVERT function will return NULL. For dd/mm/yyyy values (british) can be used style 103 and for mm/dd/yyyy values (U.S.) can be used style 101.
Example 2:
SET ANSI_WARNINGS OFF;
SET ARITHABORT OFF;
DECLARE #Results TABLE
(
ID INT PRIMARY KEY
,SV_Date VARCHAR(20) NOT NULL
);
INSERT #Results
VALUES
(258, '13/01/2010')
,(569, '15/02/2011')
,(695, '26/05/2010')
,(745, '12/06/2010');
SELECT *
,ISDATE(r.SV_Date) [IS_DATETIME]
,CONVERT(DATETIME,r.SV_Date,103) [IS_DATETIME British/French style=dd/mm/yyyy]
,CONVERT(DATETIME,r.SV_Date,101) [IS_DATETIME U.S. style=mm/dd/yyyy]
,CASE
WHEN CONVERT(DATETIME,r.SV_Date,103) IS NOT NULL AND CONVERT(DATETIME,r.SV_Date,101) IS NULL THEN 'IS_DMY'
WHEN CONVERT(DATETIME,r.SV_Date,103) IS NULL AND CONVERT(DATETIME,r.SV_Date,101) IS NOT NULL THEN 'IS_MDY'
WHEN CONVERT(DATETIME,r.SV_Date,103) IS NOT NULL AND CONVERT(DATETIME,r.SV_Date,101) IS NOT NULL THEN 'IS_DMY_OR_MDY'
WHEN CONVERT(DATETIME,r.SV_Date,103) IS NULL AND CONVERT(DATETIME,r.SV_Date,101) IS NULL THEN 'IS_NOT_DMY_OR_MDY'
END
FROM #Results r;
Results:
ID SV_Date IS_DATETIME IS_DATETIME British/French style=dd/mm/yyyy IS_DATETIME U.S. style=mm/dd/yyyy
----------- -------------------- ----------- ------------------------------------------- --------------------------------- -----------------
258 13/01/2010 0 2010-01-13 00:00:00.000 NULL IS_DMY
569 15/02/2011 0 2011-02-15 00:00:00.000 NULL IS_DMY
695 26/05/2010 0 2010-05-26 00:00:00.000 NULL IS_DMY
745 12/06/2010 1 2010-06-12 00:00:00.000 2010-12-06 00:00:00.000 IS_DMY_OR_MDY
Now, if you want to check SV_Date values for mm/dd/yyyy format (style 101 - U.S.) then you can use a CHECK constraint like this:
Example 3:
DECLARE #Results2 TABLE
(
ID INT PRIMARY KEY
,SV_Date VARCHAR(20) NOT NULL
,CHECK( CONVERT(DATETIME,SV_Date,101) IS NOT NULL )
);
SET ANSI_WARNINGS OFF;
INSERT #Results2
VALUES (258, '13/01/2010');
INSERT #Results2
VALUES (569, '15/02/2011');
INSERT #Results2
VALUES (695, '26/05/2010');
INSERT #Results2
VALUES (745, '12/06/2010');
SELECT *
FROM #Results2;
Results:
ID SV_Date
----------- --------------------
745 12/06/2010
(1 row(s) affected)
Observations:
If you want to find current DATEFORMAT setting (current session) then you can use sys.dm_exec_sessions view:
SELECT s.date_format, s.date_first
FROM sys.dm_exec_sessions s
WHERE s.session_id = ##SPID

To strictly answer the question, you could create a function (CLR or TSQL) and apply that as a column constraint/check.
But as #joe Stefanelli correctly points out, store it as a datetime data type and let the client handle the presentation format.
Edit
http://msdn.microsoft.com/en-us/library/ms190273.aspx
ALTER TABLE
dbo.Sur_Data
WITH CHECK
ADD CONSTRAINT ck_dateFormatted CHECK (dbo.VerifyDateFormat(SV_Date) = 1) ;
Which assumes you've defined a function that returns 1 if the format matches the expectation.

Related

Trying to Multiply a Case Statement to a Count

DECLARE #StartDate datetimeoffset,
#EndDate datetimeoffset
SET #StartDate = '2022-03-01 00:00:00.000 +07:00'
SET #EndDate = '2022-03-06 23:59:59.000 +07:00'
SELECT
Records.RecordID,
CONVERT(VARCHAR(10),Records.RecDate AT TIME ZONE 'SE Asia Standard Time',104) AS RecTime,
LocationStr, LocationName,
CONCAT(FirstName,' ',LastName) AS Username ,ProductCodeName,
CASE
WHEN AssetTypeName LIKE '%LTR'
THEN LEFT(AssetTypeName, LEN(AssetTypeName) - 3)
ELSE LEFT(AssetTypeName, LEN(AssetTypeName) - 2)
END * COUNT (ProductCodeName) AS 'SumProduct'
FROM
opendata.records
INNER JOIN
opendata.RecAssets ON Records.RecordId = RecAssets.RecordId
WHERE
(Records.ActionName = 'Fill')
AND (IsDeleted = '0')
AND (Records.RecDate BETWEEN #StartDate and #EndDate)
AND (LocationStr = '3031')
GROUP BY
Records.RecordId, Records.RecDate, LocationStr, LocationName,
CONCAT(FirstName,' ', LastName), ProductCodeName, AssetTypeName
ORDER BY
LocationStr ASC, RecTime ASC, ProductCodeName ASC
Tried to multiply the last to column on my Select statement but failed to do so. I get an error
Conversion failed when converting the ****** value '******' to data type ******.
Furthermore, I tried to convert the case statement to an int but also failed. How can I directly multiply it?
I'm guessing that you're using sql server seeing as it's not oracle and len() is not supported in mySQL or Postgres. You're code calls for an implicit conversion from a numerical value in a char type to a number, for example '123' to 123.
In the following example this doesn't present a problem the first time, but the second time there is a letter in the char value which cannot be converted and throws an error.
I am therefore thinking that you have values of AssetTypeName where there are letters other than in the last 2 characters for non-LTR values of elsewhere in the string for those ending in LTR.
create table test(
AssetTypeName varchar(10),
ProductCodeName int);
insert into test values
('123LTR',1),
('123LTR',1),
('123AB',1);
GO
3 rows affected
select
AssetTypeName,
CASE
WHEN AssetTypeName LIKE '%LTR'
THEN LEFT(AssetTypeName, LEN(AssetTypeName) - 3)
ELSE LEFT(AssetTypeName, LEN(AssetTypeName) - 2)
END * COUNT (ProductCodeName) AS 'SumProduct'
from test
group by AssetTypeName;
GO
AssetTypeName | SumProduct
:------------ | ---------:
123AB | 123
123LTR | 246
insert into test values ('123ABC',1);
GO
1 rows affected
select
AssetTypeName,
CASE
WHEN AssetTypeName LIKE '%LTR'
THEN LEFT(AssetTypeName, LEN(AssetTypeName) - 3)
ELSE LEFT(AssetTypeName, LEN(AssetTypeName) - 2)
END * COUNT (ProductCodeName) AS 'SumProduct'
from test
group by AssetTypeName;
GO
Msg 245 Level 16 State 1 Line 1
Conversion failed when converting the varchar value '123A' to data type int.
db<>fiddle here

MERGE statement for SCD type 2 is creating duplicate rows?

I have the below MERGE statement that I built to implement SCD type 2 on my SQL Server DB however if you look at the attached image, this sometimes inserts multiple duplicate rows which is incorrect - this should be one latest row per meterkey/meterserialnumber.
Is there anything obvious in the code I need to amend? It's weird because if I add a test row of data and run the merge, this works fine.. its just the occasional rows where it seems to be doing this.
DECLARE #DateNow int = CONVERT(INT, CONVERT(VARCHAR(8), GETDATE(), 112))
IF Object_id('tempdb..#meterkeysinsert') IS NOT NULL
DROP TABLE #meterkeysinsert;
CREATE TABLE #meterkeysinsert
(
MeterKey int,
change VARCHAR(10)
);
MERGE INTO [DIM].[MeterDetails] AS Target
using dbo.test AS Source
ON Target.meterkey = source.meterkey
and target.MeterSerialNumber = Source.MeterSerialNumber
and target.islatest = 1
WHEN matched THEN
UPDATE SET Target.islatest = 0,
Target.todatekey = #Datenow
WHEN NOT matched BY target THEN
INSERT ( meterkey
,[MeterSerialNumber]
,[lguf]
,[electricityMetertype]
,[profileType]
,[timeSwitchCode]
,[lineLossFactorId]
,[standardSettlementConfiguration]
,[energisationStatus]
,[DateSpecifiedKey]
,[distributorId]
,[gspid]
,[FromDatekey]
,[ToDatekey]
,[IsLatest])
VALUES (Source.meterkey
,Source.[MeterSerialNumber]
,Source.[lguf]
,Source.[electricityMetertype]
,Source.[profileType]
,Source.[timeSwitchCode]
,Source.[lineLossFactorId]
,Source.[standardSettlementConfiguration]
,Source.[energisationStatus]
,Source.[DateSpecifiedKey]
,Source.[distributorId]
,Source.[gspid]
,#Datenow
,NULL
,1 --IsRowCurrent
)
output Source.meterkey,
$action
INTO #meterkeysinsert;
INSERT INTO [DIM].[MeterDetails]
(MeterKey
,[MeterSerialNumber]
,[lguf]
,[electricityMetertype]
,[profileType]
,[timeSwitchCode]
,[lineLossFactorId]
,[standardSettlementConfiguration]
,[energisationStatus]
,[DateSpecifiedKey]
,[distributorId]
,[gspid]
,[FromDateKey]
,[ToDateKey]
,[IsLatest])
SELECT A.MeterKey
,[MeterSerialNumber]
,[lguf]
,[electricityMetertype]
,[profileType]
,[timeSwitchCode]
,[lineLossFactorId]
,[standardSettlementConfiguration]
,[energisationStatus]
,[DateSpecifiedKey]
,[distributorId]
,[gspid]
,#Datenow
,null
,1
FROM dbo.test a
INNER JOIN #meterkeysinsert CID
ON a.MeterKey = CID.Meterkey
AND CID.change = 'UPDATE'
`

Split a column into multiple columns in a SQL query using pipe delimiter

I have below data in a column of a table, I want to split it into further columns.
| is used as the separator in this scenario . Column header should be before : & after column is its value.
Column
-----------------------------------------------------------------------------
ID: 30000300 | Name: India | Use: New Use
ID: 30000400 | Name: Aus | New ID: 15625616 | Address 1: NEW Rd
ID: 30000400 | Name: USA | City: VIA ARAMAC | New ID: 123
ID: 30000500 | Name: Russia | New ID: 15624951 | Address 2: 2131 BEAUDESERT
Output should be:
ID Name Use New ID City Address 1 Address 2 New City
----------------------------------------------------------------------
30000300 India New Use
30000400 Aus 15625616 NEW Rd
30000400 USA 15625616 VIA ARAMAC GALILEE
30000500 Russia 15624951 2131 BEAUDESERT
You have several rows that contain key value pairs inside an nvarchar column, but you want a table that has a header based on the keys and then rows containing just the values, sans keys. There is first the issue of an input like Key1: Value1 | Key2: Value2. Should this be returned as
Key1 Key2
Value1 NULL
NULL Value2
or is this not a possible scenario? Either way, there is the issue of generating a table with dynamic column names.
The problem with your question is that this is not a scenario that would normally be solved via SQL. You should get the data in your programming language of choice, then use regular expressions or split methods to get what you need.
If you insist doing it via SQL, then the solution is to turn the original lines input into another string, that you then sp_executesql (https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-executesql-transact-sql), but I do NOT recommend it.
Here is a partial answer that you can use to return the n-th entry in a delimited string:
DECLARE #DelimitedString VARCHAR(8000);
DECLARE #Delimiter VARCHAR(100);
DECLARE #indexToReturn INT;
DECLARE #tblArray TABLE
(
ElementID INT IDENTITY(1, 1), -- Array index
Element VARCHAR(1000) -- Array element contents
);
-- Local Variable Declarations
-- ---------------------------
DECLARE #Index SMALLINT,
#Start SMALLINT,
#DelSize SMALLINT;
SET #DelSize = LEN(#Delimiter + 'x') - 1;
-- Loop through source string and add elements to destination table array
-- ----------------------------------------------------------------------
WHILE LEN(#DelimitedString) > 0
BEGIN
SET #Index = CHARINDEX(#Delimiter, #DelimitedString);
IF #Index = 0
BEGIN
INSERT INTO #tblArray
(
Element
)
VALUES
(LTRIM(RTRIM(#DelimitedString)));
BREAK;
END;
ELSE
BEGIN
INSERT INTO #tblArray
(
Element
)
VALUES
(LTRIM(RTRIM(SUBSTRING(#DelimitedString, 1, #Index - 1))));
SET #Start = #Index + #DelSize;
SET #DelimitedString = SUBSTRING(#DelimitedString, #Start, LEN(#DelimitedString) - #Start + 1);
END;
END;
DECLARE #val VARCHAR(1000);
SELECT #val = Element
FROM #tblArray AS ta
WHERE ta.ElementID = #indexToReturn;
SELECT #val;

Convert Alphanumeric value to a unique numeric value in SQL

I have a table column with values like below
V5H 3K3
V6L 4L4
V4E 5L2
V5H 3K3
I need to get a unique number against each of them so it would look something like
V5H 3K3 1111
V6L 4L4 2222
V4E 5L2 3333
V5H 3K3 1111
Is there a simple function in SQL Server that can be used to do this?
Select cast(HashBytes('MD5', 'V5H 3K3') as int)
Returns
-381163718
For Example
Declare #Table table (SomeField varchar(25))
Insert into #Table values
('V5H 3K3'),
('V6L 4L4'),
('V4E 5L2'),
('V5H 3K3')
Select *,AsAnInt = abs(cast(HashBytes('MD5', SomeField) as int))
From #Table
Returns
SomeField AsAnInt
V5H 3K3 381163718
V6L 4L4 245350301
V4E 5L2 1706996605
V5H 3K3 381163718

Select row when value is in range

Given the two column below how can I select the row 110-118 if my filter is 111? 100-118 is a range thus 111 falls between 100-118
Dest_ZIP Ground
004-005 003
009 005
068-089 002
100-118 001
Below is a simple example of how to do this in SQL using a sub query to get the start and end range. This can be expanded on to better handle parsing the string value.
Declare #Temp TABLE
(
Dest_Zip varchar(7),
Ground varchar(3)
)
INSERT INTO #Temp VALUES ('004-005','003')
INSERT INTO #Temp VALUES ('068-089','002')
INSERT INTO #Temp VALUES ('100-118','001')
SELECT A.Dest_Zip, A.Ground FROM
(
select
Convert(int, SUBSTRING(Dest_Zip,1,3)) StartNum,
Convert(int, SUBSTRING(Dest_Zip,5,3)) EndNum,
*
from #Temp
) AS A
WHERE 111 >= A.StartNum AND 111 <= A.EndNum
Fix the data. Here is a simple way using computed columns (and assuming the "zips" are always 3 characters):
alter table t
add column minzip as (left(dest_zip), 3),
add column maxzip as (right(dest_zip), 3);
Then, you can run the query as:
select t.*
from t
where '111' between t.minzip and t.maxzip;
You can even create an index on computed columns, which can help performance (although not much in this case).
If you wish to make the checks in php, maybe this sample code can help you:
<?php
$my_string = "111";
$foo = "100-118"; // our range
$bar = explode('-', $foo); // Get an array .. let's call it $bar
// Print the output, see how the array looks
//print_r($bar);
//echo $bar[0].'<br />';
//echo $bar[1].'<br />';
if(($bar[0] <= $my_string ) AND ($bar[1] >= $my_string)){ echo 'true';} else { echo 'false';}
?>