Read columns in SQL tables which are the result of another query - sql

I need to check that all primary key columns do have all values in uppercase.
So, I have a first request which returns me the table-field pairs which are part of PK.
SELECT table_name, field_name FROM dico WHERE pkey > 0;
(dico is some table which gives that information. No need to look it up in the SQL Schema…)
And, for all those pairs tx/fx listed from that first query above, I need to look for values which would not be uppercased.
SELECT DISTINCT 't1', 'f1', f1 FROM t1 WHERE f1 <> UPPER(f1) UNION ALL
SELECT DISTINCT 't2', 'f2', f2 FROM t2 WHERE f2 <> UPPER(f2) UNION ALL
...
SELECT DISTINCT 'tn', 'fn', fn FROM tn WHERE fn <> UPPER(fn);
(I'm putting the table name and field name as "strings" in the output, so that I know from where the wrong values are coming.)
As you see, I do have the code for both requests, but I do not know how to combine them (if possible, in a generic way that would work for both SQL Server and Oracle).
Can you give me some idea on how to finish that?

One way that I could think of is to use a statement block that contains a loop.
Unfortunately, the structure of a statement block will be different for every different database system (the one for SQL Server will be different for Oracle).
I wrote an example using SQL Server further below (fiddle link is at: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=85cd786adf32247da1aa73c0341d1b72).
Just in case, the dynamic query gets very long (possibly longer than the limit of varchar, which is 8000 characters), SQL Server has varchar(max) that can hold up to 2GB (https://learn.microsoft.com/en-us/sql/t-sql/data-types/char-and-varchar-transact-sql?view=sql-server-ver15). This can be used for #DynamicQuery, replacing VARCHAR(3000) in the example below (modified/alternative fiddle link, just to show that the data type really exists and can be used, is at: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=7fbb5d130aad35e682d8ce7ffaf09ede).
Please note that the example is not using your exact queries because I do not have access to the exact same data as the one you have (e.g. I cannot test the example using dico table because I do not have access to that table).
However, I made the example so that it uses a similar basic structure of logic from your queries, so that later on it can be customised to suit your exact need/scenario (e.g. by changing the table names and field names to match the ones that you use, as well as by adding the WHERE clause as you need).
In the example, your 1st query will be run immediately and the result will be handled by a cursor.
After that, a loop (using WHILE statement/structure) will loop through the cursor for the result of the 1st query to dynamically build the 2nd query (inserting the table names and the field names from the 1st query).
Note that at this point, the 2nd query is still being built, not being run yet.
Eventually, after the loop has finished, the resulting/compiled 2nd query will be run/executed (using the EXEC command).
-- START of test data creation.
create table TableA
( message varchar(200)
);
insert into TableA([message]) values ('abc');
insert into TableA([message]) values ('def');
create table TableB
( message varchar(200)
);
insert into TableB([message]) values ('ghi');
insert into TableB([message]) values ('jkl');
-- END of test data creation.
-- START of dynamic SQL
declare #TableAndFieldDetails CURSOR
declare #TableName VARCHAR(50)
declare #FieldName VARCHAR(50)
declare #DynamicQuery VARCHAR(3000) = ''
begin
SET #TableAndFieldDetails = CURSOR FOR
-- START of the 1st query
SELECT [INFORMATION_SCHEMA].COLUMNS.TABLE_NAME,
[INFORMATION_SCHEMA].COLUMNS.COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE INFORMATION_SCHEMA.COLUMNS.COLUMN_NAME LIKE '%message%'
-- END of the 1st query
-- START of dynamically building the 2nd query
OPEN #TableAndFieldDetails
FETCH NEXT FROM #TableAndFieldDetails INTO #TableName, #FieldName
WHILE ##FETCH_STATUS = 0
BEGIN
IF #DynamicQuery <> ''
BEGIN
SET #DynamicQuery += ' UNION ALL '
END
-- The one line right below is each individual part/element of the 2nd query
SET #DynamicQuery += 'SELECT ''' + #TableName + ''', ''' + #FieldName + ''', ' + #FieldName + ' FROM ' + #TableName
FETCH NEXT FROM #TableAndFieldDetails INTO #TableName, #FieldName
END
CLOSE #TableAndFieldDetails
DEALLOCATE #TableAndFieldDetails
-- END of dynamically building the 2nd query
EXEC (#DynamicQuery)
end
-- END of dynamic SQL

Related

How to pick a table_name value from one table and delete records from the table_name table based on a condition?

We have a table. Lets call it Table_A.
Table_A holds bunch of table_names and numeric value associated to each table_name. Refer to the picture below
Can someone help me write a query to:
Select table_names from TABLE_A one by one; go to that table, Check the Date_inserted of each record against NO_OF_DAYS in Table_A and if the record is older than NO_OF_DAYS in Table_A, then DELETE THAT RECORD from that specific table.
I'm guessing we have to create dynamic values for this query but I'm having a hard time.
So, in the above picture, the query should:
Select the first table_name (T_Table1) from Table_A
Go to that Table (T_Table1)
Check the date inserted of each record in (T_Table1) against the condition
If the condition (IF record was inserted prior to NO_OF_DAYS, which is 90 in this case THEN delete the record; ELSE move to next
record)
Move on to the next table (T_Table2) in Table_A
Continue till all the table_names in Table_A have been executed
What you posted as your attempt (in a comment), quite simply isn't going to work. Let's actually format that first, shall we:
SET SQL = '
DELETE [' + dbo + '].[' + TABLE_NAME + ']
where [Date_inserted ] < '
SET SQL = SQL + ' convert(varchar, DATEADD(day, ' + CONVERT(VARCHAR, NO_OF_DAYS) + ',' + '''' + CONVERT(VARCHAR, GETDATE(), 102) + '''' + '))'
PRINT SQL
EXEC (SQL)
Firstly, I actually have no idea what you're even trying to do here. You have things like [' + dbo + '], which means that you're referencing the column dbo; as you're using a SET, then no column dbo can exist. Also, variables are prefixed with a # in SQL Server; you have none.
Anyway, the solution. Some might not like this one, as I'm using a CURSOR, rather than doing it all in one go. I, however, do have my reasons. A CURSOR isn't actually a "bad" thing, like many believe; the problem is that people constantly use them incorrectly. Using a CURSOR to loop through records and create a hierarchy for example is a terrible idea; there are far better dataset approaches.
So, what are my reasons? Firstly I can parametrise the dynamic SQL; this would be harder outside a CURSOR as I'd need to declare a different parameter for every DELETE. Also, with a CURSOR, if the DELETE fails on one table, it won't on the others; one long piece of dynamic SQL would mean if one of the transactions fail, they would all be rolled back. Also, depending on the size of the deletes, that could be a very big DELETE.
It's important, however, you understand what I've done here; if you don't that's a problem unto itself. What happens if you need to trouble shoot it in the future? SO isn't a website for support like that; you need to support your own code. If you can't, understand the code you're given don't use it or learn what it's doing first (or you're doing the wrong thing).
Note I use my own objects, in the absence of consumable sample data:
CREATE TABLE TableOfTables (TableName sysname,
NoOfDays int);
GO
INSERT INTO TableOfTables
VALUES ('T1',10),
('T2',15),
('T3',5);
GO
DECLARE Deletes CURSOR FOR
SELECT TableName, NoOfDays
FROM TableOfTables;
DECLARE #SQL nvarchar(MAX), #TableName sysname, #Days int;
OPEN Deletes;
FETCH NEXT FROM Deletes
INTO #TableName, #Days;
WHILE ##FETCH_STATUS = 0 BEGIN
SET #SQL = N'DELETE FROM ' + QUOTENAME(#TableName) + NCHAR(10) +
N'WHERE DATEDIFF(DAY, InsertedDate, GETDATE()) >= #dDays;'
PRINT #SQL; --Say hello to your best friend. o/
--EXEC sp_executeSQL #SQL, N'#dDays int', #dDays = #Days; --Uncomment to run
FETCH NEXT FROM Deletes
INTO #TableName, #Days;
END
CLOSE Deletes;
DEALLOCATE Deletes;
GO
DROP TABLE TableOfTables;
GO

