SQL Group By Query With Specific First Row - sql

I'm using this query to pull information about companies and their scores from a ms sql database.
SELECT company, avg(score) AS Value FROM Responses where id=12 group by company
This is the result
| COMPANY | VALUE |
|: ------------ | ------:|
| Competitor A | 6.09 |
| Competitor B | 5.70 |
| Other Brand | 5.29 |
| Your Brand | 6.29 |
What I need is a query that will put one company that I will specify in the first position (in this case, the company is Your Brand) and then order the rest by the company like this.
| COMPANY | VALUE |
|: ------------ | -----:|
| Your Brand | 6.29 |
| Competitor A | 6.09 |
| Competitor B | 5.70 |
| Other Brand | 5.29 |

As #jarlh has suggested, use a CASE expression to order:
SELECT company, AVG(score) AS Value
FROM Responses
WHERE id = 12
GROUP BY company
ORDER BY CASE company WHEN 'Your Brand' THEN 0 ELSE 1 END,
AVG(score) DESC;

Related

Turn results of count distinct into something that can be aggregated

I have a table like this:
+----------+--------------+-------------+
| category | sub_category | customer_id |
+----------+--------------+-------------+
| A | AB2 | A876 |
| A | AB2 | A876 |
| A | AA1 | A876 |
| A | AA1 | A876 |
| A | AC3 | A756 |
| B | AB2 | A876 |
| B | AA1 | A756 |
| B | AB7 | A908 |
| C | AA1 | A756 |
| C | AB7 | A908 |
| C | AC3 | A908 |
+----------+--------------+-------------+
And I want to count distinct customers so I can easily do something like:
SELECT category, sub_category, COUNT(DISTINCT customer_id) as count_of_customers
FROM tbl
GROUP BY category, sub_category
And I get a report that gives me distinct customers for each sub_category and category. But these numbers can no longer be aggregated as there needs to be de-duplication if I just need distinct customers by category only.
For e.g customer_id = 'A876' will be counted twice in category='A' (once in sub_category = 'AB2' and once in sub_category = 'AA1') if I just sum the count_of_customers from my query result.
So here is the question, I would like to make these query results "aggregatable". Looking at the problem, it looks like this just isn't possible but I am wondering if there some clever way of distributing these results across categories? so that in my reporting layer (like an excel pivot table), I can get a result that counts 'A876' once in category='A' but counts it twice when I also include sub_category in the fields. Basically converting the results into something summable.
I should mention that this is an overly simplified example. The solution will need to generalize across n different categories and sub_categories.
I am looking for an output that would easily allow me to get either of the following results in something similar to a pivot table (think tableau-like reporting tools):
+----------+--------------------+
| category | distinct_customers |
+----------+--------------------+
| A | 2 |
| B | 3 |
| C | 2 |
+----------+--------------------+
+--------------+--------------------+
| sub_category | distinct_customers |
+--------------+--------------------+
| AA1 | 2 |
| AB2 | 1 |
| AB7 | 1 |
| AC3 | 2 |
+--------------+--------------------+
My immediate thought is to assign weights to a customer_id depending on how many categories and sub_categories it occurs in but I don't know exactly how I'd go about doing this.
You can do exactly what you want -- assigning weights. But this still won't aggregate correctly. Assuming there are no duplicates:
select category, sub_category,
count(distinct customer_id),
sum(1.0 / num_cs) as weighted_customers
from (select t.*,
count(*) over (partition by customer_id) as num_cs
from t
) t
group by category, sub_category;
This weights by both category and sub_category. Obviously, you can adjust the partition by to weight by just one or the other.

Remove Duplicate Result on Query

