SQL Server pivot with "ties" - sql

Here is my source data:
+-------+-------+-------+------+
| Categ | Nm | Value | Rnk |
+-------+-------+-------+------+
| A | Tom | 37 | 1 |
| A | Joe | 36 | 2 |
| A | Eddie | 35 | 3 |
| B | Seth | 28 | 1 |
| B | Ed | 25 | 2 |
| B | Billy | 22 | 3 |
| C | Julie | 42 | 1 |
| C | Jenny | 41 | 2 |
| C | April | 40 | 3 |
| C | Mary | 40 | 3 |
| C | Laura | 40 | 3 |
+-------+-------+-------+------+
And here is the output I would like to produce:
+------+--------+--------+-------+
| Rnk | A | B | C |
+------+--------+--------+-------+
| 1 | Tom | Seth | Julie |
| 2 | Joe | Ed | Jenny |
| 3 | Eddie | Billy | April |
| 3 | (null) | (null) | Mary |
| 3 | (null) | (null) | Laura |
+------+--------+--------+-------+
I have used the following approach (which I understand through other posts may be superior to actually using PIVOT)...and this gets me to where I see Julie/Jenny/April, but not Mary/Laura (obviously, since it is pulling the MIN in the event of a 'tie').
SELECT Rnk
, min(CASE WHEN Categ = 'A' THEN Nm END) as A
, min(CASE WHEN Categ = 'B' THEN Nm END) as B
, min(CASE WHEN Categ = 'C' THEN Nm END) as C
FROM Tbl
GROUP BY Rnk
How to get to my desired output?

Well, if you want multiple rows for each rank, you can't aggregate by rank, or at least by rank alone. So, calculate the rank-within-the-rank or as the following query calls it, the sub_rnk:
SELECT Rnk,
min(CASE WHEN Categ = 'A' THEN Nm END) as A,
min(CASE WHEN Categ = 'B' THEN Nm END) as B,
min(CASE WHEN Categ = 'C' THEN Nm END) as C
FROM (select t.*, row_number() over (partition by categ, rnk order by newid()) as sub_rnk
from Tbl t
) t
GROUP BY rnk, sub_rnk
ORDER BY rnk;

Related

Combine the data of two tables using SQL Pivot and joins for a subquery

I'm trying to create a report to find the number of users subscribed to the notification type.
I am stuck with subqueries because if these two tables
Table 1
NotificationMaster
+----+-------+
| ID | Name |
+----+-------+
| 1 | Email |
| 2 | Push |
| 3 | Call |
+----+-------+
Table 2
NotificationPreference
+------------+------------------+------------+--------------+
| ResourceID | NotificationID | IsChecked | AccountID |
+------------+------------------+------------+--------------+
| 23 | 1 | 1 1 |
| 36 | 2 | 0 2 |
| 45 | 3 | 1 3 |
| 23 | 1 | 0 1 |
| 36 | 2 | 1 2 |
| 45 | 3 | 0 3 |
| 23 | 1 | 1 1 |
| 36 | 2 | 0 3 |
| 45 | 3 | 1 3 |
+------------+------------------+--------------------------+
Expected Output
Notification Vs Resource Count
+----------+-------+------+------+
| Accountid Email | Push | Call |
+----------+-------+------+------+
| 1 | 2 | 1 | 2 |
+----------+-------+------+------+
Other Tables
AccountName
+----+-------+
| ID | Name |
+----+-------+
| 1 | Blues |
+----+-------+
| 2 | Jazz |
+----+-------+
| 3 | Rock |
+----+-------+
ResourceNames
+----------+----------------+-----------+
| Resource | Name | AccountID |
+----------+----------------+-----------+
| 23 | MJ | 1 |
| 36 | Paul | 1 |
| 45 | Jay Z | 3 |
+----------+----------------+-----------+
Progress Till Now
SELECT A.ID
,A.Name
,count(R.id) AS 'Total Resource Count'
,(SELECT count(DISTINCT np.resourceid)
FROM NotificationPreference np
INNER JOIN NotificationMaster nm ON np.notificationid = nm.id
WHERE np.accountid = A.ID
AND nm.id = 1
) AS 'Email'
FROM AccountName A
LEFT JOIN [ResourceNames] R ON A.ID = R.[AccountID]
LEFT JOIN NotificationPreference np ON np.resourceid = R.ID
GROUP BY A.ID
,A.Name
The basic pivot use conditional COUNT() :
SELECT Accountid
, COUNT( CASE WHEN nm.Name = 'Email' THEN 1 END ) as Email
, COUNT( CASE WHEN nm.Name = 'Push' THEN 1 END ) as Push
, COUNT( CASE WHEN nm.Name = 'Call' THEN 1 END ) as Call
FROM NotificationPreference np
JOIN NotificationMaster nm
ON np.NotificationID = nm.id
GROUP BY Accountid

