Query multiple tables using one join - sql

I need to query multiple tables that contain the column name 'idClient' for a specific condition.
So far I acquire the tables that I need to query using the following query:
SELECT c.name AS 'ColumnName',
t.name AS 'TableName'
FROM sys.columns c (nolock)
JOIN sys.tables t (nolock) ON c.object_id = t.object_id
WHERE c.name LIKE '%idClient%'
ORDER BY TableName,
ColumnName;
This gives me the following result (in reality there are roughly 100 tables returned):
+------------+-----------------+
| ColumnName | TableName |
+------------+-----------------+
| idClient | tbClient |
| idClient | tbClientContact |
| idClient | tbInvoice |
+------------+-----------------+
In order for me to find all client records in each of the tables I am currently running 3 separate queries for each table name. For example:
SELECT * FROM tbClientContact (nolock)
JOIN tbClient (nolock)
ON tbClientContact.idClient = tbClient.idClient
WHERE tbClient.vcSurname = 'Smith'
Instead of running the above query 3 times for each table, is there an easier way to run the same query on all results that are returned as TableName?
GOAL: I have been tasked with - in the example above - removing any client records from a database where the client surname is 'Smith'. I am running the above SELECT query to find whether the idClient of all clients with the surname of 'Smith' will leave orphan records in tables where there is a link of 'idClient'. I am joining tbClient as the column vcSurname does not exist in any other table than tbClient.

You could try
SELECT idClient FROM tbClient WHERE vcSurname = 'Smith'
UNION ALL
SELECT idClient FROM tbClientContact WHERE vcSurname = 'Smith'
UNION ALL
SELECT idClient FROM tbInvoice WHERE vcSurname = 'Smith'
If you need more columns output, all three queries must have the same number of ouput columns and all of the same type
Edit
As others have suggested, it would be helpful to know what you are doing as the method you are attempting is never the best way of doing something. However, the cursor solution below should build a dynamic query to do what you want
DECLARE #table AS NVARCHAR(128)
DECLARE #sql AS NVARCHAR(4000)
DECLARE c CURSOR FOR
-- hey all the tables with the columns LIKE '%idClient%'
SELECT t.name AS 'TableName'
FROM sys.columns c (nolock)
JOIN sys.tables t (nolock) ON c.object_id = t.object_id
WHERE c.name LIKE '%idClient%'
ORDER BY TableName
-- loop through the results of the query line by line
OPEN c
FETCH NEXT FROM c INTO #table
WHILE ##FETCH_STATUS = 0
BEGIN
-- build the query dynamically
SET #sql =CONCAT(#sql,'SELECT idClient FROM ' + #table + ' WHERE vcSurname = ''Smith'' UNION ALL ')
FETCH NEXT FROM c INTO #table
END
-- remove last "UNION ALL" text
SET #sql = STUFF(#sql,LEN(#sql)-9,11,'')
EXEC sp_executesql #stmt = #sql
CLOSE c
DEALLOCATE c
EDIT EDIT
Just seen your edit. Does your table have foreign / primary key pairs, do the foreign keys have ON DELETE CASCADE? If so, it should just be a case of
DELETE from tbClient WHERE vcSurname = 'Smith'

For automated query:
After question Edit:
SELECT 'SELECT '+c.name+' FROM '+t.name+' T WITH(nolock)
JOIN tbClient (NOLOCK)
ON T.idClient = tbClient.idClient
WHERE vcSurname = ''Smith''
UNION ALL
'
FROM sys.columns c (nolock)
JOIN sys.tables t (nolock) ON c.object_id = t.object_id
WHERE c.name LIKE '%idClient%'
PRINT the result in text format (ctrl+T).
Copy the complete result and then remove last UNION ALL

