SQL transform distinct rows into one row and multiple columns - sql

I have a table and rows with the same Name can occur like up to 5 times. Example:
| Name | Value |
|--------|---------|
| Test | Value1 |
| Test | Value2 |
| Test | Value3 |
| FooBar | Value11 |
| FooBar | Value12 |
I am trying to create a query to compress the rows to have a unique Name and transfer the values to columns. If there are less than 5 values per name the remaining columns should have NULL.
| Name | Col1 | Col2 | Col3 | Col4 | Col5 |
|--------|---------|---------|--------|------|------|
| Test | Value1 | Value2 | Value3 | NULL | NULL |
| FooBar | Value11 | Value12 | NULL | NULL | NULL |
I looked at Pivot but I don't have a column to aggregate.
I need this format for csv file.
Using SQL Server 2016.

You can construct a column using row_number():
select name,
max(case when seqnum = 1 then value end) as value_1,
max(case when seqnum = 2 then value end) as value_2,
max(case when seqnum = 3 then value end) as value_3,
max(case when seqnum = 4 then value end) as value_4,
max(case when seqnum = 5 then value end) as value_5
from (select t.*,
row_number() over (partition by name order by value) as seqnum
from t
) t
group by name;

Related

Single query to split out data of one column, into two columns, from the same table based on different criteria [SQL]

I have the following data in a table, this is a single column shown from a table that has multiple columns, but only data from this column needs to be pulled into two column output using a query:
+----------------+--+
| DataText | |
| 1 DEC20 DDD | |
| 1 JUL20 DDD | |
| 1 JAN21 DDD | |
| 1 JUN20 DDD500 | |
| 1 JUN20 DDD500 | |
| 1 JUN20DDDD500 | |
| 1 JUN20DDDD500 | |
| 1 JUL20 DDD800 | |
| 1 JUL20 DDD800 | |
| 1 JUL20DDDD800 | |
| 1 JUL20DDDD400 | |
| 1 JUL20DDDD400 | |
+----------------+--+
Required result: distinct values based on the first 13 characters of the data, split into two columns based on "long data", and "short data", BUT only giving the first 13 characters in output for both columns:
+-------------+-------------+
| ShortData | LongData |
| 1 DEC20 DDD | 1 JUN20 DDD |
| 1 JUL20 DDD | 1 JUN20DDDD |
| 1 JAN21 DDD | 1 JUL20 DDD |
| | 1 JUL20DDDD |
+-------------+-------------+
Something like:
Select
(Select DISTINCT LEFT(DataText,13)
From myTable)
Where LEN(DataText)=13) As ShortData
,
(Select DISTINCT LEFT(DataText,13)
From myTable)
Where LEN(DataText)>13) As LongData
I would also like to query/"scan" the table only once if possible. I can't get any of the SO examples modified to make such a query work.
This is quite ugly, but doable. As a starter, you need a column that defines the order of the rows - I assumed that you have such a column, and that is called id.
Then you can select the distinct texts, put them in separate groups depending on their length, and finally pivot:
select
max(case when grp = 0 then dataText end) shortData,
max(case when grp = 1 then dataText end) longData
from (
select
dataText,
grp,
row_number() over(partition by grp order by id) rn
from (
select
id,
case when len(dataText) <= 13 then 0 else 1 end grp,
substring(dataText, 1, 13) dataText
from (select min(id) id, dataText from mytable group by dataText) t
) t
) t
group by rn
If you are content with ordering the records by the string column itself, it is a bit simpler (and, for your sample data, it produces the same results):
select
max(case when grp = 0 then dataText end) shortData,
max(case when grp = 1 then dataText end) longData
from (
select
dataText,
grp,
row_number() over(partition by grp order by dataText) rn
from (
select distinct
case when len(dataText) <= 13 then 0 else 1 end grp,
substring(dataText, 1, 13) dataText
from mytable
) t
) t
group by rn
Demo on DB Fiddle:
shortData | longData
:---------- | :------------
1 DEC20 DDD | 1 JUL20 DDD80
1 JAN21 DDD | 1 JUL20DDDD40
1 JUL20 DDD | 1 JUL20DDDD80
null | 1 JUN20 DDD50
null | 1 JUN20DDDD50

