Combine rows into columns in SQL Server in a particular way - sql

I don't know how to describe my problem, but I'll do my best. I'm working in SQL Server 2014. I've simplified the problem as much as I can since I'm working with sensitive info.
I currently have a query that returns the following from a table of test answers:
test_id
question_id
is_checked
1
1
TRUE
1
2
TRUE
1
3
FALSE
1
4
FALSE
2
1
FALSE
2
2
FALSE
2
3
FALSE
2
4
TRUE
3
1
FALSE
3
2
FALSE
3
3
FALSE
3
4
FALSE
Each test has only 4 yes/no questions (and this is unlikely to ever change). For each test, one or more questions can be marked yes. Above...
test 1 has questions 1 and 2 as yes, the rest as no.
test 2 has question 4 as yes, the rest as no.
test 3 has all questions marked as no.
I want my results to look like this:
test_id
question_1
question_2
question_3
question_4
1
TRUE
TRUE
FALSE
FALSE
2
FALSE
FALSE
FALSE
TRUE
3
FALSE
FALSE
FALSE
FALSE
I tried to use PIVOT to no luck. Any help would be appreciated, and I'm happy to provide more info.
EDIT:
My attempt at using PIVOT (please forgive my likely horrible formatting):
SELECT *
FROM (
SELECT test_id, question_id, is_checked FROM example_table
) as sourcetable
pivot(
any(is_checked)
for question_id
in (question_1, question_2, question_3, question_4)
) as pivottable
Populating an example table based on the above:
CREATE TABLE example_table (test_id int, question_id int, is_checked bit);
INSERT INTO example_table (test_id, question_id, is_checked)
VALUES
('1', '1', '1'),
('1', '2', '1'),
('1', '3', '0'),
('1', '4', '0'),
('2', '1', '0'),
('2', '2', '0'),
('2', '3', '0'),
('2', '4', '1'),
('3', '1', '0'),
('3', '2', '0'),
('3', '3', '0'),
('3', '4', '0');
Finally, my SQL Server version is SQL Server 2014. I previously put SQL Server 17 above, but have corrected it.
FINAL EDIT:
The column is_checked is a bit type in my system, but someone must have set it to output TRUE and FALSE when queried. In the answer below, I replaced is_checked with CAST(is_checked AS INT) and that worked.

Please try the following solution.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (test_ID INT, question_id INT, is_checked VARCHAR(5));
INSERT INTO #tbl (test_ID, question_id, is_checked) VALUES
(1, 1, 'TRUE'),
(1, 2, 'TRUE'),
(1, 3, 'FALSE'),
(1, 4, 'FALSE'),
(2, 1, 'FALSE'),
(2, 2, 'FALSE'),
(2, 3, 'FALSE'),
(2, 4, 'TRUE'),
(3, 1, 'FALSE'),
(3, 2, 'FALSE'),
(3, 3, 'FALSE'),
(3, 4, 'FALSE');
-- DDL and sample data population, end
SELECT test_ID
, MAX(IIF(question_id = 1, is_checked, '')) AS question_1
, MAX(IIF(question_id = 2, is_checked, '')) AS question_2
, MAX(IIF(question_id = 3, is_checked, '')) AS question_3
, MAX(IIF(question_id = 4, is_checked, '')) AS question_4
FROM #tbl
GROUP BY test_ID
ORDER BY test_ID;
Output
+---------+------------+------------+------------+------------+
| test_ID | question_1 | question_2 | question_3 | question_4 |
+---------+------------+------------+------------+------------+
| 1 | TRUE | TRUE | FALSE | FALSE |
| 2 | FALSE | FALSE | FALSE | TRUE |
| 3 | FALSE | FALSE | FALSE | FALSE |
+---------+------------+------------+------------+------------+

Related

From several lines a hierarchy into one line

I have a table with data hierarchy
CREATE TABLE my_table(
object_id varchar,
parent_id varchar
);
INSERT INTO my_table(object_id , parent_id)
VALUES
('1', '0'),
('2', '0'),
('3', '1'),
('4', '1'),
('5', '1'),
('6', '3'),
('7', '2'),
('8', '2');
object_id
parent_id
1
0
2
0
3
1
4
1
5
1
6
3
7
2
8
2
I need to tranformation to
object_id
parent_id
{1,3,4,5,6},{2,7,8}
0
I think there is no way to do without an intermediate table
So far, I have only found this request, but I don’t know which way to dig further
SELECT parent_id, array_to_string(array_agg(distinct(object_id)), ' , ', '') AS object_id
FROM my_table
GROUP BY parent_id;
Thx

