i want to know in how many table the data existing in database - sql

i need sql script to check,from a database how many table have data and how many table is empty??

Try this one:
USE dbName
SELECT COUNT(*) from information_schema.tables
WHERE table_type = 'base table'

AS Suggested by # James Z in the comments you can use the standard reports in SSMS.
Right Click on the Database instance
REPORTS-->STANDARD REPORTS-->DISK USAGE BY TABLES

Please try the following...
CREATE PROCEDURE EmptyFullTableCounter AS
BEGIN
DECLARE #fldNameValue VARCHAR( 64 );
DECLARE #sqlStatementString varchar( 200 );
DECLARE #loopIndex INT = 1;
DECLARE #recordCount INT;
DROP TABLE IF EXISTS tempTblTableNames;
CREATE TABLE tempTblTableNames
(
fldName VARCHAR( 64 ),
fldCount INT
);
INSERT INTO tempTblTableNames ( fldName,
fldCount )
SELECT table_name,
0
FROM INFORMATION_SCHEMA.TABLES
WHERE table_type = 'BASE TABLE'
AND table_catalog = 'UserAccessAccounts001'
AND table_name != 'tempTblTableNames';
SET #recordCount = ( SELECT COUNT( * )
FROM tempTblTableNames );
WHILE #loopIndex <= #recordCount
BEGIN
SET #fldNameValue = ( SELECT fldName
FROM ( SELECT fldName,
ROW_NUMBER() OVER ( ORDER BY fldName ) AS recordNumber
FROM ( SELECT fldName
FROM tempTblTableNames
) AS fldNamesFinder
) AS fldNamesWithRowNumber
WHERE recordNumber = #loopIndex );
SET #sqlStatementString = 'UPDATE tempTblTableNames ' +
'SET fldCount = ( SELECT COUNT( * ) ' +
' FROM ' +
#fldNameValue +
' ) ' +
'WHERE fldName = ''' +
#fldNameValue +
''';';
EXEC ( #sqlStatementString );
SET #loopIndex = #loopIndex + 1;
END
SELECT SUM( IIF( fldCount > 0, 1, 0 ) ) AS Haves,
SUM( IIF( fldCount = 0, 1, 0 ) ) AS HaveNots
FROM tempTblTableNames;
DROP TABLE tempTblTableNames;
END
This procedure starts by creating a table to hold the names of each table, then populates it using the following statement...
INSERT INTO tempTblTableNames ( fldName,
fldCount )
SELECT table_name,
0
FROM INFORMATION_SCHEMA.TABLES
WHERE table_type = 'BASE TABLE'
AND table_catalog = 'UserAccessAccounts001'
AND table_name != 'tempTblTableNames';
Please note that the above statement excludes tempTblTableNames from our list of tables.
The procedure then stores a count of the total number of records in the variable #recordCount. This value is used as a sentinel value for a WHILE loop that extracts each table name from tempTblTableNames and constructs around it a statement that will update that table name's associated count in tempTblTableNames. This statement is then executed and the loop index iterated.
Once the loop has completed a final SELECT statement is performed that uses SUM() in conjunction with IIF() to count the number of tables that have records and the number of tables that do not have records.
If you have any questions or comments, then please feel free to post a Comment accordingly.

Related

SQL - query for combining multiple rows

I'm searching but i can't find a solution for my problem.
I've a table (say T1) with this data:
SampleCode
Name
Content
#
1
A#
#
2
B#
#
3
C#
#
1
A#
#
2
B#
#
3
C#
So i need a select query resulting in :
Column 1
Column 2
Column 3
SampleCode
#
#
Name.1
A#
A#
Name.2
B#
B#
Name.3
C#
C#
Does anyone has an hint?
Thanks
Fabio
Check out the 2 options in this article: https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/
If using TSQL, you could use a TRANSPOSE statement. Or you can create the result set using cursors/loops and dynamic SQL.
Fully Dynamic Solution
I can't believe I hammered this out... maybe I did it just to see if I could. It's long and complicated and uses several loops. I think you should re-evaluate what you're trying to do. This can't be run in a view. You would have to put into a function or stored procedure. You may want to evaluate the temp table names to see if they are compatible with your environment. Also, since the temp table name are not unique, you cannot run multiple instances; if you want to run multiple instances, you would have to add a unique identifier to a column in the temp tables or to temp table names. This is fully dynamic... you don't have to know how many columns you need ahead of time.
--**************************************************
--Header area... sample data and temp tables.
--**************************************************
--Clean up sample and result tables if they exist.
IF OBJECT_ID(N'tempdb..#T1') IS NOT NULL
DROP TABLE #T1;
IF OBJECT_ID(N'tempdb..#ResultsTemp') IS NOT NULL
DROP TABLE #ResultsTemp;
IF OBJECT_ID(N'tempdb..#codeTable') IS NOT NULL
DROP TABLE #codeTable;
--For Debugging.
DECLARE #debug int = 1; --0=no debug, 1=show debug messages, do not exec all SQL statements.
--Table var to hold sample data.
CREATE TABLE #T1 (
SampleCode nvarchar(50),
[Name] nvarchar(50),
Content nvarchar(50),
row_num int
);
--Load Sample Data.
INSERT INTO #T1 (SampleCode, [Name], Content)
VALUES
('#', '1', 'A#')
, ('#', '2', 'B#')
, ('#', '3', 'C#')
, ('#', '1', 'A#')
, ('#', '2', 'B#')
, ('#', '3', 'C#')
;
--**********END HEADER**********
--Number the rows so we can loop over them.
UPDATE #T1
SET row_num = newT1.row_num
FROM
(
SELECT t12.SampleCode
, t12.[Name]
, T12.Content
, ROW_NUMBER()OVER(ORDER BY SampleCode, [Name], Content) as row_num
FROM #T1 as t12
) AS newT1
WHERE #T1.SampleCode = newT1.SampleCode
AND #T1.[Name] = newT1.[Name]
AND #T1.Content = newT1.Content
;
SELECT * FROM #T1; --Debugging... just show the contents of #T1 after adding row_num
--Create temp table to load results.
CREATE TABLE #ResultsTemp (
Column1 nvarchar(50)
);
--Create some variable to track looping (without a cursor).
DECLARE #loopCount int = 1;
DECLARE #maxLoops int = (SELECT COUNT(DISTINCT SampleCode) FROM #T1);
DECLARE #sql nvarchar(512);
--Add columns to #ResultsTable dynamically
WHILE (#loopCount <= (#maxLoops))
BEGIN
SET #sql = 'ALTER TABLE #ResultsTemp ADD ' + QUOTENAME('Column' + CAST((#loopCount + 1) as nvarchar)) + ' nvarchar(50) NULL';
IF (#debug = 1) PRINT #sql;
EXEC (#sql);
SET #loopCount = #loopCount + 1;
END
--************************************************************
--SECTION FOR INSERTING FIRST ROW for "SampleCode"
--************************************************************
--Vars for tracking the SampleCode variations processed.
DECLARE #sampleLoop int = 1;
DECLARE #sampleCount int = (SELECT COUNT(DISTINCT SampleCode) FROM #T1);
CREATE TABLE #codeTable (
SampleCode nvarchar(50)
, row_num int
);
--Create a list of unique SampleCodes to loop over.
INSERT INTO #codeTable (SampleCode)
SELECT DISTINCT
SampleCode
FROM #T1;
UPDATE #codeTable
SET row_num = newCT.row_num
FROM
(
SELECT ct2.SampleCode
, ROW_NUMBER()OVER(ORDER BY SampleCode) as row_num
FROM #codeTable as ct2
) AS newCT
WHERE #codeTable.SampleCode = newCT.SampleCode
;
SET #sql = 'INSERT INTO #ResultsTemp (Column1) SELECT ''SampleCode''';
IF (#debug = 1) PRINT #sql;
EXEC (#sql);
WHILE (#sampleLoop <= #sampleCount)
BEGIN
SET #sql = 'UPDATE #ResultsTemp SET Column' + CAST(#SampleLoop + 1 as nvarchar) + '=(SELECT TOP 1 SampleCode FROM #codeTable WHERE row_num = ' + CAST(#sampleLoop as nvarchar) + ');';
IF (#debug = 1) PRINT #sql;
EXEC (#sql);
SET #sampleLoop = #sampleLoop + 1;
END
IF (#debug = 1) SELECT * FROM #ResultsTemp;
--**********END SECTION**********
--**************************************************
--SECTION FOR INSERTING THE REST OF THE CONTENT DATA
--**************************************************
--Vars for tracking number of rows processed from T1.
SET #loopCount = 1;
SET #maxLoops = (SELECT MAX(row_num) FROM #T1);
--Loop over each row in T1.
WHILE (#loopCount <= #maxLoops)
BEGIN
--Create a name for this row.
DECLARE #nameRaw nvarchar(50) = (SELECT TOP 1 [Name] FROM #T1 WHERE row_num = #loopCount);
DECLARE #codeNum nvarchar(50) = (
SELECT TOP 1 ct.row_num
FROM #T1 as t
INNER JOIN #codeTable as ct
ON ct.SampleCode = t.SampleCode
WHERE t.row_num = #loopCount);
DECLARE #name nvarchar(50) = 'Name.' + #nameRaw;
--First insert a row for this Name... if not already in the list.
SET #sql = 'INSERT INTO #ResultsTemp (Column1)
SELECT ''Name.'' + t.[Name]
FROM #T1 as t
LEFT OUTER JOIN #ResultsTemp as rt
ON rt.Column1 = ''' + #name + '''
WHERE t.row_num = ' + CAST(#loopCount as nvarchar) + '
AND rt.Column1 IS NULL;';
IF (#debug = 1) PRINT #sql;
EXEC (#sql);
--Update this Name row with the "content".
SET #sql = 'UPDATE rt
SET Column' + CAST(#codeNum + 1 as nvarchar) + '=t.Content
FROM #ResultsTemp as rt
INNER JOIN #T1 as t
ON t.row_num = ' + CAST(#loopCount as nvarchar) + '
AND t.[Name] = ''' + #nameRaw + '''
WHERE rt.Column1 = ''' + #name + ''';';
IF (#debug = 1) PRINT #sql;
EXEC (#sql);
SET #loopCount = #loopCount + 1;
END
--Show everything in the temp Results table.
SELECT *
FROM #ResultsTemp;
Result Set:
Static Select Solution
This next option is not dynamic. You have to know how many columns you need and then you can copy the "Column3" code to any new columns. You have to change the column name and update the "Select TOP 1" statement as commented for each new column you would copy.
WITH CodeTable AS (
SELECT DISTINCT t.SampleCode
FROM #T1 as t
)
SELECT DISTINCT
'Name.' + [Name]
, (
SELECT TOP 1 Content
FROM #T1 as t2
WHERE t2.SampleCode = (
SELECT TOP 1 SampleCode
FROM CodeTable as ct
ORDER BY SampleCode
)
AND t2.[Name] = t.[Name]
) as Column2
, (
SELECT TOP 1 Content
FROM #T1 as t2
WHERE t2.SampleCode = (
SELECT TOP 1 SampleCode
FROM CodeTable as ct
WHERE ct.SampleCode NOT IN (
SELECT TOP 1 SampleCode --Update the TOP 1 to be TOP [number of previous columns]
FROM CodeTable as ct2
ORDER BY ct2.SampleCode
)
ORDER BY ct.SampleCode
)
AND t2.[Name] = t.[Name]
) as Column3
FROM #T1 as t
Static PivotTable Solution
This solution is from the link I posted. Again, you have to know the number of columns you need and it doesn't have generic column names like you specify. But, it is another solution if you reconsider your output.
--Use PivotTable.
SELECT *
FROM
(
SELECT [Name], Content
FROM #T1
) AS SourceTable PIVOT(MAX(Content) FOR [Content] IN([A#],
[A#]
)) AS PivotTable;
Pivot table can solve your prooblem:
DECLARE #T TABLE (SampleCode sysname, Name sysname, Content sysname)
INSERT #T (SampleCode, Name, Content)
VALUES
('#', '1', 'A#'),
('#', '2', 'B#'),
('#', '3', 'C#'),
('#', '1', 'A#'),
('#', '2', 'B#'),
('#', '3', 'C#')
SELECT CONCAT('Name.', PIVOTTABLE.Name), [#], [#]
FROM
(
SELECT * FROM #T AS t
) AS SOURCE
PIVOT (
MAX(Content)
FOR SampleCode IN ([#], [#])
) AS PIVOTTABLE
Of course this is the solution for what you provided in the question.
When the columns are too many to manually write down then use Dynamic query and the only parts needed to be generate is the values in FOR expression and the same value in SELECT

ms sql server how to check table has “id” column and count rows if "id" exist

There are too many tables in my SQL Server db. Most of them have an 'id' column, but some do not. I want to know which table(s) doesn't have the 'id' column and to count the rows where id=null if an 'id' column exists. The query results may look like this:
TABLE_NAME | HAS_ID | ID_NULL_COUNT | ID_NOT_NULL_COUNT
table1 | false | 0 | 0
table2 | true | 10 | 100
How do I write this query?
Building query:
WITH cte AS (
SELECT t.*, has_id = CASE WHEN COLUMN_NAME = 'ID' THEN 'true' ELSE 'false' END
FROM INFORMATION_SCHEMA.TABLES t
OUTER APPLY (SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS c
WHERE t.TABLE_NAME = c.TABLE_NAME
AND t.[TABLE_SCHEMA] = c.[TABLE_SCHEMA]
AND c.COLUMN_NAME = 'id') s
WHERE t.TABLE_SCHEMA IN (...)
)
SELECT
query_to_run = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
'SELECT tab_name = ''<tab_name>'',
has_id = ''<has_id>'',
id_null_count = <id_null_count>,
id_not_null_count = <id_not_null_count>
FROM <schema_name>.<tab_name>'
,'<tab_name>', TABLE_NAME)
,'<schema_name>', TABLE_SCHEMA)
,'<has_id>', has_id)
,'<id_null_count>', CASE WHEN has_id = 'false' THEN '0' ELSE 'SUM(CASE WHEN id IS NULL THEN 1 END)' END)
,'<id_not_null_count>', CASE WHEN has_id = 'false' THEN '0' ELSE 'COUNT(id)' END)
FROM cte;
Copy the output and execute in separate window. UNION ALL could be added to get single resultset.
db<>fiddle demo
This might be useful for you... lists out the row count for all tables that have an "id" column. It filters out tables that start with "sys" because those are mostly internal tables. If you have a table that starts with "sys", you'll probably want to delete that part of the WHERE clause.
SELECT DISTINCT OBJECT_NAME(r.[object_id]) AS [TableName], [row_count] AS [RowCount]
FROM sys.dm_db_partition_stats r
WHERE index_id = 1
AND EXISTS (SELECT 1 FROM sys.columns c WHERE c.[object_id] = r.[object_id] AND c.[name] = N'id')
AND OBJECT_NAME(r.[object_id]) NOT LIKE 'sys%'
ORDER BY [TableName]
Note you can change the "c.[name] = N'id'" to be any column name, or even change the "=" to "<>" to find only tables without an id column
pmbAustin answers how to list all tables without "ID" column.
To know how many rows in each table, SQL Server has a built-in report for you.
Right click the database in SSMS, click "Reports", "Standard Reports" then "Disk Usage by Table"
You now know how many rows in each table, and from pmbAustin's answer you know how which tables do and do not have "ID" columns. with a simple Vlookup in Excel you can combine these two datasets to arrive at any answer you wish.
This will give you the info about which tables have or not have column named "ID":
SELECT Table_Name
, case when column_name not like '%ID%' then 'false'
else 'true'
end as HAS_ID
FROM INFORMATION_SCHEMA.COLUMNS;
Here is a small demo
And here is one way that you can use to select all the tables that have columns named ID and if this columns are null or not:
CREATE TABLE #AllIDSNullable (TABLE_NAME NVARCHAR(256) NOT NULL
, HAS_ID VARCHAR(10)
, ID_NULL_COUNT INT DEFAULT 0
, ID_NOT_NULL_COUNT INT DEFAULT 0);
DECLARE CT CURSOR FOR
SELECT Table_Name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE column_name = 'ID';
DECLARE #name NVARCHAR(MAX), #SQL NVARCHAR(MAX);
OPEN CT; FETCH NEXT FROM CT INTO #name;
WHILE ##FETCH_STATUS=0 BEGIN
SET #SQL = 'INSERT #AllIDSNullable (TABLE_NAME , HAS_ID) SELECT Table_Name, case when column_name not like ''%ID%'' then ''false'' else ''true'' end FROM INFORMATION_SCHEMA.COLUMNS;';
EXEC (#SQL);
SET #SQL = 'UPDATE #AllIDSNullable SET ID_NULL_COUNT = (SELECT COUNT(*) FROM ['+#name+'] WHERE ID IS NULL), ID_NOT_NULL_COUNT = (SELECT COUNT(*) FROM ['+#name+'] WHERE ID IS NOT NULL) WHERE TABLE_NAME='''+#name+''';';
EXEC (#SQL);
FETCH NEXT FROM CT INTO #name;
END;
CLOSE CT;
SELECT *
FROM #AllIDSNullable;
Here is a demo
Result:

Insert into table the outcome of a select on that table using Row_Number

I am creating a query where in I select data on a table, then select a number of rows from that table, to then insert those rows into another identical table in another Database, and then repeat the proces to select the next number of rows from the orignal table.
For Reference, this is what i try to do (already build it for Oracle):
$" INSERT INTO {destination-table}
SELECT * FROM {original-table}
WHERE ROWID IN (SELECT B.RID
FROM (SELECT ROWID AS RID, rownum as RID2
FROM {original-table}
WHERE {Where Claus}
AND ROWNUM <= {recordsPerStatement * iteration}
) B WHERE RID2 > {recordsPerStatement * (iteration - 1)})"
This is put through a loop in .net
For SQL server however I fail to get this done. The data i retrieve with:
$" Select B.* from (Select A.* from (Select Row_NUMBER()
OVER (order by %%physloc%%) As RowID, {original-table}.* FROM
{original-table} where {where-claus})
A Where A.RowID between {recordsPerStatement * (iteration - 1)}
AND {recordsPerStatement * iteration} B"
The problem here is that above select produces an extra column (ROWID) which prevents me from inserting the above data into the destination-table
I have been looking at ways to get rid of the ROWID column in the top select or to insert data from original-table based on the data retrieved
(something like insert into destination-table select * from original-table where exists in (rest of select query)..... but to no avail
TLDR = Get rid of a ROWID column used in calculations to then be able to insert rows into an identical table
specifications:
A LOT (millions of rows) of data (therefor processing it in bits)
Unknown tables (so i cannot call on specific column names, as they are unknown)
needs to have an order (thus the row_number) so the same data is not copied twice.
insert using a select query (as first retrieving it and doing some magic locally would severly impact performance)
If necessary additional variables can be added in here (like an order claus variable) however, any reference to data in the query will ALWAYS be a variable + If I can find a way to not add more varriables in the query then that would be preferable
I hope that someone would have an idea on what i could look at further.
This approach uses a temporary table to save the paginated data before processing it page by page. It has worked for me, but not sure if you might have problems with very large data sets. You could put the whole thing in an SP then call the SP with parameters from .net. You will need to add a parameter for the destination table name and construct/execute an INSERT statement in the final loop.
-- Parameters
DECLARE #PageSize integer = 100;
DECLARE #TableName nVarchar(200) = 'WRD_WordHits';
DECLARE #OrderBy nVarchar(3000) = 'WordID'
STEP_010: BEGIN
-- Get the column definitions for the table
DECLARE #Cols int;
SELECT TABLE_NAME, ORDINAL_POSITION, COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH
, IS_NULLABLE
INTO #Tspec
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #TableName;
-- Number of columns
SET #Cols = ##ROWCOUNT;
END;
STEP_020: BEGIN
-- Create the temporary table that will hold the paginated data
CREATE TABLE #TT2 ( PageNumber int, LineNumber int, SSEQ int )
DECLARE #STMT nvarchar(3000);
END;
STEP_030: BEGIN
-- Add columns to #TT2 using the column definitions
DECLARE #Ord int = 0;
DECLARE #Colspec nvarchar(3000) = '';
DECLARE #AllCols nvarchar(3000) = '';
DECLARE #ColName nvarchar(200) = '';
WHILE #Ord < #Cols BEGIN
SELECT #Ord = #Ord + 1;
-- Get the column name and specification
SELECT #ColName = Column_Name
, #Colspec =
Column_Name + ' ' + DATA_TYPE + CASE WHEN CHARACTER_MAXIMUM_LENGTH IS NULL THEN ''
ELSE '(' + CAST(CHARACTER_MAXIMUM_LENGTH AS varchar(30) ) + ')' END
FROM #Tspec WHERE ORDINAL_POSITION = #Ord;
-- Create and execute statement to add the column and the columns list used later
SELECT #STMT = ' ALTER TABLE #TT2 ADD ' + #Colspec + ';'
, #AllCols = #AllCols + ', ' + #ColName ;
EXEC sp_ExecuteSQL #STMT;
END;
-- Remove leading comma from columns list
SELECT #AllCols = SUBSTRING(#AllCols, 3, 3000);
PRINT #AllCols
-- Finished with the source table spec
DROP TABLE #Tspec;
END;
STEP_040: BEGIN -- Create and execute the statement used to fill #TT2 with the paginated data from the source table
-- The first two cols are the page number and row number within the page
-- The sequence is arbitrary but could use a key list for the order by clause
SELECT #STMT =
'INSERT #TT2
SELECT FLOOR( CAST( SSEQ as float) /' + CAST(#PageSize as nvarchar(10)) + ' ) + 1 PageNumber, (SSEQ) % ' + CAST(#PageSize as nvarchar(10)) + ' + 1 LineNumber, * FROM
(
SELECT ROW_NUMBER() OVER ( ORDER BY ' + #OrderBy + ' ) - 1 AS SSEQ, * FROM ' + #TableName + '
)
A; ' ;
EXEC sp_ExecuteSQL #STMT;
-- *** Test only to show that the table contains the data
--SELECT * FROM #TT2;
--SELECT #STMT = 'SELECT NULL AS EXECSELECT, ' + #AllCols + ' FROM #TT2;' ;
--EXEC sp_ExecuteSQL #STMT;
-- ***
END;
STEP_050: BEGIN -- Loop through paginated data, one page at a time.
-- Variables to control the paginated loop
DECLARE #PageMAX int;
SELECT #PageMAX = MAX(PageNumber) FROM #TT2;
PRINT 'Generated ' + CAST( #PageMAX AS varchar(10) ) + ' pages from table';
DECLARE #Page int = 0;
WHILE #Page < #PageMax BEGIN
SELECT #Page = #Page + 1;
-- Create and execute the statement to get one page of data - this could be any statement to process data page by page
SELECT #STMT = 'SELECT ' + #AllCols + ' FROM #TT2 WHERE PageNumber = ' + CAST(#Page AS Varchar(10 )) + ' ORDER BY LineNumber '
-- Execute the statment.
PRINT #STMT -- For testing
--EXEC sp_EXECUTESQL #STMT;
END;
-- Finished with Paginated data
DROP TABLE #TT2;
END;
The solution i came up with:
First reading the column_names from the database and storing them locally, to then use them again in building up the insert / select query and only select those columns from the view (which are all apart from ROWID).
commandText = $"SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = N'{table}'"
columnNames = "executionfunction with commandText"
columnNamesCount = columnNames.Rows.Count
Dim counter As Int16 = 0
commandText = String.Empty
commandText = $"INSERT INTO {destination} SELECT "
For Each row As DataRow In columnNames.Rows
If counter = columnNamesCount - 1 Then
commandText += $"B.{row("column_name")} "
Else
commandText += $"B.{row("column_name")}, "
End If
counter = counter + 1
Next
commandText += $"FROM
(Select A.* FROM (Select Row_NUMBER()
OVER(order by %%physloc%%) AS RowID, {table}.*
FROM {table} where {filter}) A
WHERE A.RowID between ({recordsPerStatement} * ({iteration}-1)) + 1
AND ({recordsPerStatement} * {iteration})) B"
EDIT: To remove the %%physloc%% clause AN OFFSET FETCH NEXT part has been build in. new approach:
commandText += $"INSERT INTO {destination} SELECT * FROM {table} WHERE {filter}"
For i As Int16 = 1 To columnNamesCount
If i = 1 Then
commandText += $"ORDER BY {columnNames.Rows(i - 1)("column_name")} ASC"
Else
commandText += $"{columnNames.Rows(i - 1)("column_name")} ASC"
End If
If i <> columnNamesCount Then
commandText += ", "
End If
Next
commandText += $" OFFSET ({recordsPerStatement} * ({iteration} -1)) ROWS FETCH Next {recordsPerStatement} ROWS ONLY"

How to UPDATE all columns of a record without having to list every column

I'm trying to figure out a way to update a record without having to list every column name that needs to be updated.
For instance, it would be nice if I could use something similar to the following:
// the parts inside braces are what I am trying to figure out
UPDATE Employee
SET {all columns, without listing each of them}
WITH {this record with id of '111' from other table}
WHERE employee_id = '100'
If this can be done, what would be the most straightforward/efficient way of writing such a query?
It's not possible.
What you're trying to do is not part of SQL specification and is not supported by any database vendor. See the specifications of SQL UPDATE statements for MySQL, Postgresql, MSSQL, Oracle, Firebird, Teradata. Every one of those supports only below syntax:
UPDATE table_reference
SET column1 = {expression} [, column2 = {expression}] ...
[WHERE ...]
This is not posible, but..
you can doit:
begin tran
delete from table where CONDITION
insert into table select * from EqualDesingTabletoTable where CONDITION
commit tran
be carefoul with identity fields.
Here's a hardcore way to do it with SQL SERVER. Carefully consider security and integrity before you try it, though.
This uses schema to get the names of all the columns and then puts together a big update statement to update all columns except ID column, which it uses to join the tables.
This only works for a single column key, not composites.
usage: EXEC UPDATE_ALL 'source_table','destination_table','id_column'
CREATE PROCEDURE UPDATE_ALL
#SOURCE VARCHAR(100),
#DEST VARCHAR(100),
#ID VARCHAR(100)
AS
DECLARE #SQL VARCHAR(MAX) =
'UPDATE D SET ' +
-- Google 'for xml path stuff' This gets the rows from query results and
-- turns into comma separated list.
STUFF((SELECT ', D.'+ COLUMN_NAME + ' = S.' + COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #DEST
AND COLUMN_NAME <> #ID
FOR XML PATH('')),1,1,'')
+ ' FROM ' + #SOURCE + ' S JOIN ' + #DEST + ' D ON S.' + #ID + ' = D.' + #ID
--SELECT #SQL
EXEC (#SQL)
In Oracle PL/SQL, you can use the following syntax:
DECLARE
r my_table%ROWTYPE;
BEGIN
r.a := 1;
r.b := 2;
...
UPDATE my_table
SET ROW = r
WHERE id = r.id;
END;
Of course that just moves the burden from the UPDATE statement to the record construction, but you might already have fetched the record from somewhere.
How about using Merge?
https://technet.microsoft.com/en-us/library/bb522522(v=sql.105).aspx
It gives you the ability to run Insert, Update, and Delete. One other piece of advice is if you're going to be updating a large data set with indexes, and the source subset is smaller than your target but both tables are very large, move the changes to a temporary table first. I tried to merge two tables that were nearly two million rows each and 20 records took 22 minutes. Once I moved the deltas over to a temp table, it took seconds.
If you are using Oracle, you can use rowtype
declare
var_x TABLE_A%ROWTYPE;
Begin
select * into var_x
from TABLE_B where rownum = 1;
update TABLE_A set row = var_x
where ID = var_x.ID;
end;
/
given that TABLE_A and TABLE_B are of same schema
It is possible. Like npe said it's not a standard practice. But if you really have to:
1. First a scalar function
CREATE FUNCTION [dte].[getCleanUpdateQuery] (#pTableName varchar(40), #pQueryFirstPart VARCHAR(200) = '', #pQueryLastPart VARCHAR(200) = '', #pIncludeCurVal BIT = 1)
RETURNS VARCHAR(8000) AS
BEGIN
DECLARE #pQuery VARCHAR(8000);
WITH cte_Temp
AS
(
SELECT
C.name
FROM SYS.COLUMNS AS C
INNER JOIN SYS.TABLES AS T ON T.object_id = C.object_id
WHERE T.name = #pTableName
)
SELECT #pQuery = (
CASE #pIncludeCurVal
WHEN 0 THEN
(
STUFF(
(SELECT ', ' + name + ' = ' + #pQueryFirstPart + #pQueryLastPart FROM cte_Temp FOR XML PATH('')), 1, 2, ''
)
)
ELSE
(
STUFF(
(SELECT ', ' + name + ' = ' + #pQueryFirstPart + name + #pQueryLastPart FROM cte_Temp FOR XML PATH('')), 1, 2, ''
)
) END)
RETURN 'UPDATE ' + #pTableName + ' SET ' + #pQuery
END
2. Use it like this
DECLARE #pQuery VARCHAR(8000) = dte.getCleanUpdateQuery(<your table name>, <query part before current value>, <query part after current value>, <1 if current value is used. 0 if updating everything to a static value>);
EXEC (#pQuery)
Example 1: make all employees columns 'Unknown' (you need to make sure column type matches the intended value:
DECLARE #pQuery VARCHAR(8000) = dte.getCleanUpdateQuery('employee', '', 'Unknown', 0);
EXEC (#pQuery)
Example 2: Remove an undesired text qualifier (e.g. #)
DECLARE #pQuery VARCHAR(8000) = dte.getCleanUpdateQuery('employee', 'REPLACE(', ', ''#'', '''')', 1);
EXEC (#pQuery)
This query can be improved. This is just the one I saved and sometime I use. You get the idea.
Similar to an upsert, you could check if the item exists on the table, if so, delete it and insert it with the new values (technically updating it) but you would lose your rowid if that's something sensitive to keep in your case.
Behold, the updelsert
IF NOT EXISTS (SELECT * FROM Employee WHERE ID = #SomeID)
INSERT INTO Employee VALUES(#SomeID, #Your, #Vals, #Here)
ELSE
DELETE FROM Employee WHERE ID = #SomeID
INSERT INTO Employee VALUES(#SomeID, #Your, #Vals, #Here)
you could do it by deleting the column in the table and adding the column back in and adding a default value of whatever you needed it to be. then saving this will require to rebuild the table

Delete Duplicate Records with Same Values

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