could help me solve this duplication problem where it returns more than 1 result for the same record I want to bring only 1 result for each id, and only the last history of each record.
My Query:
SELECT DISTINCT ON(tickets.ticket_id,ticket_histories.created_at)
ticket.id AS ticket_id,
tickets.priority,
tickets.title,
tickets.company,
tickets.ticket_statuse,
tickets.created_at AS created_ticket,
group_user.id AS group_id,
group_user.name AS user_group,
ch_history.description AS ch_description,
ch_history.created_at AS ch_history
FROM
tickets
INNER JOIN company ON (company.id = tickets.company_id)
INNER JOIN (SELECT id,
tickets_id,
description,
user_id,
MAX(tickets.created_at) AS created_ticket
FROM
ch_history
GROUP BY id,
created_at,
ticket_id,
user_id,
description
ORDER BY created_at DESC LIMIT 1) AS ch_history ON (ch_history.ticket_id = ticket.id)
INNER JOIN users ON (users.id = ch_history.user_id)
INNER JOIN group_users ON (group_users.id = users.group_user_id)
WHERE company = 15
GROUP BY
tickets.id,
ch_history.created_at DESC;
Result of my query, but returns 3 or 5 identical ids with different histories
I want to return only 1 id of each ticket, and only the last recorded history of each tick
ticket_id | priority | title | company_id | ticket_statuse | created_ticket | company | user_group | group_id | ch_description | ch_history
-----------+------------+--------------------------------------+------------+-----------------+----------------------------+------------------------------------------------------+-----------------+----------+------------------------+----------------------------
49713 | 2 | REMOVE DATA | 1 | t | 2019-12-09 17:50:35.724485 | SAME COMPANY | people | 5 | TEST 1 | 2019-12-10 09:31:45.780667
49706 | 2 | INCLUDE DATA | 1 | f | 2019-12-09 09:16:35.320708 | SAME COMPANY | people | 5 | TEST 2 | 2019-12-10 09:38:52.769515
49706 | 2 | ANY TITLE | 1 | f | 2019-12-09 09:16:35.320708 | SAME COMPANY | people | 5 | TEST 3 | 2019-12-10 09:39:22.779473
49706 | 2 | NOTING ELSE MAT | 1 | f | 2019-12-09 09:16:35.320708 | SAME COMPANY | people | 5 | TESTE 4 | 2019-12-10 09:42:59.50332
49706 | 2 | WHITESTRIPES | 1 | f | 2019-12-09 09:16:35.320708 | SAME COMPANY | people | 5 | TEST 5 | 2019-12-10 09:44:30.675434
wanted to return as below
ticket_id | priority | title | company_id | ticket_statuse | created_ticket | company | user_group | group_id | ch_description | ch_history
-----------+------------+--------------------------------------+------------+-----------------+----------------------------+------------------------------------------------------+-----------------+----------+------------------------+----------------------------
49713 | 2 | REMOVE DATA | 1 | t | 2019-12-09 17:50:10.724485 | SAME COMPANY | people | 5 | TEST 1 | 2020-01-01 18:31:45.780667
49707 | 2 | INCLUDE DATA | 1 | f | 2019-12-11 19:22:21.320701 | SAME COMPANY | people | 5 | TEST 2 | 2020-02-05 16:38:52.769515
49708 | 2 | ANY TITLE | 1 | f | 2019-12-15 07:15:57.320950 | SAME COMPANY | people | 5 | TEST 3 | 2020-02-06 07:39:22.779473
49709 | 2 | NOTING ELSE MAT | 1 | f | 2019-12-16 08:30:28.320881 | SAME COMPANY | people | 5 | TESTE 4 | 2020-01-07 11:42:59.50332
49701 | 2 | WHITESTRIPES | 1 | f | 2019-12-21 11:04:00.320450 | SAME COMPANY | people | 5 | TEST 5 | 2020-01-04 10:44:30.675434
I wanted to return as shown below, see that the field ch_description, and ch_history bring only the most recent records and only the last of each ticket listed, without duplication I wanted to bring this way could help me.
Two things jump out at me:
You have listed "created at" as part of your "distinct on," which is going to inherently give you multiple rows per ticket id (unless there happens to be only one)
The distinct on should make the subquery on the ticket history unnecessary... and even if you chose to do it this way, you again are going on the "created at" column, which will give you multiple results. The ideal subquery, should you choose this approach, would have been to group by ticket_id and only ticket_id.
Slightly related:
An alternative approach to the subquery would be an analytic function (windowing function), but I'll save that for another day.
I think the query you want, which will give you one row per ticket_id, based on the history table's created_at field would be something like this:
select distinct on (t.id)
<your fields here>
from
tickets t
join company c on t.company_id = c.id
join ch_history ch on ch.ticket_id = t.id
join users u on ch.user_id = u.ud
join group_users g on u.group_user_id = g.id
where
company = 15
order by
t.id, ch.created_at -- this is what tells distinct on which record to choose

