Creating a select to 'de-normalise' the data within a table - sql

I put the work de-normalise in quote marks, because it might not be the right way of putting it, but not too sure how else to describe it...
I have the following table
Source Priority Attribute
A 1 Name
B 2 Name
C 3 Name
A 1 Address
B 2 Address
C 3 Address
A 2 Email
B 3 Email
C 1 Email
I would like my select to return:
Source Name_Pri Addr_Pri Email_Pri
A 1 1 2
B 2 2 3
C 3 3 1
Thanks

You are looking for a pivot. I often do this using conditional aggregation:
select source,
max(case when attribute = 'Name' then priority end) as name_priority,
max(case when attribute = 'Address' then priority end) as address_priority,
max(case when attribute = 'Email' then priority end) as email_priority
from t
group by source;

Related

How to check the count of each values repeating in a row

I have two tables. Data in the first table is:
ID Username
1 Dan
2 Eli
3 Sean
4 John
Second Table Data:
user_id Status_id
1 2
1 3
4 1
3 2
2 3
1 1
3 3
3 3
3 3
. .
goes on goes on
These are my both tables.
I want to find the frequency of individual users doing 'status_id'
My expected result is:
username status_id(1) status_id(2) status_id(3)
Dan 1 1 1
Eli 0 0 1
Sean 0 1 2
John 1 0 0
My current code is:
SELECT b.username , COUNT(a.status_id)
FROM masterdb.auth_user b
left outer join masterdb.xmlform_joblist a
on a.user1_id = b.id
GROUP BY b.username, b.id, a.status_id
This gives me the separate count but in a single row without mentioning which status_id each column represents
This is called pivot and it works in two steps:
extracts the data for the specific field using a CASE statement
aggregates the data on users, to make every field value lie on the same record for each user
SELECT Username,
SUM(CASE WHEN status_id = 1 THEN 1 END) AS status_id_1,
SUM(CASE WHEN status_id = 2 THEN 1 END) AS status_id_2,
SUM(CASE WHEN status_id = 3 THEN 1 END) AS status_id_3
FROM t2
INNER JOIN t1
ON t2.user_id = t1._ID
GROUP BY Username
ORDER BY Username
Check the demo here.
Note: This solution assumes that there are 3 status_id values. If you need to generalize on the amount of status ids, you would require a dynamic query. In any case, it's better to avoid dynamic queries if you can.

How to check the value of any row in a group after a previous one fulfils a condition?

I have a dataset grouped by test subjects that is filled according to the actions they perform. I need to find which customer does A and then, at some point, does B; but it doesn't necessarily have to be in the next action/row. And it can't be first does B and then A, it has to be specifically in that order. For example, I have this table:
Subject ActionID ActionOrder
1 A 1
1 C 2
1 D 3
1 B 4
1 C 5
2 D 1
2 A 2
2 C 3
2 B 4
3 B 1
3 D 2
3 A 3
4 A 1
Here subjects 1 and 2 are the ones that fulfil the order of actions condition. While 3 does not because it performs the actions in reverse order. And 4 only does action A
How can I get only subjects 1 and 2 as results? Thank you very much
Use conditional aggregation:
SELECT Subject
FROM tablename
WHERE ActionID IN ('A', 'B')
GROUP BY Subject
HAVING MAX(CASE WHEN ActionID = 'A' THEN ActionOrder END) <
MIN(CASE WHEN ActionID = 'B' THEN ActionOrder END)
See the demo.
Consider below option
select Subject
from (
select Subject,
regexp_replace(string_agg(ActionID, '' order by ActionOrder), r'[^AB]', '') check
from `project.dataset.table`
group by Subject
)
where not starts_with(check, 'B')
and check like '%AB%'
Above assumes that Subject can potentially do same actions multiple times that's why few extra checks in where clause. Other wise it would be just check = 'AB'

SQL Query - Convert data values into attributes into antother table.