SQL query to fetch distinct records

Can someone help me out with this sql query on postgres which I have to write but I just can't come up with, I have tried my best to simplify the problem from 1 million records and more constraints to this, I know this looks easy, but I am still unable to resolve this somehow :-
Table_name = t
Column_1_name = id
Column_2_name = st
Column_1_elements = [1,1,1,1,2,2,2,3,3]
Column_2_elements = [a,b,c,d,a,c,d,b,d]
Now I want to print to those distinct ids from id where they do not have their corresponding st equals to 'b' or 'a'.
For example, for the above example, the ouput should be [2,3] as 2 does not have corresponding 'b' and 3 does not have 'a'. [even though 3 does not have c also, but we are not concerned about 'c']. id=1 is not returned in solution as it has a relation with both 'a' and 'b'.
Let me know if you need more clarity.
Thanks in advance for helping.
edit1:- The number of elements for id = 1,2,3 could be anything. I just want those ids where there corresponding st does not "contain" 'a' or 'b'.
if there is an id=4 which has just one st which is 'r', and there is an id=5 which contains 'a','b','c','d','e','f','k','z'.
Then we want id=4 in the output as well as it does not contain 'a' or 'b'..
You might need to correct the syntax a little bit based on you SQL engine but this one is a working solution in Google BigQuery -
with temp as (
select 1 as id, 'a' as st union all
select 1 as id, 'b' as st union all
select 1 as id, 'c' as st union all
select 1 as id, 'd' as st union all
select 2 as id, 'a' as st union all
select 2 as id, 'c' as st union all
select 2 as id, 'd' as st union all
select 3 as id, 'b' as st union all
select 3 as id, 'd' as st union all
select 4 as id, 'e' as st union all
select 5 as id, 'g' as st union all
select 5 as id, 'h' as st
)
-- add 2 columns for is_a and is_b flags
, temp2 as (
select *
, case when st = 'a' then 1 else 0 end is_a
,case when st = 'b' then 1 else 0 end as is_b
from temp
)
-- IDs that have both the flags as 1 should be filtered out (like ID = 1)
select id
from temp2
group by 1
having max(is_a) + max(is_b) < 2
This solution takes care of the problem you mentioned with ID 4 . Let me know if this works for you.
See if this works:
create table t (id integer, st varchar);
insert into t values (1, 'a'), (1, 'b'), (1, 'c'), (1, 'd'), (2, 'a'), (2, 'c'), (2, 'd'), (3, 'b'), (3, 'd'), (4, 'r');
insert into t values (5, 'a'), (5, 'b'), (5, 'c'), (5, 'd'), (5, 'e'), (5, 'f'), (5, 'k'), (5, 'z');
select id, array['a', 'b'] <# array_agg(st)::text[] as tf from t group by id;
id | tf
----+----
3 | f
5 | t
4 | f
2 | f
1 | t
select * from (select id, array['a', 'b'] <# array_agg(st)::text[] as tf from t group by id) as agg where agg.tf = 'f';
id | tf
----+----
3 | f
4 | f
2 | f
In the first select query the array_agg(st) aggregates all the st values for an id via the group by id. array['a', 'b'] <# array_agg(st)::text[] then asks if the a and b are both in the array_agg.
The query is then turned into a sub-query where the outer query selects those rows that where 'f'(false), in other words did not have both a and b in the aggregated id values.

Selecting rows from a table with specific values per id

I have the below table
Table 1
Id WFID data1 data2
1 12 'd' 'e'
1 13 '3' '4f'
1 15 'e' 'dd'
2 12 'f' 'ee'
3 17 'd' 'f'
2 17 'd' 'f'
4 12 'd' 'f'
5 20 'd' 'f'
From this table I just want to select the rows which has 12 and 17 only exclusively. Like from the table I just want to retrieve the distinct id's 2,3 and 4. 1 is excluded because it has 12 but also has 13 and 15. 5 is excluded because it has 20.
2 in included because it has just 12 and 17.
3 is included because it has just 17
4 is included because it has just 12
If you just want the list of distinct ids that satisfy the conditions, you can use aggregation and filter with a having clause:
select id
from mytable
group by id
having max(case when wfid not in (12, 17) then 1 else 0 end) = 0
This filters out groups that have any wfid other than 12 or 17.
If you want the entire corresponding rows, then window functions are more appropriate:
select
from (
select t.*,
max(case when wfid not in (12, 17) then 1 else 0 end) over(partition by id) flag
from mytable t
) t
where flag = 0
You really need to start thinking in terms of sets. And it helps everyone if you provide a script that can be used to experiment and demonstrate. Here is another approach using the EXCEPT operator. The idea is to first generate a set of IDs that we want based on the filter. You then generate a set of IDs that we do not want. Using EXCEPT we can then remove the 2nd set from the 1st.
declare #x table (Id tinyint, WFID tinyint, data1 char(1), data2 varchar(4));
insert #x (Id, WFID, data1, data2) values
(1, 12, 'd', 'e'),
(1, 13, '3', '4f'),
(1, 15, 'e', 'dd'),
(2, 12, 'f', 'ee'),
(3, 17, 'd', 'f'),
(2, 17, 'd', 'f'),
(4, 12, 'd', 'f'),
(2, 12, 'z', 'ef'),
(5, 20, 'd', 'f');
select * from #x
select id from #x where WFID not in (12, 17);
select id from #x where WFID in (12, 17)
except
select id from #x where WFID not in (12, 17);
Notice the added row to demonstrate what happens when there are "duplicates".

How to recode a field into this specific structure using T-SQL?

I am using SQL Server 2014 and I have a column (ID) in a table (tbl1). The column ID is a nvarchar field.
Here are some examples of what it contains:
ID
18FD64245
533040174
12AZ61356
19AK13355
18HD24189
I would like to run a T-SQL query to recode those values based on the following logic:
IF THEN IF THEN
A 1 0 3
B 2 1 6
C 3 2 7
D 4 3 1
E 5 4 2
F 6 5 4
G 7 6 8
H 8 7 9
I 9 8 5
J 10 9 0
K 11
L 12
M 13
N 14
O 15
P 16
Q 17
R 18
S 19
T 20
U 21
V 22
W 23
X 24
Y 25
Z 26
Therefore the first 2 values shown above would be recoded as:
ID ID2
18FD64245 656482724
533040174 411323692
I am having a hard time approaching the problem from a T-SQL point of view. I am thinking about using CASE Statements to solve the problem. I also had a look at the REPLACE function.
But I am stuck as to how to go about it since the ID field is an alpha-numeric field.
Any ideas on how I move forward with this?
Edit (to show my sql codes as per answer proposed by #Squirrel):
declare #map table
(
map_fr char(1),
map_to varchar(2)
)
insert into #map
values
('A', '1'),
('B', '2'),
('C', '3'),
('D', '4'),
('E', '5'),
('F', '6'),
('G', '7'),
('H', '8'),
('I', '9'),
('J', '10'),
('K', '11'),
('L', '12'),
('M', '13'),
('N', '14'),
('O', '15'),
('P', '16'),
('Q', '17'),
('R', '18'),
('S', '19'),
('T', '20'),
('U', '21'),
('V', '22'),
('W', '23'),
('X', '24'),
('Y', '25'),
('Z', '26')
; with rcte as
(
select [ID], idx = 1, ch = substring([ID], 1, 1)
from Table1
WHERE [ID] IS NOT NULL
union all
select [ID], idx = idx + 1, ch = substring([ID], idx + 1, 1)
from rcte
where idx < len([ID])
),
cte as
(
select r.[ID], r.idx, m.map_to
from rcte r
inner join #map m on r.ch = m.map_fr
)
select [ID],
(select '' + map_to from cte x where x.[ID] = c.[ID] order by idx for xml path('')) as ID2
from cte c
group by [ID]
order by [ID]
I would create a mapping table like
declare #map table
(
map_fr char(1),
map_to varchar(2)
)
and insert the mapping there
insert into #map
values ('A', '1'), ('B', '2'), ('C', '3'), ('D', '4'), ('E', '5'), ('F', '6'),
('G', '7'), ('H', '8'), ('I', '9'), ('J', '10'),('K', '11'),('L', '12'),
('M', '13'),('N', '14'),('O', '15'),('P', '16'),('Q', '17'),('R', '18'),
('S', '19'),('T', '20'),('U', '21'),('V', '22'),('W', '23'),('X', '24'),
('Y', '25'),('Z', '26'),
('0', '3'), ('1', '6'), ('2', '7'), ('3', '1'), ('4', '2'), ('5', '4'),
('6', '8'), ('7', '9'), ('8', '5'), ('9', '0')
then use recursive CTE to split the character and join to the mapping table. And finally concatenate back the string using the mapped value.
; with rcte as
(
select ID, idx = 1, ch = substring(ID, 1, 1)
from yourtbl
union all
select ID, idx = idx + 1, ch = substring(ID, idx + 1, 1)
from rcte
where idx < len(ID)
),
cte as
(
select r.ID, r.idx, m.map_to
from rcte r
inner join #map m on r.ch = m.map_fr
)
select ID,
(select '' + map_to from cte x where x.ID = c.ID order by idx for xml path('')) as ID2
from cte c
group by ID
order by ID
This is better suited as an scalar function, but if you want to do it all on a single SQL statement, here is a way :
select ID,
case substring(ID, 1, 1) when 'A' then '1'
when 'B' then '2'
...
when '9' then '0'
end
+
case substring(ID, 2, 1) when 'A' then '1'
when 'B' then '2'
...
when '9' then '0'
end
+
...
...
case substring(ID, 9, 1) when 'A' then '1'
when 'B' then '2'
...
when '9' then '0'
end
as ID2
from MY_TABLE
You can also map these using a tally table and some of the new features of SQL Server 2017 (STRING_AGG):
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE IDS
(
ID NVARCHAR(9)
)
INSERT INTO IDS
VALUES ('18FD64245'),
('533040174'),
('12AZ61356'),
('19AK13355'),
('18HD24189');
Query 1:
WITH Tally
AS
(
SELECT ROW_NUMBER() OVER (ORDER BY Nums.Num) AS Number
FROM (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) AS Nums(Num)
CROSS APPLY (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) AS Nums2(Num)
),
Chars
As
(
-- Turn each character of ID to new row
SELECT ID, SUBSTRING(ID, Number, 1) AS OldChar, Number As Ind
FROM IDS
CROSS APPLY Tally
WHERE SUBSTRING(ID, Number, 1) <> ''
),
NewChars
AS
(
-- Map old characters to new characters
SELECT *,
CASE WHEN ISNumeric(OldChar) = 1 THEN
-- effectively a mapping string to map old characters to new
SUBSTRING('3671248950', CHARINDEX(OldChar, '0123456789'), 1)
ELSE
-- for alphanumeric we can simply make 'A' be 1 and 'B' be 2
-- by subtracting the ASCII value of 'A' from the ASCII of the
-- Character and add 1
ASCII(OldChar) - ASCII('A') + 1
END As NewChar
FROM Chars
)
-- Recombine New Characters to form new Id (SQL Server 2017 only)
SELECT ID, STRING_AGG(NewChar,'') WITHIN GROUP (ORDER BY Ind) AS NewId
FROM NewChars
GROUP BY ID
ORDER BY Id
Results:
| ID | NewId |
|-----------|------------|
| 12AZ61356 | 6712686148 |
| 18FD64245 | 656482724 |
| 18HD24189 | 658472650 |
| 19AK13355 | 6011161144 |
| 533040174 | 411323692 |

I want to fetch data in sequence of their branched data if exists

Hi i have data in one table as Question Table
QuestionID QuestionDescription
2 This is test
3 test is tst
4 3
6 5
17 6
18 7
19 8
20 9
5 4
and in one Table QuestionBranching Table as
QuestionBranchingID QuestionID Response NextQuestionID ParentQuestionID
1 3 True 5 3
2 3 False 6 3
7 5 True 19 3
8 5 False 20 3
9 18 True 17 18
10 18 False 4 18
So if any QuestionID exists in the QuestionBranching table then the Select Join query should fetch data in that sequence order. for ex.:
If QuestionID exists in QuestionBranching Table then NextQuestionID will be next in the sequence.
and If not then normal flow.
So the desired result i am looking for is :
QuestionID
2
3(if it exists in QuestionBranching then NextQuestionID will be next i.e. '5')
5
6
19
20
18
17
4
Try this:
select isnull(b.NextQuestionID,q.QuestionID) as QuestionID
from Question q
left join QuestionBranching b on q.QuestionID=b.QuestionID
maybe this can help you
declare #question table (QuestionID int, QuestionDescription varchar(100))
declare #branching table (BranchingID int, QuestionID int, Response bit, NextQuestionID int)
insert into #question values (2, 'this is a test'), (3, 'test is tst'), (4, '3'), (6, '5'), (17, '6'), (18, '7'), (19, '8'), (20, '9'), (5, '4')
insert into #branching values (1, 3, 1, 5), (2, 3, 0, 6), (7, 5, 1, 19), (8, 5, 0, 20)
select t.nq
from ( select q.QuestionID, q.QuestionID as nq
from #question q
union all
select isnull((select b2.QuestionID from #branching b2 where b2.NextQuestionID = b.QuestionID), b.QuestionID) as QuestionID, b.NextQuestionID as nq
from #branching b
) t
order by t.QuestionID, t.nq