How to aggregate from result data Oracle SQL?

I have table :
+------+-------+-----------------+
| id | name | code | desc |
+------+-------+-----------------+
| 1 | aa | 032016 | grape |
| 1 | aa | 012016 | apple |
| 1 | aa | 032016 | grape |
| 1 | aa | 022016 | orange |
| 1 | aa | 012016 | apple |
| 1 | aa | 032016 | grape |
+------+-------+-----------------+
i tried with query:
SELECT id, name, code, desc, COUNT(code) as view
FROM mytable
GROUP BY id, name, code, desc
and the result is :
+------+-------+------------------------+
| id | name | code | desc | view |
+------+-------+------------------------+
| 1 | aa | 012016 | apple | 2 |
| 1 | aa | 022016 | orange | 1 |
| 1 | aa | 032016 | grape | 3 |
+------+-------+------------------------+
what i expected is like this :
+------+-------+----------------------------------------------------+
| id | name | code | desc | view |
+------+-------+----------------------------------------------------+
| 1 | aa | 012016,022016,032016 | apple,orange,grape | 2,1,3 |
+------+-------+----------------------------------------------------+
can anyone help me how to aggregate the result?
thanks in advance
Your table design has me a bit worried. Is it coincidence that one fruit always has the same code in the table? Then why store it redundantly? There should be a fruit table holding each fruit and its code only once. You know why this is called a relational database system, don't you?
However, with your query you are almost where you wanted to get. You have the counts per id, name, code, and desc. Now you want to aggregate even further. So in the next step group by id and name, because you want one result row per id and name it seems. Use LISTAGG to concatenate the strings in the group:
SELECT
id,
name,
listagg(code, ',') within group(order by code) as codes,
listagg(desc, ',') within group(order by code) as descs,
listagg(view, ',') within group(order by code) as views
FROM
(
SELECT id, name, code, desc, COUNT(*) as view
FROM mytable
GROUP BY id, name, code, desc
)
GROUP BY id, name
ORDER BY id, name;

Split column into two columns based on type code in third column

