Equals in sql (null vs empty string(''))
Create Date: March 10, 2022 at 09:11 PM         | Tag: SQL         | Author Name: Sun, Charles |
declare @test varchar(12)
set @test = null --CHAR(13)+CHAR(10)
print len(@test)
if @test > ''
print 'yes'
else
print 'no'
set @test = null
if @test = null --is null returns the correct result
print 'yes, it is null'
else
print 'not null'
SQL Server: Null VS Empty String
Be careful with nulls and checking for inequality in sql server.
For example
select * from foo where bla <> 'something'
will NOT return records where bla is null. Even though logically it should.
So the right way to check would be
select * from foo where isnull(bla,'') <> 'something'
Which of course people often forget and then get weird bugs.
New CommentParsing datetime as special string format
Create Date: April 16, 2021 at 12:41 PM         | Tag: SQL         | Author Name: Sun, Charles |
At my current work, my team has a very knowledgable dicussion about parsing datetime as a special string.
declare @tens char(31) = '0000000001111111111222222222233'; declare @ones char(31) = '1234567890123456789012345678901'; declare @days char(62) = '01020304050607080910111213141516171819202122232425262728293031'; select top 100 m.dob, right(convert(char(8), m.dob, 112), 4) + left(convert(char(8), m.dob, 112), 4) as right_left_4, substring(@tens, month(m.dob), 1) + substring(@ones, month(m.dob), 1) + substring(@tens, day(m.dob), 1) + substring(@ones, day(m.dob), 1) + cast(year(m.dob) as char(4)) as tens_and_ones, substring(@days, 2*month(m.dob) - 1, 2) + substring(@days, 2*day(m.dob) -1,2) + cast(year(m.dob) as char(4)) as days from member m right(convert(char(9), 100000000 + month(m.dob) * 1000000 + day(m.dob) * 10000 + year(m.dob)), 8) //fast -> slow 2, 3 ,1 1. format(m.dob,'MMddyyyy') as [Birth Date], 2. RIGHT('0'+CAST(MONTH(m.dob) AS VARCHAR(2)),2) + RIGHT('0'+CAST(DAY(m.dob) AS VARCHAR(2)),2) + CAST(YEAR(m.dob) AS VARCHAR(4)) as [Birth Date], 3. replace(convert(varchar(10),m.dob,101),'/','') as [Birth Date],
Results for various proposed alternative methods. The baseline is 1194 seconds. The first two are close enough to the baseline that they are essentially equivalent to it.
right(convert(char(8),m.dob,112),4)+convert(char(4),m.dob,112)
(1204 seconds)
right(convert(char(9), 100000000 + month(m.dob) * 1000000 + day(m.dob) * 10000 + year(m.dob)), 8)
(1213 seconds; using cast instead of convert yielded 1221 seconds)
substring(@days, 2*month(m.dob) - 1, 2) + substring(@days, 2*day(m.dob) -1,2) + cast(year(m.dob) as char(4))
(1317 seconds)
substring(@tens, month(m.dob), 1) + substring(@ones, month(m.dob), 1) + substring(@tens, day(m.dob), 1) + substring(@ones, day(m.dob), 1) + cast(year(m.dob) as char(4))
(1402 seconds)
Wrapping the baseline version in a scalar function
(2563 seconds)
For my select of about 22.5 million records with 20 columns, Method #2 is fastest (1194 seconds), followed by #3 (1448 seconds, 21% slower), followed by #1 (1645 seconds, 38% slower).
Style 112 returns a date in yyyymmdd format, not the required mmddyyyy; T-SQL doesn’t supply a style that will do so directly, which is why I tried #3.
Using CHAR does make a difference: we don’t get the correct results. CHAR always contains the specified number of characters and the CAST omits the leading zero. So any single digit value is the digit followed by a blank and prepending the zero gives us a 3 character string with a blank at the end and we extract the two rightmost characters; using VARCHAR, the CAST gives us just the digit. Run the following to see what I mean (example will work until October 1):
select '['+RIGHT('0'+CAST(MONTH(getdate()) AS CHAR(2)),2)+']','['+RIGHT('0'+CAST(MONTH(getdate()) AS VARCHAR(2)),2)+']'
New CommentA useful stored procedure template + data clean up using dynamic sql
Create Date: December 29, 2020 at 09:05 PM         | Tag: SQL         | Author Name: Sun, Charles |
This is very useful stored procedure template and an example with cleaning up data with muptiple relational tables using dynamic sql.
if object_id(N'dbo.sp') is null
begin
-- create stub
exec( 'create procedure dbo.sp as begin raiserror(''NOT IMPLEMENTED. STUB ONLY.'', 16, 1) end' )
end
go
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
ALTER PROCEDURE [dbo].[sp] (
@id int
, @begin_proc_id int --smaller integer
, @end_proc_id int --bigger interger
, @audit_id varchar(20)
)
AS
declare @proc_id varchar(50)
, @table_name varchar(128)
, @column_name varchar(128)
, @proc_id_column_name varchar(128)
, @backup_table_name varchar(128)
, @sql nvarchar(max)
, @servername VARCHAR(225) = @@SERVERNAME
, @trancode VARCHAR(1000) = ''
--get all proc_id for the table
if object_id('tempdb..#proc_ids') is not null drop table #proc_ids
create table #proc_ids
(
proc_id int
)
insert into #proc_ids
select proc_id
from proc_table
where id = @id and proc_id between @begin_proc_id and @end_proc_id
--perform cleanup if the table is not empty
if exists(select 1 from #proc_ids)
begin
--the process can be possibly gotten blocked. Off-work time is recommended.
BEGIN TRAN T1
BEGIN TRY
--list all possible tables for clean-up
declare @all_tables table
(
table_name varchar(128)
, proc_id_name varchar(128)
, is_backup_needed char(1)
, backup_table_name varchar(128)
, sql_statement nvarchar(max)
)
insert into @all_tables
values('table1', 'proc_id',
iif(exists(select 1 from table1 tn join #proc_ids cpi on cpi.proc_id = tn.proc_id), '1', '0'),
'table1_audit_' + @audit_id, null)
, ('table2', 'proc_id',
iif(exists(select 1 from table2 tn join #proc_ids cpi on cpi.proc_id = tn.proc_id), '1', '0'),
'table2_audit_' + @audit_id, null)
, ('table3', 'proc_id',
iif(exists(select 1 from table3 tn join #proc_ids cpi on cpi.proc_id = tn.proc_id), '1', '0'),
'table3_audit_' + @audit_id, null)
, ('table4', 'proc_id',
iif(exists(select 1 from table4 tn join #proc_ids cpi on cpi.proc_id = tn.proc_id), '1', '0'),
'table4_audit_' + @audit_id, null)
, ('table5', 'proc_id',
iif(exists(select 1 from table5 tn join #proc_ids cpi on cpi.proc_id = tn.proc_id), '1', '0'),
'table5_audit_' + @audit_id, null)
, ('table6', 'proc_id',
iif(exists(select 1 from table6 tn join #proc_ids cpi on cpi.proc_id = tn.proc_id), '1', '0'),
'table6_audit_' + @audit_id, null)
, ('table7', 'proc_id',
iif(exists(select 1 from table7 tn join #proc_ids cpi on cpi.proc_id = tn.proc_id), '1', '0'),
'table7_audit_' + @audit_id, null)
, ('table8', 'tableProcedureID',
iif(exists(select 1 from table8 tn join #proc_ids cpi on cpi.proc_id = tn.tableID), '1', '0'),
'table8_audit_' + @audit_id, null)
, ('proc_table', 'proc_id',
iif(exists(select 1 from proc_table tn join #proc_ids cpi on cpi.proc_id = tn.proc_id), '1', '0'),
'proc_table_audit_' + @audit_id, null)
--populate the sql_statement field for @all_tables
update t
--if starts
set t.sql_statement ='if ' + t.is_backup_needed + ' = 1 begin ' + 'if object_id(''' + t.backup_table_name +''') is null CREATE TABLE ' + t.backup_table_name + ' (' +
stuff((select c.column_name + ' ' +
CASE WHEN c.data_type = 'timestamp' THEN 'varbinary(8)'
when c.data_type in ('char','nchar','binary','varchar', 'nvarchar','varbinary') then c.data_type + '(' + iif(c.CHARACTER_MAXIMUM_LENGTH = -1, 'MAX', cast(c.CHARACTER_MAXIMUM_LENGTH as varchar(20))) + ')'
WHEN c.data_type in ('numeric', 'decimal') THEN c.data_type + '(' + cast(c.NUMERIC_PRECISION as varchar(10)) + ',' + cast(c.NUMERIC_SCALE as varchar(10)) + ')'
ELSE c.data_type
END + ', ' -- we don't need (or really want) any other column attributes like NOT NULL...
FROM INFORMATION_SCHEMA.COLUMNS c where t.table_name = c.TABLE_NAME ORDER BY c.ORDINAL_POSITION for xml path('')), 1, 0, '')
from @all_tables t
--the second update is for using len()
update t
set t.sql_statement = left(t.sql_statement, len(t.sql_statement) - 1) + ', run_date datetime) ' +
--delete data from the source table and insert the deleted data into the backup table
'delete from ' + t.table_name + ' output deleted.*, getdate() into ' + t.backup_table_name +
' where ' + t.proc_id_name + ' in (select proc_id from #proc_ids) ' +
--if ends
'end'
from @all_tables t
--get dynamic sql ready
set @sql = stuff((select sql_statement + '; ' from @all_tables for xml path('')), 1, 0, '')
exec(@sql)
COMMIT TRAN T1
END TRY
BEGIN CATCH
ROLLBACK TRAN T1
SET @trancode = 'T1 Broken, cannot backup or delete records using sp on '+ @servername
SELECT @trancode as TranCode, ERROR_NUMBER() AS ErrorNumber
,ERROR_MESSAGE() AS ErrorMessage
END CATCH
if @trancode = ''
print 'The script runs successfully.'
else
print 'Please check ErrorMessage. Most of cases, we just need to run the script at another off-work time.'
end
else
begin
print 'There is no proc_id retured. Please check the input paramters like @begin_proc_id, @end_proc_id etc.'
end
GO
-- Install or update extended properties for this SP.
declare @install_ts nvarchar(23);
set @install_ts = convert(nvarchar(23), getdate(), 121);
IF EXISTS (
SELECT 1 FROM sys.extended_properties
WHERE [major_id] = OBJECT_ID(N'dbo.sp')
AND [name] = N'Installed'
)
BEGIN
EXEC sys.sp_updateextendedproperty
@name = N'Installed',
@value = @install_ts,
@level0type = N'SCHEMA', @level0name = 'dbo',
@level1type = N'PROCEDURE', @level1name = 'sp';
END
ELSE
BEGIN
EXEC sys.sp_addextendedproperty
@name = N'Installed',
@value = @install_ts,
@level0type = N'SCHEMA', @level0name = 'dbo',
@level1type = N'PROCEDURE', @level1name = 'sp';
END
New Comment