T-SQL Summarize Numbers in One Row - tsql

I need to summarize numbers like
CREATE TABLE #t(num int);
INSERT #t
SELECT 10001 UNION ALL
SELECT 10002 UNION ALL
SELECT 10003 UNION ALL
SELECT 10004 UNION ALL
SELECT 10005 UNION ALL
SELECT 10006 UNION ALL
SELECT 10007 UNION ALL
SELECT 10008 UNION ALL
SELECT 10009 UNION ALL
SELECT 10010 UNION ALL
SELECT 10020 UNION ALL
SELECT 10030 UNION ALL
SELECT 10040 UNION ALL
SELECT 10041 UNION ALL
SELECT 10042 UNION ALL
SELECT 10043 UNION ALL
SELECT 10050 UNION ALL
SELECT 10060 UNION ALL
SELECT 10070 UNION ALL
SELECT 10075 UNION ALL
SELECT 10076 UNION ALL
SELECT 10077;
--DROP TABLE #T;
to a simpler form in such a way that result would be.
10001-10010, 10020, 10030, 10040-10043, 10050, 10060, 10070, 10075-10077
Any help would be highly appreciated.

Try this:
WITH CTE1 AS
(
SELECT *, num - ROW_NUMBER() OVER(ORDER BY num) Corr
FROM #t
), CTE2 AS
(
SELECT MIN(num) MinNum, MAX(num) MaxNum, Corr
FROM CTE1
GROUP BY Corr
)
SELECT CAST(MinNum AS VARCHAR) +
CASE WHEN MaxNum != MinNum THEN ' - ' + CAST(MaxNum AS VARCHAR) ELSE '' END Res
FROM CTE2

DECLARE #Result VARCHAR(MAX);
WITH q AS (
SELECT
Num,
Num - (ROW_NUMBER() OVER (ORDER BY Num)) AS RowNumber
FROM
#t
)
SELECT
#Result = ISNULL(#Result + ',', '') +
CASE WHEN MIN(Num) != MAX(Num)
THEN CAST(MIN(Num) AS VARCHAR) + '-' + CAST(MAX(Num) AS VARCHAR)
ELSE CAST(MIN(Num) AS VARCHAR)
END
FROM q
GROUP BY RowNumber
PRINT #Result
-- Or... SELECT #Result.. whichever.

