Transpose in Postgresql - sql

I am trying to design a database of customer details. Where customers can have up to two different phone numbers.
When I run the Select * command to bring out the customers that match criteria, I get this:
Name | Number
James | 12344532
James | 23232422
I would like it to display all customers with two numbers this way:
Name | Number | Number
James 12344532 23232422
John 32443322
Jude 12121212 23232422
I am using Postgresql server on Azure Data studio.
Please assist.
I tried using this command:
Select * FROM name.name,
min(details.number) AS number1,
max(details.number) AS number2
FROM name
JOIN details
ON name.id=details.id
GROUP BY name.name
I got this:
Name | Number | Number
James 12344532 23232422
John 32443322 32443322
Jude 12121212 23232422
Customers with just 1 phone number gets duplicated in the table. How do I go about this?

I would aggregate the numbers into an array, then extract the array elements:
select n.name,
d.numbers[1] as number_1,
d.numbers[2] as number_2
from name n
join (
select id, array_agg(number) as numbers
from details
group by id
) d on d.id = n.id
order by name;
This is also easy to extend if you have more than two numbers.

Try using the following query:
SELECT
Name,
MIN(CASE WHEN rn = 1 THEN Number END) AS Number1,
MIN(CASE WHEN rn = 2 THEN Number END) AS Number2
FROM
(SELECT
Name, Number,
ROW_NUMBER() OVER (PARTITION BY Name ORDER BY Number) AS rn
FROM name) t
GROUP BY Name
This query will use the ROW_NUMBER() function to assign a unique row number to each phone number for each customer. The ROW_NUMBER() function is ordered by the Number column, so the lowest number will have a row number of 1, and the second lowest number will have a row number of 2, etc.
Then we use the outer query to group customer by name and use MIN() function to get the first and second number based on the row number.
This query will return the desired output, with two columns, one showing the customer's first phone number and the other showing their second phone number.
Note: The query above assumes that the phone number is unique for each customer. If a customer has duplicate phone numbers, the query will return the first one it encounters.

Related

Get rollup group value in SQL Server

