Creating a Query for each row in a SQL Table - sql-server

I have a schema in my SQL table out of which some table has a time value stamp (same column name 'timestamp' in all the tables in the schema) and I need to create a new table which will give the latest time stamp for each such table. I have achieved a part which will give me a table with 2 columns, one the table name column and another column which gives the query for each table which if runs will give me the latest timeStamp for each table in table name Column. The script I used is as follows and I show 3 rows as an example:
WITH CTE AS
(
SELECT
CONCAT(schema_name(t.schema_id), '.',t.name) AS table_name,
c.name AS 'time_stamp'
FROM
sys.tables t
INNER JOIN
sys.columns c ON c.object_id = t.object_id
WHERE
schema_name(t.schema_id) = 'PROD'
AND c.name = 'timestamp'
)
SELECT table_name, time_stamp
INTO #TEMP_TABLE
FROM CTE
DECLARE #i int = 1, #c int = (SELECT COUNT(*) FROM #TEMP_TABLE)
DECLARE #Result TABLE
(
tName varchar(500),
tStamp varchar(500)
)
WHILE (#i <= #c)
BEGIN
INSERT INTO #Result
SELECT
table_name,
'SELECT MAX('+ time_stamp +') FROM ' + table_name
FROM #TEMP_TABLE;
SET #i = #i + 1
END
DROP TABLE #TEMP_TABLE
SELECT * FROM #RESULT
When I run this script I get the following table (3 rows shown as an illustration)
My output (O)
tName tStamp
-----------------------------------------------------------
PROD.table_A SELECT MAX(time_stamp) FROM PROD.table_A
PROD.table_B SELECT MAX(time_stamp) FROM PROD.table_B
PROD.table_C SELECT MAX(time_stamp) FROM PROD.table_C
However what I want is the value of the query in the tStamp column and not the query string. So actually the output table should look like (say assuming the query in each of the above rows in column tStamp. I put in some max values as an example when we run each query in tStamp column)
My final expected output (F)
tName tStamp
------------------------------------------
PROD.table_A 2021-10-12 14:20:56.000
PROD.table_B 2021-11-01 19:04:35.000
PROD.table_C 2021-10-23 08:07:12.000
I am in a limbo at this stage not sure, how to get the table F from table O. So I will really appreciate any help. If it can be possible to tweak something which I am doing to get directly the output table F or if we can work on the table O to get to table F anything can help.
Thanks in advance.

If this is a one shot thing, I would consider just using a macro (vim, excel) to generate the query text for each table using your CTE results and then paste it back in and run.
If not, you could consider some of the suggestions for dynamic sql in this article: [https://www.mssqltips.com/sqlservertip/1160/execute-dynamic-sql-commands-in-sql-server/][1]

Related

How to get only the columns that have at least one non-null value in a table existing in SQL server

I have a table named Product with following data for instance:
p_id
p_name
p_cat
1
shirt
null
2
null
null
3
cap
null
Suppose I don't know numbre of rows and columns in the table as well as I don't know which columns are compltely null (no non-null value in all of its rows). How to write a query to retrieve just the columns that have atleast one non-null value in its rows. My approach is as follows but not getting a corret output:
select
column_name
into #TempColumns
from information_schema.columns
where
table_name = 'Product'
and table_schema = 'DDB'
declare #CurrentColumn nvarchar(max) = '', #IsNull bit, #NonNullCols nvarchar(max) = ''
declare Cur cursor for
select column_name from #TempColumns
open Cur
while 1=1
begin
fetch next from Cur into #CurrentColumn
select #IsNull = case when count(#CurrentColumn) > 0 then 0 else 1 end
from Product
if #IsNull = 1
begin
set #NonNullCols = #NonNullCols + ',' + #CurrentColumn
end
if ##fetch_status <> 0 break
end
close Cur
deallocate Cur
select #NonNullCols as NullColumns
drop table #TempColumns
If there is any other approach or correction in my above (T-SQL) query. Thanks in advance.
First I just create a temporary table to store all the column names avaialbe in the Product table. Then I looped in this temporary table and feteched each row and checked it on the product table whether the column is comptely null or not using the count() function. The condition sets the bit variable 1 if the column is completely null and then that particular column name is stored in anothe variable which is then retrieved as null columns.
Here is a conceptual example for you.
It is using SQL Server XML and XQuery powers without dynamic SQL and cursors/loops.
The algorithm is very simple.
When we are converting each row into XML, columns that hold NULL value are missing from the XML.
SQL
USE tempdb;
GO
DROP TABLE IF EXISTS #tmpTable;
CREATE TABLE #tmpTable (
client_id int,
client_name varchar(500),
client_surname varchar(500),
city varchar(500),
state varchar(500));
INSERT #tmpTable VALUES
(1,'Miriam',NULL,'Las Vegas',NULL),
(2,'Astrid',NULL,'Chicago',NULL),
(3,'David',NULL,'Phoenix',NULL),
(4,'Hiroki',NULL,'Orlando',NULL);
SELECT DISTINCT x.value('local-name(.)', 'SYSNAME') AS NotNULLColumns
FROM #tmpTable AS t
CROSS APPLY (SELECT t.* FOR XML PATH(''), TYPE, ROOT('root')) AS t1(c)
CROSS APPLY c.nodes('/root/*') AS t2(x);
SQL #2
To handle edge cases.
SELECT DISTINCT x.value('local-name(.)', 'SYSNAME') AS NotNULLColumns
FROM #tmpTable AS t
CROSS APPLY (SELECT t.* FOR XML RAW, ELEMENTS, BINARY BASE64, TYPE, ROOT('root')) AS t1(c)
CROSS APPLY c.nodes('/root/row/*') AS t2(x);
Output
NotNULLColumns
city
client_id
client_name

How to fiind out the missing records (ID) from an indexed [order] table in sql

I have a table [Order] that has records with sequential ID (in odd number only, i.e. 1,3,5,7...989, 991, 993, 995, 997, 999), it is seen that a few records were accidentally deleted and should be inserted back, first thing is to find out what records are missing in the current table, there are hundreds of records in this table
Don't know how to write the query, can anyone kindly help, please?
I am thinking if I have to write a stored procedure or function but would be better if I can avoid them for environment reasons.
Below peuso code is what I am thinking:
set #MaxValue = Max(numberfield)
set #TestValue = 1
open cursor on recordset ordered by numberfield
foreach numberfield
while (numberfield != #testvalue) and (#testvalue < #MaxValue) then
Insert #testvalue into #temp table
set #testvalue = #textvalue + 2
Next
Next
UPDATE:
Expected result:
Order ID = 7 should be picked up as the only missing record.
Update 2:
If I use
WHERE
o.id IS NULL;
It returns nothing:
Since I didn't get a response from you, in the comments, I've altered the script for you to fill in accordingly:
declare #id int
declare #maxid int
set #id = 1
select #maxid = max([Your ID Column Name]) from [Your Table Name]
declare #IDseq table (id int)
while #id < #maxid --whatever you max is
begin
insert into #IDseq values(#id)
set #id = #id + 1
end
select
s.id
from #IDseq s
left join [Your Table Name] t on s.id = t.[Your ID Column Name]
where t.[Your ID Column Name] is null
Where you see [Your ID Column Name], replace everything with your column name and the same goes for [Your Table Name].
I'm sure this will give you the results you seek.
We can try joining to a number table, which contains all the odd numbers which you might expect to appear in your own table.
DECLARE #start int = 1
DECLARE #end int = 1000
WITH cte AS (
SELECT #start num
UNION ALL
SELECT num + 2 FROM cte WHERE num < #end
)
SELECT num
FROM cte t
LEFT JOIN [Order] o
ON t.num = o.numberfield
WHERE
o.numberfield IS NULL;

How do I loop through a table, search with that data, and then return search criteria and result to new table?

I have a set of records that need to be validated (searched) in a SQL table. I will call these ValData and SearchTable respectively. A colleague created a SQL query in which a record from the ValData can be copied and pasted in to a string variable, and then it is searched in the SearchTable. The best result from the SearchTable is returned. This works very well.
I want to automate this process. I loaded the ValData to SQL in a table like so:
RowID INT, FirstName, LastName, DOB, Date1, Date2, TextDescription.
I want to loop through this set of data, by RowID, and then create a result table that is the ValData joined with the best match from the SearchTable. Again, I already have a query that does that portion. I just need the loop portion, and my SQL skills are virtually non-existent.
Suedo code would be:
DECLARE #SearchID INT = 1
DECLARE #MaxSearchID INT = 15000
DECLARE #FName VARCHAR(50) = ''
DECLARE #FName VARCHAR(50) = ''
etc...
WHILE #SearchID <= #MaxSearchID
BEGIN
SET #FNAME = (SELECT [Fname] FROM ValData WHERE [RowID] = #SearchID)
SET #LNAME = (SELECT [Lname] FROM ValData WHERE [RowID] = #SearchID)
etc...
Do colleague's query, and then insert(?) search criteria joined with the result from the SearchTable in to a temporary result table.
END
SELECT * FROM FinalResultTable;
My biggest lack of knowledge comes in how do I create a temporary result table that is ValData's fields + SearchTable's fields, and during the loop iterations how do I add one row at a time to this temporary result table that includes the ValData joined with the result from the SearchTable?
If it helps, I'm using/wanting to join all fields from ValData and all fields from SearchTable.
Wouldn't this be far easier with a query like this..?
SELECT FNAME,
LNAME
FROM ValData
WHERE (FName = #Fname
OR LName = #Lname)
AND RowID <= #MaxSearchID
ORDER BY RowID ASC;
There is literally no reason to use a WHILE other than to destroy performance of the query.
With a bit more trial and error, I was able to answer what I was looking for (which, at its core, was creating a temp table and then inserting rows in to it).
CREATE TABLE #RESULTTABLE(
[feedname] VARCHAR(100),
...
[SCORE] INT,
[Max Score] INT,
[% Score] FLOAT(4),
[RowID] SMALLINT
)
SET #SearchID = 1
SET #MaxSearchID = (SELECT MAX([RowID]) FROM ValidationData
WHILE #SearchID <= #MaxSearchID
BEGIN
SET #FNAME = (SELECT [Fname] FROM ValidationData WHERE [RowID] = #SearchID)
...
--BEST MATCH QUERY HERE
--Select the "top" best match (order not guaranteed) in to the RESULTTABLE.
INSERT INTO #RESULTTABLE
SELECT TOP 1 *, #SearchID AS RowID
--INTO #RESULTTABLE
FROM #TABLE3
WHERE [% Score] IN (SELECT MAX([% Score]) FROM #TABLE3)
--Drop temp tables that were created/used during best match query.
DROP TABLE #TABLE1
DROP TABLE #TABLE2
DROP TABLE #TABLE3
SET #SearchID = #SearchID + 1
END;
--Join the data that was validated (searched) to the results that were found.
SELECT *
FROM ValidationData vd
LEFT JOIN #RESULTTABLE rt ON rt.[RowID] = vd.[RowID]
ORDER BY vd.[RowID]
DROP TABLE #RESULTTABLE
I know this could be approved by doing a join, probably with the "BEST MATCH QUERY" as an inner query. I am just not that skilled yet. This takes a manual process which took hours upon hours and shortens it to just an hour or so.

How to use two temporary tables with the same name but different structures

Following is what my logic is supposed to do
IF #id = 1
BEGIN
SELECT * INTO #abc from table1
END
IF #id = 2
BEGIN
SELECT * INTO #abc frm table2
END
However, when I execute my statements I get the error saying
there is already an object named #abc..
Any suggestions to overcome this error please?
You can't. The parser doesn't understand your IF logic and it treats both SELECT INTO statements as things that will happen.
What you should do is:
IF #id = 1
BEGIN
SELECT * INTO #abc1 from table1
END
IF #id = 2
BEGIN
SELECT * INTO #abc2 frm table2
END
IF #id = 1
SELECT * FROM #abc1;
ELSE
SELECT * FROM #abc2;
After all, you need to know the different columns that are in the #temp table in order to do anything meaningful with it, right?
(Or avoid temp tables altogether.)
Another possible solution:
CREATE TABLE #abc (
--put schema here
)
IF #id = 1
BEGIN
insert into #abc
select * from table1
END
IF #id = 2
BEGIN
insert into #abc
select * from table2
END
select * from #abc
drop table #abc;
You should always use the column names instead of * because it's better in terms of performance.
And also, select * finds all the columns currently in a table, changes in the structure of a table such as adding, removing, or renaming columns automatically modify the results of select *. Listing columns individually gives you more precise control over the results.

SQL Server: UPDATE a table by using ORDER BY

I would like to know if there is a way to use an order by clause when updating a table. I am updating a table and setting a consecutive number, that's why the order of the update is important. Using the following sql statement, I was able to solve it without using a cursor:
DECLARE #Number INT = 0
UPDATE Test
SET #Number = Number = #Number +1
now what I'd like to to do is an order by clause like so:
DECLARE #Number INT = 0
UPDATE Test
SET #Number = Number = #Number +1
ORDER BY Test.Id DESC
I've read: How to update and order by using ms sql The solutions to this question do not solve the ordering problem - they just filter the items on which the update is applied.
Take care,
Martin
No.
Not a documented 100% supported way. There is an approach sometimes used for calculating running totals called "quirky update" that suggests that it might update in order of clustered index if certain conditions are met but as far as I know this relies completely on empirical observation rather than any guarantee.
But what version of SQL Server are you on? If SQL2005+ you might be able to do something with row_number and a CTE (You can update the CTE)
With cte As
(
SELECT id,Number,
ROW_NUMBER() OVER (ORDER BY id DESC) AS RN
FROM Test
)
UPDATE cte SET Number=RN
You can not use ORDER BY as part of the UPDATE statement (you can use in sub-selects that are part of the update).
UPDATE Test
SET Number = rowNumber
FROM Test
INNER JOIN
(SELECT ID, row_number() OVER (ORDER BY ID DESC) as rowNumber
FROM Test) drRowNumbers ON drRowNumbers.ID = Test.ID
Edit
Following solution could have problems with clustered indexes involved as mentioned here. Thanks to Martin for pointing this out.
The answer is kept to educate those (like me) who don't know all side-effects or ins and outs of SQL Server.
Expanding on the answer gaven by Quassnoi in your link, following works
DECLARE #Test TABLE (Number INTEGER, AText VARCHAR(2), ID INTEGER)
DECLARE #Number INT
INSERT INTO #Test VALUES (1, 'A', 1)
INSERT INTO #Test VALUES (2, 'B', 2)
INSERT INTO #Test VALUES (1, 'E', 5)
INSERT INTO #Test VALUES (3, 'C', 3)
INSERT INTO #Test VALUES (2, 'D', 4)
SET #Number = 0
;WITH q AS (
SELECT TOP 1000000 *
FROM #Test
ORDER BY
ID
)
UPDATE q
SET #Number = Number = #Number + 1
The row_number() function would be the best approach to this problem.
UPDATE T
SET T.Number = R.rowNum
FROM Test T
JOIN (
SELECT T2.id,row_number() over (order by T2.Id desc) rowNum from Test T2
) R on T.id=R.id
update based on Ordering by the order of values in a SQL IN() clause
Solution:
DECLARE #counter int
SET #counter = 0
;WITH q AS
(
select * from Products WHERE ID in (SELECT TOP (10) ID FROM Products WHERE ID IN( 3,2,1)
ORDER BY ID DESC)
)
update q set Display= #counter, #counter = #counter + 1
This updates based on descending 3,2,1
Hope helps someone.
I had a similar problem and solved it using ROW_NUMBER() in combination with the OVER keyword. The task was to retrospectively populate a new TicketNo (integer) field in a simple table based on the original CreatedDate, and grouped by ModuleId - so that ticket numbers started at 1 within each Module group and incremented by date. The table already had a TicketID primary key (a GUID).
Here's the SQL:
UPDATE Tickets SET TicketNo=T2.RowNo
FROM Tickets
INNER JOIN
(select TicketID, TicketNo,
ROW_NUMBER() OVER (PARTITION BY ModuleId ORDER BY DateCreated) AS RowNo from Tickets)
AS T2 ON T2.TicketID = Tickets.TicketID
Worked a treat!
I ran into the same problem and was able to resolve it in very powerful way that allows unlimited sorting possibilities.
I created a View using (saving) 2 sort orders (*explanation on how to do so below).
After that I simply applied the update queries to the View created and it worked great.
Here are the 2 queries I used on the view:
1st Query:
Update MyView
Set SortID=0
2nd Query:
DECLARE #sortID int
SET #sortID = 0
UPDATE MyView
SET #sortID = sortID = #sortID + 1
*To be able to save the sorting on the View I put TOP into the SELECT statement. This very useful workaround allows the View results to be returned sorted as set when the View was created when the View is opened. In my case it looked like:
(NOTE: Using this workaround will place an big load on the server if using a large table and it is therefore recommended to include as few fields as possible in the view if working with large tables)
SELECT TOP (600000)
dbo.Items.ID, dbo.Items.Code, dbo.Items.SortID, dbo.Supplier.Date,
dbo.Supplier.Code AS Expr1
FROM dbo.Items INNER JOIN
dbo.Supplier ON dbo.Items.SupplierCode = dbo.Supplier.Code
ORDER BY dbo.Supplier.Date, dbo.Items.ID DESC
Running: SQL Server 2005 on a Windows Server 2003
Additional Keywords: How to Update a SQL column with Ascending or Descending Numbers - Numeric Values / how to set order in SQL update statement / how to save order by in sql view / increment sql update / auto autoincrement sql update / create sql field with ascending numbers
SET #pos := 0;
UPDATE TABLE_NAME SET Roll_No = ( SELECT #pos := #pos + 1 ) ORDER BY First_Name ASC;
In the above example query simply update the student Roll_No column depending on the student Frist_Name column. From 1 to No_of_records in the table. I hope it's clear now.
IF OBJECT_ID('tempdb..#TAB') IS NOT NULL
BEGIN
DROP TABLE #TAB
END
CREATE TABLE #TAB(CH1 INT,CH2 INT,CH3 INT)
DECLARE #CH2 INT = NULL , #CH3 INT=NULL,#SPID INT=NULL,#SQL NVARCHAR(4000)='', #ParmDefinition NVARCHAR(50)= '',
#RET_MESSAGE AS VARCHAR(8000)='',#RET_ERROR INT=0
SET #ParmDefinition='#SPID INT,#CH2 INT OUTPUT,#CH3 INT OUTPUT'
SET #SQL='UPDATE T
SET CH1=#SPID,#CH2= T.CH2,#CH3= T.CH3
FROM #TAB T WITH(ROWLOCK)
INNER JOIN (
SELECT TOP(1) CH1,CH2,CH3
FROM
#TAB WITH(NOLOCK)
WHERE CH1 IS NULL
ORDER BY CH2 DESC) V ON T.CH2= V.CH2 AND T.CH3= V.CH3'
INSERT INTO #TAB
(CH2 ,CH3 )
SELECT 1,2 UNION ALL
SELECT 2,3 UNION ALL
SELECT 3,4
BEGIN TRY
WHILE EXISTS(SELECT TOP 1 1 FROM #TAB WHERE CH1 IS NULL)
BEGIN
EXECUTE #RET_ERROR = sp_executesql #SQL, #ParmDefinition,#SPID =##SPID, #CH2=#CH2 OUTPUT,#CH3=#CH3 OUTPUT;
SELECT * FROM #TAB
SELECT #CH2,#CH3
END
END TRY
BEGIN CATCH
SET #RET_ERROR=ERROR_NUMBER()
SET #RET_MESSAGE = '#ERROR_NUMBER : ' + CAST(ERROR_NUMBER() AS VARCHAR(255)) + '#ERROR_SEVERITY :' + CAST( ERROR_SEVERITY() AS VARCHAR(255))
+ '#ERROR_STATE :' + CAST(ERROR_STATE() AS VARCHAR(255)) + '#ERROR_LINE :' + CAST( ERROR_LINE() AS VARCHAR(255))
+ '#ERROR_MESSAGE :' + ERROR_MESSAGE() ;
SELECT #RET_ERROR,#RET_MESSAGE;
END CATCH

Resources