Moving result from 1:n query to repeating columns - sql

I would like to achieve the following from a result of a 1:n join using T-sql
Surname | Givename |..Address | City
Name1....| Givename1.|..Addr11...| City11
Name1....| Givename1.|..Addr12...| City12
Name2....| Givename2.|..Addr21...| City21
Name2....| Givename2.|..Addr22...| City22
Name2....| Givename2.|..Addr23...| City23
TO:
Surname | Givename.. | Address | City... | Address | City... | Address | City
Name1....| Givename1...| Addr11....| City11. | Addr12....| City12. |
Name2....| Givename2...| Addr21....| City21. | Addr22....| City22. | Addr23....| City23
I not care about repeating columnames. Up if there is a soultion with numbers in the repeating columns it would be fine too.
Thanks
In my opinion Pivot is not a solution in this case. Because the column name should repat, and in pivot, cell values are moved to column names, also is unlike pivot no aggregate function involved.

In my thought, the following query will handle your issue. However the SQL Server has column number limit for tables.
Columns per table 1,024 Tables that include sparse column sets
include up to 30,000 columns. See sparse column sets.
You should take this considiration for your data.
DROP TABLE IF EXISTS #Test
DROP TABLE IF EXISTS ##PivotUnlimited
CREATE TABLE #Test(Surname VARCHAR(100) , GivenName VARCHAR(200),
Adress VARCHAR(100),City VARCHAR(100))
INSERT INTO #Test
VALUES
('Name1','Givename1','Addr11','City11'),
('Name1','Givename1','Addr12','City12'),
('Name2','Givename2','Addr21','City21'),
('Name2','Givename2','Addr22','City21'),
('Name2','Givename2','Addr23','City23')
,('Name5','Givename5','Addr51','City51'),
('Name5','Givename5','Addr52','City52'),
('Name5','Givename5','Addr53','City53'),
('Name5','Givename5','Addr54','City54'),
('Name3','Givename3','Addr31','City31')
DECLARE #Counter AS INT=1
DECLARE #Max AS INT
DECLARE #SQL AS VARCHAR(MAX)
SELECT #Max= MAX(DetMax) FROM
(
SELECT ROW_NUMBER() OVER(Partition by Surname ORDER BY Surname ) AS DetMax FROM #Test
) AS TMP
DROP TABLE IF EXISTS ##PivotUnlimited
CREATE TABLE ##PivotUnlimited (Surname VARCHAR(100),GivenName VARCHAR(100))
WHILE #Counter <=#Max
BEGIN
SET #SQL= 'ALTER TABLE ##PivotUnlimited ADD ADDR' + CONVERT(VARCHAR,#Counter) + ' VARCHAR(100)'
EXEC(#SQL)
SET #SQL= 'ALTER TABLE ##PivotUnlimited ADD City' + CONVERT(VARCHAR,#Counter) + ' VARCHAR(100)'
EXEC(#SQL)
SET #Counter=#Counter+1
END
INSERT INTO ##PivotUnlimited (Surname,GivenName)
SELECT DISTINCT Surname , GivenName FROM #Test
DECLARE #Name AS VARCHAR(100)
DECLARE cursorT CURSOR
FOR
SELECT DISTINCT Surname from #test
OPEN cursorT
FETCH NEXT FROM cursorT INTO #Name
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #CounterCursor AS INT=1
DECLARE #MaxCursort AS INT =0
DECLARE #UpdateSQL AS VARCHAR(MAX)
DECLARE #UptAdr AS VARCHAR(100)
DECLARE #UptCity AS VARCHAR(100)
SELECT #MaxCursort=RowNumber FROM (
SELECT ROW_NUMBER() OVER(Partition by Surname ORDER BY Surname ) AS RowNumber FROM #Test
WHERE Surname=#Name
) AS TMP_TBL
WHILE #CounterCursor<= #MaxCursort
BEGIN
SELECT #UptAdr=Adress ,#UptCity=City FROM (
SELECT ROW_NUMBER() OVER(Partition by Surname ORDER BY Surname ) AS RowNumber,* FROM #Test
) AS TMP_TBL WHERE RowNumber=#CounterCursor and Surname=#Name
SET #UpdateSQL= 'UPDATE ##PivotUnlimited SET ADDR' + CONVERT(VARCHAR,#CounterCursor) + ' = ' + '''' + #UptAdr +'''' + ' , '
+ ' City' + CONVERT(VARCHAR,#CounterCursor) + ' = ' + '''' + #UptCity +'''' + ' WHERE Surname = ' + '''' + #Name + ''''
EXEC (#UpdateSQL)
SET #CounterCursor=#CounterCursor+1
END
FETCH NEXT FROM cursorT INTO #Name
END
CLOSE cursorT
DEALLOCATE cursorT
SELECT * FROM ##PivotUnlimited
+---------+-----------+--------+--------+--------+--------+--------+--------+--------+--------+
| Surname | GivenName | ADDR1 | City1 | ADDR2 | City2 | ADDR3 | City3 | ADDR4 | City4 |
+---------+-----------+--------+--------+--------+--------+--------+--------+--------+--------+
| Name1 | Givename1 | Addr11 | City11 | Addr12 | City12 | NULL | NULL | NULL | NULL |
| Name2 | Givename2 | Addr21 | City21 | Addr22 | City21 | Addr23 | City23 | NULL | NULL |
| Name3 | Givename3 | Addr31 | City31 | NULL | NULL | NULL | NULL | NULL | NULL |
| Name5 | Givename5 | Addr51 | City51 | Addr52 | City52 | Addr53 | City53 | Addr54 | City54 |
+---------+-----------+--------+--------+--------+--------+--------+--------+--------+--------+

Related

SQL Concatenate Strings in order of line numbers

I am using SQL Server 2014 Standard.
I have the following query...
SELECT ach.amt, ades.dsline, ades.des
FROM ##ACHTrans ach
LEFT OUTER JOIN apvodes ades on 1=1 and ades.vo_id = ach.vo_id
WHERE ades.voline = '100'
ORDER by ach.apnum, ach.cknum, ach.vo_id, ach.amt desc
Which gives me the results...
+------------+---------------+------------------------------+
| ach.amt | ades.dsline | ades.des |
+------------+---------------+------------------------------+
| 1232.50 | 1 | This is the description for |
| 1232.50 | 2 | The $1,232.50 ACH Amount |
| 245.18 | 1 | This one is for the $245.18 |
| 245.18 | 2 | transactions details |
| 245.18 | 3 | that has four lines of info |
| 245.18 | 4 | in the description. |
| 79.25 | 1 | This $79.25 item has 1 line. |
| 15.00 | 1 | So does this $15.00 one. |
+------------+---------------+------------------------------+
I need a way to snag this info by the ach.amt line, and concatenate the ades.des info for results similar to:
+------------+--------------------------------------------------------------------------------------------------+
| Amount | Description |
+------------+--------------------------------------------------------------------------------------------------+
| 1232.50 | This is the description for The $1,232.50 ACH Amount |
| 245.18 | This one is for the $245.18 transactions details that has four lines of info in the description. |
| 79.25 | This $79.25 item has 1 line. |
| 15.00 | So does this $15.00 one. |
+------------+--------------------------------------------------------------------------------------------------+
This is what string_agg() does:
select ach.amt,
string_agg(des, ',') within group (order by dsline)
from t
group by ach.amt;
Without STRING_AGG you would use for XML PATH like so:
DECLARE #table TABLE (amt MONEY, dsline INT, [des] VARCHAR(1000));
INSERT #table VALUES
(1232.50,1,'This is the description for'),
(1232.50,2,'The $1,232.50 ACH Amount'),
( 245.18,1,'This one is for the $245.18'),
( 245.18,2,'transactions details'),
( 245.18,3,'that has four lines of info'),
( 245.18,4,'in the description.'),
( 79.25,1,'This $79.25 item has 1 line.'),
( 15.00,1,'So does this $15.00 one.');
SELECT
amt,
[Description] =
(
SELECT t2.[des]+''
FROM #table AS t2
WHERE t.amt = t2.amt
ORDER BY t2.dsline
FOR XML PATH('')
)
-- string_agg(des, ',') within group (order by dsline)
FROM #table AS t
GROUP BY amt;
Results:
amt Description
--------------------- ---------------------------------------------------------------------------------------------
15.00 So does this $15.00 one.
79.25 This $79.25 item has 1 line.
245.18 This one is for the $245.18transactions detailsthat has four lines of infoin the description.
1232.50 This is the description forThe $1,232.50 ACH Amount
This may not be the prettiest solution but I have had to deal with something similar and used a cursor to concatenate my strings in a temporary table and then used that in my final join statement back to the original table. I used table variables so you can play with it yourself.
Following is a code example you can play with:
declare #tableAmt table (
IDNum int,
Amt Money
)
declare #tableDesc table (
IDNum int,
LineNum int,
Info varchar(10)
)
set nocount on
insert #tableAmt (IDNum, Amt)
values (1,100.00),
(2,125.00)
insert #tableDesc (IDNum, LineNum, Info)
values (1,1,'some text'),
(1,2,'more text'),
(2,1,'different'),
(2,2,'text'),
(2,3,'final')
declare #description table
(IDNum int,
ConcatDesc varchar(30)
)
declare #id int,
#oldid int,
#string char(10),
#finalstring varchar(30)
declare getdata_cursor cursor for
select IDNum, Info
from #tableDesc
order by IDNum, LineNum
open getdata_cursor
fetch next from getdata_cursor into
#id, #string
while ##FETCH_STATUS=0
begin
if #oldid <> #id
begin
insert #description(IDNum, ConcatDesc)
values(#oldid, #finalstring)
select #finalstring = ''
end
select #finalstring = isnull(#finalstring,'') + rtrim(#string) + ' '
select #string = '', #oldid = #id
fetch next from getdata_cursor into
#id, #string
end
insert #description(IDNum, ConcatDesc)
values(#oldid, #finalstring)
close getdata_cursor
deallocate getdata_cursor
select ta.IDNum, Amt, ConcatDesc from #tableAmt ta join #description d
on ta.IDNum = d.IDNum

Get values from 2 Tables in different Databases into another table

I have a Database for say MasterDB which has list of Some Databases Name in a Table tbl_B .Each DataBase Name is identified by an ID.
The structure of the table tbl_B is like the following
tbl_B
ID | DB_Name
-------------
1 | DelhiDB
2 | MumbaiDB
There are DataBases with the same name i.e DelhiDB and MumbaiDB and each of them have a Table with name tbl_C which will have some data for eg.
tbl_C for Delhi
custIDDelhi | custNameDelhi | CustPhoneDelhi |
----------------------------------------------
1 | John | 123456 |
2 | Monika | 789945 |
Please note here that the column names for Both the databases can be Different
Also Please note that DelhiDB and MumbaiDB are separate Database each having a table named tbl_C
I want to create a Table called tblCusotmer_Dictionary in MasterDB
With Data something like this
ColumnName | DataBaseName | DataBaseID | db_ColumnNamme
-----------------------------------------------------------
CustomerID | DelhiDB | 1 | custIDDelhi
CustomerName | DelhiDB | 1 | custNameDelhi
CustomerPhone | DelhiDB | 1 | CustPhoneDElhi
CustomerID | MumbaiDB | 2 | custIDMumbai
CustomerName | MumbaiDB | 2 | custNameMumbai
CustomerPhone | MumbaiDB | 2 | CustPhoneMumbai
Here I dont want any customer data just a list of column name from both the databases along with Database name and ID ,
the column ColumnName in the above table is the Generic Name I am giving to the column db_ColumnNamme
I have taken example for 2 databases and 3 columns for simplicity But there can can be N number for databases each having a table with a same name ( tbl_c here) with fixed no of columns.
Let me know in comments for any clarifications.
if I understood your question correctly then below is the solution which you are looking for. Let me know if it works for you.
DECLARE #tblDatabaseName AS TABLE (Id INT, dbName VARCHAR(100))
--DROP TABLE #tmpREcord
INSERT INTO #tblDatabaseName(id,dbName) VALUES (1,'DelhiDB'),(1,'MumbaiDB')
DECLARE #SQL AS VARCHAR(8000)
DECLARE #Id INT
DECLARE #dbName AS VARCHAR(100)
CREATE TABLE #tmpRecord (
columnName VARCHAR(20),DBID INT, DatabaseName VARCHAR(100))
DECLARE cur_Traverse CURSOR FOR SELECT Id , dbName FROM #tblDatabaseName
OPEN cur_Traverse
FETCH NEXT FROM cur_Traverse INTO #id ,#dbName
WHILE ##FETCH_STATUS =0
BEGIN
SET #SQL = 'INSERT INTO #tmpRecord (ColumnName,DbId,DatabaseName )
SELECT name ,' + CONVERT(VARCHAR(10),#Id) + ' AS DBID, ''' + #dbName + ''' as dbname'
+ ' FROM ' + #dbName + '.sys.all_columns s
WHERE object_Id = (SELECT TOP(1) object_Id FROM ' + #dbName + '.sys.all_objects WHERE name=''tbl_C'')'
PRINT #SQL
EXECUTE (#SQL)
FETCH NEXT FROM cur_Traverse INTO #Id, #dbName
END
CLOSE cur_Traverse
DEALLOCATE cur_Traverse
SELECT * FROM #tmpRecord
You appears to want :
select t.colsname as ColumnName,
b.db_name as DataBaseName,
b.id as DataBaseID,
t.cols as db_ColumnNamme
from tbl_C c
cross apply (values ('custID', 'CustomerID'), ('custName', 'CustomerName'),
('CustPhone', 'CustomerPhone')
) t (cols, colsname)
inner join tbl_B b on b.id = c.custID;

SQL Comparing a table attribute from values from other table

I want to compare values from the first table InputStrings with values in the second table StringConstraints in Sql.
InputStrings
+-----------+------------+-----------+
| Name | Address | City |
+-----------+------------+-----------+
| abcabcabc | xyxyxyxy | qweqweqwe |
| abbcabc | xyxxyxy | qweqwe |
| abccabc | xyxyxyxyxy | qwweqwe |
+-----------+------------+-----------+
StringConstraints
+---------+-----------+-----------+
| colName | minlength | maxlength |
+---------+-----------+-----------+
| Name | 2 | 20 |
| Address | 4 | 10 |
| City | 5 | 10 |
+---------+-----------+-----------+
I want to check if the length of the values in the Name column is between 2 and 20; length of values in the Address column is between 4 and 10; and length of values in the City column is between 5 and 10.
There are 68 rows like this in my table. InputString has 40 columns in it. I can't write for each and every row.
Can anyone help me make a generalized solution to compare the values?
I'm new to the database area.
this will return bad records:
Select *
from InputStrings
where (len(Name) not between (select minlength from StringContraints where colName = 'Name')
and
(select maxlength from StringContraints where colName = 'Name'))
or
(len(Address) not between (select minlength from StringContraints where colName = 'Address')
and
(select maxlength from StringContraints where colName = 'Address'))
or
(len(City) not between (select minlength from StringContraints where colName = 'City')
and
(select maxlength from StringContraints where colName = 'City'))
This is a way to create the same thing dynamically. Please keep in mind that Stack Overflow is not a coding service.
declare #sql varchar(max) =''
,#Name varchar(50)
,#min int
,#max int
,#counter int =1;
declare csr cursor
for
select colName, minlength, [maxlength] from (values ('Name',2,20),('Address',4,10),('City',5,10)) a(colName,minlength,[maxlength])
open csr
fetch next from csr
into #Name,#min,#max
set #sql = 'select * from inputstrings where '
while ##FETCH_STATUS = 0
begin
if(#counter =1 ) set #sql = #sql + '(len('+ #name+') not between ' + cast(#min as varchar(5)) + ' and ' + cast(#max as varchar(5)) + ') '
else set #sql = #sql + 'or (len('+ #name+') not between ' + cast(#min as varchar(5)) + ' and ' + cast(#max as varchar(5)) + ') '
set #counter=#counter +1
fetch next from csr
into #Name,#min,#max
end
close csr
deallocate csr
print #sql
exec(#sql)
You didn't state your DMBS, so this is a solution for Postgres:
select *
from (
select i.id,
t.*,
c.minlength,
c.maxlength,
length(t.value) between c.minlength and c.maxlength as is_valid
from inputstrings i
cross join lateral jsonb_each_text(to_jsonb(i) - 'id') as t(colname, value)
join stringconstraints c on lower(c.colname) = lower(t.colname)
) t
where not is_valid
order by id;
This first turns each row from the table inputstrings into key/value pairs and the result of that is joined to the stringconstraints table. From there it's easy to validate the column values based on the constraints. This is independent of the number of columns in inputstrings. The result will be one row per column value that violates the constraints.
For the following setup:
create table inputstrings (id integer, name text, address text, city text);
insert into inputstrings
values
(1, 'Name OK','Some Address that is too long','City Name OK'),
(2, 'N', 'Address OK', 'Cty'),
(3, 'Good Name', 'Good Address', 'Good City');
create table stringconstraints (colname text, minlength int, maxlength int);
insert into stringconstraints
values
('Name', 2, 20),
('Address', 4, 12),
('City', 5, 15);
The query returns this result:
id | colname | value | minlength | maxlength | is_valid
---+---------+-------------------------------+-----------+-----------+---------
1 | address | Some Address that is too long | 4 | 12 | false
2 | name | N | 2 | 20 | false
2 | city | Cty | 5 | 15 | false
I added the id column so that it is possible to match a invalid column value to the actual source row.
Online example: http://rextester.com/VVG55773
Second example with more columns: http://rextester.com/QCKG53573 (note the query hasn't changed)

How to prepend column names into column values?

Having the following table:
| Some Table |
| Id | Name | Age |
| 23 | Marc | 41 |
| 54 | Edu | 34 |
I want to get:
|Another Table's Column| Id | Name | Age |
| ..... | Id#23 | Name#Marc | Age#41 |
| ..... | Id#54 | Name#Edu | Age#34 |
This query will be used inside a dynamic sql, the name of the table is going to have passed as a parameter.
The final query must show data of at least two tables, and only one of them must show data with his column names as a prefix.
Not sure I understand what you want, but here is the script to show column names within the result for any passed table:
DECLARE #t NVARCHAR(128) = '[Person].[Person]';
DECLARE #l NVARCHAR(2000) = '';
DECLARE #s NVARCHAR(4000) = '';
SELECT #l = (
SELECT ' ''#' + Name + ''' + CAST([' + Name + '] as VARCHAR(max)) as [' + Name + '],'
FROM sys.columns
WHERE object_id = OBJECT_ID(#t)
ORDER BY column_id
FOR XML PATH(''));
SELECT #s = 'SELECT ' + LEFT(#l,LEN(#l)-1) + ' FROM ' + #t + ';'
PRINT #s
EXEC (#s)
You can tune it to link another table or many tables

SQL Server Pivot is inserting NULL values

Please find the table (OututTable) that needs to be transposed. Here the QuestionID is formed by concatenating two values -[Question:AnswerID]
refID | SessionID | QuestionID | AnswerValue
9000 | 205545715 | [4907] | Good morning
12251 | 205543469 | [10576:16307] | 3
12255 | 205543469 | [10907:17001] | 4
13157 | 205543703 | [10576:16307] | 3
14387 | 205543493 | [10907:17001] | 2
14389 | 205543493 | [10911:17007] | 3
The expected output should have one row per SessionID and the number of columns are dynamic
SessionID | [4097] | [10576:16307] | [10907:17001] | [10911:17007]
205545715 |Good morning | | |
205543469 | | 3 | 4 |
205543703 | | 3 | |
205543493 | | | 2 | 3
I have the output in the above format but there are only NULL values inserted instead of Answer values
I am thinking there might a mismatch in column names. Any help would be great! please let me know.
Code:
set #Questions = (STUFF((SELECT distinct ',[' + cast(i.SessionID as varchar(20)) + ']'
FROM OutputTable i
FOR XML PATH(''), TYPE).value('.','VARCHAR(max)'), 1, 1, ''))
print #Questions
set #SQLQuery = 'select QuestionID,'+ #Questions +' from '+'('+ 'select SessionID,QuestionID,AnswerValue from OutputTable '+ ') p '+ 'PIVOT'+ '('+'max(Answervalue)'+'FOR p.SessionID IN ('+ #Questions +')' +') as pvt'
Great Question! The problem is with the brackets in the QuestionID. While these are necessary for the Pivot Column Aliases, these don't work as string filters.
The code sample also switches QuestionID and SessionID for the expected output.
This code will return the expected output, sorted slightly differently. A temp table is created here to simulate the OutputTable object. This will need to be switched out with the DB Table.
declare
#Questions varchar(max),
#SQLQuery varchar(max)
create table #OutputTable
(
refID int,
SessionID int,
QuestionID varchar(50),
AnswerValue varchar(50)
)
insert into #OutputTable
values
(9000,205545715,'[4907]','Good morning'),
(12251,205543469,'[10576:16307]','3'),
(12255,205543469,'[10907:17001]','4'),
(13157,205543703,'[10576:16307]','3'),
(14387,205543493,'[10907:17001]','2'),
(14389,205543493,'[10911:17007]','3')
set #Questions = (STUFF((SELECT distinct ',' + cast(i.QuestionID as varchar(20))
FROM #OutputTable i
FOR XML PATH(''), TYPE).value('.','VARCHAR(max)'), 1, 1, ''))
set #SQLQuery = '
select SessionID,'+ #Questions +'
from (
select
SessionID,
replace(replace(QuestionID,''['',''''),'']'','''') QuestionID,
AnswerValue
from #OutputTable
) p
PIVOT (
max(Answervalue)
FOR p.QuestionID IN ('+ #Questions +')
) as pvt
order by SessionID desc'
exec(#SQLQuery)