I have a table with following data:
Name
Score
A
2
B
3
A
1
B
3
I want a query which returns the following output.
Name
Score
A
2
A
1
Subtotal: A
3
B
3
B
3
Subtotal: B
6
I am able to get "Subtotal" with group by rollup query but I want to get subtotal along with group column value.
Please help me with some SQL code
If score has at most one value per name, you can use GROUPING SETS`:
select name, sum(score) as score
from t
group by grouping sets ((name, score), (name));
If name is never null, I would just use:
coalesce(name, 'Grouping ' + name)
Otherwise you need to use grouping().

if count value of the column is greater than 1, I want to print the count of the column else I want to print value in the field

I am writing a query which fetches details from different tables. In one column I want to print count value of a column. If the count value of the column is greater than 1, I want to print the count of the column else I want to print value in the field.
I want to build a query which will give me count of user_id from table 1 & 2. if the count user_id is greater than 1, then print count (user_id) else print value of user_id
Table:1
| user_id |
| John |
| Bob |
| Kris |
| Tom |
Table:2
| user_id |
| Rob |
query result should list count of table1 as it greater than 1. Table2 should list Rob as it is lesser than 2
You want to select user IDs (names actually) from a table. If it's just one row then show that name, otherwise show the number of entries instead. So, just use a CASE expression to check whether count is 1 or greater than 1.
You probably need CAST or CONVERT to turn the count number into a string, so the CASE expression always returns the same type (this is how CASE works).
select
case when count(*) > 1
then cast(count(*) as varchar(100))
else max(user_id)
end as name_or_count
from mytable
Window Functions come to mind but since your user_ids are not numbers, you'll run into an issue where you can't have two different data types in the same column. See how this works for you. Make sure to cast the varchar numbers back to integer if this script is part of a larger process.
with cte as
(select 'John' as user_id union all
select 'Bob' as user_id union all
select 'Kris' as user_id union all
select 'Tom' as user_id)
select distinct case when count(*) over() > 1
then cast(count(*) over() as varchar) else user_id end
from cte
with cte as
(select 'Rob' as user_id)
select distinct case when count(*) over() > 1
then cast(count(*) over() as varchar) else user_id end
from cte

How to compute percentage of total based on Counts of 1 field and filtered by another field

Data and Desired Result:
I have the above data, i would like to compute the percentage and show the corresponding counts of the records. The CountKey is a concatenation of 3 fields, i only want to count it when it is unique by LastName, I then would like to find out the percentage of total for each different Status type by last name. The CountKeyTotal is the total count of unique CountKeys for Smith, CountKey is the Total unique Countkeys by LastName by Status
I am fairly new to SQL and have only been able to get either totals in whole (as an example using the data provided, Smith 40 3 12 25%
any help would be appreciated
You can use a group by and a dinamic temp table for total
select
a.name, a.status
, count(a.countKey) as CountKEy
, c2 as CountKeyTotal
, (a.count(*) / b.c1) *100 as percentage
from my_table as a
inner join( select name, count(*) as c1 , count(countKey) c2 from my_table
group by name) b on a.name = b.name
group by a.name

Complex SQL query or queries

I looked at other examples, but I don't know enough about SQL to adapt it to my needs. I have a table that looks like this:
ID Month NAME COUNT First LAST TOTAL
------------------------------------------------------
1 JAN2013 fred 4
2 MAR2013 fred 5
3 APR2014 fred 1
4 JAN2013 Tom 6
5 MAR2014 Tom 1
6 APR2014 Tom 1
This could be in separate queries, but I need 'First' to equal the first month that a particular name is used, so every row with fred would have JAN2013 in the first field for example. I need the 'Last" column to equal the month of the last record of each name, and finally I need the 'total' column to be the sum of all the counts for each name, so in each row that had fred the total would be 10 in this sample data. This is over my head. Can one of you assist?
This is crude but should do the trick. I renamed your fields a bit because you are using a bunch of "RESERVED" sql words and that is bad form.
;WITH cte as
(
Select
[NAME]
,[nmCOUNT]
,ROW_NUMBER() over (partition by NAME order by txtMONTH ASC) as 'FirstMonth'
,ROW_NUMBER() over (partition by NAME order by txtMONTH DESC) as 'LastMonth'
,SUM([nmCOUNT]) as 'TotNameCount'
From Table
Group by NAME, [nmCOUNT]
)
,cteFirst as
(
Select
NAME
,[nmCOUNT]
,[TotNameCount]
,[txtMONTH] as 'ansFirst'
From cte
Where FirstMonth = 1
)
,cteLast as
(
Select
NAME
,[txtMONTH] as 'ansLast'
From cte
Where LastMonth = 1
Select c.NAME, c.nmCount, c.ansFirst, l.ansLast, c.TotNameCount
From cteFirst c
LEFT JOIN cteLast l on c.NAME = l.NAME

SQL listing combinations in different columns only once

My DB looks as followed
**Customer Order Product**
cid(PK) oid(PK) pid(PK)
fname cid(FK) pname
lname pid(FK) pprice
Now I'm using a query to get the following results:
**fname lname oid pname pprice**
Bill Gates 1111 Router 40,-
Bill Gates 1112 Laptop 699,-
Steve Jobs 1113 Tablet 1299,-
Steve Jobs 1114 Watch 699,-
What I want however is to list the first and last name only once if the person has more then one orders. How would I be able to achieve this?
Example expected output:
**fname lname oid pname pprice**
Bill Gates 1111 Router 40,-
1112 Laptop 699,-
Steve Jobs 1113 Tablet 1299,-
1114 Watch 699,-
This problem can be solved with LAG function.
LAG function returns value of a given column from previous row.
Then we can compare this value from previous row with value from current row.
We can use CASE statement to return value depending on result of this comparison.
Here is a query which gives desired result:
SELECT
CASE
WHEN lag(concat(cop.fname, cop.lname), 1, 0) OVER (order by cop.oid) = concat(cop.fname, cop.lname) THEN null
ELSE cop.fname
END AS fname,
CASE
WHEN lag(concat(cop.fname, cop.lname), 1, 0) OVER (order by cop.oid) = concat(cop.fname, cop.lname) THEN null
ELSE cop.lname
END AS lname,
cop.oid,
cop.pname,
cop.pprice
FROM
(SELECT c.fname, c.lname, o.oid, p.pname, p.pprice
FROM customer c
LEFT JOIN myorder o on c.cid=o.cid
LEFT JOIN product p on o.pid=p.pid) cop
It is not good idea to choose ORDER as a name for table because ORDER is keyword in SQL. So I choosed MYORDER as a name for table containing orders.
Above query works for Oracle database
Use ROW_NUMBER() over(Partition by fname, Lname Order By Oid) as RN in your query to number the lines. Then in your outer query or your next query if you dump the results to a temporary table use a case statement to set fname and lname to empty string whenever RN is not 1.
You need an additional column such as Customer No to order on so you would order by CustomerNo, RN, Lname, Fname