1 Determine involved tables by querying systables (sysforeignkeys or syscolumns if you don't have FKs).
2 Manually write a comprehensive stored proc that is meant to do everything right
create proc dbo.Client_Del
#client_id int
as
begin try
if not exists(select 1 from dbo.Client c where c.id = #client_id)
raiserror("Client %d not found", 16, 1, #client_id)
begin tran
delete ct
from dbo.ClientContacts ct
where ct.client_id = #client_id
delete idt
from dbo.InvoiceDetail idt
inner join dbo.Invoice i
on i.invoice_id = idt.invoice_id
where i.client_id = #client_id
delete i
from dbo.Invoice i
where i.client_id = #client_id
delete c
from dbo.Client c
where c.client_id = #client_id
commit tran
end try
begin catch
if ##trancount > 0
rollback tran
throw
end catch
GO
3 Invoke your stored proc with an argument
declare #id int
set #id = (select c.client_id from dbo.Client c where c.LastName = 'Smith')
exec dbo.Client_Del
#client_id = #id

I am not sure on what is your need. And this post is enhanced code of #Luv. Use of this, you just run it.
declare #select nvarchar(max) = N''
set #select =
N'
declare #sql nvarchar(max) = N''''
SELECT #sql += '' UNION ALL SELECT ''+c.name+'' FROM ''+t.name+'' T WITH(nolock)
JOIN tbClient (NOLOCK)
ON T.idClient = tbClient.idClient
WHERE vcSurname = ''''Smith''''''
FROM sys.columns c (nolock)
JOIN sys.tables t (nolock) ON c.object_id = t.object_id
WHERE c.name LIKE ''%yourColumnName%''
set #sql = STUFF(#sql, 1, 10, '''')
print #sql
exec sp_executesql #sql
'
exec sp_executesql #select

Related

Dynamically stating FROM which table name to SELECT from

Would it be possible to use a system query to retrieve TABLE name and then SELECT * FROM that TABLE name. Along the lines of:
SELECT * FROM CAST (( SELECT TOP 1 t.Name
FROM sys.tables t
JOIN sys.columns c ON c.OBJECT_ID = t.OBJECT_ID
WHERE c.NAME = 'SomeColumnID' ) AS sys.tables )
The current issue is that the SELECT TOP 1 t.Name will return a string and could it be then cast into a valid Tables.Name.
You need dynamic sql for this: that is, build a query string from a query, then execute it with sp_executesql.
For your use case, that would look like:
declare #q nvarchar(max);
select top (1) #q = N'select * from ' + t.name
from sys.tables t
join sys.columns c on c.object_id = t.object_id
where c.name = 'SomeColumnID'
-- debuug the query
select #q sql;
-- execute the query
execute sp_executesql #q;

Can I join sys.columns to their data?

Pinal Dave has this neat query where he finds all the columns named EmployeeID across all the tables in his database:
select t.name as table_name,
schema_name(schema_id) as schema_name,
c.name as column_name
from sys.tables as t
inner join sys.columns c on t.object_id = c.object_id
where c.name like '%EmployeeID%'
order by schema_name, table_name;
Is there a way to join that data to the data in the matched columns?
I've tried adding an additional join and looking through the other sys.* items to see if one of them sounded like it might be the data.
I have also tried constructing an outer select around this one, but that requires the joins to be in a format that (clearly) doesn't work:
select ( /* above */ ) as foo
inner join foo.table_name bar on bar.something = foo.something --???
Is there something else I can be doing? My ultimate aim is to update EmployeeID for all of the rows in all of the tables.
As mentioned, this can't be done as you are imagining, as data is stored in tables, and the catalog views (sys.) contain information about database objects.
You can achieve what you're after with a cursor and dynamic sql:
DECLARE #col VARCHAR(MAX)
,#table VARCHAR(MAX)
,#schema VARCHAR(MAX)
,#strSQL VARCHAR(MAX)
DECLARE xyz CURSOR
FOR
SELECT c.name
,t.name
,s.name
FROM sys.tables t
JOIN sys.columns c
ON t.object_ID = c.object_ID
JOIN sys.schemas s
ON t.schema_id = s.schema_id
WHERE c.name LIKE '%EmployeeID%';
OPEN xyz
FETCH NEXT FROM xyz
INTO #col,#table,#schema
WHILE ##FETCH_STATUS = 0
BEGIN
SET #strSQL =
'UPDATE '+#schema+'.'+#table+'
SET '+#col+' = ''Something''
'
PRINT (#strSQL)
FETCH NEXT FROM xyz
INTO #col,#table,#schema
END
CLOSE xyz
DEALLOCATE xyz
GO
It's a good idea to test dynamic sql with the PRINT command before actually running it via EXEC.
No, you can't in the way you seem to want.
Try writing code that generates T-SQL in something like LINQPad. Or write code that generates dynamic SQL in T-SQL itself.

SQL Server Select from a table returned by a query as list of table names

If I have these tables below:
PLAYERS
ID Name
== ===========
1 Mick
2 Matt
COACHES
ID Name
== ===========
1 Bill
2 Don
And I have a script below to find all tables that has a column called "Name":
SELECT t.name AS table_name FROM sys.tables AS t
INNER JOIN sys.columns c ON t.OBJECT_ID = c.OBJECT_ID
WHERE c.name LIKE 'Name'
Which returns the following:
table_name
===========
PLAYERS
COACHES
How can I select all the rows from both tables returned by the query above?
You will have to use dynamic sql, try something like this:
declare #t table( tablename varchar(50))
declare #sql varchar(max)
set #sql = ''
insert into #t
SELECT t.name AS table_name FROM sys.tables AS t
INNER JOIN sys.columns c ON t.OBJECT_ID = c.OBJECT_ID
WHERE c.name LIKE 'Name'
select #sql = #sql + 'Select * From ' + tablename + ' union ' from #t
--remove the trailing 'union'
Select #sql = substring(#sql, 1, len(#sql) - 6)
exec (#sql)
The above script creates and executes the following sql
select * from coaches
union
select * from players
Since we are using union here, it is important that all your tables that have name as column is of same structure.
See more about dynamic sql from http://msdn.microsoft.com/en-us/library/ms188001.aspx
SELECT p.Id,p.Name,c.Id,c.Name
FROM Players p JOIN Coaches c
ON p.Id=c.Id
May be this can help you.

How to fetch the row count for all tables in a SQL SERVER database [duplicate]

This question already has answers here:
Query to list number of records in each table in a database
(23 answers)
Closed 8 years ago.
I am searching for a SQL Script that can be used to determine if there is any data (i.e. row count) in any of the tables of a given database.
The idea is to re-incarnate the database in case there are any rows existing (in any of the database).
The database being spoken of is Microsoft SQL SERVER.
Could someone suggest a sample script?
The following SQL will get you the row count of all tables in a database:
CREATE TABLE #counts
(
table_name varchar(255),
row_count int
)
EXEC sp_MSForEachTable #command1='INSERT #counts (table_name, row_count) SELECT ''?'', COUNT(*) FROM ?'
SELECT table_name, row_count FROM #counts ORDER BY table_name, row_count DESC
DROP TABLE #counts
The output will be a list of tables and their row counts.
If you just want the total row count across the whole database, appending:
SELECT SUM(row_count) AS total_row_count FROM #counts
will get you a single value for the total number of rows in the whole database.
If you want to by pass the time and resources it takes to count(*) your 3million row tables. Try this per SQL SERVER Central by Kendal Van Dyke.
Row Counts Using sysindexes
If you're using SQL 2000 you'll need to use sysindexes like so:
-- Shows all user tables and row counts for the current database
-- Remove OBJECTPROPERTY function call to include system objects
SELECT o.NAME,
i.rowcnt
FROM sysindexes AS i
INNER JOIN sysobjects AS o ON i.id = o.id
WHERE i.indid < 2 AND OBJECTPROPERTY(o.id, 'IsMSShipped') = 0
ORDER BY o.NAME
If you're using SQL 2005 or 2008 querying sysindexes will still work but Microsoft advises that sysindexes may be removed in a future version of SQL Server so as a good practice you should use the DMVs instead, like so:
-- Shows all user tables and row counts for the current database
-- Remove is_ms_shipped = 0 check to include system objects
-- i.index_id < 2 indicates clustered index (1) or hash table (0)
SELECT o.name,
ddps.row_count
FROM sys.indexes AS i
INNER JOIN sys.objects AS o ON i.OBJECT_ID = o.OBJECT_ID
INNER JOIN sys.dm_db_partition_stats AS ddps ON i.OBJECT_ID = ddps.OBJECT_ID
AND i.index_id = ddps.index_id
WHERE i.index_id < 2 AND o.is_ms_shipped = 0 ORDER BY o.NAME
Works on Azure, doesn't require stored procs.
SELECT t.name AS table_name
,s.row_count AS row_count
FROM sys.tables t
JOIN sys.dm_db_partition_stats s
ON t.OBJECT_ID = s.OBJECT_ID
AND t.type_desc = 'USER_TABLE'
AND t.name NOT LIKE '%dss%' --Exclude tables created by SQL Data Sync for Azure.
AND s.index_id IN (0, 1)
ORDER BY table_name;
Credit.
This one looks better than the others I think.
USE [enter your db name here]
GO
SELECT SCHEMA_NAME(A.schema_id) + '.' +
--A.Name, SUM(B.rows) AS 'RowCount' Use AVG instead of SUM
A.Name, AVG(B.rows) AS 'RowCount'
FROM sys.objects A
INNER JOIN sys.partitions B ON A.object_id = B.object_id
WHERE A.type = 'U'
GROUP BY A.schema_id, A.Name
GO
Short and sweet
sp_MSForEachTable 'DECLARE #t AS VARCHAR(MAX);
SELECT #t = CAST(COUNT(1) as VARCHAR(MAX))
+ CHAR(9) + CHAR(9) + ''?'' FROM ? ; PRINT #t'
Output:
SELECT
sc.name +'.'+ ta.name TableName, SUM(pa.rows) RowCnt
FROM
sys.tables ta
INNER JOIN sys.partitions pa
ON pa.OBJECT_ID = ta.OBJECT_ID
INNER JOIN sys.schemas sc
ON ta.schema_id = sc.schema_id
WHERE ta.is_ms_shipped = 0 AND pa.index_id IN (1,0)
GROUP BY sc.name,ta.name
ORDER BY SUM(pa.rows) DESC
SQL Server 2005 or later gives quite a nice report showing table sizes - including row counts etc. It's in Standard Reports - and it is Disc Usage by Table.
Programmatically, there's a nice solution at:
http://www.sqlservercentral.com/articles/T-SQL/67624/
Don't use SELECT COUNT(*) FROM TABLENAME, since that is a resource intensive operation. One should use SQL Server Dynamic Management Views or System Catalogs to get the row count information for all tables in a database.
I would make a minor change to Frederik's solution. I would use the sp_spaceused system stored procedure which will also include data and index sizes.
declare c_tables cursor fast_forward for
select table_name from information_schema.tables
open c_tables
declare #tablename varchar(255)
declare #stmt nvarchar(2000)
declare #rowcount int
fetch next from c_tables into #tablename
while ##fetch_status = 0
begin
select #stmt = 'sp_spaceused ' + #tablename
exec sp_executesql #stmt
fetch next from c_tables into #tablename
end
close c_tables
deallocate c_tables
Here's a dynamic SQL approach that also gives you the schema as well:
DECLARE #sql nvarchar(MAX)
SELECT
#sql = COALESCE(#sql + ' UNION ALL ', '') +
'SELECT
''' + s.name + ''' AS ''Schema'',
''' + t.name + ''' AS ''Table'',
COUNT(*) AS Count
FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name)
FROM sys.schemas s
INNER JOIN sys.tables t ON t.schema_id = s.schema_id
ORDER BY
s.name,
t.name
EXEC(#sql)
If needed, it would be trivial to extend this to run over all databases in the instance (join to sys.databases).
select all rows from the information_schema.tables view, and issue a count(*) statement for each entry that has been returned from that view.
declare c_tables cursor fast_forward for
select table_name from information_schema.tables
open c_tables
declare #tablename varchar(255)
declare #stmt nvarchar(2000)
declare #rowcount int
fetch next from c_tables into #tablename
while ##fetch_status = 0
begin
select #stmt = 'select #rowcount = count(*) from ' + #tablename
exec sp_executesql #stmt, N'#rowcount int output', #rowcount=#rowcount OUTPUT
print N'table: ' + #tablename + ' has ' + convert(nvarchar(1000),#rowcount) + ' rows'
fetch next from c_tables into #tablename
end
close c_tables
deallocate c_tables
This is my favorite solution for SQL 2008 , which puts the results into a "TEST" temp table that I can use to sort and get the results that I need :
SET NOCOUNT ON
DBCC UPDATEUSAGE(0)
DROP TABLE #t;
CREATE TABLE #t
(
[name] NVARCHAR(128),
[rows] CHAR(11),
reserved VARCHAR(18),
data VARCHAR(18),
index_size VARCHAR(18),
unused VARCHAR(18)
) ;
INSERT #t EXEC sp_msForEachTable 'EXEC sp_spaceused ''?'''
SELECT * INTO TEST FROM #t;
DROP TABLE #t;
SELECT name, [rows], reserved, data, index_size, unused FROM TEST \
WHERE ([rows] > 0) AND (name LIKE 'XXX%')
SELECT
SUM(sdmvPTNS.row_count) AS [DBRows]
FROM
sys.objects AS sOBJ
INNER JOIN sys.dm_db_partition_stats AS sdmvPTNS
ON sOBJ.object_id = sdmvPTNS.object_id
WHERE
sOBJ.type = 'U'
AND sOBJ.is_ms_shipped = 0
AND sdmvPTNS.index_id < 2
GO

Constructing the FROM in SQL

I'm looking to pull a specific line from a number of table that have a field name criteria1. The problem I'm having is that when I combine the Owner and Table Name and try to call "select criteria1 from #t where linenum = 1" SQL is expecting #t to be a table. I need to know how to construct the full table name and then pass it to this query. I know I can us a programming language to access the DB but i need this to be in SQL. If someone knows of a better way of doing this that would be great too.
declare #next as varchar
declare #owner varchar
while 1=1
begin
set #next = (select top 1 o.name FROM syscolumns c inner join sysobjects o on c.id = o.id
where c.name = 'criteria1' and o.id > #next order by o.id)
if #next is null
break
else
begin
set #owner = (select top 1 u.name
FROM syscolumns c inner join
sysobjects o on c.id = o.id left join
sysusers u on o.uid=u.uid
where c.name = 'criteria1' and o.id = #next order by o.id)
declare #t as varchar
set #t = #owner+'.'+#next
select criteria1 from #t where linenum = 1
end
continue
end
You can build the entire query you want as a varchar() and then execute it with the sp_executesql stored procedure.
http://msdn.microsoft.com/en-us/library/ms188001.aspx
In your case, that bit at the end becomes
declare #sql varchar(512);
set #sql = 'select criteria1 from ' + #t + ' where linenum = 1'
sp_executesql #sql
Have you considered the following construct in a stored procedure?
CASE #tablename
WHEN 'table1' THEN SELECT * FROM table1
WHEN 'table2' THEN SELECT * FROM table2
WHEN 'table3' THEN SELECT * FROM table3
WHEN 'table4' THEN SELECT * FROM table4
END
In case you're married to dynamic SQL (considered to be a bad choice for this problem space), this guide to dynamic SQL should help a lot. It helped me and I've used dynamic SQL extensively.
Thanks for all the help. This is what I ended up with.
declare cur cursor for
select u.name + '.' + o.name tname
FROM sysobject o left join
syscolumns c on c.id = o.id left join
sysusers u on o.uid=u.uid
where c.name = 'criteria1'
declare #tn as varchar(512)
open cur
fetch next from cur into #tn
create table holding_table ( val varchar(512), table_name varchar(512))
declare #sql nvarchar(1000)
while ##FETCH_STATUS = 0
begin
set #sql = 'insert into holding_table select criteria1, ''' + #tn + ''' from ' + #tn + ' where linenum = 1'
execute sp_executesql #sql
fetch next from cur into #tn
end
close cur
deallocate cur
Maybe a view can be used here?
CREATE VIEW vCriterias
AS
SELECT 'Table1' AS TableName,
linenum,
criteria1
FROM Table1
UNION ALL
SELECT 'Table2' AS TableName,
linenum,
criteria1
FROM Table2
UNION ALL
SELECT 'Table3' AS TableName,
linenum,
criteria1
FROM Table3
go
Then selection is like:
SELECT criteria1
FROM vCriterias
WHERE linenum = 3
AND TableName IN ('Table1','Table3')