Pivoting a date and value column to get their combination - sql

I have a table with this structure:
+--------+-----------+------------+------+
| userid | date | item | rank |
+--------+-----------+------------+------+
| 34444 | 01-Jul-15 | pen | 3 |
| 34444 | 04-Jul-15 | TV | 2 |
| 34444 | 09-Jul-15 | controller | 1 |
| 531 | 03-Jul-15 | keyboard | 3 |
| 531 | 06-Jul-15 | pen | 2 |
| 531 | 10-Jul-15 | bowl | 1 |
+--------+-----------+------------+------+
Each item has already been ranked based on their dates with a limit of 3 items per user. I have their last 3 items and the dates associated with them. The items can be anything.
I want to produce a view in a way that pivots the date and item combination. For example, the desired view for this table is:
+--------+------------+-----------+-------+-----------+----------+-----------+
| userid | item1 | date1 | item2 | date2 | item3 | date3 |
+--------+------------+-----------+-------+-----------+----------+-----------+
| 34444 | controller | 09-Jul-15 | TV | 04-Jul-15 | pen | 01-Jul-15 |
| 531 | bowl | 10-Jul-15 | pen | 06-Jul-15 | keyboard | 03-Jul-15 |
+--------+------------+-----------+-------+-----------+----------+-----------+
Is this possible?
Thanks

You just need to do a pivot. In more recent versions, you can use the actual pivot keyword. Or in any version, you can just do
SELECT userid,
max( case when rank = 1 then item else null end) item1,
max( case when rank = 1 then date else null end) date1,
max( case when rank = 2 then item else null end) item2,
max( case when rank = 2 then date else null end) date2,
max( case when rank = 3 then item else null end) item3,
max( case when rank = 3 then date else null end) date3
FROM your_table
GROUP BY userid

Related

Transposing SQL rows data to column

In SQL we need to transform a table in the following way:
Table1:
+-----+---------+-----------+
| ID | insured | DOD |
+-----+---------+-----------+
| 123 | Pam | 6/18/2013 |
| 123 | Nam | 2/12/2010 |
| 123 | Tam | 2/10/2013 |
| 456 | Jessi | 4/6/2003 |
| 457 | Ron | 4/10/2010 |
| 457 | Tom | 5/5/2008 |
+-----+---------+-----------+
Desired output table:
+-----+---------+-----------+-----------+-----------+
| ID | insured | DOD1 | DOD2 | DOD3 |
+-----+---------+-----------+-----------+-----------+
| 123 | Pam | 6/18/2013 | 2/12/2010 | 2/10/2013 |
| 456 | Jessi | 4/6/2003 | null | null |
| 457 | Ron | 4/10/2010 | 5/5/2008 | null |
+-----+---------+-----------+-----------+-----------+
I have seen somewhere that we can use pivot and unpivot, but I am not sure how can I use it here.
Your help is much appreciated.
Assuming that you really want -- or can accept -- the dates in descending order, then you can use conditional aggregation for this:
select id,
max(case when seqnum = 1 then insured end) as insured,
max(case when seqnum = 1 then dod end) as dod_1,
max(case when seqnum = 2 then dod end) as dod_2,
max(case when seqnum = 3 then dod end) as dod_3
from (select t.*,
row_number() over (partition by id order by dod desc) as seqnum
from t
) t
group by id;
If you want to preserve the original ordering, then your question does not have enough information. If you have a column with the ordering, that can be used for row_number().
I would run it like this, guessing that your id is a number and that you want only those records. from vertical to horizontal , pivot is the best option
select id , insured, DOD from yourtable
pivot(max(DOD) for ID in (123,456,457));

SQL Server : get Count() of a related table column where some condition

Given tables CollegeMajors
| Id | Major |
|----|-------------|
| 1 | Accounting |
| 2 | Math |
| 3 | Engineering |
and EnrolledStudents
| Id | CollegeMajorId | Name | HasGraduated |
|----|----------------|-----------------|--------------|
| 1 | 1 | Grace Smith | 1 |
| 2 | 1 | Tony Fabio | 0 |
| 3 | 1 | Michael Ross | 1 |
| 4 | 3 | Fletcher Thomas | 1 |
| 5 | 2 | Dwayne Johnson | 0 |
I want to do a query like
Select
CollegeMajors.Major,
Count(select number of students who have graduated) AS TotalGraduated,
Count(select number of students who have not graduated) AS TotalNotGraduated
From
CollegeMajors
Inner Join
EnrolledStudents On EnrolledStudents.CollegeMajorId = CollegeMajors.Id
and I'm expecting these kind of results
| Major | TotalGraduated | TotalNotGraduated |
|-------------|----------------|-------------------|
| Accounting | 2 | 1 |
| Math | 0 | 1 |
| Engineering | 1 | 0 |
So the question is, what kind of query goes inside the COUNT to achieve the above?
Select CollegeMajors.Major
, COUNT(CASE WHEN EnrolledStudents.HasGraduated= 0 then 1 ELSE NULL END) as "TotalNotGraduated",
COUNT(CASE WHEN EnrolledStudents.HasGraduated = 1 then 1 ELSE NULL END) as "TotalGraduated"
From CollegeMajors
InnerJoin EnrolledStudents On EnrolledStudents.CollegeMajorId = CollegeMajors.Id
GROUP BY CollegeMajors.Major
You can use the CASE statement inside your COUNT to achieve the desired result.Please try the below updated query.
Select CollegeMajors.Major
, COUNT(CASE WHEN EnrolledStudents.HasGraduated= 0 then 1 ELSE NULL END) as "TotalNotGraduated",
COUNT(CASE WHEN EnrolledStudents.HasGraduated = 1 then 1 ELSE NULL END) as "TotalGraduated"
From CollegeMajors
InnerJoin EnrolledStudents On EnrolledStudents.CollegeMajorId = CollegeMajors.Id
GROUP BY CollegeMajors.Major
You can try this for graduated count:
Select Count(*) From EnrolledStudents group by CollegeMajorId having HasGraduated = 1
And change 1 to zero for not graduated ones:
Select Count(*) From EnrolledStudents group by CollegeMajorId having HasGraduated = 0

