How to loop through several database in SQL server 2005? - sql-server-2005

I have around 50 different databases and inside each of these database there is a table with the same name. Now, I have a written a query which extract selected fields from the table, but I need to manually select the database. How do I write a query so that I can pass database name and pull the data from that table?
Here is my code:
;WITH Review as (
select external_id as sponsorid, name as sponsorname, memberid, startdate, paidamt, code
from test.dev.sraw c left join client.dbo.sponsors s on c.customerid = s.external_id
where year(startdate) >2009 and startdate <> '0001-01-01' and startdate <> '1000-01-01'),
FinalDataCollection as (select sponsorid, sponsorname, count(*) as NbrOfClaims, count(distinct memberid) as NbrOfMembers, month(startdate) as mnth, year(startdate) as yr, sum(cast(paidamt as money)) as dollars, case when code > '0' then 'RX' else 'med' end as category, case when count(distinct memberid)> 0 then sum(cast(paidamt as money))/count(distinct memberid) else 0 end as costpm
from Review
group by sponsorid, sponsorname,year(startdate), month(startdate),case when code > '0' then 'RX' else 'med' end)
select * from FinalDataCollection
UPDATE #1
How do I replace ''SELECT top 5 * FROM '' + #db + ''.dbo.RAW'' of the following query with the above SQL query that start with ;WITH CTE
DECLARE #dbname NVARCHAR(200)
DECLARE #SQLString NVARCHAR (MAX)
SET #SQLString =
'DECLARE #db NVARCHAR(255)
DECLARE DB_CURSOR CURSOR LOCAL FAST_FORWARD FOR
SELECT db.name from sys.databases db WHERE db.name IN
(''A'',''B'',''C'' ) ORDER BY db.name
OPEN DB_CURSOR
FETCH NEXT FROM DB_CURSOR INTO #db
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC(''SELECT top 5 * FROM '' + #db + ''.dbo.RAW'')
FETCH NEXT FROM DB_CURSOR INTO #db
END
CLOSE DB_CURSOR
DEALLOCATE DB_CURSOR'
EXEC sp_executesql #SQLString
Thank you

You could create a stored procedure having one parameter #DbName thus:
CREATE PROCEDURE dbo.GetDataFromMyTable (
#DbName SYSNAME -- or NVARCHAR(128)
)
AS
BEGIN
IF DB_ID(#DbName) IS NOT NULL AND #DbName IN (N'MyDB1', N'MyDB2', N'MyDB3', ...)
BEGIN
DECLARE #SqlStatement NVARCHAR(MAX);
SET #SqlStatement = N'USE ' + QUOTENAME(#DbName) + N'; SELECT t.Col1, t.Col2 FROM dbo.MyTable';
EXEC sp_executesql #SqlStatement;
END
ELSE
RAISERROR('Wrong database.', 16, 1);
END
END;
Notes:
DB_ID(#DbName) IS NOT NULL -- It checks if #DbName exists
#DbName IN (N'MyDB1', N'MyDB2', ..., N'MyDBn') -- It checks if #DbName is on the white list with allowed database (from this point of view, first check is, somehow, redundant).
Why I've used QUOTENAME(#DbName) ? See section Wrapping Parameters with QUOTENAME() and REPLACE(): if the content of #variable is a securable (ex. a database / table) then the recommended wrapper is QUOTENAME(#variable).
You could replace #DbName IN (N'MyDB1', N'MyDB2', ..., N'MyDBn') with exists thus:
EXISTS (
SELECT *
FROM (
SELECT N'MyDB1' UNION ALL
SELECT N'MyDB2' UNION ALL
...
SELECT N'MyDBn'
) dbs(DbName)
WHERE dbs.DbName = #DbName
)
Update #1:
You have to replace
SELECT t.Col1, t.Col2 FROM dbo.MyTable
with
WITH Review as (
select external_id as sponsorid, name as sponsorname, memberid, startdate, paidamt, code
from test.dev.sraw c left join client.dbo.sponsors s on c.customerid = s.external_id
where year(startdate) >2009 and startdate <> ''0001-01-01'' and startdate <> ''1000-01-01''),
FinalDataCollection as (select sponsorid, sponsorname, count(*) as NbrOfClaims, count(distinct memberid) as NbrOfMembers, month(startdate) as mnth, year(startdate) as yr, sum(cast(paidamt as money)) as dollars, case when code > ''0'' then ''RX'' else ''med'' end as category, case when count(distinct memberid)> 0 then sum(cast(paidamt as money))/count(distinct memberid) else 0 end as costpm
from Review
group by sponsorid, sponsorname,year(startdate), month(startdate),case when code > ''0'' then ''RX'' else ''med'' end)
select * from FinalDataCollection

Related

Dynamic Database in SubQuery

SELECT
email, password,GP_employee_id, company,
(select distinct CHEKNMBR from [BSL].[dbo].[UPR30300] WHERE EMPLOYID = GP_employee_id and CHEKDATE > GETDATE() - 20 ) as slip_number,
(select distinct CONVERT(date , CHEKDATE) from [BSL].[dbo].[UPR30300] WHERE EMPLOYID = GP_employee_id and CHEKDATE > GETDATE() - 20 ) as slip_number
FROM [payslips].[dbo].[myapp_user]
I would like [BSL] to be dynamic. The value would depend on the company field of the main query. So I want something like this [company].[dbo].[UPR30300]
You can do one big dynamic UNION ALL query
DECLARE #unioned nvarchar(max) = (
SELECT STRING_AGG(CAST(
'
SELECT *, company = ' + QUOTENAME(company, '''') + '
FROM ' + QUOTENAME(company) + '.[dbo].[UPR30300]
WHERE CHEKDATE > DATEADD(day, -20, GETDATE())
'
AS nvarchar(max)), 'UNION ALL')
FROM (
SELECT DISTINCT company
FROM [payslips].[dbo].[myapp_user]
) au
);
DECLARE #sql nvarchar(max) = '
SELECT
au.email,
au.password,
au.GP_employee_id,
au.company,
slip_number = u.CHEKNMBR,
slip_number2 = CONVERT(date, u.CHEKDATE)
FROM [payslips].[dbo].[myapp_user] au
LEFT JOIN (
' + #unioned + '
) u ON u.company = au.company
AND u.EMPLOYID = au.GP_employee_id;
';
PRINT #sql; -- for testing
EXEC sp_executesql #sql;
DECLARE #sql VARCHAR(1000);
DECLARE #company VARCHAR(50);
DROP TABLE IF EXISTS #myapp_user;
CREATE TABLE #myapp_user
(
email VARCHAR(256),
[password] VARCHAR(256),
GP_employee_id INT,
slip_number INT,
slip_number2 INT
);
DECLARE db_cursor CURSOR FOR
SELECT DISTINCT company FROM [payslips].[dbo].[myapp_user];
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #company
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = '
SELECT
email,
password,
GP_employee_id,
slip_number = (select distinct CHEKNMBR from ' + QUOTENAME(#company) + '.[dbo].[UPR30300] WHERE EMPLOYID = GP_employee_id and CHEKDATE > GETDATE() - 20),
slip_number2 = (select distinct CONVERT(date , CHEKDATE) from ' + QUOTENAME(#company) + '.[dbo].[UPR30300] WHERE EMPLOYID = GP_employee_id and CHEKDATE > GETDATE() - 20)
FROM
[payslips].[dbo].[myapp_user]';
INSERT INTO #myapp_user EXECUTE (#sql);
FETCH NEXT FROM db_cursor INTO #company;
END
CLOSE db_cursor
DEALLOCATE db_cursor
SELECT * FROM #myapp_user;

Query to get max insert stamp of each table from a database

Objective:
I would like to know how recent a table was updated on a daily basis.
Is there a more a efficient way to query the max(insert_stamp) of each table in a db without having to do this:
select 't1' as table_name, max(insert_stamp) as latest_update
from t1
union all
select 't2' as table_name, max(insert_stamp)
from t2
...
or something along the lines of:
use products
go
SELECT st.name as table_name, ... (column name with insert_stamp)
from sys.tables st
where st.name not like ('staging%')
group by st.name
order by 1
I actually really hate this but this is something I just typed up real fast.
I hate using dynamic sql for stuff like this but sometimes you have no choice
This probably couldve been a cursor or a recursive cte
also, ew gross loops.
However this will get you what you want.
SELECT
name
, ROW_NUMBER() OVER (Order by name asc) as ROWID
INTO #tmp
FROM sys.Objects WHERE type='U'
declare #count int, #rowMax int, #date datetime, #sql varchar(max);
set #count = 1
set #rowMax =(SELECT Max(ROWID) FROM #tmp);
CREATE TABLE #dates
(
name varchar(255)
, timestamp datetime
)
WHILE #count <= #RowMax
BEGIN
set #sql = 'SELECT MAX(insert_stamp) FROM '+(SELECT Name FROM #tmp WHERE ROWID=#count)+';'
exec sp_executesql #sql, N'#x datetime out', #date out
INSERT INTO #dates SELECT (SELECT Name FROM #tmp WHERE ROWID=#count), #date
set #count=#count+1
END
SELECT * FROM #dates

Query Across DBs

I'm trying to run a query across multiple databases. Each database is a different customer but I'm querying the same tables across all of them. I want to loop through all the databases (about 100) and put all the results in a table. I've tried a few different ways but I can't quite seem to get it working and it seems like it might be a syntax thing. Below is my code:
IF OBJECT_ID('KW.dbo.Result') IS NOT NULL DROP TABLE KW.dbo.Result;
SELECT name as DBName, ROW_NUMBER() OVER(PARTITION BY 1 ORDER BY name) AS RNo
INTO #DBName
FROM sys.databases
WHERE name LIKE 'Customer%'
ORDER BY name;
SELECT * FROM #DBName
DECLARE #sql varchar;
SET #sql = N'SELECT bh.ID,
b.LINE AS Line#,
c.State,
bh.ZIP,
REPLACE(p.Phone, '-', '') AS Phone,
cc.COMPANY_NAME,
b.Amount
INTO KW.dbo.Result
FROM dbo.Customer c
LEFT JOIN dbo.Change ch ON ch.CIDNo = c.CIDNo
LEFT JOIN dbo.Provider p ON p.PIDNo = ch.PIDNo
LEFT JOIN dbo.BossHead bh ON bh.CHIDNo = ch.CHIDNo
LEFT JOIN dbo.Bills b ON b.BIDNo = bh.BIDNo
LEFT JOIN dbo.PartnerTerms pt ON pt.BIdNO = b.BIdNo
LEFT JOIN dbo.CompanyCode cc ON cc.CompanyCode = pt.CompanyCode
WHERE REPLACE(p.Phone, '-', '') IN
(
SELECT *
FROM KW.dbo.PhoneNumbers
)
AND bh.CreateDate BETWEEN "09-01-2016" AND "10-01-2017"
AND pt.CompanyCode IS NOT NULL
ORDER BY 1';
DECLARE #DB varchar;
DECLARE #i int;
BEGIN TRANSACTION;
SET #i = 1;
WHILE #i <= (SELECT MAX(RNo) FROM #DBName)
BEGIN
SET #DB = (SELECT DBName FROM #DBName WHERE RNo = #i)
USE #DB;
EXECUTE sp_executesql #sql
SET #i = #i + 1
END
COMMIT TRANSACTION;
GO
Error Message: "Msg 102, Level 15, State 1, Line 55
Incorrect syntax near '#DB'."
Any help will be greatly appreciated, thank you!
You can't use a variable to switch tables. What you can do is create a dynamic query that uses the fully qualified database and table name to go across all of the databases at the same time.
Create a query that creates your query:
Use master
Select top(1) case when Row_Number() over (order by d.name) = 1 then '' else
'union ' end + 'select c.name from ' + quotename(d.name) +'.' +'.[dbo].[Customer]'
From sys.databases d
Order by d.name
Work with that until the query does what you want, then remove the top 1 and run the whole query against all of your databases.

How to compare columns and return only 1 of them in SQL

Background: I need to write a function in T-SQL on SQL Server 2008 10.0.5869.
Here's the table I'm working on (for the sake of simplicity - I only put in 3 columns here - but I have 10 columns for the actual work):
ID | Column1 | Column2 | Column3
1 | 2014-05 | 2015-02 | 2013-04
2 | 2012-09 | 2011-02 | 2013-03
ID is varchar and Column(x) are all datetime.
My end goal is to design a function fn_CompareDate to do something like this:
select fn_CompareDate(ID) from table where ID = 1
The query above should return the latest date from Column(x)s which should be 2015-02.
I used CASE WHEN but it would be almost impossible to use it for 10 columns. Is there another way to achieve the same result?
One approach is to use apply:
select d.maxd
from table t cross apply
(select max(d) as maxd
from values ((id, column1), (id, column2), (id, column3)) as val(id, d)
where val.id = t.id
) d
where t.id = 1;
EDIT:
You can do this without values():
select d.maxd
from table t cross apply
(select max(d) as maxd
from (select id, column1 as d union all
select id, column2 union all
select id, column3 union all
select id, column4
) val
where t.id = val.id
) d
where t.id = 1;
I think the below Function serves requirment better
CREATE FUNCTION fn_CompareDate(#ID VARCHAR(10))
RETURNS DATETIME
AS
BEGIN
DECLARE #maxDate DATETIME;
SELECT #maxDate =
(SELECT Max(v)
FROM (VALUES (COLUMN1), (COLUMN2), (COLUMN3)) AS value(v))
FROM table
WHERE ID = #ID
RETURN #maxDate;
END;
Now run the below query
select dbo.fn_CompareDate(ID) from table where ID = 1
Hope you got it.
You can use dynamic sql and INFORMATION_SCHEMA.COLUMNS. It supposed to work in SQL Server 2008. Try this:
CREATE PROCEDURE sp_CompareDate
#ID int,
#tableName NVARCHAR(MAX) = 'table2', -- Your table name
#dbName NVARCHAR(MAX) = 'temp' -- Your database name
AS
BEGIN
DECLARE #maxFieldValue DATETIME
DECLARE #curFieldName NVARCHAR(MAX)
DECLARE #curFieldValue DATETIME
DECLARE #sql NVARCHAR(MAX)
DECLARE fieldCursor CURSOR FOR
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #tableName
AND TABLE_CATALOG = #dbName AND COLUMN_NAME != 'ID'
OPEN fieldCursor
FETCH NEXT FROM fieldCursor INTO #curFieldName
SET #sql = N'USE [' + #dbName + N'] SELECT #curDate=' + #curFieldName
+ N' FROM ' + #tableName + N' WHERE ID=' + CAST(#ID AS NVARCHAR)
EXEC sp_executesql #sql, N'#curDate DATETIME output', #curFieldValue output;
SET #maxFieldValue = #curFieldValue
WHILE (##FETCH_STATUS = 0)
BEGIN
SET #sql = N'USE [' + #dbName + N'] SELECT #curDate=' + #curFieldName
+ N' FROM ' + #tableName + N' WHERE ID=' + CAST(#ID AS NVARCHAR)
EXEC sp_executesql #sql, N'#curDate DATETIME output', #curFieldValue output;
FETCH NEXT FROM fieldCursor INTO #curFieldName
IF (#maxFieldValue < #curFieldValue) SET #maxFieldValue = #curFieldValue
END
CLOSE fieldCursor;
DEALLOCATE fieldCursor;
SELECT #maxFieldValue
END
Hope this helps.
I found the 2nd solution from this question works quite well for me:
Create a function similar to this:
select max(col) from
(
select column1 [col] from table where id = #id
union all
select column2 from table where id = #id
union all
select column3 from table where id = #id
)

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')