how to get a table with columns named after key in mapping table

I'd like a "flat" representation of a table and a corresponding mapping table with key-value pairs, where the keys are used as columns in the result.
E.g. given I have a table with a one to manny relation like this:
Table Items(ID, Name)
| ID | Name |
|----|-------|
| 1 | Item1 |
| 2 | Other |
Table KeyValue(ID, Key, Value, ItemID[FK->Items])
| ID | Key | Value | ItemID[FK->Items] |
|----|------|--------|-------------------|
| 1 | key1 | value1 | 1 |
| 2 | key2 | value2 | 1 |
| 3 | key3 | value3 | 1 |
| 4 | key1 | valueX | 2 |
| 5 | key3 | valueY | 2 |
| 6 | key4 | valueZ | 2 |
how would I join/process the tables to get a table like this:
| ID | Name | key1 | key2 | key3 | key4 |
|----|-------|--------|--------|--------|--------|
| 1 | Item1 | value1 | value2 | value3 | NULL |
| 2 | Other | valueX | NULL | valueY | valueZ |
EDIT:
The keys would be variable irl.
In the real world I'm trying to implement a garden-database.
There I have a mapping table "dates" with important dates for the plants - e.g.
Table Dates(ID, Event, Date, PlantID[FK->Plants])
so if I add a Date with an event to a plant, and another plant has a date with the same event-name the event should be one column.
| Plant | planted | harvested | cut |
| Tree | 01/01/2018 | 01/09/2019| 02/09/2019 |
| Tree2 | 01/01/2017 | - | 02/09/2019 |
if I'd add a "harvested"-Date to Tree2 it should appear in column "harvested".
If I'd add a "newEvent"-Date to Tree2 there should be an additional column "newEvent" with NULL entry for Tree1.
…but I do not know yet, what possible events will be stored.
use conditional aggregation
select t1.id,name
max(case when t2.key='key1' then value end),
max(case when t2.key='key4' then value end),
max(case when t2.key='key2' then value end),
max(case when t2.key='key3' then value end)
from Items t1 join KeyValue t2 on t1.id=t2.ItemID
group by t1.id,name
select t1.ID, t1.Name,
max(case when t2.Kee = "key1" then t2.Value end) as key1,
max(case when t2.Kee = "key2" then t2.Value end) as key2,
max(case when t2.Kee = "key3" then t2.Value end) as key3,
max(case when t2.Kee = "key4" then t2.Value end) as key4
from Items t1
join KeyValue t2 on t1.ID = t2.ItemID
group by t1.ID, t1.Name

How to make 2 columns from one in one select in sqlite?

I've got one database with two columns (id and value). There are two types of values and each id has both of this values. How can I make a select to this database to have three columns in result (id, value1 and value2)
I've tried CASE and GROUP BY, but it shows only one result of each id
Example of a db:
| id | value |
| 0 | a |
| 0 | b |
| 1 | a |
| 1 | b |
Example of the result I am looking for is:
| id | value_a | value_b |
| 0 | a | b |
| 1 | a | b |
UPDATE:
As it was noted in comments, there is too simple data in the example.
The problem is more complicated
An example that would better describe it:
DB:
| id | value | value2 | value3 |
| 0 | a | a2 | a3 |
| 0 | b | b2 | b3 |
| 1 | a | c2 | c3 |
| 1 | b | d2 | d3 |
RESULT:
| id | value_a | value_b | value2_a | value2_b | value3_a | value3_b |
| 0 | a | b | a2 | b2 | a3 | b3 |
| 1 | a | b | c2 | d2 | c3 | d3 |
The output should be sorted by id an have all info from the both rows of each id.
If there are always two values per ID, you can try an aggregation using min() and max().
SELECT id,
min(value) value_a,
max(value) value_b
FROM elbat
GROUP BY id;
select t0.id,t0.Value as Value_A, t1.Value as Value_B
from test t0
inner join test t1 on t0.id = t1.id
where t0.Value = 'a' and t1.value = 'b';
I have used this method to turn "rows" into "columns". Depending on the number of unique values that exist in the table, you may or may not want to use this :)
SELECT id, SUM(CASE WHEN value = "a" then 1 else 0 END) value_a,
SUM(CASE WHEN value = "b" then 1 else 0 END) value_b,
SUM(CASE WHEN value = "c" then 1 else 0 END) value_c,
SUM(CASE WHEN value ="a2" then 1 else 0 END) value_a2,
.
.
.
FROM table
GROUP BY id;
Thanks all for the answers! This is the way how I did this:
WITH a_table AS
(
SELECT id, value, value2, value3 FROM table1 WHERE table1.value = 0
),
b_table AS
(
SELECT id, value, value2, value3 FROM table1 WHERE table1.value = 1
)
SELECT DISTINCT
a_table.id AS id,
a_table.value AS value_a,
a_table.value2 AS value2_a,
a_table.value3 AS value3_a,
b_table.value AS value_b,
b_table.value2 AS value2_b,
b_table.value3 AS value3_b
FROM a_table
JOIN b_table ON a_table.id = b_table.id
GROUP BY id;

