Home > Back-end >  Run query for multiple tables and columns
Run query for multiple tables and columns

Time:02-01

I have the following code, I need to run this code through over 200 tables, can I create a list and a for loop in the variable to go through all the tables and fetch the results for each? Also, how can I include the table name for each row in this query? I also need to go through a list on the column but I can't figure that out from the table list.

  DECLARE @table AS VARCHAR(100)
  DECLARE @column as varchar(50)

  set @table = 'x'
  set @column = 'y'

  declare @query as varchar(max)
  set @query = '  SELECT  CAST(MONTH('   @column   ') AS VARCHAR(2))   ''-''   CAST(YEAR('   @column   ') AS VARCHAR(4)) as date, count(*) as SRC_CNT
  FROM '   @table  
  ' WHERE '   @column   ' >= ''2018-01-01'' AND  '   @column   '< ''2021-12-01''
  group BY CAST(MONTH('   @column   ') AS VARCHAR(2))   ''-''     CAST(YEAR('   @column   ') AS VARCHAR(4))
  order by date;'

  exec(@query)

CodePudding user response:

Rather than loop, I would create a dynamic batch, with all the statements you need, which you can then execute. You can use your best friend to debug the statement(s) if needed:

DECLARE @Column sysname = N'y',
        @DateFrom date = '20180101',
        @DateTo date = '20211201';

DECLARE @SQL nvarchar(MAX),
        @CRLF nchar(2) = NCHAR(13)   NCHAR(10);

SELECT @SQL = STRING_AGG(N'SELECT RIGHT(CONVERT(varchar(10),'   QUOTENAME(@Column)   N',105),7) AS [Date],'   @CRLF  
                         --N'       N'   QUOTENAME(t.[name],'''')   N' AS TableName,'   @CRLF   --Uncomment this line if you need it.
                         N'       COUNT(*) AS SRC_CNT'   @CRLF  
                         N'FROM '   QUOTENAME(s.[name])   N'.'   QUOTENAME(t.[name])   @CRLF  
                         N'WHERE '   QUOTENAME(@Column)   N' >= @DateFrom AND '   QUOTENAME(@Column)   N' < @DateTo'   @CRLF  
                         N'GROUP BY RIGHT(CONVERT(varchar(10),'   QUOTENAME(@Column)   N',105),7)'   @CRLF  
                         N'ORDER BY [date];',@CRLF)
FROM sys.schemas s
     JOIN sys.tables t ON s.schema_id = t.schema_id
WHERE EXISTS (SELECT 1
              FROM sys.columns c
              WHERE c.object_id = t.object_id
                AND c.[name] = @Column);

--PRINT @SQL; --Your best friend
EXEC sys.sp_executesql @SQL, N'@DateFrom date, @DateTo date', @DateFrom, @DateTo;

CodePudding user response:

Given tables and sample data:

CREATE TABLE dbo.x(y date, r int);
CREATE TABLE dbo.y(y date, r int);
CREATE TABLE dbo.z(y date, r int);

INSERT dbo.x(y,r) VALUES('20180105',5),
                  ('20180107',6),('20180509',7);
INSERT dbo.y(y,r) VALUES('20180905',5),
                  ('20181007',6),('20181009',7);
INSERT dbo.z(y,r) VALUES('20180605',5),
                  ('20180607',6),('20180609',7);

This dynamic SQL can be generated:

DECLARE @col       sysname = N'y',
        @StartDate date    = '20180101',
        @EndDate   date    = '20211201';


DECLARE @sql  nvarchar(max) = N'',
        @base nvarchar(max) = N'SELECT [Table Name] = $tblQ$, 
    [date] = DATEFROMPARTS(YEAR($col$), MONTH($col$), 1), 
    SRC_CNT = COUNT(*)
  FROM dbo.$tbl$ WHERE $col$ >= @s AND y < @e
  GROUP BY YEAR($col$), MONTH($col$)';
  
SELECT @sql = STRING_AGG(REPLACE(REPLACE(REPLACE
        (@base, N'$tblQ$', QUOTENAME(t.name, char(39))
        ),N'$tbl$',QUOTENAME(t.name)), N'$col$', @col), N'
   UNION ALL
   ')   ' ORDER BY [date];'
  FROM sys.tables AS t
  WHERE EXISTS (SELECT 1 FROM sys.columns
    WHERE [object_id] = t.[object_id]
    AND name = @col);
  
  PRINT @sql;

  EXEC sys.sp_executesql @sql, 
       N'@s date, @e date', 
       @StartDate, @EndDate;

Which produces a query like this (only tables that actually have that column name, could be made even safer by making sure they're using a date/time type):

SELECT [Table Name] = 'x', 
    [date] = DATEFROMPARTS(YEAR(y), MONTH(y), 1), 
    SRC_CNT = COUNT(*)
  FROM dbo.[x] WHERE y >= @s AND y < @e
  GROUP BY YEAR(y), MONTH(y)
   UNION ALL
   SELECT [Table Name] = 'y', 
    [date] = DATEFROMPARTS(YEAR(y), MONTH(y), 1), 
    SRC_CNT = COUNT(*)
  FROM dbo.[y] WHERE y >= @s AND y < @e
  GROUP BY YEAR(y), MONTH(y)
   UNION ALL
   SELECT [Table Name] = 'z', 
    [date] = DATEFROMPARTS(YEAR(y), MONTH(y), 1), 
    SRC_CNT = COUNT(*)
  FROM dbo.[z] WHERE y >= @s AND y < @e
  GROUP BY YEAR(y), MONTH(y) ORDER BY [date];

That generates output like this:

Table Name date SRC_CNT
x 2018-01-01 2
x 2018-05-01 1
z 2018-06-01 3
y 2018-09-01 1
y 2018-10-01 2

If you really want yyyy-MM instead of yyyy-MM-dd on the output, you can just change this line in the declaration of @base:

[date] = CONVERT(char(7), DATEFROMPARTS(YEAR($col$), MONTH($col$), 1), 120),

And I realized the requirement was MM-yyyy, in which case:

[date] = RIGHT(CONVERT(char(10), 
         DATEFROMPARTS(YEAR($col$), MONTH($col$), 1), 105), 7),

CodePudding user response:

I usually do the following for tables:

SELECT TABLE_NAME 
FROM [<DATABASE_NAME>].INFORMATION_SCHEMA.TABLES 
WHERE TABLE_TYPE = 'BASE TABLE'

You said you could handle columns from there.

  •  Tags:  
  • Related