SQL Select Column From Table Based on Another Select Statement - sql

I have a table containing column names from another table. I want to run an update statement to update some values from that table, based off of the other.
EX:
TableA
ID|Column1|Column2
1 | 1.3 | 2.3
2 | 0 | 7
3 | 2.5 | 12.1
TableB
ID|ColumnName|MaxValue
1 | Column1 | NULL
2 | Column2 | NULL
Something along the lines of this:
So in this case, I would want to update MaxValue in TableB to be the max value from TableA where ColumnName is a colum in TableA.
Is this possible?

You can do it with a cursor and some dynamic sql. This isn't the best thing to do but if you needed a quick and dirty solution here you go:
DECLARE #colName VARCHAR(50), #str VARCHAR(2000), #id int
DECLARE c CURSOR FOR
SELECT id, columnName
FROM tableB
OPEN c
FETCH NEXT FROM c INTO #id, #columnName
WHILE ##fetch_status = 0
BEGIN
SET #str = 'update tableB set MaxValue = ( select max(' + #colName + ') from
tableA ) where id = ' + CONVERT(VARCHAR, #id)
EXEC ( #str )
FETCH NEXT FROM c INTO #id, #columnName
END
CLOSE c
DEALLOCATE c

If you do not want to use dynamic SQL, you could always do something like this
Update TableB
Set MaxValue = MaxValues.MaxValue
From TableB
Join
(
Select MaxValue = Max(Column1)
,ColumnName = 'Column1'
From TableA
Union All
Select MaxValue = Max(Column2)
,ColumnName = 'Column2'
From TableA
-- Union All ... and so on for all columns
) MaxValues
On TableB.ColumnName = MaxValues.ColumnName
Remember, if the TableA DDL changes, you must update this DML.

Related

SQL pivot table1 into table2 without using dynamic SQL pivot or hardcode query

I have seen many questions and answers given about pivoting table with SQL, with dynamic SQL pivot or hard code query with CASE WHEN.
However is there any way I can pivot table without using those 2?
Table 1:
| col1 | col2 | col3 |
|--------|-------|--------|
| ABCD | 1 | XY123 |
| ABCD | 2 | RT789 |
| PQST | 3 | XY123 |
| PQST | 4 | RT789 |
Pivoting to
| col1 | ABCD | PQST |
|--------|-------|-------|
| XY123 | 1 | 3 |
| RT789 | 2 | 4 |
My idea was to retrieve the structure of the col with:
WITH
structure AS (
SELECT DISTINCT
col3 AS col1, col1 AS colName, col2 AS values
FROM table1 ori
)
and then extracting matched values of each cell with joins and storing them temporarily. At last JOIN again populating them in the output. However I am stuck after the above step. I can't use PIVOT and have to do this dynamically (i.e. can't use the method to hardcode each value with CASE WHEN)
How can I achieve this?
This is not as efficient (and not as easy to code) as a dynamic pivot. However, it is doable.
It does all need to be dynamic e.g., creating each SQL statement as a string and executing that.
The process involves
Determine the column names (store in a temporary table)
Creating the table with the first column only
Populating that first column
For each additional column name
Adding a column to the table (dynamically)
Populating that column with data
You haven't specified the database - I'll illustrate the following below using SQL Server/T-SQL.
The following are in this db<>fiddle so you can see what's going on.
CREATE TABLE #ColNames (ColNum int IDENTITY(1,1), ColName nvarchar(100), ColNametxt nvarchar(100));
INSERT INTO #ColNames (ColName, ColNametxt)
SELECT DISTINCT QUOTENAME(Col1), Col1
FROM table1;
This will populate the #ColNames table with the values 1, [ABCD], ABCD, 2, [PQST], PQST.
The next step is to create your output table - I'll call it #pvttable
CREATE TABLE #pvttable (col1 nvarchar(100) PRIMARY KEY);
INSERT INTO #pvttable (col1)
SELECT DISTINCT Col3
FROM table1;
This creates your table with 1 column (col1) with values XY123 and RT789).
The write your favorite loop (e.g., cursor, while loop). In each step
Get the next column name
Add the column to the table
Update that column with appropriate data
e.g., the following is an illustrative example with your data.
DECLARE #CustomSQL nvarchar(4000);
DECLARE #n int = 1;
DECLARE #ColName nvarchar(100);
DECLARE #ColNametxt nvarchar(100);
SELECT #ColName = ColName,
#ColNameTxt = ColNameTxt
FROM #ColNames
WHERE ColNum = #n;
WHILE #ColName IS NOT NULL
BEGIN
SET #CustomSQL = N'ALTER TABLE #pvttable ADD ' + #ColName + N' nvarchar(100);';
EXEC (#CustomSQL);
SET #CustomSQL =
N'UPDATE #pvttable SET ' + #Colname + N' = table1.col2'
+ N' FROM #pvttable INNER JOIN table1 ON #pvttable.col1 = table1.col3'
+ N' WHERE table1.col1 = N''' + #ColNametxt + N''';';
EXEC (#CustomSQL);
SET #n += 1;
SET #ColName = NULL;
SET #ColNametxt = NULL;
SELECT #ColName = ColName,
#ColNameTxt = ColNameTxt
FROM #ColNames
WHERE ColNum = #n;
END;
SELECT * FROM #pvttable;

SQL Loop through tables and columns to find which columns are NOT empty

I created a temp table #test containing 3 fields: ColumnName, TableName, and Id.
I would like to see which rows in the #test table (columns in their respective tables) are not empty? I.e., for every column name that i have in the ColumnName field, and for the corresponding table found in the TableName field, i would like to see whether the column is empty or not. Tried some things (see below) but didn't get anywhere. Help, please.
declare #LoopCounter INT = 1, #maxloopcounter int, #test varchar(100),
#test2 varchar(100), #check int
set #maxloopcounter = (select count(TableName) from #test)
while #LoopCounter <= #maxloopcounter
begin
DECLARE #PropIDs TABLE (tablename varchar(max), id int )
Insert into #PropIDs (tablename, id)
SELECT [tableName], id FROM #test
where id = #LoopCounter
set #test2 = (select columnname from #test where id = #LoopCounter)
declare #sss varchar(max)
set #sss = (select tablename from #PropIDs where id = #LoopCounter)
set #check = (select count(#test2)
from (select tablename
from #PropIDs
where id = #LoopCounter) A
)
print #test2
print #sss
print #check
set #LoopCounter = #LoopCounter + 1
end
In order to use variables as column names and table names in your #Check= query, you will need to use Dynamic SQL.
There is most likely a better way to do this but I cant think of one off hand. Here is what I would do.
Use the select and declare a cursor rather than a while loop as you have it. That way you dont have to count on sequential id's. The cursor would fetch fields columnname, id and tablename
In the loop build a dynamic sql statement
Set #Sql = 'Select Count(*) Cnt Into #Temp2 From ' + TableName + ' Where ' + #columnname + ' Is not null And ' + #columnname <> '''''
Exec(#Sql)
Then check #Temp2 for a value greater than 0 and if this is what you desire you can use the #id that was fetched to update your #Temp table. Putting the result into a scalar variable rather than a temp table would be preferred but cant remember the best way to do that and using a temp table allows you to use an update join so it would well in my opinion.
https://www.mssqltips.com/sqlservertip/1599/sql-server-cursor-example/
http://www.sommarskog.se/dynamic_sql.html
Found a way to extract all non-empty tables from the schema, then just joined with the initial temp table that I had created.
select A.tablename, B.[row_count]
from (select * from #test) A
left join
(SELECT r.table_name, r.row_count, r.[object_id]
FROM sys.tables t
INNER JOIN (
SELECT OBJECT_NAME(s.[object_id]) table_name, SUM(s.row_count) row_count, s.[object_id]
FROM sys.dm_db_partition_stats s
WHERE s.index_id in (0,1)
GROUP BY s.[object_id]
) r on t.[object_id] = r.[object_id]
WHERE r.row_count > 0 ) B
on A.[TableName] = B.[table_name]
WHERE ROW_COUNT > 0
order by b.row_count desc
How about this one - bitmask computed column checks for NULLability. Value in the bitmask tells you if a column is NULL or not. Counting base 2.
CREATE TABLE FindNullComputedMask
(ID int
,val int
,valstr varchar(3)
,NotEmpty as
CASE WHEN ID IS NULL THEN 0 ELSE 1 END
|
CASE WHEN val IS NULL THEN 0 ELSE 2 END
|
CASE WHEN valstr IS NULL THEN 0 ELSE 4 END
)
INSERT FindNullComputedMask
SELECT 1,1,NULL
INSERT FindNullComputedMask
SELECT NULL,2,NULL
INSERT FindNullComputedMask
SELECT 2,NULL, NULL
INSERT FindNullComputedMask
SELECT 3,3,3
SELECT *
FROM FindNullComputedMask

SQL Server query performance: Nested cursors

I have a stored procedure which is selecting some 1-n relations and related data to the referenced column as XML data.
The purpose is to return a record and it's 1-n relations as ONE record with extra data columns, this is done using adding these related data as XML.
Reference table: (TABLE A)
ID NAME VALUE
---------------------
1 Sepehr 1000
2 Sarah 1001
Related table: (TABLE B)
ID Value FK_Value ORDER TITLE
-------------------------------------
1 A 1000 1 t1
2 B 1000 2 t2
3 C 1000 3 t3
I want to get this output:
ID NAME FK_Value Attribs
-----------------------------------------------------
1 Sepehr 1000 <XML><ID>1</ID><ID>2</ID><ID>3</ID></XML>
2 Sarah 1001 null
Actually I was hoping to create a view to do this, but I couldn't and someone told me its not possible using views.
Finally this is the stored procedure I have written - is this a correct approach or are there any other ways?
DECLARE #T1 table (A_ID int,Attribs XML)
DECLARE db_cursorLegendRowsValues CURSOR FOR
SELECT ID, VALUE
FROM A
OPEN db_cursorLegendRowsValues
FETCH NEXT FROM db_cursorLegendRowsValues INTO #loop_ID, #loop_VALUE
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE db_cursorInternal CURSOR FOR
SELECT TITLE, ORDER
FROM B
WHERE FK_Value = #loop_VALUE
OPEN db_cursorInternal
FETCH NEXT FROM db_cursorInternal INTO #tmpTitle, #ORDER
WHILE ##FETCH_STATUS = 0
BEGIN
SET #querySelect = #querySelect + ', MAX(CASE WHEN order = ' + cast(#ORDER as nvarchar(max)) + ' THEN value END) AS [' +REPLACE (#tmpTitle,' ','_') + '] '
FETCH NEXT FROM db_cursorInternal INTO #tmpTitle, #ORDER
END
CLOSE db_cursorInternal
DEALLOCATE db_cursorInternal
SET #query =
' SELECT ' + cast(#loop_ID as nvarchar(max)) +',(
SELECT A.Value,
'
SET #query = #query + STUFF(#querySelect,1,1,'') + ' FROM A
WHERE [A.Value] = ' + cast(#loop_VALUE as nvarchar(max)) + '
FOR XML RAW (''Item''), root (''Items'') , ELEMENTS XSINIL )'
SET #querySelect = ''
--PRINT(#query)
INSERT into #T1 execute (#query )
FETCH NEXT FROM db_cursorLegendRowsValues INTO #loop_ID, #loop_VALUE
END
CLOSE db_cursorLegendRowsValues
DEALLOCATE db_cursorLegendRowsValues
SELECT * FROM #T1
The whole lot can be condensed to a few lines, no cursors needed at all. This wil be useable in a VIEW:
DECLARE #tblA TABLE(ID INT IDENTITY,NAME VARCHAR(100),VALUE INT);
INSERT INTO #tblA VALUES
('Sepehr',1000)
,('Sarah',1001);
DECLARE #tblB TABLE(ID INT IDENTITY,Value VARCHAR(100),FK_Value INT,[ORDER] INT,TITLE VARCHAR(100));
INSERT INTO #tblB VALUES
('A',1000,1,'t1')
,('B',1000,2,'t2')
,('C',1000,3,'t3');
SELECT a.*
,(SELECT ID FROM #tblB AS b WHERE b.FK_Value=a.VALUE FOR XML PATH(''),ROOT('XML'),TYPE) AS Attribs
FROM #tblA AS a
The result
ID NAME VALUE Attribs
1 Sepehr 1000 <XML><ID>1</ID><ID>2</ID><ID>3</ID></XML>
2 Sarah 1001 NULL
It is possible to do this with just one query - you can use a subquery with your select as follows:
select id
, name
, value as fk_value
, (select id from #table_b b
where a.value = b.fk_value
for xml path (''), root ('xml'))
from #table_a a

Fetch specific column and row data based on row values from another table in SQL server

Does anyone know if it is possible to fetch data from a column in a table based on row values from another table?
e.g.
Table 1:
Name| Date
----|-----
Bob | D1
Jon | D2
Stu | D3
Amy | D4
Table 2:
Date |Bob |Jon |Stu |Amy
-----|----|----|----|----
D1 | A | B | C | D
D2 | B | C | D | A
D3 | C | D | A | B
D4 | D | A | B | C
I need to match the date but bring through the correct letter for each name
So Table 3 would be:
Name| Date | Letter
----|------|-------
Bob | D1 | A
Jon | D2 | C
Stu | D3 | A
Amy | D4 | C
Any suggestions are welcome.
thanks
If you are looking for way without column hardcodes, you can try this.
Lets your tables has names #table1, #table2. Then:
select
[Name] = [t1].[Name]
,[Date] = [t1].[Date]
,[Letter] = [col2].[value]('.', 'varchar(10)')
from
#table1 as [t1]
cross apply
(
select [t2_xml] = cast((select * from #table2 for xml path('t2')) as xml)
) as [t2]
cross apply
[t2].[t2_xml].[nodes]('t2[Date/text()=sql:column("[t1].[Date]")]') as [tab]([col])
cross apply
[col].[nodes]('*[local-name(.)=sql:column("[t1].[Name]")]') as [tab2]([col2]);
There are many ways to achieve the desired output. My solution uses a combination of cursors and dynamic TSQL.
Here is the code, commented step by step:
--1. create test tables
create table table1 ([Name] nvarchar(50),[Date] nvarchar(50))
create table table2 ([Date] nvarchar(50),Bob nvarchar(50),Jon nvarchar(50),Stu nvarchar(50),Amy nvarchar(50))
create table table3 ([Name] nvarchar(50),[Date] nvarchar(50),[Letter] nvarchar(50))
--2. populate test tables
insert into table1
select 'Bob','D1'
union all select 'Jon','D2'
union all select 'Stu','D3'
union all select 'Amy','D4'
insert into table2
select 'D1','A','B','C','D'
union all select 'D2','B','C','D','A'
union all select 'D3','C','D','A','B'
union all select 'D4','D','A','B','C'
--3. declare variables
DECLARE #query NVARCHAR(max); --this variable will hold the dynamic TSQL query
DECLARE #name NVARCHAR(50);
DECLARE #date NVARCHAR(50);
DECLARE #result NVARCHAR(50) --this variable will hold "letter" value returned by dynamic TSQL query
DECLARE #testCursor CURSOR;
--4. define the cursor that will scan all rows in table1
SET #testCursor = CURSOR FOR SELECT [Name], [Date] FROM table1;
OPEN #testCursor;
FETCH NEXT FROM #testCursor INTO #name, #date;
WHILE ##FETCH_STATUS = 0
BEGIN
--5. for each row in table 1 create a dynamic query that retrieves the correct "Letter" value from table2
set #query = 'select #res=' + #name + ' from table2 where [Date] =''' + #date +''''
--6. executes dynamic TSQL query saving result in #result variable
EXECUTE sp_executesql #query, N'#res nvarchar(50) OUTPUT', #res=#result OUTPUT
--inserts data in table3 that holds final results
insert into table3 select #name, #date, #result
FETCH NEXT FROM #testCursor INTO #name, #date;
END
CLOSE #testCursor;
DEALLOCATE #testCursor;
select * from table1
select * from table2
select * from table3
Here are the results. The first two tables show the inputs, the third table contains the actual results:

How to create loop based on value of row?

I have problem when I use my query bellow to have a looping inside the cursor.
data in table1 will be like this:
id | data
----|---------
A | 4
B | 2
C | 5
the result in table2 should be like this:
id | data
----|---------
A | 1
A | 1
A | 1
A | 1
B | 1
B | 1
C | 1
C | 1
C | 1
C | 1
C | 1
I have SQL query with cursor like this:
DECLARE #table2 table ( id VARCHAR(500), data INTEGER)
DECLARE Cur CURSOR FOR
SELECT id, data FROM table1
OPEN Cur
WHILE ( ##FETCH_STATUS = 0 )
BEGIN
DECLARE #LoopNum INTEGER
DECLARE #tempID VARCHAR(255)
DECLARE #tempDATA INTEGER
FETCH NEXT FROM Cur INTO #tempID, #tempDATA
set #LoopNum = 0
WHILE #LoopNum < #tempDATA
BEGIN
INSERT INTO table2 (id, data)
VALUES( #tempID, 1)
SET #LoopNum = #LoopNum + 1
END
END
CLOSE Cur
DEALLOCATE Cur
SELECT * FROM table2
but the query didn't work. is there something wrong with my query?
Thank you.
Use this query to the expected result.
CREATE TABLE #test
(id CHAR(1),data INT)
INSERT #test VALUES ('A',4)
INSERT #test VALUES('B',2)
INSERT #test VALUES('C',5);
SELECT s.id, 1 AS data
FROM #test s
INNER JOIN
master.dbo.spt_values t ON t.type='P'
AND t.number BETWEEN 1 AND s.data
Note: Refer this Why (and how) to split column using master..spt_values?
You actually don't need a loop
IF OBJECT_ID('TEMPDB..#TEMP') IS NOT NULL
DROP TABLE #TEMP
SELECT 'A' AS ID, 4 AS DATA
INTO #TEMP UNION
SELECT 'B', 2 UNION
SELECT 'C', 5
;WITH CTE AS
(
SELECT 1 AS NUMBER
UNION ALL
SELECT NUMBER + 1
FROM CTE
WHERE NUMBER < 100
)
SELECT T.ID, 1
FROM CTE C
INNER JOIN #TEMP T
ON C.NUMBER <= T.DATA
ORDER BY T.ID
Carefull that if you want ot generate a large set of numbers in the CTE it may become slower.
Use a Recursive CTE which will help you to loop through the records.
CREATE TABLE #test
(id CHAR(1),data INT)
INSERT #test
VALUES ('A',4),('B',2),('C',5);
WITH cte
AS (SELECT 1 AS da,id,data
FROM #test a
UNION ALL
SELECT da + 1,id,data
FROM cte a
WHERE da < (SELECT data
FROM #test b
WHERE a.id = b.id))
SELECT id,
1 AS data
FROM cte
ORDER BY id
i used two loops
1. for each row
2. for number for duplicate insert
SET NOCOUNT on;
DECLARE #t table(row int IDENTITY(1,1),id varchar(10),data int)
INSERT INTO #t
SELECT * from xyz
DECLARE #x table(id varchar(10),data int) --table to hold the new data
DECLARE #i int=(SELECT count (*) from xyz) --number of rows main table
DECLARE #y int --number of duplicate
DECLARE #p int=1 --number of rows
WHILE #i!=0 --loop until last row of main table
BEGIN
SET #y=(SELECT data FROM #t WHERE row=#p) --set #y for number of 'row duplicate'
WHILE #y!=0
BEGIN
INSERT INTO #x
SELECT id,1
FROM #t
WHERE row=#p
SET #y=#y-1
END
SET #p=#p+1
SET #i=#i-1
END
SELECT * FROM #x