SQL Query for sorting and getting unique count - mysql

I have a table which consists of the following details
Customer
Deal
DealStage
A
D1
Lost
A
D2
Won
A
D3
Contacted
B
D4
Conatcted
B
D5
Lost
C
D6
Lost
D
D7
Lost
I have to develop a query where I should get the unique highest stage for each customer. The Stage priority is Won > Contacted > Lost. For Example, A is having three deals which are Won, Lost, and Contacted. So I should be considering Won. Similarly Contacted for B and Lost for C and D
Is it possible to get an Output like
Customer
Highets Stage
A
Won
B
Contacted
C
Lost
D
Lost
By this, I can generate a pivot table that looks like
Stage
CustomerCount
Won
1
Contacted
1
Lost
2
Thanks in Advance

One option uses aggregation and field():
select customer,
case min(field(deal_stage, 'Won', 'Contacted', 'Lost'))
when 1 then 'Won'
when 2 then 'Contacted'
when 3 then 'Lost'
end as highest_stage
from mytable
group by customer
Actually we could combine this with elt():
select customer,
elt(
min(field(deal_stage, 'Won', 'Contacted', 'Lost')),
'Won', 'Contacted', 'Lost'
) as highest_stage
from mytable
group by customer
You can then generate the final result with another level of aggregation:
select highest_stage, count(*)
from (
select customer,
elt(
min(field(deal_stage, 'Won', 'Contacted', 'Lost')),
'Won', 'Contacted', 'Lost'
) as highest_stage
from mytable
group by customer
) t
group by highest_stage

Use windows function as follows:
select * from
(select t.*,
row_number() over (partition by customer
order by case when dealstage = 'Won' then 1
when dealstage = 'Contacted' then 2
when dealstage = 'Lost' then 3
end
) as rn
from your_table t)
where rn = 1;

These are really two different problems. I would, in fact, recommend different approaches to the two. For the first, conditional aggregation:
select customer,
coalesce(max(case when state = 'Won' then state end),
max(case when state = 'Contacted' then state end),
max(case when state = 'Lost' then state end)
) as biggest_state
from t
group by customer;
However, for your final result, I would recommend a correlated subquery:
select t.state, count(*)
from t
where t.state = (select t2.state
from t2
where t2.customer = t.customer
order by field(state, 'Won', 'Contact', 'Lost')
limit 1
)
group by t.state;
Note: This assumes that the original data does not have duplicate rows. If it does, then count(distinct) is one adjustment.

Related

Count Group By and Separate If Included in Both Group

Not sure if this question is duplicated yet or not.
I have a simplified table below
User
Interest
Jason
Art
Jason
Sport
Sam
Sport
Sam
Art
Steve
Sport
Desmond
Sport
Tania
Art
Here's the result that I want to achieve
Interest
Count
Art
2
Sport
2
Both
2
I Managed to make a subquery to achieve the value for the Both data by this query
SELECT COUNT(USER) FROM (
SELECT User, COUNT(DISTINCT Interest) as interest_type FROM table WHERE interest_type = 2)
But for the user that are exclusively have Interest in Art and in Sport it's not separated.
You could use conditional aggregation here:
WITH cte AS (
SELECT User,
CASE WHEN COUNT(CASE WHEN Interest = 'Art' THEN 1 END) > 0 AND
COUNT(CASE WHEN Interest = 'Sport' THEN 1 END) > 0
THEN 'Both'
WHEN COUNT(CASE WHEN Interest = 'Art' THEN 1 END) > 0
THEN 'Art'
ELSE 'Sport' END AS Interest
FROM yourTable
GROUP BY User
)
SELECT Interest, COUNT(*) AS Count
FROM cte
GROUP BY Interest;
On MySQL or BigQuery, we can shorten the above to:
WITH cte AS (
SELECT User,
CASE WHEN SUM(Interest = 'Art') > 0 AND SUM (Interest = 'Sport') > 0
THEN 'Both'
WHEN SUM(Interest = 'Art') > 0
THEN 'Art'
ELSE 'Sport' END AS Interest
FROM yourTable
GROUP BY User
)
SELECT Interest, COUNT(*) AS Count
FROM cte
GROUP BY Interest;
Assuming your database supports the over() clause:
select
case when num_interests = 1 then interest else 'both' end as interest
, count(distinct user) as "Count"
from (
select
interest
, user
, count(*) over(partition by user) as num_interests
from yourTable
) d
group by
case when num_interests = 1 then interest else 'both' end

MySQL sorting distinct/unique values