No rowid or key need most recent row

I am trying my hardest to get a list of the most recent rows by date in a DB2 file. The file has no unique id, so I am trying to get the entries by matching a set of columns. I need DESCGA most importantly as that changes often. When it does they keep another row for historical reasons.
SELECT B.COGA, B.COMSUBGA, B.ACCTGA, B.PRFXGA, B.DESCGA
FROM mylib.myfile B
WHERE
(
SELECT COUNT(*)
FROM
(
SELECT A.COGA,A.COMSUBGA,A.ACCTGA,A.PRFXGA,MAX(A.DATEGA) AS EDATE
FROM mylib.myfile A
GROUP BY A.COGA, A.COMSUBGA, A.ACCTGA, A.PRFXGA
) T
WHERE
(B.ACCTGA = T.ACCTGA AND
B.COGA = T.COGA AND
B.COMSUBGA = T.COMSUBGA AND
B.PRFXGA = T.PRFXGA AND
B.DATEGA = T.EDATE)
) > 1
This is what I am trying and so far I get 0 results.
If I remove
B.ACCTGA = T.ACCTGA AND
It will return results (of course wrong).
I am using ODBC in VS 2013 to structure this query.
I have a table with the following
| a | b | descri | date |
-----------------------------
| 1 | 0 | string | 20140102 |
| 2 | 1 | string | 20140103 |
| 1 | 1 | string | 20140101 |
| 1 | 1 | string | 20150101 |
| 1 | 0 | string | 20150102 |
| 2 | 1 | string | 20150103 |
| 1 | 1 | string | 20150103 |
and i need
| 1 | 0 | string | 20150102 |
| 2 | 1 | string | 20150103 |
| 1 | 1 | string | 20150103 |
You can use row_number():
select t.*
from (select t.*,
row_number() over (partition by a, b order by date desc) as seqnum
from mylib.myfile t
) t
where seqnum = 1;

SQL - Splitting a column based on the values