Function to select rows from multiple tables based on conditions from different tables

Can anyone please help with this query?
I’m using SQL server 2008 . Objective is to select rows from multiple tables based on condition and values from different tables .
I have table1, table2, tableN with columns as ID,ColumnName,ColumnValue . These are the table I need to select rows based on conditions from below table
Control table with columns Number,Function and Enable
Repository table with columns Function and tableName
I need pass Number and ID as parameters and get details of all Function values from Control table which has Enable value = 1 and by using these Function values collect tableNames from Repository table . And for each tableName returned from Repository table get all rows by using ID value.
The way I understand it you have two tables with schema like this:
table Control (Number int, Function nvarchar, Enable bit)
table Repository (Function nvarchar, TableName nvarchar)
Control and Repositories are related via Function column.
You also have a number of other tables and names of those tables are saved in Repositories tables. All those tables have ID column.
You want to get those table names based on a number and then select from all those tables by their ID column.
If that indeed is what you are trying to do, code bellow should be enough to solve your problem.
declare
-- arguments
#id int = 123,
#number int = 123456,
-- helper variables we'll use along the way
#function nvarchar(4000),
#tableName nvarchar(256),
#query nvarchar(4000)
-- create cursor to iterate over every returned row one by one
declare cursor #tables readonly fast_forward
for
select
c.Function,
r.TableName
from [Control] as c
join [Repository] as r on r.Function = c.Function
where c.Number = #number
and c.Enable = 1
-- initialise cursor
open #tables
-- get first row into variables
fetch next from #tables
into #function, #tableName
-- will be 0 as long as fetch next returns new values
while ##fetch_status = 0
begin
-- build a dynamic query
set #query = 'select * from ' + #tableName + ' where ID = ' + #id
-- execute dynamic query. you might get permission problems
-- dynamic queries are best to avoid, but I don't think there's another solution for this
exec(#query)
-- get next row
fetch next from #tables
into #function, #tableName
end
-- destroy cursor
close #tables
deallocate #tables

SQL Server 2008 : cursor to retrieve and replace data

Maybe there's a better way about this but this has to be somewhat dynamic.
From a vb.net form I need to restore or replace data from one table to another. The two tables are identical except for a couple different columns.
First I wrote some SQL to grab the column names of the table passed in. Then through ordinal position I get only the tables I want values from. I store these tables names in a temp table.
Now I want to get those values from the backup table using the temp table column names and place them in the master table.
So I guess I suppose I need a cursor to loop through in some way.. I haven't touched a cursor since college and wow.
I'll embarrass myself and post my current code.
SET #getColCURSOR = CURSOR FOR
SELECT name
FROM #MyTempTable --created previously as table only holding column names
OPEN #getColCURSOR
FETCH NEXT FROM #getColCURSOR
INTO #columnName
WHILE ##FETCH_STATUS = 0
BEGIN
select #columnName --this variable should as a column name and change
from AUDIT_TABLE a where a.ID = 7 -- 7 is just for testing is dynamic variable
FETCH NEXT FROM #getColCURSOR
INTO #columnName
END
CLOSE #getColCURSOR
DEALLOCATE #getColCURSOR
I'm not going to comment on whether this could be done without a cursor, since I'm a bit lost on what you're trying to do. But one issue with your cursor is that you can't parameterize a column name in a select statement. So you'll need to replace this:
WHILE ##FETCH_STATUS = 0
BEGIN
select #columnName --this variable should as a column name and change
from AUDIT_TABLE a where a.ID = 7 -- 7 is just for testing is dynamic variable
FETCH NEXT FROM getColCURSOR
INTO #columnName
END
--with dynamic SQL like this:
DECLARE #SQL nvarchar(max)
WHILE ##FETCH_STATUS = 0
BEGIN
set #SQL =
N'select ' + QUOTENAME(#columnName) + ' from AUDIT_TABLE a where a.ID = 7'
EXEC (#SQL); --w/o brackets assumes you've calling a stored proc
FETCH NEXT FROM getColCURSOR
INTO #columnName
END
That could possibly introduce other issues, since dynamic SQL statements execute in their own scope. I'd definitely encourage you to look into whether there's a set-based solution to this, since using dynamic SQL will make this even messier, and I don't think you'll be able to escape dynamic SQL if you want to use a variable for column names.

Return multiple columns as single comma separated row in SQL Server 2005

I'm curious to see if this is possible.
I have a table or this could be specific to any old table with data. A simple SELECT will return the columns and rows as a result set. What I'm trying to find out if is possible to return rows but rather than columns, the columns concatenated and are comma separated. So expected amount of rows returned but only one varchar column holding comma separated results of all the columns just like a CSV file.
Thanks.
[UPDATE]
Here is a bit more detail why I'm asking. I don't have the option to do this on the client, this is a task I'm trying to do with SSIS.
Scenario: I have a table that is dynamically created in SSIS but the column names change each time it's built. The original package uses BCP to grab the data and put it into a flat file but due to permissions when run as a job BCP can't create the flat file at the required destination. We can't get this changed either.
The other issue is that with SSIS 2005, using the flat files destination, you have to map the column name from the input source which I can't do because the column names keep changing.
I've written a script task to grab all the data from the original tables and then use stream writer to write to the CSV but I have to loop through each row then through each column to produce the string built up of all the columns. I want to measure performance of this concatenation of columns on sql server against a nasty loop with vb.net.
If I can get sql to produce a single column for each row I can just write a single line to the text file instead of iterating though each column to build the row.
I Think You Should try This
SELECT UserName +','+ Password AS ColumnZ
FROM UserTable
Assuming you know what columns the table has, and you don't want to do something dynamic and crazy, you can do this
SELECT CONCAT(ColumnA, ',', ColumnB) AS ColumnZ
FROM Table
There is a fancy way to this using SQL Server's XML functions, but for starters could you just cast the contents of the columns you care about as varchar and concatenate them with commas?
SELECT cast(colA as varchar)+', '+cast(colB as varchar)+', '+cast(colC as varchar)
FROM table
Note, that this will get tripped up if any of your contents have a comma or double quotes in them, in which case you can also use a replace function on each cast to escape them.
This could stand to be cleaned up some, but you can do this by using the metadata stored in sys.objects and sys.columns along with dynamic SQL. Note that I am NOT a fan of dynamic SQL, but for reporting purposes it shouldn't be too much of a problem.
Some SQL to create test data:
if (object_id('test') is not null)
drop table test;
create table test
(
id uniqueidentifier not null default newId()
,col0 nvarchar(255)
,col1 nvarchar(255)
,col2 nvarchar(255)
,col3 nvarchar(255)
,col4 nvarchar(255)
);
insert into test (col0,col1,col2,col3,col4)
select 'alice','bob','charlie','dave','emily'
union
select 'abby','bill','charlotte','daniel','evan'
A stored proc to build CSV rows:
-- emit the contents of a table as a CSV.
-- #table_name: name of a permanent (in sys.objects) table
-- #debug: set to 1 to print the generated query
create procedure emit_csv(#table_name nvarchar(max), #debug bit = 0)
as
declare #object_id int;
set nocount on;
set #object_id = object_id(#table_name);
declare #name nvarchar(max);
declare db_cursor cursor for
select name
from sys.columns
where object_id = #object_id;
open db_cursor;
fetch next from db_cursor into #name
declare #query nvarchar(max);
set #query = '';
while ##FETCH_STATUS = 0
begin
-- TODO: modify appended clause to escape commas in addition to trimming
set #query = #query + 'rtrim(cast('+#name+' as nvarchar(max)))'
fetch next from db_cursor into #name;
-- add concatenation to the end of the query.
-- TODO: Rearrange #query construction order to make this unnecessary
if (##fetch_status = 0)
set #query = #query + ' + '','' +'
end;
close db_cursor;
deallocate db_cursor;
set #query = 'select rtrim('+#query+') as csvrow from '+#table_name;
if #debug != 0
begin
declare #newline nvarchar(2);
set #newline = char(13) + char(10)
print 'Generated SQL:' + #newline + #query + #newline + #newline;
end
exec (#query);
For my test table, this generates the query:
select
rtrim(rtrim(cast(id as nvarchar(max)))
+ ','
+rtrim(cast(col0 as nvarchar(max)))
+ ','
+rtrim(cast(col1 as nvarchar(max)))
+ ','
+rtrim(cast(col2 as nvarchar(max)))
+ ','
+rtrim(cast(col3 as nvarchar(max)))
+ ','
+rtrim(cast(col4 as nvarchar(max))))
as csvrow
from test
and the result set:
csvrow
-------------------------------------------------------------------------------------------
EEE16C3A-036E-4524-A8B8-7CCD2E575519,alice,bob,charlie,dave,emily
F1EE6C84-D6D9-4621-97E6-AA8716C0643B,abby,bill,charlotte,daniel,evan
Suggestions
Modify the cursor loop to escape commas
Make sure that #table_name refers to a valid table (if object_id(#table_name) is null) in the sproc
Some exception handling would be good
Set permissions on this so that only the account that runs the report can execute it. String concatenation in dynamic SQL can be a big security hole, but I don't see another way to do this.
Some error handling to ensure that the cursor gets closed and deallocated might be nice.
This can be used for any table that is not a #temp table. In that case, you'd have to use sys.objects and sys.columns from tempdb...
select STUFF((select ','+ convert(varchar,l.Subject) from tbl_Student B,tbl_StudentMarks L
where B.Id=L.Id FOR XML PATH('')),1,1,'') Subject FROM tbl_Student A where A.Id=10

Dynamic cursor used in a block in TSQL?

I have the following TSQL codes:
-- 1. define a cursor
DECLARE c_Temp CURSOR FOR
SELECT name FROM employees;
DECLARE #name varchar(100);
-- 2. open it
OPEN c_Temp;
-- 3. first fetch
FETCH NEXT FROM c_Temp INTO #name;
WHILE ##FETCH_STATUS = 0
BEGIN
print #name;
FETCH NEXT FROM c_Temp INTO #name; -- fetch again in a loop
END
-- 4. close it
....
I use the name value only in a loop block. Here I have to
define a cursor variable,
open it,
fetch twice and
close it.
In PL/SQL, the loop can be like this:
FOR rRec IN (SELECT name FROM employees) LOOP
DBMS_OUTPUT.put_line(rRec.name);
END LOOP;
It is much simpler than my TSQL codes. No need to define a cursor. It is created dynamically which is accessible within a loop block (much like C# for loop). Not sure if there something similar like this in TSQL?
Something along these lines might work for you, although it depends on having an ID column or some other unique identifier
Declare #au_id Varchar(20)
Select #au_id = Min(au_id) from authors
While #au_id IS NOT NULL
Begin
Select au_id, au_lname, au_fname from authors Where au_id = #au_id
Select #au_id = min(au_id) from authors where au_id > #au_id
End
Cursors are evil in Sql Server as they can really degrade performance - my favoured approach is to use a Table Variable (>= Sql Server 2005) with an auto inc ID column:
Declare #LoopTable as table (
ID int identity(1,1),
column1 varchar(10),
column2 datetime
)
insert into #LoopTable (column1, column2)
select name, startdate from employees
declare #count int
declare #max int
select #max = max(ID) from #LoopTable
select #count = 1
while #count <= #max
begin
--do something here using row number '#count' from #looptable
set #count = #count + 1
end
It looks pretty long winded however works in any situation and should be far more lightweight than a cursor
Since you are coming from an Oracle background where cursors are used frequently, you may not be aware that in SQl Server cursors are performance killers. Depending on what you are actually doing (surely not just printing the variable), there may be a much faster set-based solution.
In some cases, its also possible to use trick like this one:
DECLARE #name VARCHAR(MAX)
SELECT #name = ISNULL(#name + CHAR(13) + CHAR(10), '') + name
FROM employees
PRINT #name
For a list of employee names.
It can also be used to make comma-separated string, just replace + CHAR(13) + CHAR(10) with + ', '
Why not simply just return the recordset using a select statement. I assume the object is to copy and paste the values in the UI (based on the fact that you are simply printing the output)? In Management studio you can copy and paste from the grid, or press +T and then run the query and return the results as part of the messages tab in plain text.
If you were to run this via an application, the application wouldn't be able to access the printed statements as they are not being returned within a recordset.