I created a temp table #test containing 3 fields: ColumnName, TableName, and Id.
I would like to see which rows in the #test table (columns in their respective tables) are not empty? I.e., for every column name that i have in the ColumnName field, and for the corresponding table found in the TableName field, i would like to see whether the column is empty or not. Tried some things (see below) but didn't get anywhere. Help, please.
declare #LoopCounter INT = 1, #maxloopcounter int, #test varchar(100),
#test2 varchar(100), #check int
set #maxloopcounter = (select count(TableName) from #test)
while #LoopCounter <= #maxloopcounter
begin
DECLARE #PropIDs TABLE (tablename varchar(max), id int )
Insert into #PropIDs (tablename, id)
SELECT [tableName], id FROM #test
where id = #LoopCounter
set #test2 = (select columnname from #test where id = #LoopCounter)
declare #sss varchar(max)
set #sss = (select tablename from #PropIDs where id = #LoopCounter)
set #check = (select count(#test2)
from (select tablename
from #PropIDs
where id = #LoopCounter) A
)
print #test2
print #sss
print #check
set #LoopCounter = #LoopCounter + 1
end
In order to use variables as column names and table names in your #Check= query, you will need to use Dynamic SQL.
There is most likely a better way to do this but I cant think of one off hand. Here is what I would do.
Use the select and declare a cursor rather than a while loop as you have it. That way you dont have to count on sequential id's. The cursor would fetch fields columnname, id and tablename
In the loop build a dynamic sql statement
Set #Sql = 'Select Count(*) Cnt Into #Temp2 From ' + TableName + ' Where ' + #columnname + ' Is not null And ' + #columnname <> '''''
Exec(#Sql)
Then check #Temp2 for a value greater than 0 and if this is what you desire you can use the #id that was fetched to update your #Temp table. Putting the result into a scalar variable rather than a temp table would be preferred but cant remember the best way to do that and using a temp table allows you to use an update join so it would well in my opinion.
https://www.mssqltips.com/sqlservertip/1599/sql-server-cursor-example/
http://www.sommarskog.se/dynamic_sql.html
Found a way to extract all non-empty tables from the schema, then just joined with the initial temp table that I had created.
select A.tablename, B.[row_count]
from (select * from #test) A
left join
(SELECT r.table_name, r.row_count, r.[object_id]
FROM sys.tables t
INNER JOIN (
SELECT OBJECT_NAME(s.[object_id]) table_name, SUM(s.row_count) row_count, s.[object_id]
FROM sys.dm_db_partition_stats s
WHERE s.index_id in (0,1)
GROUP BY s.[object_id]
) r on t.[object_id] = r.[object_id]
WHERE r.row_count > 0 ) B
on A.[TableName] = B.[table_name]
WHERE ROW_COUNT > 0
order by b.row_count desc
How about this one - bitmask computed column checks for NULLability. Value in the bitmask tells you if a column is NULL or not. Counting base 2.
CREATE TABLE FindNullComputedMask
(ID int
,val int
,valstr varchar(3)
,NotEmpty as
CASE WHEN ID IS NULL THEN 0 ELSE 1 END
|
CASE WHEN val IS NULL THEN 0 ELSE 2 END
|
CASE WHEN valstr IS NULL THEN 0 ELSE 4 END
)
INSERT FindNullComputedMask
SELECT 1,1,NULL
INSERT FindNullComputedMask
SELECT NULL,2,NULL
INSERT FindNullComputedMask
SELECT 2,NULL, NULL
INSERT FindNullComputedMask
SELECT 3,3,3
SELECT *
FROM FindNullComputedMask
I have a TSQL statement that is taking several hours to run. I'm sure I need to look into the import process to avoid duplicates being inserted but for the time being I'd just like to remove all records except one with duplicate values. ParameterValueId is the primary key on the table but I have many duplicate entries that need to be deleted. I only need one record for each ParameterId, SiteId, MeasurementDateTime, and ParameterValue. Below is my current method for deleting duplicate records. It finds all values that have a count > 1. It then finds the first Id with those values and deletes all of the records with those values that don't match the first ID found by those values. Besides the print statements is there a more efficient way of doing this. Can I do a way with the cursor at all to improve performance?
BEGIN TRANSACTION
SET NOCOUNT ON
DECLARE #BeginningRecordCount INT
SET #BeginningRecordCount =
(
SELECT COUNT(*)
FROM ParameterValues
)
DECLARE #ParameterId UNIQUEIDENTIFIER
DECLARE #SiteId UNIQUEIDENTIFIER
DECLARE #MeasurementDateTime DATETIME
DECLARE #ParameterValue FLOAT
DECLARE CDuplicateValues CURSOR FOR
SELECT
[ParameterId]
,[SiteId]
,[MeasurementDateTime]
,[ParameterValue]
FROM [ParameterValues]
GROUP BY
[ParameterId]
,[SiteId]
,[MeasurementDateTime]
,[ParameterValue]
HAVING COUNT(*) > 1
OPEN CDuplicateValues
FETCH NEXT FROM CDuplicateValues INTO
#ParameterId
,#SiteId
,#MeasurementDateTime
,#ParameterValue
DECLARE #FirstParameterValueId UNIQUEIDENTIFIER
DECLARE #DuplicateRecordsDeleting INT
WHILE ##FETCH_STATUS <> -1
BEGIN
SET #FirstParameterValueId =
(
SELECT TOP 1 ParameterValueId
FROM ParameterValues
WHERE
ParameterId = #ParameterId
AND SiteId = #SiteId
AND MeasurementDateTime = #MeasurementDateTime
AND ParameterValue = #ParameterValue
)
SET #DuplicateRecordsDeleting =
(
SELECT COUNT(*)
FROM ParameterValues
WHERE
ParameterId = #ParameterId
AND SiteId = #SiteId
AND MeasurementDateTime = #MeasurementDateTime
AND ParameterValue = #ParameterValue
AND ParameterValueId <> #FirstParameterValueId
)
PRINT 'DELETING ' + CAST(#DuplicateRecordsDeleting AS NVARCHAR(50))
+ ' records with values ParameterId : ' + CAST(#ParameterId AS NVARCHAR(50))
+ ', SiteId : ' + CAST (#SiteId AS NVARCHAR(50))
+ ', MeasurementDateTime : ' + CAST(#MeasurementDateTime AS NVARCHAR(50))
+ ', ParameterValue : ' + CAST(#ParameterValue AS NVARCHAR(50))
DELETE FROM ParameterValues
WHERE
ParameterId = #ParameterId
AND SiteId = #SiteId
AND MeasurementDateTime = #MeasurementDateTime
AND ParameterValue = #ParameterValue
AND ParameterValueId <> #FirstParameterValueId
FETCH NEXT FROM CDuplicateValues INTO
#ParameterId
,#SiteId
,#MeasurementDateTime
,#ParameterValue
END
CLOSE CDuplicateValues
DEALLOCATE CDuplicateValues
DECLARE #EndingRecordCount INT
SET #EndingRecordCount =
(
SELECT COUNT(*)
FROM ParameterValues
)
PRINT 'Beginning Record Count : ' + CAST(#BeginningRecordCount AS NVARCHAR(50))
PRINT 'Ending Record Count : ' + CAST(#EndingRecordCount AS NVARCHAR(50))
PRINT 'Total Records Deleted : ' + CAST((#BeginningRecordCount - #EndingRecordCount) AS NVARCHAR(50))
SET NOCOUNT OFF
PRINT 'RUN THE COMMIT OR ROLLBACK STATEMENT AFTER VERIFYING DATA.'
--COMMIT
--ROLLBACK
Use option with CTE and OVER clause. OUTPUT.. INTO clause saves the information from rows affected by an DELETE statement into #delParameterValues table. Further, in the body of procedure, you can use this table to print the affected rows.
DECLARE #delParameterValues TABLE
(
ParameterId UNIQUEIDENTIFIER,
SiteId UNIQUEIDENTIFIER,
MeasurementDateTime DATETIME,
ParameterValue FLOAT,
DeletedRecordCount int
)
;WITH cte AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY [ParameterId],[SiteId],[MeasurementDateTime],[ParameterValue] ORDER BY 1/0) AS rn,
COUNT(*) OVER (PARTITION BY [ParameterId],[SiteId],[MeasurementDateTime],[ParameterValue]) AS cnt
FROM [ParameterValues]
)
DELETE cte
OUTPUT DELETED.[ParameterId],
DELETED.[SiteId],
DELETED.[MeasurementDateTime],
DELETED.[ParameterValue],
DELETED.cnt INTO #delParameterValues
WHERE rn != 1
SELECT DISTINCT *
FROM #delParameterValues
Demo on SQLFiddle
you can do it in a single sql:
DELETE p FROM ParameterValues p
LEFT JOIN
(SELECT ParameterId, SiteId, MeasurementDateTime, ParameterValue, MAX(ParameterValueId) AS MAX_PARAM
FROM ParameterValues
GROUP BY ParameterId, SiteId, MeasurementDateTime, ParameterValue
) m
ON m.ParameterId = p.ParameterId
AND m.SiteId = p.SiteId
AND m.MeasurementDateTime = p.MeasurementDateTime
AND m.ParameterValue = p.ParameterValue
AND m.MAX_PARAM = p.ParameterValueId
WHERE m.ParameterId IS NULL
Of course it will not print the output, but you can still print the rows before and after
I have a varchar string of delimited numbers separated by commas that I want to use in my SQL script but I need to compare with a bigint field in the database. Need to know to convert it:
DECLARE #RegionID varchar(200) = null
SET #RegionID = '853,834,16,467,841,460,495,44,859,457,437,836,864,434,86,838,458,472,832,433,142,154,159,839,831,469,442,275,840,299,446,220,300,225,227,447,301,450,230,837,441,835,302,477,855,411,395,279,303'
SELECT a.ClassAdID, -- 1
a.AdURL, -- 2
a.AdTitle, -- 3
a.ClassAdCatID, -- 4
b.ClassAdCat, -- 5
a.Img1, -- 6
a.AdText, -- 7
a.MemberID, -- 9
a.Viewed, -- 10
c.Domain, -- 11
a.CreateDate -- 12
FROM ClassAd a
INNER JOIN ClassAdCat b ON b.ClassAdCAtID = a.ClassAdCAtID
INNER JOIN Region c ON c.RegionID = a.RegionID
AND a.PostType = 'CPN'
AND DATEDIFF(d, GETDATE(), ExpirationDate) >= 0
AND a.RegionID IN (#RegionID)
AND Viewable = 'Y'
This fails with the following error:
Error converting data type varchar to bigint.
RegionID In the database is a bigint field.. need to convert the varchar to bigint.. any ideas..?
Many thanks in advance,
neojakey
create this function:
CREATE function [dbo].[f_split]
(
#param nvarchar(max),
#delimiter char(1)
)
returns #t table (val nvarchar(max), seq int)
as
begin
set #param += #delimiter
;with a as
(
select cast(1 as bigint) f, charindex(#delimiter, #param) t, 1 seq
union all
select t + 1, charindex(#delimiter, #param, t + 1), seq + 1
from a
where charindex(#delimiter, #param, t + 1) > 0
)
insert #t
select substring(#param, f, t - f), seq from a
option (maxrecursion 0)
return
end
change this part:
AND a.RegionID IN (select val from dbo.f_split(#regionID, ','))
Change this for better overall performance:
AND DATEDIFF(d, 0, GETDATE()) <= ExpirationDate
Your query does not know that those are separate values, you can use dynamic sql for this:
DECLARE #RegionID varchar(200) = null
SET #RegionID = '853,834,16,467,841,460,495,44,859,457,437,836,864,434,86,838,458,472,832,433,142,154,159,839,831,469,442,275,840,299,446,220,300,225,227,447,301,450,230,837,441,835,302,477,855,411,395,279,303'
declare #sql nvarchar(Max)
set #sql = 'SELECT a.ClassAdID, -- 1
a.AdURL, -- 2
a.AdTitle, -- 3
a.ClassAdCatID, -- 4
b.ClassAdCat, -- 5
a.Img1, -- 6
a.AdText, -- 7
a.MemberID, -- 9
a.Viewed, -- 10
c.Domain, -- 11
a.CreateDate -- 12
FROM ClassAd a
INNER JOIN ClassAdCat b ON b.ClassAdCAtID = a.ClassAdCAtID
INNER JOIN Region c ON c.RegionID = a.RegionID
AND a.PostType = ''CPN''
AND DATEDIFF(d, GETDATE(), ExpirationDate) >= 0
AND a.RegionID IN ('+#RegionID+')
AND Viewable = ''Y'''
exec sp_executesql #sql
I use this apporach sometimes and find it very good.
It transfors your comma-separated string into an AUX table (called #ARRAY) and then query the main table based on the AUX table:
declare #RegionID varchar(50)
SET #RegionID = '853,834,16,467,841,460,495,44,859,457,437,836,864,434,86,838,458,472,832,433,142,154,159,839,831,469,442,275,840,299,446,220,300,225,227,447,301,450,230,837,441,835,302,477,855,411,395,279,303'
declare #S varchar(20)
if LEN(#RegionID) > 0 SET #RegionID = #RegionID + ','
CREATE TABLE #ARRAY(region_ID VARCHAR(20))
WHILE LEN(#RegionID) > 0 BEGIN
SELECT #S = LTRIM(SUBSTRING(#RegionID, 1, CHARINDEX(',', #RegionID) - 1))
INSERT INTO #ARRAY (region_ID) VALUES (#S)
SELECT #RegionID = SUBSTRING(#RegionID, CHARINDEX(',', #RegionID) + 1, LEN(#RegionID))
END
select * from your_table
where regionID IN (select region_ID from #ARRAY)
It avoids you from ahving to concatenate the query string and then use EXEC to execute it, which I dont think it is a very good approach.
if you need to run the code twice you will need to drop the temp table
I think the answer should be kept simple.
Try using CHARINDEX like this:
DECLARE #RegionID VARCHAR(200) = NULL
SET #RegionID =
'853,834,16,467,841,460,495,44,859,457,437,836,864,434,86,838,458,472,832,433,142,154,159,839,831,469,442,275,840,299,446,220,300,225,227,447,301,450,230,837,441,835,302,477,855,411,395,279,303'
SELECT 1
WHERE Charindex('834', #RegionID) > 0
SELECT 1
WHERE Charindex('999', #RegionID) > 0
When CHARINDEX finds the value in the large string variable, it will return it's position, otherwise it return 0.
Use this as a search tool.
The easiest way to change this query is to replace the IN function with a string function. Here is what I consider the safest approach using LIKE (which is portable among databases):
AND ','+#RegionID+',' like '%,'+cast(a.RegionID as varchar(255))+',%'
Or CHARINDEX:
AND charindex(','+cast(a.RegionID as varchar(255))+',', ','+#RegionID+',') > 0
However, if you are explicitly putting the list in your code, why not use a temporary table?
declare #RegionIds table (RegionId int);
insert into #RegionIds
select 853 union all
select 834 union all
. . .
select 303
Then you can use the table in the IN clause:
AND a.RegionId in (select RegionId from #RegionIds)
or in a JOIN clause.
I like Diego's answer some, but I think my modification is a little better because you are declaring a table variable and not creating an actual table. I know the "in" statement can be a little slow, so I did an inner join since I needed some info from the Company table anyway.
declare #companyIdList varchar(1000)
set #companyIdList = '1,2,3'
if LEN(#companyIdList) > 0 SET #companyIdList = #companyIdList + ','
declare #CompanyIds TABLE (CompanyId bigint)
declare #S varchar(20)
WHILE LEN(#companyIdList) > 0 BEGIN
SELECT #S = LTRIM(SUBSTRING(#companyIdList, 1, CHARINDEX(',', #companyIdList) - 1))
INSERT INTO #CompanyIds (CompanyId) VALUES (#S)
SELECT #companyIdList = SUBSTRING(#companyIdList, CHARINDEX(',', #companyIdList) + 1, LEN(#companyIdList))
END
select d.Id, d.Name, c.Id, c.Name
from [Division] d
inner join [Company] c on d.CompanyId = c.Id
inner join #CompanyIds cids on c.Id = cids.CompanyId