I'm trying to split a column from a result set into 2 columns based on the values from the column.
So a user can subscribe to multiple items and the user can have 2 email addresses which can receive this subscription.
The result set gives a list of subscriptions and their corresponding entries for subscribed email ids.
DB details
Table 1 - user_subscriptions
user_id
email_id - 1 for email id 1 and 2 for email id 2
subscription_id
Table 2 - subscriptions
subscription_id
subscription_name
Now I need all the subscriptions for the user whether subscribed by either of the email ids or not.
So I get a result set something like this
+----------------------+----------+
| subscription_name | email_id |
+----------------------+----------+
| item1 | 1 |
| item1 | 2 |
| item2 | null |
| item3 | 1 |
| item4 | null |
| item5 | 2 |
+----------------------+----------+
So I'm looking to split the above result set into something like below
+-------------------+---------+---------+
| subscription_name | email_1 | email_2 |
+-------------------+---------+---------+
| item1 | 1 or Y | 1 or Y |
| item2 | 0 or N | 0 |
| item3 | 1 | 0 |
| item4 | 0 | 0 |
| item5 | 0 | 1 |
+-------------------+---------+---------+
Hope this question makes sense. Any help would be appreciated!
Updated -----------
Sample Data:
subscriptions -
+-----------------+-------------------+
| subscription_id | subscription_name |
+-----------------+-------------------+
| 1 | item1 |
| 2 | item2 |
| 3 | item3 |
| 4 | item4 |
| 5 | item5 |
+-----------------+-------------------+
user_subscriptions
+---------+----------+-----------------+
| user_id | email_id | subscription_id |
+---------+----------+-----------------+
| 101 | 1 | 1 |
| 101 | 2 | 1 |
| 101 | 1 | 3 |
| 101 | 2 | 5 |
| 102 | 1 | 1 |
| 102 | 2 | 1 |
+---------+----------+-----------------+
Expected Result:
For user_id = 101
+-----------------+-------------------+--------+--------+
| subscription_id | subscription_name | mail_1 | mail_2 |
+-----------------+-------------------+--------+--------+
| 1 | item1 | Y | Y |
| 2 | item2 | N | N |
| 3 | item3 | Y | N |
| 4 | item4 | N | N |
| 5 | item5 | N | Y |
+-----------------+-------------------+--------+--------+
SELECT
S.subscription_id,
S.subscription_name,
CASE
WHEN US1.mail_ID IS NULL THEN 'N'
ELSE 'Y'
END mail_1,
CASE
WHEN US2.mail_ID IS NULL THEN 'N'
ELSE 'Y'
END mail_2
FROM subscriptions S
LEFT JOIN user_subscriptions US1
ON S.subscription_id = US1.subscription_id
AND US1.mail_id = 1
LEFT JOIN user_subscriptions US2
ON S.subscription_id = US2.subscription_id
AND US2.mail_id = 2
WHERE us1.user_id = 5 -- or use a variable #user_ID
OR us2.user_id = 5
You need a conditional aggregate:
select us.subscription_name,
-- there's at least one email
CASE WHEN MIN(us.email_id) IS NOT NULL THEN 'Y' ELSE 'N' END as email_1,
-- there's more than one email
CASE WHEN MIN(us.email_id) <> MAX(us.email_id) THEN 'Y' ELSE 'N' END as email_2
from subscriptions as s
left join user_subscriptions as us
on s.subscription_id = us.subscription_id
where us.user_id = ...
group by us.subscription_name
I've not worked in sybase before, but I'm fairly sure the following SQL will translate easily (or even run directly):
SELECT
s.subscription_name,
COUNT(email_1.subscription_id) AS email_1,
COUNT(email_2.subscription_id) AS email_2
FROM subscriptions AS s
LEFT JOIN user_subscriptions AS email_1 ON (
s.subscription_id = email_1.subscription_id AND
email_1.email_id = 1
)
LEFT JOIN user_subscriptions AS email_2 ON (
s.subscription_id = email_2.subscription_id AND
email_2.email_id = 2
)
;
You could also say IF(email_1.subscription_id IS NOT NULL, 'Y', 'N') etc in the SELECT to return a straight-forward yes/no rather than a count etc.
It works on the principle that the list of LEFT JOIN statements will match any "user subscription" record with email_id=1 and email_id=2 etc.
My lack of sybase knowledge disclaimer: ANSI SQL is can't perform PIVOT - if sybase does, you could do this far more elegantly I'm sure. There's another question+answer which hints that sybase can do such things; it'd be worth your while looking there: https://stackoverflow.com/a/8114446/817132
Hope it helps!

Horizontal Count SQL

I apologize if this is a duplicate question but I could not find my answer.
I am trying to take data that is horizontal, and get a count of how many times a specific number appears.
Example table
+-------+-------+-------+-------+
| Empid | KPI_A | KPI_B | KPI_C |
+-------+-------+-------+-------+
| 232 | 1 | 3 | 3 |
| 112 | 2 | 3 | 2 |
| 143 | 3 | 1 | 1 |
+-------+-------+-------+-------+
I need to see the following:
+-------+--------------+--------------+--------------+
| EmpID | (1's Scored) | (2's Scored) | (3's Scored) |
+-------+--------------+--------------+--------------+
| 232 | 1 | 0 | 2 |
| 112 | 0 | 2 | 1 |
| 143 | 2 | 0 | 1 |
+-------+--------------+--------------+--------------+
I hope that makes sense. Any help would be appreciated.
Since you are counting data across multiple columns, it might be easier to unpivot your KPI columns first, then count the scores.
You could use either the UNPIVOT function or CROSS APPLY to convert your KPI columns into multiple rows. The syntax would be similar to:
select EmpId, KPI, Val
from yourtable
cross apply
(
select 'A', KPI_A union all
select 'B', KPI_B union all
select 'C', KPI_C
) c (KPI, Val)
See SQL Fiddle with Demo. This gets your multiple columns into multiple rows, which is then easier to work with:
| EMPID | KPI | VAL |
|-------|-----|-----|
| 232 | A | 1 |
| 232 | B | 3 |
| 232 | C | 3 |
| 112 | A | 2 |
Now you can easily count the number of 1's, 2's, and 3's that you have using an aggregate function with a CASE expression:
select EmpId,
sum(case when val = 1 then 1 else 0 end) Score_1,
sum(case when val = 2 then 1 else 0 end) Score_2,
sum(case when val = 3 then 1 else 0 end) Score_3
from
(
select EmpId, KPI, Val
from yourtable
cross apply
(
select 'A', KPI_A union all
select 'B', KPI_B union all
select 'C', KPI_C
) c (KPI, Val)
) d
group by EmpId;
See SQL Fiddle with Demo. This gives a final result of:
| EMPID | SCORE_1 | SCORE_2 | SCORE_3 |
|-------|---------|---------|---------|
| 112 | 0 | 2 | 1 |
| 143 | 2 | 0 | 1 |
| 232 | 1 | 0 | 2 |