Transpose columns to rows with ANSI sql - 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.

Related

SQL query looping for each value in a list

New to SQL here - I am trying to get 1 row from a table matching to a particular criteria
Typically this would look like
SELECT TOP 1 *
FROM myTable
WHERE id = 'abc'
The output may look like
value id
--------------
1 abc
The table has many entries for an 'id', and I am trying to get one entry per 'id'. Now I have list of 'id's. How would I execute something like
SELECT TOP 1 *
FROM myTable
FOR EACH id
WHERE id IN ('abc', 'edf', 'fgh')
Expecting result like
value id
--------------
1 abc
10 edf
12 fgh
I do not know if it is some sort union or concat operation, but would like to learn. I am working on Azure SQL Server
The table has many entries for an 'id', and I am trying to get one entry per 'id'. Now I have list of 'id's.
A typical method is row_number():
select t.*
from (select t.*,
row_number() over (partition by id order by id) as seqnum
from mytable t
) t
where seqnum = 1;
Note: you can filter on particular ids, if you want. It is unclear if that is really required for your question.
If you happen to be using SQL Server (as select top suggests), you can use the more concise, but somewhat less performant:
select top (1) with ties t.*
from mytable t
order by row_number() over (order by id order by (select null));

Transposing a column of data in 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.

SQL Query to group text based on numeric column

I have a table 'TEST' as shown below
Number | Seq | Name
-------+-------+------
123 | 1 | Hello
123 | 2 | Hi
123 | 3 | Greetings
234 | 1 | Goodbye
234 | 2 | Bye
I want to write a query, to group the table by 'Number', and select the rows with the maximum sequence number (MAX(Seq)). The output of the query would be
Number | Seq | Name
-------+-------+------
123 | 3 | Greetings
234 | 2 | Bye
How do I go about this?
EDIT: TEST is actually a table that is the result from a long query (joining multiple tables) that I have already written. I already have a (SELECT ...) statement to get the values I need. Is there a way to remove duplicate rows (with the same 'Number' as shown above) and select only the one with maximum 'Seq' value.
I am on Microsoft SQL Server 2008 (SP2)
I was hoping there would be a way to achieve this by
SELECT * FROM (SELECT ...) TEST <condition to group>
You can use a select win in clause
select * from test
where (number, count) in (select number, max(count) from test group by Number)
Another option is to use a windowed ROW_NUMBER() function with a partition on the number:
With Cte As
(
Select *,
Row_Number() Over (Partition By Number Order By Count Desc) RN
From TEST
)
Select Number, Count, Name
From Cte
Where RN = 1
SELECT *
FROM (SELECT test.*, MAX (seq) OVER (PARTITION BY num) max_seq
FROM test)
WHERE seq = max_seq
I changed the column name from number because you can't use a reserved word for a column name. This is pretty much the same as the other answers, except that it explicitly gets the maximum sequence number for each NUM.
You want to use an ANALYTIC function together with a conditional clause to get you only the rows of TEST that you desire.
WITH TEST as (
...your really complex query that generates TEST...
)
SELECT
Number, Seq, Name,
RANK() OVER (PARTITION By Number ORDER BY Seq DESC) AS aRank
FROM Test
WHERE aRank = 1
;
This returns the Number, Seq, Name for each Number grouping where the Seq is maximum. Yes, it also returns a column named aRank with all '1' in it...hopefully it can be ignored.
The solution to this is to do an self join on only the MAX(Seq) values.
This answer can be found at SQL Select only rows with Max Value on a Column

Is SQL row_number order guaranteed when a CTE is referenced many times?