Get column with two two rows having specific values

I have a table that looks like this:
| col1 | col2 |
|------|------|
| a | 1 |
| a | 2 |
| a | 3 |
| b | 1 |
| b | 3 |
| c | 1 |
| c | 2 |
I need to find the value of col1 where two rows with the same col1 value exist that has a col2 value of 1 and 2
results would be:
| col1 |
|------|
| a |
| c |
You can filter the rows with the col2 values you want, then group by col1 and only take the groups with count = 2
select col1
from yourTable
where col2 in (1, 2)
group by col1
having count(distinct col2) = 2
Another solution would be
select col1
from your_table
group by col1
having sum(case when col2 = 1 then 1 else 0 end) > 0
and sum(case when col2 = 2 then 1 else 0 end) > 0

Compare two dates from columns SQL

I am having some issues with getting this working. I have a table with this data in it.
| DateStarted | Field9 | Field2 | ID | Field6 |
----------------------------------------------------------------------------------------
| 2013-04-15 09:23:00 | TEST1 | TEST2 | 1 | 2000 |
| 2013-04-08 09:23:00 | TEST1 | TEST2 | 2 | 180 |
| 2013-04-15 09:23:00 | TEST2 | TEST3 | 3 | 1000 |
| 2013-04-04 09:23:00 | TEST2 | TEST3 | 7 | 80 |
| 2013-04-03 09:23:00 | TEST2 | TEST4 | 5 | 70 |
What my end goal is was to have the last two dates for the value for Field9 be returned so that I could subtract the value of Field6 for each unique instance of Field9. Below is an example of the return.
| DateStarted | Field1 | Field2 | ID | SUB |
----------------------------------------------------------------------------------------
| 2013-04-15 09:23:00 | TEST1 | TEST2 | 1 | 1820 |
| 2013-04-15 09:23:00 | TEST2 | TEST3 | 3 | 920 |
So for the second row it took the two greatest dates and then took the value of field6 and subtracted them returning just the one row.
You can get the latest row for each unique value of Field1 by using partitioned windowing functions.
;WITH x AS
(
SELECT DateStarted, Field9, Field2, ID, Field6,
rn = ROW_NUMBER() OVER (PARTITION BY Field9 ORDER BY DateStarted DESC)
FROM dbo.your_table_name
),
y AS
(
SELECT x.*, [SUB] = x.Field6 - COALESCE(y.Field6, 0)
FROM x LEFT OUTER JOIN x AS y
ON x.Field9 = y.Field9
AND x.rn = 1 AND y.rn = 2
)
SELECT DateStarted, Field1 = Field9, Field2, ID, [SUB]
FROM y
WHERE rn = 1
ORDER BY Field1;
SQL fiddle demo
One way to get the difference is to identify the two rows and then aggregate them together:
select MAX(case when seqnum = 1 then DateStarted end), Field1,
max(case when seqnum = 1 then Field2 end) as Field2
MAX(case when seqnum = 1 then id end) as Id,
MAX(case when seqnum = 1 then field3 end) - MAX(case when seqnum = 2 then field 3 end) as sub
from (SELECT DateStarted, Field1, Field2, ID, Field3,
ROW_NUMBER() OVER (PARTITION BY Field1 ORDER BY DateStarted DESC) as seqnum
FROM t
) t
group by Field1
This uses conditional aggregation to get the difference.