I am building a report and I am stuck formulating a query. I am bringing the following data from multiple tables after a lot of joins.
ID TYPE RATING
----- ---- ------
ID_R1 A 1
ID_R1 B 3
ID_R2 A 2
ID_R2 B 1
ID_R3 A 4
ID_R3 B 4
ID_R4 A 2
ID_R4 B 3
ID_R5 A 2
ID_R5 B 3
What actually is happening is that Every ID will have a Rating for Type A & B so what I need to do is transform the above into the following
ID Type_A_Rating Type_B_Rating
----- ------------- -------------
ID_R1 1 3
ID_R2 3 1
ID_R3 4 4
ID_R4 2 3
ID_R5 2 3
I have think group by and different techniques but so far I am unable to come up with a solution. Need help F1! F1!
p.s just for the record my end game is getting the count of (A,B) combinations
Type_A_Rating Type_B_Rating Count
------------- ------------- -----
1 1 0
1 2 0
1 3 1
1 4 0
2 1 0
2 2 0
2 3 2
2 4 0
3 1 1
3 2 0
3 3 0
3 4 0
4 1 0
4 2 0
4 3 0
4 4 1
From this you can see that a simple GROUP BY with any form AND OR conditions doesn't suffice until I get the data as mentioned. I could use two intermediate/temp tables, in one get Type_A_Rating with ID and then in second Type_B_Rating with ID and then in another combine both but isn't there a better way.
This should work as SQL engine agnostic solution (provided that there is exactly one row with type A for each ID and one row with type B for each ID):
select
TA.ID,
TA.RATING as Type_A_Rating,
TB.RATING as Type_B_Rating
from
(select ID, RATING
from T where TYPE = 'A') as TA
inner join
(select ID, RATING
from T where TYPE = 'B') as TB
on TA.ID = TB.ID
Related SQL Fiddle: http://sqlfiddle.com/#!9/7e6fd9/2
Alternative (simpler) solution:
select
ID,
sum(case when TYPE = 'A' then RATING else 0 end) as Type_A_Rating,
sum(case when TYPE = 'B' then RATING else 0 end) as Type_B_Rating
from
T
group by
ID
Fiddle: http://sqlfiddle.com/#!9/7e6fd9/3
EDIT:
The above is correct but both can be simplified a bit:
select TA.ID, TA.RATING as Type_A_Rating, TB.RATING as Type_B_Rating
from T TA join
T TB
on TA.ID = TB.ID AND A.type = 'A' and B.type = 'B';
And (because I prefer NULL when there are no matches:
select ID,
max(case when TYPE = 'A' then RATING end) as Type_A_Rating,
max(case when TYPE = 'B' then RATING end) as Type_B_Rating
from T
group by ID

SQL: A count inside a case inside a case perhaps?

Good day all.
below is an image relating to what I am attempting to achieve.
In one table there is two fields one is an ID and one is a Type.
I figured a picture paints a thousand words, so check the below
I have tried a few things with case and other things but none worked.
There is a couple of things to note: We cannot use temporary tables, inserts or deletes due to certain limitations.
Data Sample:
ID Type
3 bad
2 zeal
4 tro
3 pol
2 tro
2 lata
4 wrong
3 dead
2 wrong
3 dead
4 wrong
3 lata
2 bad
2 zeal
First of all you need a table containing the type groups:
type typegroup
bad 1
tro 1
zeal 1
dead 2
lata 2
wrong 2
pol 3
Then join, group by type group in order to get one result line per type group and count.
select
tg.typegroup,
count(case when id = 2 then 1 end) as id2,
count(case when id = 3 then 1 end) as id3
count(case when id = 4 then 1 end) as id4
from typegroups tg
join mytable m on m.type = tg.type
group by tg.typegroup
order by tg.typegroup;
UPDATE: Of course you can create such table on-the-fly.
...
from
(
select 'bad' as type, 1 as typegroup
union all
select 'tro' as type, 1 as typegroup
union all
...
) tg
join mytable m on m.type = tg.type
...
And you can move this to a WITH clause if you prefer so.

SQL get all IDs where Sub-IDs are exactly specified without getting other IDs where some Sub-ID's are not present

Sorry for that title, I don't know how to describe my problem in one sentence.
I have Table like this:
event | thema
-------------
1 1
1 2
2 1
2 2
2 3
3 1
3 2
3 3
3 4
4 1
4 2
4 3
What I want are the event IDs where the thema is exaclty 1, 2 and 3, not the event ID where it is only 1 and 2 or 1,2,3 and 4.
SELECT event WHERE thema=1 OR thema=2 OR thema=3
returns them all
SELECT event WHERE thema=1 AND thema=2 AND thema=3
returns nothing.
I think this should be absolutely simple, but stack is overflown...
Thanks for some help!
Group by the event and take only those having at least one thema 1 and 2 and 3 and not any other
SELECT event
from your_table
group by event
having sum(case when thema = 1 then 1 else 0 end) > 0
and sum(case when thema = 2 then 1 else 0 end) > 0
and sum(case when thema = 3 then 1 else 0 end) > 0
and sum(case when thema not in (1,2,3) then 1 else 0 end) = 0
This type of query is a "set-within-sets" query (your are looking for sets of "thema" for each event). The most general approach is aggregation using a having clause. This might be the shortest way to write the query using standard SQL:
select event
from table t
group by event
having count(distinct (case when thema in (1, 2, 3) then thema end)) = 3;
or,
first create table #themas (depending on vendor, make this a temp table or a simple table-valued variable) that contains user-specified list of thema values, then
Select event from your_table y
Where not exists
(Select * From #Themas t
where Not Exists
(Select * From your_table
where event = y.event
and thema = t.thema))
and not exists (Select * From your_table
where event = t.event
and thema not in
(Select thema From #Themas ))