Decided to break my query in to bits, to help communicate what I am trying to achieve.
I have some information about a Customer and the Coupon Code they've used, in this format
Customer
Coupon Code
1
FreeDel
1
FreeDel
1
FreeDel
1
1562733
1
8842939
1
847hr64
1
83jd63j
1
FreeDel
1
8eh33jr
1
AA-2637
1
AA-9837
1
Save200
1
Save200
I want to sort it so that: If a known coupon-code prefix is available, then using syntax like this:
CASE WHEN Coupon Code LIKE 'AA-%' THEN 'AA-'
CASE WHEN Coupon Code IS UNIQUE THEN 'UNIQUE'
CASE WHEN Coupon Code IS NOT UNIQUE THEN 'NON-UNIQUE'
And so to output
Count
Customer
Coupon Code Type
6
1
Non-Unique
2
1
AA-
5
1
Unique
In this ideal, the known prefixes are added first as cases, and then unknown, unrepeated Coupon Codes are labelled Unique and then repeated Coupon Codes are labelled Non-Unique.
Any help would be super appreciated! :)
Table edits
You could count them separately:
select sum(cnt), customer, 'Non-Unique'
from (
select customer, coupon_code, count(*) as cnt
from ccodes
where coupon_code not like 'AA-%'
group by customer, coupon_code
having cnt>1
) as q
group by customer
union
select count(*), customer, 'AA-'
from ccodes
where coupon_code like 'AA-%'
group by customer
union
select sum(cnt), customer, 'Unique'
from (
select customer, coupon_code, count(*) as cnt
from ccodes
where coupon_code not like 'AA-%'
group by customer, coupon_code
having cnt=1
) as q
group by customer
See db-fiddle
You can use aggregation and conditional logic:
(case when sum( code like 'AA-%' ) > 0 then 'AA-'
when min(code) = max(code) then 'UNIQUE'
else 'NON-UNIQUE'
end)
You can incorporate this into an aggregation query as:
select grp, count(*)
from (select c.customer,
(case when sum( code like 'AA-%' ) > 0 then 'AA-'
when min(code) = max(code) then 'UNIQUE'
else 'NON-UNIQUE'
end) as grp,
count(*)
from c
group by customer
) x
group by grp;

How to select rows as a column for View in TSQL?

Assume I have 3 tables: Animal, CareTaker, and Apppointment. Schema, with some data like so:
Create Table Animal (Id int identity, Name varchar(25))
Create Table CareTaker(Id int identity, Name varchar(50))
Create Table Appointments(Id int identity, AnimalId int, CareTakerId int, AppointmentDate DateTime, BookingDate DateTime)
Insert into Animal(Name) Values('Ghost'), ('Nymeria'), ('Greywind'), ('Summer')
Insert into CareTaker(Name) Values ('Jon'), ('Arya'), ('Rob'), ('Bran')
Insert into Appointments(AnimalId, CareTakerId, AppointmentDate, BookingDate) Values
(1, 1, GETDATE() + 7, GetDate()), -- Ghost cared by Jon
(1, 2, GETDATE() + 6, GetDate()), -- Ghost cared by Arya
(4, 3, GETDATE() + 8, GetDate()) -- Summer cared by Rob
I want to select only 3 caretakers for each animal as a columns. Something like this:
I don't care about other appointments, just the next three, for each animal. If there aren't three appointments, it can be blank / null.
I'm quite confused about how to do this.
I tried it with Sub queries, something like so:
select Name,
-- Care Taker 1
(Select Top 1 C.Name
From Appointments A
Join CareTaker C on C.Id = A.CareTakerId
Where A.AppointmentDate > GETDATE()
And A.AnimalId = Animal.Id
Order By AppointmentDate) As CareTaker1,
-- Appointment Date 1
(Select Top 1 AppointmentDate
From Appointments
Where AppointmentDate > GETDATE()
And AnimalId = Animal.Id
Order By AppointmentDate) As AppointmentDate1
From Animal
But for the second caretaker, I would have to go second level select on where clause to exclude the id from top 1 (because not sure how else to get second row), something like select top 1 after excluding first row id; where first row id is (select top 1) situtation.
Anyhow, that doesn't look like a great way to do this.
How can I get the desired output please?
You can get all the information in rows using:
select an.name as animal, ct.name as caretaker, a.appointmentdate
from appointments a join
animals an
on a.id = an.animalid join
caretaker c
on a.caretakerid = c.id;
Then, you basically want to pivot this. One method uses the pivot keyword. Another conditional aggregation. I prefer the latter. For either, you need a pivot column, which is provided using row_number():
select animal,
max(case when seqnum = 1 then caretaker end) as caretaker1,
max(case when seqnum = 1 then appointmentdate end) as appointmentdate1,
max(case when seqnum = 2 then caretaker end) as caretaker2,
max(case when seqnum = 2 then appointmentdate end) as appointmentdate2,
max(case when seqnum = 3 then caretaker end) as caretaker3,
max(case when seqnum = 3 then appointmentdate end) as appointmentdate3
from (select an.name as animal, ct.name as caretaker, a.appointmentdate,
row_number() over (partition by an.id order by a.appointmentdate) as seqnum
from appointments a join
animals an
on a.id = an.animalid join
caretaker c
on a.caretakerid = c.id
) a
group by animal;