My SQL is very rusty. I'm trying to transform this table:
+----+-----+--------------+-------+
| ID | SIN | CONTACT | TYPE |
+----+-----+--------------+-------+
| 1 | 737 | b#bacon.com | email |
| 2 | 760 | 250-555-0100 | phone |
| 3 | 737 | 250-555-0101 | phone |
| 4 | 800 | 250-555-0102 | phone |
| 5 | 850 | l#lemon.com | email |
+----+-----+--------------+-------+
Into this table:
+----+-----+--------------+-------------+
| ID | SIN | PHONE | EMAIL |
+----+-----+--------------+-------------+
| 1 | 737 | 250-555-0101 | b#bacon.com |
| 2 | 760 | 250-555-0100 | |
| 4 | 800 | 250-555-0102 | |
| 5 | 850 | | l#lemon.com |
+----+-----+--------------+-------------+
I wrote this query:
SELECT *
FROM (SELECT *
FROM people
WHERE TYPE = 'phone') phoneNumbers
FULL JOIN (SELECT *
FROM people
WHERE TYPE = 'email') emailAddresses
ON phoneNumbers.SIN = emailAddresses.SIN;
Which produces:
+----+-----+--------------+-------+------+-------+-------------+--------+
| ID | SIN | CONTACT | TYPE | ID_1 | SIN_1 | CONTACT_1 | TYPE_1 |
+----+-----+--------------+-------+------+-------+-------------+--------+
| 2 | 760 | 250-555-0100 | phone | | | | |
| 3 | 737 | 250-555-0101 | phone | 1 | 737 | b#bacon.com | email |
| 4 | 800 | 250-555-0102 | phone | | | | |
| | | | | 5 | 850 | l#lemon.com | email |
+----+-----+--------------+-------+------+-------+-------------+--------+
I know that I can select the columns I want, but the SIN column is incomplete. I seem to recall that I should join in the table a third time to get a complete SIN column, but I cannot remember how.
How can I produce my target table (ID, SIN, PHONE, EMAIL)?
Edit and clarification: I am grateful for the answers I have received so far, but as a SQL greenhorn I am unfamiliar with the techniques you are using (case statements, conditional aggregation, and pivoting). Can this not be done using JOIN and SELECT? Please excuse my ignorance in this matter. (It's not that I am not interested in superior techniques, but I do not want to move too fast too soon.)
One way to approach this is conditional aggregation:
select min(ID), SIN,
max(case when type = 'phone' then contact end) as phone,
max(case when type = 'email' then contact end) as email
from people t
group by sin;
Seems a pivot (oracle.com) would work easily here.
SELECT ID, SIN, PHONE, EMAIL
FROM PEOPLE
PIVOT (
MAX(CONTACT)
FOR TYPE IN ('EMAIL', 'PHONE')
)
I realize this is less elegant than all the solutions posted, but here it is anyhow, a solution using only JOIN and SELECT:
SELECT sins.SIN, phone, email
FROM ((SELECT SIN email_sin, contact email
FROM people
WHERE TYPE = 'email') email
FULL JOIN (SELECT SIN phone_sin, contact phone
FROM people
WHERE TYPE = 'phone') phone
ON email.email_sin = phone.phone_sin)
RIGHT JOIN (SELECT DISTINCT SIN FROM people) sins
ON sins.SIN = phone_sin OR sins.SIN = email_sin;
This lacks the ID column.

Create a pivot table from two tables based on dates

I have two MS Access tables sharing a one to many relationship. Their structures are like the following:
tbl_Persons
+----------+------------+-----------+
| PersonID | PersonName | OtherData |
+----------+------------+-----------+
| 1 | PersonA | etc. |
| 2 | PersonB | |
| 3 | PersonC | |
tbl_Visits
+----------+------------+------------+-----------------------
| VisitID | PersonID | VisitDate | dozens of other fields
+----------+------------+------------+-----------
| 1 | 1 | 09/01/13 |
| 2 | 1 | 09/02/13 |
| 3 | 2 | 09/03/13 |
| 4 | 2 | 09/04/13 | etc...
I wish to create a new table based on the VisitDate field, the column headings of which are Visit-n where n is 1 to the number of visits, Visit-n-Data1, Visit-n-Data2, Visit-n-Data3 etc.
MergedTable
+----------+----------+---------------+-----------------+----------+----------------+
| PersonID | Visit1 | Visit1Data1 | Visit1Data2... | Visit2 | Visit2Data1... |
+----------+----------+---------------+-----------
| 1 | 09/01/13 | | | 09/02/13 |
| 2 | 09/03/13 | | | 09/04/13 |
| 3 | etc. | |
I am really not sure how to do this. Whether SQL query or using DAO then looping through records and columns. It is essential that there is only 1 PersonID per row and all his data appears chronologically into columns.
Start of by ranking the visits with something like
SELECT PersonID, VisitID,
(SELECT COUNT(VisitID) FROM tbl_Visits AS C
WHERE C.PersonID = tbl_Visits.PersonID
AND C.VisitDate < tbl_Visits.VisitDate) AS RankNumber
FROM tbl_Visits
Use this query as a base for the 'pivot'
Since you seem to have some visits of persons on the same day (visit 1 and 2) the WHERE clause needs to be a bit more sophisticated. But I hope you get the basic concept.
Pivoting can be done with multiple LEFT JOINs.
I question if my solution will have a high performance, since I did not test it. It is easier in SQL Server than in MS Access to accomplish.