Inserting four columns into one - sql

Good morning,
I have a table TestSeed that stores a multiple choices test with the following structure:
QNo QText QA1 QA2 QA3 QA4
It already contains data.
I would like to move some of the columns to a temp table with the following structure:
QNo QA
Where QNo will store the question number from the first table and QA will store QA1, QA2, QA3 and QA4 over four rows of data.
I am trying to do it in a SQL stored procedure. And it got down to the following situation:
I want to create a nested loop where I can go through the TestSeed table rows in the outer loop and then go through the four QA fields and insert them in the inner loop.
So my code will look something like this:
Declare #TempAnswers as table
(
[QNo] int,
[QAnswer] [nvarchar](50) NULL,
)
DECLARE #QNO int
DECLARE QROW CURSOR LOCAL FOR select QNo from #TempSeed
OPEN QROW
FETCH NEXT FROM QROW into #QNO
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #i INT
SET #i = 1
WHILE (#i <=4)
Begin
insert into #TempAnswers
(
[QNo],
[QAnswer]
)
select QNo, 'QA'+#i --This is the part I need
from #TempSeed
SET #i = #i +1
END
FETCH NEXT FROM QROW into #QNO
END
CLOSE IDs
DEALLOCATE IDs
So I guess my question is: can I use a concatenated string to refer to a column name in SQL? and if so how?
I am sort of a beginner. I would appreciate any help I can.

No need for loop, you can simply use the UNPIVOT table operator to do this:
INSERT INTO temp
SELECT
QNO,
val
FROM Testseed AS t
UNPIVOT
(
val
FOR col IN([QA1], [QA2], [QA3], [QA4])
) AS u;
For example, if you have the following sample data:
| QNO | QTEXT | QA1 | QA2 | QA3 | QA4 |
|-----|-------|-----|-----|-----|-----|
| 1 | q1 | a | b | c | d |
| 2 | q2 | b | c | d | e |
| 3 | q3 | e | a | b | c |
| 4 | q4 | a | c | d | e |
| 5 | q5 | c | d | e | a |
The previous query will fill the temp table with:
| QNO | QA |
|-----|----|
| 1 | a |
| 1 | b |
| 1 | c |
| 1 | d |
| 2 | b |
| 2 | c |
| 2 | d |
| 2 | e |
| 3 | e |
| 3 | a |
| 3 | b |
| 3 | c |
| 4 | a |
| 4 | c |
| 4 | d |
| 4 | e |
| 5 | c |
| 5 | d |
| 5 | e |
| 5 | a |
SQL Fiddle Demo
The UNPIVOT table operator, will convert the values of the four columns [QA1], [QA2], [QA3], [QA4] into rows, only one row.
Then you can put that query inside a stored procedure.

So, to answer your last question, you can use Dynamic SQL which involves creating your query as a STRING and then executing it, in case you really want to stick to the method you already started.
You will have to declare a variable to store the text of your query:
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT QNo, QA' + #i + ' FROM #TempSeed'
EXEC sp_executesql #query
This will have to be done everytime you build your query which is to be executed (declaration, seting the text of the query and executing it).
If you want something simpler, there are other answers here which will work.

Try this:
Declare #TempAnswers as table
(
[QNo] int,
[QAnswer] [nvarchar](50) NULL,
);
INSERT INTO #TempAnswers(QNo, QAnswer)
SELECT QNo, QA
FROM (SELECT QNo, QA1 AS QA FROM TestSeed
UNION
SELECT QNo, QA2 AS QA FROM TestSeed
UNION
SELECT QNo, QA3 AS QA FROM TestSeed
UNION
SELECT QNo, QA4 AS QA FROM TestSeed
) AS A
ORDER BY QNo;

Related

Linking Related IDs together through two other ID columns

I have a table of about 100k rows with the following layout:
+----+-----------+------------+-------------------+
| ID | PIN | RAID | Desired Output ID |
+----+-----------+------------+-------------------+
| 1 | 80602627 | 1737852-1 | 1 |
| 2 | 80602627 | 34046655-1 | 1 |
| 3 | 351418172 | 33661 | 2 |
| 4 | 351418172 | 33661 | 2 |
| 5 | 351418172 | 33661 | 2 |
| 6 | 351418172 | 34443321-1 | 2 |
| 7 | 491863017 | 26136 | 3 |
| 8 | 491863017 | 34575 | 3 |
| 9 | 491863017 | 34575 | 3 |
| 10 | 661254727 | 26136 | 3 |
| 11 | 661254727 | 26136 | 3 |
| 12 | NULL | 7517 | 4 |
| 13 | NULL | 7517 | 4 |
| 14 | NULL | 7517 | 4 |
| 15 | NULL | 7517 | 4 |
| 16 | NULL | 7517 | 4 |
| 17 | 554843813 | 33661 | 2 |
| 18 | 554843813 | 33661 | 2 |
+----+-----------+------------+-------------------+
The ID column has unique values, with the PIN and RAID columns being two separate identifying numbers used to group linked IDs together. The Desired Output ID column is what I would like SQL to do, essentially looking at both the PIN and RAID columns to spot where there are any relationships between them.
So for example Where Desired Output ID = 2, IDs 3-6 match on PIN = 351418172, and then IDs 17-18 also match as the RAID of 33661 was in the rows for IDs 3-5.
To add as well, NULLs will be in the PIN Column but not in any others.
I did spot a similar question Text however as it is in BigQuery I wasnt sure it would help.
Have been trying to crack this one for a while with no luck, any help massively appreciated.
I suppose DENSE_RANK can solve your problem. Not sure what the combination of PIN and RAID should be, but I think you'll be able to figure it out how to do it like this:
SELECT *,DENSE_RANK( ) over (ORDER BY isnull(pin,id) ),DENSE_RANK( ) over (ORDER BY raid)
FROM accounts
I believe I have found a bit of a bodged solution to this. It runs very slowly as it goes row by row and will only go two links deep on PIN/RAID, but this should be sufficient for 99%+ cases.
Would appreciate any suggestions to speeding it up if anything is immediately obvious.
ID in post above is DebtorNo in Code:
DECLARE #Counter INT = 1
DECLARE #EndCounter INT = 0
IF OBJECT_ID('Tempdb..#OrigACs') IS NOT NULL
BEGIN
DROP TABLE #OrigACs
END
SELECT DebtorNo,
Name,
PostCode,
DOB,
RAJoin,
COALESCE(PIN,DebtorNo COLLATE DATABASE_DEFAULT) AS PIN,
RelatedAssets,
RAID,
PINRelatedAssets
INTO #OrigACs
FROM MIReporting..HC_RA_Test_Data RA
IF OBJECT_ID('Tempdb..#Accounts') IS NOT NULL
BEGIN
DROP TABLE #Accounts
END
SELECT *,
ROW_NUMBER() OVER (ORDER BY CAST(RA.DebtorNo AS INT)) AS Row
INTO #Accounts
FROM #OrigACs RA
ORDER BY CAST(RA.DebtorNo AS INT)
CREATE INDEX Temp_HC_Index ON #OrigACs (RAID,PIN)
SET #EndCounter = (SELECT MAX(Row) FROM #Accounts)
WHILE #Counter <= #EndCounter
BEGIN
IF OBJECT_ID('Tempdb..#RAID1') IS NOT NULL
BEGIN
DROP TABLE #RAID1
END
SELECT *
INTO #RAID1
FROM #OrigACs A
WHERE A.RAID IN (SELECT RAID FROM #Accounts WHERE [Row] = #Counter)
IF OBJECT_ID('Tempdb..#PIN1') IS NOT NULL
BEGIN
DROP TABLE #PIN1
END
SELECT *
INTO #PIN1
FROM #OrigACs A
WHERE A.PIN IN (SELECT PIN FROM #RAID1)
IF OBJECT_ID('Tempdb..#RAID2') IS NOT NULL
BEGIN
DROP TABLE #RAID2
END
SELECT *
INTO #RAID2
FROM #OrigACs A
WHERE A.RAID IN (SELECT RAID FROM #PIN1)
IF OBJECT_ID('Tempdb..#PIN2') IS NOT NULL
BEGIN
DROP TABLE #PIN2
END
SELECT *
INTO #PIN2
FROM #OrigACs A
WHERE A.PIN IN (SELECT PIN FROM #RAID2)
INSERT INTO MIReporting..HC_RA_Final_ACs
SELECT DebtorNo,
Name,
PostCode,
DOB,
RAJoin,
CASE
WHEN PIN = DebtorNo COLLATE DATABASE_DEFAULT THEN NULL
ELSE PIN
END AS PIN,
RelatedAssets,
RAID,
PINRelatedAssets,
COALESCE((SELECT MAX(FRAID) FROM MIReporting..HC_RA_Final_ACs),0) + 1 AS FRAID
FROM #PIN2
SET #Counter = (SELECT MIN([ROW]) FROM #Accounts O WHERE O.DebtorNo NOT IN (SELECT DebtorNo FROM MIReporting..HC_RA_Final_ACs));
END;
SELECT *
FROM MIReporting..HC_RA_Final_ACs
DROP TABLE #OrigACs
DROP TABLE #Accounts
DROP TABLE #RAID1
DROP TABLE #PIN1
DROP TABLE #RAID2
DROP TABLE #PIN2

Create table of sum of events in SQL table

I'm trying to pivot a table from the format
| ID | access date |
--------------
| 1 | 08.10|
| 1 | 08.10|
| 4 | 08.10|
| 2 | 02.09|
To
|ID | 02.09 | 03.09 | 04.09 | ....
| 1 | 4 | 0 | 2 |
| 2 | 1 | 2 | 5 |
| 3 |
.
.
.
I've tried using the PIVOT function but since I have a lot of different dates I don't want to type out the query
SELECT *
FROM (
SELECT [Sequence of events] as ID
,[Submission Date] as access_date
FROM [database_name].[dbo].[Event Logging]
) AS SOURCE_TABLE
PIVOT( SUM(ID) for access_date IN ("08.01", "09.01", "10.01"....)
) as pvt_table
I'm very new to SQL so I'd appreciate some insight into how to solve this problem.
This is not answer about solving problem in your way but it is about solving it another way.
What i would do is create 2 tables. First one would be called DATE_DB where i would store DATEID and DATE and it would look like this:
| DATEID | DATE |
| 1 | 01.01|
| 2 | 02.02|
....
Then in second table I store data like this:
| ID | DATEID | VALUE |
| 1 | 2 | 10 |
| 2 | 2 | 3 |
| 3 | 3 | 4 |
| 4 | 2 | 5 |
So in second table column ID is used only for primary key and has nothing to do but with tables like this and JOIN command you can use it like this:
SELECT DATE_DB.DATE, SECONDTABLE.VALUE
FROM SECONDTABLE
LEFT JOIN DATE_DB ON SECONDTABLE.DATEID = DATE_DB.DATE
ORDER BY DATE_DB.DATE
which will display result like this:
| DATE | VALUE |
| 02.01 | 10 |
| 02.01 | 3 |
| 02.01 | 5 |
| 03.01 | 4 |
Try it out like this, you need dynamic sql, note script isn't tested out, also when you naming your columns try not to have space, ether use CamelCase or underscore to separate words
And last thing, this is for SQL-Server, as you didn't tag anything and your code looks like sql-server
declare #cols nvarchar(max)
select #cols = STUFF((SELECT DISTINCT ',' + QUOTENAME([Submission Date])
from [database_name].[dbo].[Event Logging]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
declare #sql nvarchar(max);
set #sql = '
SELECT *
FROM (
SELECT [Sequence of events] as ID
,[Submission Date] as access_date
FROM [database_name].[dbo].[Event Logging]
) AS SOURCE_TABLE
PIVOT( SUM(ID) for access_date IN (' + #cols + ')
) as pvt_table';
-- print (#sql)
execute (#sql)

Convert tuple value to column names

Got something like:
+-------+------+-------+
| count | id | grade |
+-------+------+-------+
| 1 | 0 | A |
| 2 | 0 | B |
| 1 | 1 | F |
| 3 | 1 | D |
| 5 | 2 | B |
| 1 | 2 | C |
I need:
+-----+---+----+---+---+---+
| id | A | B | C | D | F |
+-----+---+----+---+---+---+
| 0 | 1 | 2 | 0 | 0 | 0 |
| 1 | 0 | 0 | 0 | 1 | 1 |
| 2 | 0 | 5 | 1 | 0 | 0 |
I don't know if I can even do this. I can group by id but how would you read the count value for each grade column?
CREATE TABLE #MyTable(_count INT,id INT , grade VARCHAR(10))
INSERT INTO #MyTable( _count ,id , grade )
SELECT 1,0,'A' UNION ALL
SELECT 2,0,'B' UNION ALL
SELECT 1,1,'F' UNION ALL
SELECT 3,1,'D' UNION ALL
SELECT 5,2,'B' UNION ALL
SELECT 1,2,'C'
SELECT *
FROM
(
SELECT _count ,id ,grade
FROM #MyTable
)A
PIVOT
(
MAX(_count) FOR grade IN ([A],[B],[C],[D],[F])
)P
You need a "pivot" table or "cross-tabulation". You can use a combination of aggregation and CASE statements, or, more elegantly the crosstab() function provided by the additional module tablefunc. All basics here:
PostgreSQL Crosstab Query
Since not all keys in grade have values, you need the 2-parameter form. Like this:
SELECT * FROM crosstab(
'SELECT id, grade, count FROM table ORDER BY 1,2'
, $$SELECT unnest('{A,B,C,D,F}'::text[])$$
) ct(id text, "A" int, "B" int, "C" int, "D" int, "F" int);

Create New Table From Other Table After Grouping

How can I insert to a table a value from "grouping" other table?
That means I have 2 table with different structure.
The table ORDRE with existed DATA
Table ORDRE:
ORDRE ID | CODE_DEST |
-------------------------
1 | a |
2 | b |
3 | c |
4 | a |
5 | a |
6 | b |
7 | g |
I want to INSERT the value FROM Table ORDRE INTO TABLE VOIT:
ID_VOIT | ORDRE ID | CODE_DEST |
---------------------------------------
1 | 1 | a |
1 | 4 | a |
1 | 5 | a |
2 | 2 | b |
2 | 6 | b |
3 | 3 | c |
4 | 7 | g |
This is my best guess on what you need using only the info available.
declare #Ordre table
(
ordre_id int,
code_dest char(1)
)
declare #Voit table
(
id_voit int,
ordre_id int,
code_dest char(1)
)
insert into #Ordre values
(1,'a'),
(2,'b'),
(3,'c'),
(4,'a'),
(5,'a'),
(6,'b'),
(7,'g')
insert into #Voit
select id_voit, ordre_id, rsOrdre.code_dest
from #Ordre rsOrdre
inner join
(
select code_dest, ROW_NUMBER() over (order by code_dest) as id_voit
from #Ordre
group by code_dest
) rsVoit on rsVoit.code_dest = rsOrdre.code_dest
order by id_voit, ordre_id
select * from #Voit
Working Example.
For the specific data you give as an example, this works:
insert into VOIT
select
case code_dest
when 'a' then 1
when 'b' then 2
when 'c' then 3
when 'g' then 4
else 0
end, orderId, code_dest from ORDRE order by code_dest, orderId
But it kind of sucks because it requires hard-coding in a huge case statement.
Test is here - https://data.stackexchange.com/stackoverflow/q/119442/
What I like more is moving the VOIT ID / Code_Dest associations to a new table, so then you could do an inner join instead.
insert into VOIT
select voit_id, orderId, t.code_dest
from ORDRE t
join Voit_CodeDest t2 on t.code_dest = t2.code_dest
order by code_dest, orderId
Working example of that here - https://data.stackexchange.com/stackoverflow/q/119443/

Use SQL to clone data in two tables that have a 1-1 relationship with each other

Using MS SQL 2005,
Table 1
ID | T1Value | T2ID | GroupID
----------------------------------
1 | a | 10 | 1
2 | b | 11 | 1
3 | c | 12 | 1
4 | a | 22 | 2
Table 2
ID | T2Value
----------------
10 | H
11 | J
12 | K
22 | H
I want to clone the data for GroupID == 1 into a new GroupID so that I result with the following:
Table 1
ID | T1Value | T2ID | GroupID
----------------------------------
1 | a | 10 | 1
2 | b | 11 | 1
3 | c | 12 | 1
4 | a | 22 | 2
5 | a | 23 | 3
6 | b | 24 | 3
7 | c | 25 | 3
Table 2
ID | T2Value
----------------
10 | H
11 | J
12 | K
22 | H
23 | H
24 | J
25 | K
I've found some SQL clone patterns that allow me to clone data in the same table well... but as I start to deal with cloning data in two tables at the same time and then linking up the new rows correctly... that's just not something I feel like I have a good grasp of.
I thought I could do some self-joins to deal with this, but I am worried in the cases where the non-key fields have the same data in multiple rows.
Is my only option to use a cursor to keep track of the ID mapping? Here is some pseudo-code I wrote... not tested yet. I was hoping for something more concise. Is this my only option?
DECLARE #NewT2Key INT
DECLARE #OldT2Key INT
DECLARE #T2Value VARCHAR(50)
DECLARE #T2KeyNewOld TABLE (OldT2Key INT, NewT2Key INT)
DECLARE #NewGroupID INT
DECLARE #OldGroupID INT
SET #NewGroupID = 3
SET #OldGroupID = 1
--
-- STEP 1: CLONE THE TABLE2 DATA AND KEEP MAPPING OF OLD-to-NEW IDs
--
DECLARE curT2Keys
CURSOR FAST_FORWARD LOCAL FOR
SELECT t2.ID,
t2.T2Value
FROM dbo.Table2 t2
JOIN dbo.Table1 t1
ON t2.ID = t1.T2ID
WHERE t1.GroupID = #OldGroupID
ORDER BY t1.ID
OPEN curT2Keys
FETCH NEXT FROM curT2Keys INTO #OldT2Key, #T2Value
WHILE ##FETCH_STATUS = 0
BEGIN
SET #NewT2Key = (SELECT MAX(ID)+1 FROM dbo.Table2)
INSERT INTO dbo.Table2(ID, T2Value)
VALUES(#NewT2Key, #T2Value)
INSERT INTO #T2KeyNewOld(OldT2Key, NewT2Key)
VALUES(#OldT2Key, #NewT2Key)
FETCH NEXT FROM curT2Keys INTO #OldT2Key, #T2Value
END
CLOSE curT2Keys
DEALLOCATE curT2Keys
--
-- STEP 2: CLONE THE TABLE1 DATA AND UPDATE IDs WITH NEW MAPPING
--
INSERT INTO dbo.Table1([ID], [T1Value], [T2ID], [GroupID])
(SELECT
(SELECT MAX(ID) FROM dbo.Table1) + ROW_NUMBER() OVER (ORDER BY GroupID),
t1.[T1Value],
t2.[NewT2Key],
#NewGroupID
FROM dbo.Table1 t1
JOIN #T2KeyNewOld t2
ON t1.T2ID = t2.OldT2Key
WHERE t1.GroupID = #OldGroupID
)