Try a cursor.
Run your script to create a table, then this
DECLARE cur CURSOR FOR
SELECT num FROM #t
OPEN cur
DECLARE #nm INT;
DECLARE #start INT;
DECLARE #prev INT;
DECLARE #retVal VARCHAR(MAX);
SET #retVal = '';
FETCH NEXT FROM cur INTO #nm
SET #prev = NULL;
SET #start = NULL;
WHILE (##fetch_status = 0)
BEGIN
IF (#prev IS NOT NULL)
BEGIN
IF (#prev = #nm - 1)
BEGIN
SET #prev = #nm;
END
ELSE
BEGIN
IF (#start = #prev)
SET #retVal = #retVal + CAST(#start AS VARCHAR) + ', '
ELSE
SET #retVal = #retVal + CAST(#start AS VARCHAR) + ' - ' + CAST(#prev AS VARCHAR) + ', '
SET #prev = #nm;
SET #start = #nm;
END
END
ELSE
BEGIN
SET #prev = #nm;
SET #start = #nm;
END
--PRINT(#nm)
FETCH NEXT FROM cur INTO #nm
END
CLOSE cur
DEALLOCATE cur
IF (#start = #prev)
SET #retVal = #retVal + CAST(#start AS VARCHAR) + ', '
ELSE
SET #retVal = #retVal + CAST(#start AS VARCHAR) + ' - ' + CAST(#prev AS VARCHAR) + ', '
IF (LEN(#retVal) > 1)
SET #retVal = SUBSTRING(#retVal, 1, LEN(#retVal) - 1)
PRINT(#retVal)

Related

Error converting data type varchar to numeric in standard audit trigger

I have been using a standard block of TSQL for auditing of various tables for some time now. However I now have a problem when running the trigger on a new table: "Error converting data type varchar to numeric". This occurs when running the EXEC (#sql) line. I've determined that the code for #sql is:
insert Audit_AppointmentsWS
(Type,
TableName,
PK,
FieldName,
OldValue,
NewValue,
UpdateDate,
UserName)
SELECT 'U',
'AppointmentsWorkshop',
+convert(varchar(100), coalesce(i.UniqueID,d.UniqueID)),
'[JobHours]',
convert(varchar(1000),d.[JobHours]),
convert(varchar(1000),i.[JobHours]),
'20220816 12:32:43:410',
'DELLXPS\ian'
from #ins i full outer join #del d on i.UniqueID = d.UniqueID where ISNULL(i.JobHours],'') <> ISNULL(d.[JobHours],'')
I've tried deleting the trigger & the audit table and then recreating them but no joy. I've also tried copying an existing trigger and just changing the table details but I still get the same error. I'm completely stumped on this and would appreciate some feedback. Many thanks in advance!
Here is the trigger:
/****** Object: Trigger [dbo].[tr_AppointmentsWS] Script Date: 16/08/2022 12:02:10 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
create TRIGGER [dbo].[tr_AppointmentsWS] ON [dbo].AppointmentsWorkshop FOR UPDATE, DELETE
AS
DECLARE #bit INT ,
#field INT ,
#maxfield INT ,
#char INT ,
#fieldname VARCHAR(128) ,
#TableName VARCHAR(128) ,
#AuditTable VARCHAR(128) ,
#PKCols VARCHAR(MAX) ,
#sql VARCHAR(2000),
#UpdateDate VARCHAR(21) ,
#UserName VARCHAR(128) ,
#Type CHAR(1) ,
#PKSelect VARCHAR(MAX)
--Changes required:
-- 1. Change the name of the trigger and the table, above
-- 2. Change #TableName to match the table to be audited
-- 3. Change the #AuditTable to the table holding the changes
SELECT #TableName = 'AppointmentsWorkshop'
SELECT #AuditTable = 'Audit_AppointmentsWS'
-- date and user
SELECT #UserName = SYSTEM_USER ,
#UpdateDate = CONVERT(VARCHAR(8), GETDATE(), 112) + ' ' + CONVERT(VARCHAR(12), GETDATE(), 114)
-- Action
IF EXISTS (SELECT * FROM inserted)
IF EXISTS (SELECT * FROM deleted)
SELECT #Type = 'U'
ELSE
SELECT #Type = 'I'
ELSE
SELECT #Type = 'D'
-- get list of columns
SELECT * INTO #ins FROM inserted
SELECT * INTO #del FROM deleted
-- Get primary key columns for full outer join
SELECT #PKCols = COALESCE(#PKCols + ' and', ' on') + ' i.' + c.COLUMN_NAME + ' = d.' + c.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk, INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
WHERE pk.TABLE_NAME = #TableName
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
-- Get primary key select for insert
SELECT #PKSelect = COALESCE(#PKSelect+'+','') + '+convert(varchar(100), coalesce(i.' + COLUMN_NAME +',d.' + COLUMN_NAME + '))'
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk, INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
WHERE pk.TABLE_NAME = #TableName
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
IF #PKCols IS NULL
BEGIN
RAISERROR('no PK on table %s', 16, -1, #TableName)
RETURN
END
SELECT #field = 0, #maxfield = MAX(COLUMNPROPERTY(OBJECT_ID(TABLE_SCHEMA + '.' + #Tablename),COLUMN_NAME, 'ColumnID'))
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = #TableName
WHILE #field < #maxfield
BEGIN
SELECT #field = MIN(COLUMNPROPERTY(OBJECT_ID(TABLE_SCHEMA + '.' + #Tablename),COLUMN_NAME, 'ColumnID'))
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #TableName
AND COLUMNPROPERTY(OBJECT_ID(TABLE_SCHEMA + '.' + #Tablename),COLUMN_NAME, 'ColumnID') > #field
SELECT #bit = (#field - 1 )% 8 + 1
SELECT #bit = POWER(2,#bit - 1)
SELECT #char = ((#field - 1) / 8) + 1
IF SUBSTRING(COLUMNS_UPDATED(),#char, 1) & #bit > 0 OR #Type IN ('I','D')
BEGIN
SELECT #fieldname = COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #TableName
AND COLUMNPROPERTY(OBJECT_ID(TABLE_SCHEMA + '.' + #Tablename),COLUMN_NAME, 'ColumnID') = #field
SELECT #sql = 'insert ' + #AuditTable + '
(Type,
TableName,
PK,
FieldName,
OldValue,
NewValue,
UpdateDate,
UserName)
SELECT ''' + #Type + ''','''
+ #TableName + ''',' + #PKSelect
+ ',''[' + #fieldname + ']'''
+ ',convert(varchar(1000),d.[' + #fieldname + '])'
+ ',convert(varchar(1000),i.[' + #fieldname + '])'
+ ',''' + #UpdateDate + ''''
+ ',''' + #UserName + ''''
+ ' from #ins i full outer join #del d'
+ #PKCols
+ ' where ISNULL(i.[' + #fieldname + '],'''') <> ISNULL(d.[' + #fieldname + '],'''')' --Skip identical values and excludes NULLS vs empty strings
EXEC (#sql)
END
END
Well I finally figured it out. The error is being generated with columns of data type 'decimal' and it is down to the ISNULL section of the last SELECT. I've fixed it by checking for the decimal type and then using the following code (which included a zero rather than an empty sting):
+ ' where ISNULL(i.[' + #fieldname + '],''0'') <> ISNULL(d.[' + #fieldname + '],''0'')' --Skip identical values and excludes NULLS vs empty strings

No Column was specified for column 1 for cte3

I get this error message
declare #i as int
declare #CategoryList varchar(1000);
set #i=294;
with
cte1 as (
select * from Project where Id=#i
),
cte2 as (
select dbo.CableProperty.*
from CableProperty
inner join cte1 on dbo.CableProperty.ProjectId = cte1.Id
),
cte3 as (
select #CategoryList = coalesce(#CategoryList + ', ', '') + FromBay
from cable
inner join cte2 on dbo.cable.CablePropertyId = cte2.Id
select #CategoryList
)
select * from cte3
the above code is a very simple of what i want to do. but the concept are like that.
why i get that error?
Thank you
I think it is not possible to have two independent SELECT statements in a CTE (Present in CTE3). And you will be getting only one column from CTE3[AliasName], so I guess we don't have to specify SELECT #CategoryList.
DECLARE #i AS INT
DECLARE #CategoryList VARCHAR(1000);
SET #i = 294;
;WITH CTE AS (
SELECT * FROM Project WHERE ID = #i
)
,CTE2 AS (
SELECT * FROM CableProperty INNER JOIN CTE1 ON ProjectID = CTE1.ID
)
,CTE3 AS (
SELECT COALESCE(#CategoryList + ', ','') + FromBay [AliasName] FROM Cable
INNER JOIN CTE2 ON CablePropertyID = CTE2.ID
)
SELECT * FROM CTE3
Try this. Hope it helps.
declare #i as int
declare #CategoryList varchar(1000);
set #i=294;
select #CategoryList = coalesce(#CategoryList + ', ', '') + FromBay
from cable
join CableProperty
on cable.CablePropertyId = CableProperty.Id
join Project
on CableProperty.ProjectId = Project.Id
and Project.Id = #i
select #CategoryList
what table has FromBay?

SQL0107N The name "insert into OH09ZC1.cte3 with cte2 (list, cnt, cnt_max) AS (SELECT" is too long. The maximum length is "128"

Here is the procedure where I use a statement with temp table:
CREATE OR REPLACE PROCEDURE dept_query (
p_table CHAR(10),
p_date CHAR(10),
p_start CHAR(6)
)
LANGUAGE SQL
BEGIN
DECLARE v_table VARCHAR(128);
DECLARE v_colname VARCHAR(128);
DECLARE v_month CHAR(6);
DECLARE v_int integer default 0;
DECLARE stat VARCHAR(1000);
DECLARE stmt STATEMENT;
.......
set stat = 'insert into cte1 select colname, row_number() over() as row_nbr from syscat.columns where tabname = ?';
PREPARE stmt FROM stat;
execute stmt using p_table;
set stat = "insert into OH09ZC1.cte3 -- cte, cte1 & cte3 are ordinary tables
with cte2 (list, cnt, cnt_max) AS
(SELECT VARCHAR('', 32000), 0, count(colname) FROM OH09ZC1.cte1
UNION ALL
SELECT
case when cte2.list = '' THEN RTRIM(CHAR(cte1.colname))
else cte2.list || ', ' || RTRIM(CHAR(cte1.colname)) end,
cte2.cnt + 1,
cte2.cnt_max
FROM OH09ZC1.cte1, cte2
WHERE cte1.row_nbr = cte2.cnt + 1 AND cte2.cnt < cte2.cnt_max )
select list FROM cte2 where cte2.cnt = cte2.cnt_max fetch first 1 rows only";
PREPARE stmt FROM stat;
execute stmt;
......
END;
This gets me a list of the columns in a table.
The problem is that 'insert into OH09ZC1.cte3 with cte2 (list, cnt, cnt_max) AS ' doesn't contain any long names, so why do I get this error:
SQL0107N The name "insert into OH09ZC1.cte3 with cte2 (list, cnt, cnt_max) AS (SELEC" is too long. The maximum length is "128".
I also got the same error when 'insert into OH09ZC1.cte3' was not there.
set stat = "insert into... -- SQL string literals should use single quotation marks. Double quotation marks indicate an identifier, and "insert into cte3 ...", in addition to being too long for an identifier, is invalid anyway.

Step through 2 temp tables compare to table

I have a SP that is supposed to compare 2 temp tables generated from a function to another table. My issue is that instead of stepping through each temp table it goes through both at the same time. I am not sure how to write the code to step through each table one at a time to get the desired results.
DROP PROCEDURE uspJudgments;
GO
CREATE PROCEDURE uspJudgments
#fullName varchar(100), #fullName1 varchar(100)
AS
BEGIN
SELECT *
INTO #tmpFullname
FROM dbo.DelimitedSplit8K(#fullName, ',')
SELECT *
INTO #tmpFullname1
FROM dbo.DelimitedSplit8K(#fullName1, ',')
SELECT *
FROM #tmpFullName
SELECT *
FROM #tmpFullName1
DECLARE #MaxRownum int
SET #MaxRownum = (SELECT MAX(ItemNumber) FROM #tmpFullname)
DECLARE #Iter int
SET #Iter = (SELECT MIN(ItemNumber) FROM #tmpFullname)
DECLARE #MaxRownum1 int
SET #MaxRownum1 = (SELECT MAX(ItemNumber) FROM #tmpFullname1)
DECLARE #Iter1 int
SET #Iter1 = (SELECT MIN(ItemNumber) FROM #tmpFullname1)
DECLARE #Name varchar(25)
DECLARE #Name1 varchar(25)
WHILE #Iter <= #MaxRownum AND #iter1 <= #Maxrownum1
BEGIN
SET #Name = (SELECT Item FROM #tmpFullname WHERE ItemNumber = #Iter)
SET #Name1 = (SELECT Item FROM #tmpFullname1 WHERE ItemNumber = #Iter1)
SELECT *
--INTO #tmpDefSelect
FROM defendants_ALL
WHERE combined_name LIKE '%' + #Name + '%' AND combined_name LIKE '%' + #Name1 + '%';
SET #Iter = #Iter + 1
SET #Iter1 = #Iter1 + 1
END
END
DROP TABLE #tmpFullname
DROP TABLE #tmpFullname1
EXEC uspJudgments #fullName = 'grein,smit', #fullName1 = 'joh,jon,j.'
I need to get ALL results for grein -- joh, jon, j. AND smit, joh, jon, j.
Currently the above code only returns grein joh AND smit, jon.. In our database that returns no results for the first combination and 38 results for jonathon smith and jon smith. How do I properly step through each temp table to get the desired results?
You only get the two combinations grein joh and smith jon because of the way you are iterating through #Iter and #iter1. You increment both of them at the same time. You need a nested loop like this:
declare #start_val integer = #iter1
while #Iter <= MaxRownum
begin
set #iter1 = #start_val
while #iter1 <= #Maxrownum1
begin
set #Name = ...
set #Name1 = ...
...
set #iter1 = #iter1 + 1
end
set #Iter = #Iter + 1
end
But I don't think you even need the WHILE loops:
select d.*
from defendants_ALL, #tmpFullname t1, #tmpFullname1 t2
where d.combined_name = t1.Item + ' ' + t2.Item
Or (if you need to still use LIKE):
select d.*
from defendants_ALL, #tmpFullname t1, #tmpFullname1 t2
where d.combined_name like '%' + t1.Item + '%'
and d.combined_name like '%' + t2.Item + '%'
(All SQL is untested...)

Implementing and applying a string split in T-SQL

I have this statement in T-SQL.
SELECT Bay From TABLE where uid in (
select B_Numbers from Info_Step WHERE uid = 'number'
)
I am selecting "multiple" BAYs from TABLE where their uid is equal to a string of numbers like this:
B_Numbers = 1:45:34:98
Therefore, I should be selecting 4 different BAYs from TABLE. I basically need to split the string 1:45:34:98 up into 4 different numbers.
I'm thinking that Split() would work, but it doesn't and I get a syntax error.
Any thoughts from the T-SQL gods would be awesome!
Here is an implementation of a split function that returns the list of numbers as a table:
http://rbgupta.blogspot.com/2007/03/split-function-tsql.html
Looks like this would set you on your way...
Here is a method that uses an auxiliary numbers table to parse the input string. The logic can easily be added to a function that returns a table. That table can then be joined to lookup the correct rows.
Step 1: Create the Numbers table
SET NOCOUNT ON
GO
IF EXISTS
(
SELECT 1
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'Numbers'
AND TABLE_SCHEMA = 'dbo'
AND TABLE_TYPE = 'BASE TABLE'
)
BEGIN
DROP TABLE dbo.Numbers
END
GO
CREATE TABLE dbo.Numbers
(
Number smallint IDENTITY(1, 1) PRIMARY KEY
)
GO
WHILE 1 = 1
BEGIN
INSERT INTO dbo.Numbers DEFAULT VALUES
IF SCOPE_IDENTITY() = 32767
BEGIN
BREAK
END
END
GO
Step 2: Parse the Input String
CREATE FUNCTION dbo.ParseString(#input_string varchar(8000), #delim varchar(8000) = " ")
RETURNS TABLE
AS RETURN
(
SELECT Number
FROM dbo.Numbers
WHERE CHARINDEX
(
#delim + CONVERT(VARCHAR(12),Number) + #delim,
#delim + #input_string + #delim
) > 0
)
GO
**EXAMPLE**
SELECT * FROM dbo.ParseString('1:45:34:98',':')
Step 3: Use the results however you want/need
Number
------
1
34
45
98
End-To-End Example
Create function that returns the appropriate BNumber (of course change it to use the commented out SQL)
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION dbo.GetBNumber (#uid int)
RETURNS VARCHAR(8000)
AS
BEGIN
RETURN '1:45:34:98'
--select B_Numbers from Info_Step WHERE uid = #uid
END
GO
Use the use functions to return the desired results
-- Using Test Data
SELECT N.Number FROM Numbers N
JOIN dbo.ParseString(dbo.GetBNumber(12345),':') Q ON Q.Number = N.Number
-- Using Your Data (Untested but should work.)
SELECT N.Bay
FROM TABLE N
JOIN dbo.ParseString(dbo.GetBNumber(ENTER YOU NUMBER HERE),':') Q ON Q.Number = N.uid
Results
Number
------
1
34
45
98
You should keep your arrays as rows but if I understand your question I think this will work.
SELECT
Bay
From
TABLE
join Info_Step
on B_Numbers like '%'+ uid +'%'
where
Info_Step.uid = 'number'
This query will do a full table scan because of the like operator.
What you can do is loop through the B_Numbers entries and do your own split on : Insert those entries into a temp table and then perform your query.
DECLARE #i int
DECLARE #start int
DECLARE #B_Numbers nvarchar(20)
DECLARE #temp table (
number nvarchar(10)
)
-- SELECT B_Numbers FROM Info_Step WHERE uid = 'number'
SELECT #B_Numbers = '1:45:34:98'
SET #i = 0
SET #start = 0
-- Parse out characters delimited by ":";
-- Would make a nice user defined function.
WHILE #i < len(#B_Numbers)
BEGIN
IF substring(#B_Numbers, #i, 1) = ':'
BEGIN
INSERT INTO #temp
VALUES (substring(#B_Numbers, #start, #i - #start))
SET #start = #i + 1
END
SET #i = #i + 1
END
-- Insert last item
INSERT INTO #temp
VALUES (substring(#B_Numbers, #start, #i - #start + 1))
-- Do query with parsed values
SELECT Bay FROM TABLE WHERE uid in (SELECT * FROM #temp)
You can even try this
declare #str varchar(50)
set #str = '1:45:34:98'
;with numcte as(
select 1 as rn union all select rn+1 from numcte where rn<LEN(#str)),
getchars as(select
ROW_NUMBER() over(order by rn) slno,
rn,chars from numcte
cross apply(select SUBSTRING(#str,rn,1) chars)X where chars = ':')
select top 1
Bay1 = SUBSTRING(#str,0,(select rn from getchars where slno = 1))
,Bay2 = SUBSTRING(#str,
(select rn from getchars where slno = 1) + 1,
(((select rn from getchars where slno = 2)-
(select rn from getchars where slno = 1)
)-1))
,Bay3 = SUBSTRING(#str,
(select rn from getchars where slno = 2) + 1,
(((select rn from getchars where slno = 3)-
(select rn from getchars where slno = 2)
)-1))
,Bay4 = SUBSTRING(#str,
(select rn from getchars where slno = 3)+1,
LEN(#str))
from getchars
Output:
Bay1 Bay2 Bay3 Bay4
1 45 34 98