mysql Sub queries in COUNT along with GROUP BY YEAR

Having some trouble figuring out the best way to do this.
Here is what I'm trying to do:
SELECT
YEAR(t.voucher_date) as period,
COUNT(t.id) as total_count,
(SELECT COUNT(t2.id) FROM booking_global as t2 where t2.booking_status = 'CONFIRMED') as confirmed,
(SELECT COUNT(t3.id) FROM booking_global as t3 where t3.booking_status = 'PENDING') as pending
FROM booking_global t
GROUP BY YEAR(t.voucher_date)
This produces the below result.
period total_count CONFIRMED PENDING
2014 4 5 3
2015 4 5 3
Expected Result
period total_count CONFIRMED PENDING
2014 4 3 1
2015 4 2 2
Here i want to get CONFIRMED / PENDING count's for respective years, rather than getting count of all statuses.
I am not sure how to use my query as a sub query and run another query on the results.
Flowing should give you right rsult
SELECT
YEAR(t.voucher_date) as period,
COUNT(t.id) as total_count,
(SELECT COUNT(t2.id) FROM booking_global as t2 where t2.booking_status = 'CONFIRMED' and YEAR(t2.voucher_date) = YEAR(t.voucher_date)) as confirmed,
(SELECT COUNT(t3.id) FROM booking_global as t3 where t3.booking_status = 'PENDING' and YEAR(t3.voucher_date) = YEAR(t.voucher_date)) as pending
FROM booking_global t
GROUP BY YEAR(t.voucher_date)
You can have a subquery that calculates each booking_status for each year. The result of which is then joined on table booking_global. Example,
SELECT YEAR(t.voucher_date) voucher_date_year,
COUNT(t.id) total_count,
IFNULL(calc.confirmed_count, 0) confirmed_count,
IFNULL(calc.pending_count, 0) pending_count
FROM booking_global t
LEFT JOIN
(
SELECT YEAR(voucher_date) voucher_date_year,
SUM(booking_status = 'CONFIRMED') confirmed_count,
SUM(booking_status = 'PENDING') pending_count
FROM booking_global
GROUP BY YEAR(voucher_date)
) calc ON calc.voucher_date_year = YEAR(t.voucher_date)
GROUP BY YEAR(t.voucher_date)

Issue using the count function in SQL

I am running this query to get a count of the bookedby users total number of sales with insurance and total number of sales without insurance. However, all of the users are getting the same count for some reason. how can i change my query to show each users totals instead.
what i want is basically to figure out how many bookings each user had with and without insurance sales
bookedby is the agent
and T0 is the table that includes the information about bookings that do not include insurance
and t1 is the table that includes information about bookings with insurance
while both tables provide the same information how can i get a total by booked by for each agent from both tables
SELECT t0.BookedBy, count(t0.resnumber) as NonInsurance, COUNT(t1.resnumber) as Insurance
FROM (SELECT BookedBy, ResNumber, DATEPART(year, BookDate) AS Year, DATEPART(month, BookDate) AS month
FROM dbo.ResGeneral
WHERE ResNumber NOT IN (SELECT ResNumber FROM dbo.ResItinerary_insurance)
and ResStatus = 'a'
GROUP BY BookedBy, ResNumber, BookDate) t0
left JOIN (SELECT BookedBy, ResNumber, DATEPART(year, BookDate) AS Year, DATEPART(month, BookDate) AS month
FROM dbo.ResGeneral
WHERE ResNumber IN (SELECT ResNumber FROM dbo.ResItinerary_insurance)
and ResStatus = 'a') t1
ON t1.year = t0.year
group by t0.bookedby
I think this query is equivalent:
SELECT g.BookedBy,
SUM(CASE WHEN i.ResNumber IS NULL THEN 1 ELSE 0 END) AS NonInsurance,
SUM(CASE WHEN i.ResNumber IS NOT NULL THEN 1 ELSE 0 END) AS Insurance
FROM dbo.ResGeneral g
LEFT JOIN dbo.ResItinerary_insurance i
ON g.ResNumber = i.ResNumber
WHERE g.ResStatus = 'a'
GROUP BY g.BookedBy;
Your join condition looks incorrect:
ON t1.year = t0.year
This will cross join all rows with the same year. You probably want to use a more specific condition, for example t1.BookedBy = t0.BookedBy.