If I have a CTE definition that uses row_number() ordered by a non-unique column, and I reference that CTE twice in my query, is the row_number() value for each row guaranteed to be the same for both references to the CTE?
Example 1:
with tab as (
select 1 as id, 'john' as name
union
select 2, 'john'
union
select 3, 'brian'
),
ordered1 as (
select ROW_NUMBER() over (order by name) as rown, id, name
from tab
)
select o1.rown, o1.id, o1.name, o1.id - o2.id as id_diff
from ordered1 o1
join ordered1 o2 on o2.rown = o1.rown
Output:
+------+----+-------+---------+
| rown | id | name | id_diff |
+------+----+-------+---------+
| 1 | 3 | brian | 0 |
| 2 | 1 | john | 0 |
| 3 | 2 | john | 0 |
+------+----+-------+---------+
Is it guaranteed that id_diff = 0 for all rows?
Example 2:
with tab as (
select 1 as id, 'john' as name
union
select 2, 'john'
union
select 3, 'brian'
),
ordered1 as (
select ROW_NUMBER() over (order by name) as rown, id, name
from tab
),
ordered2 as (
select ROW_NUMBER() over (order by name) as rown, id, name
from tab
)
select o1.rown, o1.id, o1.name, o1.id - o2.id as id_diff
from ordered1 o1
join ordered2 o2 on o2.rown = o1.rown
Same output as above when I ran it, but that doesn't prove anything.
Now that I am joining two queries ordered1 and ordered2, can any guarantee be made about the value of id_diff = 0 in the result?
Example queries on http://rextester.com/AQDXP74920
I suspect that there is no guarantee in either case. If there is no such guarantee, then all CTEs using row_number() should always order by a unique combination of columns if the CTE may be referenced more than once in the query.
I have never heard this advice before, and would like some expert opinion.
No, there is no guarantee that ROW_NUMBER on a non-unique sort list returns the same sequence when a CTE is referenced multiple times. It is very likely to happen, but not guranteed, as the CTE is merely a view.
So always make the sort list unique in such a case, e.g. order by name, id.
The answer that Thorsten gave is correct, I just want to add some more details.
Users of SQL Server often think of CTEs as "temporary tables" or "derived tables. However, they are nothing of the sort. Although some databases do materialize CTEs (at least some of the time), SQL Server never materializes CTEs.
In fact, what happens, is that the CTE logic is inserted into the query -- just as if "replace(, )" were used on the query. This affects non-unique sorting keys. It also affects some non-deterministic functions, such as NEWID().
The advice in your case is simple: Whenever you use order by, include a unique key as the last order by key. You should do this whether order by is used in a window function or for a query. It is just a safe habit to get used to.

SQL Query to get all rows with duplicate values but are not part of the same group

The database schema is organized as follows:
ID | GroupID | VALUE
--------------------
1 | 1 | A
2 | 1 | A
3 | 2 | B
4 | 3 | B
In this example, I want to GET all Rows with duplicate VALUE, but are not part of the same group. So the desired result set should be IDs (3, 4), because they are not in the same group (2, 3) but still have the same VALUE (B).
I'm having trouble writing a SQL Query and would appreciate any guidance. Thanks.
So far, I'm using SQL Count, but can't figure out what to do with the GroupId.
SELECT *
FROM TABLE T
HAVING COUNT(T.VALUE) > 1
GROUP BY ID, GroupId, VALUE
The simplest method for this is using EXISTS:
SELECT
ID
FROM
MyTable T1
WHERE
EXISTS (SELECT 1
FROM MyTable
WHERE Value = t1.Value
AND GroupID <> t1.GroupID)
Here is one method. First you have to identify the values that appear in more than one group and then use that information to find the right rows in the original table:
select *
from t
where value in (SELECT value
FROM TABLE T
GROUP BY VALUE
HAVING COUNT(distinct groupid) > 1
)
order by value
Actually, I prefer a slight variant in this case, by changing the HAVING clause:
HAVING min(groupid) <> max(groupid)
This works when you are looking for more than one group and should be faster than the COUNT DISTINCT version.
SELECT ALL_.*
FROM (SELECT *
FROM TABLE_
GROUP BY ID, GROUPID, VALUE
ORDER BY ID) GROUPED,
TABLE_ ALL_
WHERE GROUPED.VALUE = ALL_.VALUE
AND GROUPED.GROUPID <> ALL_.GROUPID