Transposing a column of data in SQL - sql

In SQL my data output is
Agreement ID
ProductStatus
125
A
125
C
125
N
I want to see this instead as
Agreement ID
ProductStatus
125
A,C, N
OR
Agreement ID
ProductStatus1
ProductStatus2
ProductStatus3
125
A
C
N
I've tried a few simple pivots, but the values a, c & n CAN be different and random values each time.
Can anyone help?

You can use a group_concat function. Your query will be something like this
SELECT agreement_id, group_concat(Product_Status)
FrOM mytable
group by agreement_id
This is for MySQL, for other databases you can search for group_concat alternative function for that particular database.
Seems like you are new to database. You can use this reference to learn more.
https://www.mysqltutorial.org/basic-mysql-tutorial.aspx

If you can get three values in different columns using conditional aggregation:
select agreementid,
max(case when seqnum = 1 then ProductStatus end) as ProductStatus_1,
max(case when seqnum = 2 then ProductStatus end) as ProductStatus_2,
max(case when seqnum = 3 then ProductStatus end) as ProductStatus_3
from (select t.*,
row_number() over (partition by agreementid order by agreementid) as seqnum
from t
) t
group by agreementid;
The SQL standard for creating a list is:
select agreementid,
list_agg(ProductStatus, ',') within group (order by productstatus) as productstatuses
from t
group by agreementid;
Many databases have different names for this function.
In both of these cases, the ordering of the columns or elements of the list are indeterminate. SQL table represent unordered sets (well, technically multisets) so there is no ordering unless a column specifies the ordering.

Related

SQL arrange row in column

I have following SQL query:
SELECT
PatientMRN,
OrderDate,
PatientMRN,
OrdereName,
FROM OrderItem
For some MRN patient have many orders and I would like to have order related to each patient in a column rather than have duplicate row of same MRN with different order.
So instead of having:
MRN OrdereName
012 Name one
012 Name two
012 Name three
012 Name four
I want to have it as follow:
MRN OrdereName1 OrderName2 OrderName3
012 Name one Name two Name three
unfortunately it is a confidential DB so I can attach it here.
If you know in advance the maximum number of orders per patient, you can use window functions and conditional aggregation:
select patientmrn,
max(case when rn = 1 then orderename end) as orderename1,
max(case when rn = 2 then orderename end) as orderename2,
max(case when rn = 3 then orderename end) as orderename3
from (
select t.*, row_number() over(partition by patientmrn order by orderename) rn
from mytable t
) t
group by patientmrn
You can expand the select clause with more conditional max() as needed (additional columns show null values when there is no more orders to display).
You control how orders are spread in the columns using the order by clause of the row_number(). Here, orders are orderes by ascending name. If, for example, you wanted to order them by date, you would change the order by clause to order by orderdate.

How to get first and last record from same group in SQL Server?

I'm a new SQL user and need help.
Let's say I have a vehicle number 123 and I've traveled from Region 3 to final destination Region 4. In between, I've visited Region 1 and 5 as well but that's not my concern.
Simple example would be as follow.
Original Table
Desired Output
How can this be done in SQL query?
You have a sequence number so you can use some form of aggregation. One method is:
select records,
max(case when sequence = 1 then fromregion end) as fromregion,
max(case when sequence = maxsequence then toregion) as toregion
from (select t.*, max(sequence) over (partition by records) as max_sequence
from t
) t
group by records;
Unfortunately, SQL Server doesn't offer "first()" or "last()" as aggregation functions. But it does support first_value() as a window function. This allows you to do the logic without a subquery:
select distinct records,
first_value(fromRegion) over (partition by records order by sequence) as fromregion,
first_value(toRegion) over (partition by records order by sequence desc) as toregion
from t;

Transpose columns to rows with ANSI sql