Hive - over (partition by ...) with a column not in group by

Is it possible to do something like:
select
avg(count(distinct user_id))
over (partition by some_date) as average_users_per_day
from user_activity
group by user_type
(notably, the partition by column, some_date, is not in the group by columns)
The idea I'm going for is something like: the average users per day by user type.
I know how to do it using subqueries (see below), but I'd like to know if there is a nice way using only over (partition by ...) and group by.
Notes:
From reading this answer, my understanding (correct me if I'm wrong) is that the following query:
select
avg(count(distinct a)) over (partition by b)
from foo
group by b
can be expanded equivalently to:
select
avg(count_distinct_a)
from (
select
b,
count(distinct a) as count_distinct_a
from foo
group by b
)
group by b
And from that, I can tweak it a bit to achieve what I want:
select
avg(count_distinct_user_id) as average_users_per_day
from (
select
user_type,
count(distinct user_id) as count_distinct_user_id
from user_activity
group by user_type, some_date
)
group by user_type
(notably, the inner group by user_type, some_date differs from the outer group by user_type)
I'd like to be able to tell the partition by-group by interaction to use a "sub-group-by" for the windowing part. Please let me know if my understanding of partition by/group by is completely off.
EDIT: Some sample data and desired output.
Source table:
+---------+-----------+-----------+
| user_id | user_type | some_date |
+---------+-----------+-----------+
| 1 | a | 1 |
| 1 | a | 2 |
| 2 | a | 1 |
| 3 | a | 2 |
| 3 | a | 2 |
| 4 | b | 2 |
| 5 | b | 1 |
| 5 | b | 3 |
| 5 | b | 3 |
| 6 | c | 1 |
| 7 | c | 1 |
| 8 | c | 4 |
| 9 | c | 2 |
| 9 | c | 3 |
| 9 | c | 4 |
+---------+-----------+-----------+
Sample intermediate table (for reasoning with):
+-----------+-----------+---------------------+
| user_type | some_date | distinct_user_count |
+-----------+-----------+---------------------+
| a | 1 | 2 |
| a | 2 | 2 |
| b | 1 | 1 |
| b | 2 | 1 |
| b | 3 | 1 |
| c | 1 | 2 |
| c | 2 | 1 |
| c | 3 | 1 |
| c | 4 | 2 |
+-----------+-----------+---------------------+
SQL is: select user_type, some_date, count(distinct user_id) from user_activity group by user_type, some_date.
Desired result:
+-----------+---------------------+
| user_type | average_daily_users |
+-----------+---------------------+
| a | 2 |
| b | 1 |
| c | 1.5 |
+-----------+---------------------+

PostgreSQL multiple row as columns

I have a table like this:
| id | name | segment | date_created | question | answer |
|----|------|---------|--------------|----------|--------|
| 1 | John | 1 | 2018-01-01 | 10 | 28 |
| 1 | John | 1 | 2018-01-01 | 14 | 37 |
| 1 | John | 1 | 2018-01-01 | 9 | 83 |
| 2 | Jack | 3 | 2018-03-11 | 22 | 13 |
| 2 | Jack | 3 | 2018-03-11 | 23 | 16 |
And I want to show this information in a single row, transpose all the questions and answers as columns:
| id | name | segment | date_created | question_01 | answer_01 | question_02 | answer_02 | question_03 | answer_03 |
|----|------|---------|--------------|-------------|-----------|-------------|-----------|-------------|-----------|
| 1 | John | 1 | 2018-01-01 | 10 | 28 | 14 | 37 | 9 | 83 |
| 2 | Jack | 3 | 2018-03-11 | 22 | 13 | 23 | 16 | | |
The number os questions/answers for the same ID is known. Maximum of 15.
I'm already tried using crosstab, but it only accepts a single value as category and I can have 2 (question/answer). Any help how to solve this?
You can try to use row_number to make a number in subquery then, do Aggregate function condition in the main query.
SELECT ID,
Name,
segment,
date_created,
max(CASE WHEN rn = 1 THEN question END) question_01 ,
max(CASE WHEN rn = 1 THEN answer END) answer_01 ,
max(CASE WHEN rn = 2 THEN question END) question_02,
max(CASE WHEN rn = 2 THEN answer END) answer_02,
max(CASE WHEN rn = 3 THEN question END) question_03,
max(CASE WHEN rn = 3 THEN answer END) answer_03
FROM (
select *,Row_number() over(partition by ID,Name,segment,date_created order by (select 1)) rn
from T
) t1
GROUP BY ID,Name,segment,date_created
sqlfiddle
[Results]:
| id | name | segment | date_created | question_01 | answer_01 | question_02 | answer_02 | question_03 | answer_03 |
|----|------|---------|--------------|-------------|-----------|-------------|-----------|-------------|-----------|
| 1 | John | 1 | 2018-01-01 | 1 | 28 | 14 | 37 | 9 | 83 |
| 2 | Jack | 3 | 2018-03-11 | 22 | 13 | 23 | 16 | (null) | (null) |

Complicated SELECT statement in Oracle DB

Can you please help me with one complicated select statement?
I have a table like this:
+----+-----------+-----------+-----------------+
| ID | User_name | Situation | Date_time |
+----+-----------+-----------+-----------------+
| 1 | Alex | 1 | 14.3.18 11:30 |
| 4 | Alex | 2 | 14.3.18 11:35 |
| 6 | Alex | 3 | 14.3.18 12:30 |
| 7 | Johnny | 1 | 15.3.18 10:01 |
| 9 | Johnny | 2 | 15.3.18 10:05 |
| 12 | Johnny | 3 | 15.3.18 10:20 |
| 14 | Alex | 1 | 20.3.18 20:00 |
| 15 | Alex | 2 | 20.3.18 20:25 |
| 17 | Alex | 3 | 20.3.18 21:25 |
+----+-----------+-----------+-----------------+
And I need a select statement, which will give me the following result:
User_name, Date_time_1 (Date_time of situation 1), Date_time_3 (Date_time of situation 3).
*In this case the result will have just 3 rows (2 for Alex and 1 for Johnny). Each row will contain 3 columns as described above.
And sorry for the formatting - I posted that from a mobile. I will add the result table when I will get to PC.*
That's how the output should looks like:
+----+-----------+-------------+-----------------+
| ID | User_name |Date_time_1 | Date_time_3 |
+----+-----------+-------------+-----------------+
| 1 | Alex |14.3.18 11:30| 14.3.18 12:30 |
| 2 | Johnny |15.3.18 10:01| 15.3.18 10:20 |
| 3 | Alex |20.3.18 20:00| 20.3.18 21:25 |
+----+-----------+-------------+-----------------+
You could use conditional aggregation:
SELECT User_name,
MAX(CASE WHEN Situation = 1 THEN Date_time END) AS date_time_1,
MAX(CASE WHEN Situation = 3 THEN Date_time END) AS date_time_3
FROM tab
GROUP BY User_name;
EDIT
In this case the result will have just 3 rows (2 for Alex and 1 for Johnny)
WITH cte AS (
SELECT t.*, SUM(CASE WHEN Situation=1 THEN 1 ELSE 0 END)
OVER(PARTITION BY User_name ORDER BY id) AS s
FROM tab t
)
SELECT User_name,
MAX(CASE WHEN Situation = 1 THEN Date_time END) AS date_time_1,
MAX(CASE WHEN Situation = 3 THEN Date_time END) AS date_time_3
FROM cte
GROUP BY s, User_name;
DBFiddle Demo

Crosstab multi columns

Hello I have a problem with SQL in SQL Server 2005.
Suppose that I have a table called myTable with data as below:
| NAME | CREDIT | GRADE | YEAR | SEMESTER |
---------------------------------------------
| Name1 | 1 | A | 1 | 1 |
| Name2 | 4 | B | 1 | 1 |
| Name3 | 2 | E | 1 | 1 |
| Name4 | 7 | F | 1 | 1 |
| Name5 | 4 | A | 1 | 2 |
| Name6 | 3 | C | 1 | 2 |
| Name7 | 6 | D | 1 | 2 |
| Name8 | 1 | A | 1 | 2 |
| Name9 | 1 | A | 1 | 2 |
| Name10 | 1 | A | 1 | 2 |
| Name11 | 3 | C | 2 | 1 |
| Name12 | 6 | E | 2 | 1 |
| Name13 | 4 | C | 2 | 1 |
| Name14 | 2 | B | 2 | 2 |
| Name15 | 1 | A | 2 | 2 |
| Name16 | 1 | A | 2 | 2 |
| Name17 | 1 | A | 2 | 2 |
| Name18 | 5 | D | 3 | 1 |
| Name19 | 1 | A | 3 | 1 |
| Name20 | 1 | A | 3 | 1 |
| Name18 | 5 | D | 3 | 2 |
| Name19 | 1 | A | 3 | 2 |
| Name20 | 1 | A | 3 | 2 |
I want to output the result as below:
| NAM1 | CRDT1 | GRD1 | YEAR1 | SEMER1 | NAM2 | CRDT2 | GRD2 | YEAR2 | SEMES2 |
-----------------------------------------------------------------------------
| Name1| 1 | A | 1 | 1 |Name5 | 4 | A | 1 | 2 |
| Name2| 4 | B | 1 | 1 |Name6 | 3 | C | 1 | 2 |
| Name3| 2 | E | 1 | 1 |Name7 | 6 | D | 1 | 2 |
| Name4| 7 | F | 1 | 1 |Name8 | 1 | A | 1 | 2 |
|Name9 | 1 | A | 1 | 2 |
|Name10| 1 | A | 1 | 2 |
| Name11| 3 | C | 2 | 1 |Name14| 2 | B | 2 | 2 |
| Name12| 6 | E | 2 | 1 |Name15| 1 | A | 2 | 2 |
| Name13| 4 | C | 2 | 1 |Name16| 1 | A | 2 | 2 |
|Name17| 1 | A | 2 | 2 |
| Name18| 5 | D | 3 | 1 |Name18| 5 | D | 3 | 2 |
| Name19| 1 | A | 3 | 1 |Name19| 1 | A | 3 | 2 |
| Name20| 1 | A | 3 | 1 |Name20| 1 | A | 3 | 2 |
Where
- Nam1= Name in Semester 1
- CRDT1= Credit in Semester 1
- GRD1= Grade in Semester 1
- Year1= Year in Semester 1
- Semer1 = Semester in Semester 1
- Nam2= Name in Semester 2
- CRDT2= Credit in Semester 2
- GRD2= Grade in Semester 2
- Year2= Year in Semester 2
- Semer2 = Semester in Semester 2
Please go to this URL to test this SQL: http://sqlfiddle.com/#!3/196c6/1
How Can I create SQL to make output like this?
select
s1.Name as nam1, s1.credit as crdt1, s1.Year as year1, s1.semester as semer1,
s2.Name as nam2, s2.credit as crdt2, s2.Year as year2, s2.semester as semer2
from
(select *, ROW_NUMBER() over (partition by year order by name) rn from myTable where semester=1 ) s1
full outer join
(select *, ROW_NUMBER() over (partition by year order by name) rn from myTable where semester=2 ) s2
on s1.year = s2.year
and s1.rn = s2.rn
I don't like doing an outer join, when a simple group by is sufficient:
select max(case when semester = 1 then Name end) as name1,
max(case when semester = 1 then credit end) as credit1,
max(case when semester = 1 then year end) as year1,
max(case when semester = 1 then semester end) as semester1,
max(case when semester = 2 then Name end) as name2,
max(case when semester = 2 then credit end) as credit2,
max(case when semester = 2 then year end) as year2,
max(case when semester = 2 then semester end) as semester2
from (select t.*,
row_number() over (partition by semester order by name) as rownum
from t
) t2
group by rownum
order by rownum
select Name,credit, grade, year,semester from myTable
group by semester,year, Name,credit, grade;
now we have to make a dynamic query with this previous query:
create as temporary table as there are semster first
create dynamically a select query with all fields of all semester table in a loop:
foreach temporary table concat all fields of this table in select query
and add construct label field with semester value of this table
and add temporary table with union
'select' + #tbls1.fieldName + ',' + ... + + #tbls2.fieldName +