SQL - Splitting a column based on the values - sql

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!

Related

Select from a concatenation of two columns after a left join

Problem description
Let the tables C and V have those values
>> Table V <<
| UnID | BillID | ProductDesc | Value | ... |
| 1 | 1 | 'Orange Juice' | 3.05 | ... |
| 1 | 1 | 'Apple Juice' | 3.05 | ... |
| 1 | 2 | 'Pizza' | 12.05 | ... |
| 1 | 2 | 'Chocolates' | 9.98 | ... |
| 1 | 2 | 'Honey' | 15.98 | ... |
| 1 | 3 | 'Bread' | 3.98 | ... |
| 2 | 1 | 'Yogurt' | 8.55 | ... |
| 2 | 1 | 'Ice Cream' | 7.05 | ... |
| 2 | 1 | 'Beer' | 9.98 | ... |
| 2 | 2 | 'League of Legends RP' | 40.00 | ... |
>> Table C <<
| UnID | BillID | ClientName | ... |
| 1 | 1 | 'Alexander' | ... |
| 1 | 2 | 'Tom' | ... |
| 1 | 3 | 'Julia' | ... |
| 2 | 1 | 'Tom' | ... |
| 2 | 2 | 'Alexander' | ... |
Table C have the values of each product, which is associated with a bill number. Table V has the relationship between the client name and the bill number. However, the bill number has a counter that is dependent on the UnId, which is the store unity ID. That being said, each store has it`s own Bill number 1, number 2, etc. Also, the number of bills from each store are not equal.
Solution description
I'm trying to make select between the C left join V without sucess. Because each BillID is dependent on the UnID, I have to make the join considering the concatenation between those two columns.
I've used this script, but it gives me an error.
SELECT
SUM(C.Value),
V.ClientName
FROM
C
LEFT JOIN
V
ON
CONCAT(C.UnID, C.BillID) = CONCAT(V.UnID, V.BillID)
GROUP BY
V.ClientName
and SQL server returns me this 'CONCAT' is not a recognized built-in function name.
I'm using Microsoft SQL Server 2008 R2
Is the use of CONCAT wrong? Or is it the way I tried to SELECT? Could you give me a hand?
[OBS: The tables I've present you are just for the purpose of explaining my difficulties. That being said, if you find any errors in the explanation, please let me know to correct them.]
You should be joining on the equality of the UnID and BillID columns in the two tables:
SELECT
c.ClientName,
COALESCE(SUM(v.Value), 0) AS total
FROM C c
LEFT JOIN V v
ON c.UnID = v.UnID AND
c.BillID = v.BillID
GROUP BY
c.ClientName;
In theory you could try joining on CONCAT(UnID, BillID). However, you could run into problems. For example, UnID = 1 with BillID = 23 would, concatenated together, be the same as UnID = 12 and BillID = 3.
Note: We wrap the sum with COALESCE, because should a given client have no entries in the V table, the sum would return NULL, which we then replace with zero.
concat is only available in sql server 2012.
Here's one option.
SELECT
SUM(C.Value),
V.ClientName
FROM
C
LEFT JOIN
V
ON
cast(C.UnID as varchar(100)) + cast(C.BillID as varchar(100)) = cast(V.UnID as varchar(100)) + cast(V.BillID as varchar(100))
GROUP BY
V.ClientName

case expression for multiple condition

OriData
+-----------------+-----------+-------+-------+------+
| selected_RowNum | V6_RowNum | SeqNo | Name | IDNo |
+-----------------+-----------+-------+-------+------+
| 1 | 1 | A1234 | Yummy | 1234 |
| 1 | 2 | A1234 | Yummy | 1234 |
| 1 | 3 | A1234 | Yummy | 1234 |
| 1 | 4 | A1234 | Yummy | 1234 |
| 1 | 1 | B123 | Yummy | 1234 | << I want this
| 1 | 1 | C123 | Yummy | 1234 | << I want this
+-----------------+-----------+-------+-------+------+
Result I want
+-----------------+-----------+-------+-------+------+
| selected_RowNum | V6_RowNum | SeqNo | Name | IDNo |
+-----------------+-----------+-------+-------+------+
| 1 | 1 | B123 | Yummy | 1234 |
| 1 | 1 | C123 | Yummy | 1234 |
+-----------------+-----------+-------+-------+------+
Here is my query:
select
case
when selected_rownum=V6_RowNum and V6_RowNum=1 then 'updateonetime'
when selected_rownum=V6_RowNum and V6_RowNum>1 then 'updatemanytimes'
else '0'
end as NewColumnA,
*
from Table #A
I inner join V6 and Selected table and into #A
I want to check any update between 2 tables, so I inner join both table and created rowNum for 2 tables named Selected_rowNum and v6_rownum (that sort by date).
Selected_RowNum = 1 and V6_rowNum = 1 (and this V6_rownum is not repeating for same SeqNo, IDNo)
If I update 1 time, it will triggered in V6 table. If I update many times, it will triggered V6 table many time as you can see in SeqNo=A1234. As you can see, even the IDNo is repeating but it may created many applications. So, it need filter based on IDNo and SeqNo and Selected_RowNum=1 and V6_RowNum=1.
Any idea, how to get the result I want?
From the looks of it, you only want to display the results for rows where there aren't any entries with updates (V6_RowNum > 1 only?).
To do this, you'd need to check that those rows don't exist in the table, like this:
SELECT CASE
WHEN selected_rownum=V6_RowNum and V6_RowNum=1 THEN 'updateonetime'
WHEN selected_rownum=V6_RowNum and V6_RowNum>1 THEN 'updatemanytimes'
ELSE '0'
END as NewColumnA,
*
FROM #A a
WHERE selected_RowNum = 1
AND V6_RowNum = 1
AND NOT EXISTS
(
SELECT 1 FROM #A a2
WHERE a2.SeqNo = a.SeqNo
AND a2.V6_RowNum > 1
)
Please try this.
If select updateonlyone time then
Select selected_RowNum,seqNo,IDNo
,'updateonetime' As OneTime
from #tbl
group by selected_RowNum,seqNo,IDNO
having count(*) = 1
If select updateonlyone and updatemanytimes time then
Select selected_RowNum,seqNo,IDNo
,CASE WHEN Count(*) > 1 THEN 'updatemanytimes' ELSE 'updateonetime' END
from #tbl
group by selected_RowNum,seqNo,IDNO

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

Check if relation exists and return true or false

I have 3 tables, Category Step and CategoryStep, where CategoryStep relates the two other tables together. I want to return all categories with a true/false column whether or not the relation exists in CategoryStep based on a StepID.
The schema for the tables is simple,
Category:
CategoryID | CategoryName
Step:
StepID | StepName
CategoryStep:
CategoryStepID | CategoryID | StepID
When trying to get results based on StepID, I only get the relations that exist, and not ones that don't.
SELECT [CategoryID], [Category], CAST(CASE WHEN [CategoryStep].[CategoryStep] IS NULL THEN 0 ELSE 1 END AS BIT) AS related
FROM Category
LEFT JOIN CategoryStep ON Category.CategoryID = CategoryStep.CategoryID
INNER JOIN Step ON CategoryStep.StepID = Step.StepID
WHERE Step.StepID = 2
Step Table:
|StepID | StepName
|-------|---------
| 1 | StepOne
| 2 | StepTwo
| 3 | StepThree
Category Table:
| CategoryID | CategoryName
|------------|-------------
| 1 | Holidays
| 2 | States
| 3 | Cities
| 4 | Animals
| 5 | Food
CategoryStep Table
| CategoryStepID | CategoryID | StepID
|----------------|------------|-------
| 1 | 1 | 1
| 2 | 1 | 2 <--
| 3 | 2 | 1
| 4 | 2 | 3
| 5 | 3 | 2 <--
| 6 | 4 | 1
| 7 | 4 | 2 <--
| 8 | 4 | 3
| 9 | 5 | 1
| 10 | 5 | 3
So, if I was looking for StepID = 2 the result table I am looking for is:
| CategoryID | Category | Related
|------------|----------|--------
| 1 | Holidays | 1
| 2 | States | 0
| 3 | Cities | 1
| 4 | Animals | 1
| 5 | Food | 0
Try replacing the INNER JOIN with a LEFT JOIN.
Update:
The fatal flaw with your original attempt was the WHERE clause. You were performing the correct LEFT JOIN, but the WHERE clause was filtering off category records which did not match. In the query below, I moved the check for step ID into the join condition, where it belongs.
SELECT [CategoryID], [Category],
CAST(CASE WHEN [CategoryStep].[CategoryStep] IS NULL THEN 0 ELSE 1 END AS BIT) AS related
FROM Category
LEFT JOIN CategoryStep
ON Category.CategoryID = CategoryStep.CategoryID AND
CategoryStep.StepCodeID = 2
LEFT JOIN Step
ON CategoryStep.StepID = Step.StepID

Pivoting a date and value column to get their combination

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