| id |
+----+
| 1 |
| 2 |
| 4 |
| 5 |
+----+
Output as row
1 2 4 5
Without use of Pivot method ,no hardcoding like checking with case with when 1 = 1.No dynamic Sql .with out any inbuilt function.
I have searched questions but could not find anything in pure sql
Is there a way?
Most SQL databases support some sort of group concatenation ability. For example, in MySQL we could use GROUP_CONCAT:
SELECT GROUP_CONCAT(id ORDER BY id SEPARATOR ' ') AS output
FROM yourTable;
The ANSI standard may not define anything, but SQL Server, Oracle, and Postgres, to name a few, can do something similar to the above.
I would use correlated subquery & do conditional aggergation since some DBMS doesn't have a row_number() :
SELECT MAX(CASE WHEN Seq = 1 THEN Id END) AS ID1,
. . .
MAX(CASE WHEN Seq = 4 THEN Id END) AS ID4
FROM (SELECT t.*,
(SELECT COUNT(*) FROM table t1 WHERE t1.id <= t.id) AS Seq
FROM table t
) t;
However, this method might fail if the ID is not in Sequential manner if so, then you would need to use PK (IDENTITY Column) that specify column ordering.
Maybe your database supports the LISTAGG function of ANSI SQL:2016:
It is a function to transform values from a group of rows into a delimited string.
See syntax as well as database support and alternatives here:
Listagg is an ordered set function, which require the within group clause to specify an order. The minimal syntax is:
LISTAGG(<expression>, <separator>) WITHIN GROUP(ORDER BY …)
If you know you have four values, you can use conditional aggregation:
select max(case when seqnum = 1 then id end) as id_1,
max(case when seqnum = 2 then id end) as id_2,
max(case when seqnum = 3 then id end) as id_3,
max(case when seqnum = 4 then id end) as id_4
from (select t.*, row_number() over (order by id) as seqnum
from t
) t;
I'm not sure if this fits your requirements. If you are looking for something with a variable number of columns, then you need to use dynamic SQL.

How to do a Postgresql group aggregation: 2 fields using one to select the other

I have a table - Data - of rows, simplified, like so:
Name,Amount,Last,Date
A,16,31,1-Jan-2014
A,27,38,1-Feb-2014
A,12,34,1-Mar-2014
B,8,37,1-Jan-2014
B,3,38,1-Feb-2014
B,17,39,1-Mar-2014
I wish to group them similar to:
select Name,sum(Amount),aggr(Last),max(Date) from Data group by Name
For aggr(Last) I want the value of 'Last' from the row that contains max(Date)
So the result I want would be 2 rows
Name,Amount,Last,Date
A,55,34,1-Mar-2014
B,28,39,1-Mar-2014
i.e. in both cases, the value of Last is the one from the row that contained 1-Mar-2014
The query I'm actually doing is basically the same, but with many more sum() fields and millions of rows, so I'm guessing an aggregate function could avoid multiple extra requests each group of incoming rows.
Instead, use row_number() and conditional aggregation:
select Name, sum(Amount),
max(case when seqnum = 1 then Last end) as Last,
max(date)
from (select d.*, row_number() over (partition by name order by date desc) as seqnum
from data d
) d
group by Name;

Is it possible to calculate the sum of each group in a table without using group by clause

I am trying to find out if there is any way to aggregate a sales for each product. I realise I can achieve it either by using group-by clause or by writing a procedure.
example:
Table name: Details
Sales Product
10 a
20 a
4 b
12 b
3 b
5 c
Is there a way possible to perform the following query with out using group by query
select
product,
sum(sales)
from
Details
group by
product
having
sum(sales) > 20
I realize it is possible using Procedure, could it be done in any other way?
You could do
SELECT product,
(SELECT SUM(sales) FROM details x where x.product = a.product) sales
from Details a;
(and wrap it into another select to simulate the HAVING).
It's possible to use analytic functions to do the sum calculation, and then wrap that with another query to do your filtering.
See and play with the example here.
select
running_sum,
OwnerUserId
from (
select
id,
score,
OwnerUserId,
sum(score) over (partition by OwnerUserId order by Id) running_sum,
last_value(id) over (partition by OwnerUserId order by OwnerUserId) last_id
from
Posts
where
OwnerUserId in (2934433, 10583)
) inner_q
where inner_q.id = inner_q.last_id
--and running_sum > 20;
We keep a running sum going on the partition of the owner (product), and we tally up the last id for the same window, which is the ID we'll use to get the total sum. Wrap it all up with another query to make sure you get the "last id", take the sum, and then do any filtering you want on the result.
This is an extremely round-about way to avoid using GROUP BY though.
If you don't want nested select statements (run slower), use CASE:
select
sum(case
when c.qty > 20
then c.qty
else 0
end) as mySum
from Sales.CustOrders c