SQL - Transpose different rows in columns - sql

I have 1 table (I can not modify it before) from a bulk insert, and I need to transpose all the rows in "VALUES" column into single columns. Except for the "YEAR" (going from 0 to 50) and the "VALUES" columns, the other 3 columns are filled with a unique value (example: in year 0 size is 'L', color is 'red' and price is '10' and this size,color and price values are constant for all my 50 years).
The input table is like this:
I would like to have as output all the "VALUES" in columns called for example "VALUE_0" "VALUE_1" "VALUE_2" "VALUE_3" etc, where the numbers 0,1,2,3 stand for the year considered.
CREATE TABLE #CURVE(
YEAR INT,
SIZE VARCHAR(100),
COLOR VARCHAR(100),
PRICE VARCHAR(100),
VALUES FLOAT
)
The Output should be:

One option uses conditional aggregation:
select
size,
color,
price,
max(case when year = 0 then value end) value_0,
max(case when year = 1 then value end) value_1,
max(case when year = 2 then value end) value_2,
max(case when year = 3 then value end) value_3,
max(case when year = 4 then value end) value_4,
max(case when year = 5 then value end) value_5
from #curve
group by size, color, price
You can easily extend the select clause to handle more years.
This is a cross-database solution, that usually performs as good or better than vendor-specific implementations of pivot. On top of that (and for what it's worth), I find it easier to understand.

we need to know how many values do you expect in Values column for a year?
Otherwise the query looks like this:
SELECT [YEAR], [SIZE], [COLOR], [PRICE], [1] as [Value1], [2] as [Value2], [3] as [Value3]
FROM (
SELECT *, ROW_NUMBER() OVER(PARTITION BY [YEAR], [SIZE], [COLOR], [PRICE] ORDER BY (SELECT NULL)) ID
FROM #CURVE c
) t
PIVOT(MAX([Value]) FOR ID IN ([1], [2], [3]])) p

Related

How do I take the values of one record in a table and split it into multiple columns?

I have two tables which contain the following fields I need to use:
Master Data: Master_ID (PK)
Item Data: Crate_ID, Master_ID (FK), Item_Type_ID, Item_Type_Description, Item_Date
The Item_Type_ID has several different numerical values, i.e. 10, 20, 30, 40, 50 ... 100 ... etc.
Each numerical value represents a type, i.e. Veggie, Fruit, Grains, Meat, etc.
The Item_Type_Description are things like: Fruit, Veggies, Grains, Meat, etc.
The Item_Date is a single date that identifies when that particular item (based upon Item_ID) was added to the Crate.
Note that there can only ever be one unique Item_Type_ID per Master_ID. Meaning, Item_Type_ID '10' can only ever be related to Master_ID '1234' once. An Item_Type_ID can be related to many different Master_IDs, but each of those Master_IDs, it can only be related once.
The issue I am having is that I can get the combined results, but for each Item_Type_ID, a distinct record/row is being created.
Here is the code I have generated thus far, which is giving me the incorrect Results:
USE Shipping
GO
BEGIN
SELECT
vmi.master_id
,CASE
WHEN vid.item_type_id = 10 THEN vid_item_date
ELSE NULL
END as 'Fruit_Item_Date'
,CASE
WHEN vid.item_type_id = 20 THEN vid_item_date
ELSE NULL
END as 'Veggie_Item_Date'
,CASE
WHEN vid.item_type_id = 30 THEN vid_item_date
ELSE NULL
END as 'Grains_Item_Date'
,CASE
WHEN vid.item_type_id = 40 THEN vid_item_date
ELSE NULL
END as 'Meat_Item_Date'
FROM v_master_data vmi
LEFT JOIN v_item_data vid ON vmi.master_id = vid.master_id
WHERE vid.item_type_id IN (10,20,30,40)
END
GO
Any input, pointers, assistance, direction, advice, is greatly appreciated.
Running SQL Server 2016, accessed via SQL Server Management Studio v18.
Perhaps this will give you a little nudge
PIVOT
Select Master_ID
,Fruit_Date = [10]
,Veggie_Date = [20]
,Grains_Date = [30]
,Meat_Date = [40]
From (
Select Master_ID
,Item_Type_ID
,Item_Date
From YourTable
) src
Pivot ( max(Item_Date) for Item_Type_ID in ( [10],[20],[30],[40] ) ) pvt
Conditional Aggregation
Select Master_ID
,Fruit_Date = max( case when Iten_Type_ID =10 then Item_Date end)
,Veggie_Date = max( case when Iten_Type_ID =20 then Item_Date end)
,Grains_Date = max( case when Iten_Type_ID =30 then Item_Date end)
,Meat_Date = max( case when Iten_Type_ID =40 then Item_Date end)
From YourTable
Group By Master_ID
A conditional aggregation offers a bit more flexibility and is often more performant.

How to split values for the same id into columns

How to split values for the same id into columns?
Example Table
I want to achieve this:
I try:
SUM (CASE WHEN Type = 1 THEN Price END) AS Type_1
SUM (CASE WHEN Type = 2 THEN Price END) AS Type_2
SUM (CASE WHEN Type = 3 THEN Price END) AS Type_3
--SUM (CASE WHEN Type = 1 THEN CarsID END) AS CarsID_Type1
--SUM (CASE WHEN Type = 2 THEN CarsID END) AS CarsID_Type2
--SUM (CASE WHEN Type = 3 THEN CarsID END) AS CarsID_Type3
GROUP BY ID
3 additional columns (Type_1, Type_2, Type_3) are correctly created and everything is in one line. Unfortunately, the last commented out part causes an error:
Operand data type uniqueidentifier is invalid for sum operator.
What to replace with SUM to make the query run correctly.
I will be grateful for your help.
This query is more complex then I thought, but it will do what you want for any kind of id. But there is a problem, it was construct to work with the max of 3 different "types". If your column "Type" have more then 3 different values for the same "id" you will need to adapt the code below.
/*Shifting the value to another column*/
with first_lag as (
select
id
, type_
, case when count(type_)over(partition by id) > 1 then lag(type_)over(order by type_ desc)
end as lag_type_1
, CarsID
, case when count(CarsID)over(partition by id) > 1 then lag(CarsID)over(order by CarsID desc )
end as lag_cars_1
, price::int
from stack_overflow so
)
/*Shifting again the value to another column*/
, second_lag as(
select
id
, type_
, lag_type_1
, lag(lag_type_1)over(order by lag_type_1 desc) lag_type_2
, CarsID
, lag_cars_1
, lag(lag_cars_1)over(order by lag_cars_1 desc) lag_cars_2
, sum(price) over(partition by id) as price_by_id
from first_lag
)
/*Counting how many "types" the same id have (preparing to filter)*/
, counting_rows as (
select
*
, count(type_)over(partition by type_) +count(lag_type_1) over(partition by type_) +count(lag_type_2) over(partition by type_) as counting
from second_lag
)
/*Knowing which row have the max number of "types" (preparing to filter)*/
, selecting_max as (
select
*
,max(counting)over(partition by id) as max_flag
from counting_rows
)
/*Selecting the columns and filtering just the row with the max number of "types"*/
select id,type_,lag_type_1,lag_type_2,carsid,lag_cars_1,lag_cars_2,price_by_id
from selecting_max
where counting = max_flag
--group by id
Note it will work for different ids. (But only if it has 3 or less different"types")
Image

flatten data in SQL based on fixed set of column

I am stuck with a specific scenario of flattening the data and need help for it. I need the output as flattened data where the column values are not fixed. Due to this I want to restrict the output to fixed set of columns.
Given Table 'test_table'
ID
Name
Property
1
C1
xxx
2
C2
xyz
2
C3
zz
The scenario is, column Name can have any no. of values corresponding to an ID. I need to flatten the data based in such a way that there is one row per ID field. Since the Name field varies with each ID, I want to flatten it for fix 3 columns like Co1, Co2, Co3. The output should look like
ID
Co1
Co1_Property
Co2
Co2_Property
Co3
Co3_Property
1
C1
xxx
null
null
2
C2
xyz
C3
zz
Could not think of a solution using Pivot or aggregation. Any help would be appreciated.
You can use arrays:
select id,
array_agg(name order by name)[safe_ordinal(1)] as name_1,
array_agg(property order by name)[safe_ordinal(1)] as property_1,
array_agg(name order by name)[safe_ordinal(2)] as name_2,
array_agg(property order by name)[safe_ordinal(2)] as property_2,
array_agg(name order by name)[safe_ordinal(3)] as name_3,
array_agg(property order by name)[safe_ordinal(3)] as property_3
from t
group by id;
All current answers are too verbose and involve heavy repetition of same fragments of code again and again and if you need to account more columns you need to copy paste and add more lines which will make it even more verbose!
My preference is to avoid such type of coding and rather use something more generic as in below example
select * from (
select *, row_number() over(partition by id) col
from `project.dataset.table`)
pivot (max(name) as name, max(property) as property for col in (1, 2, 3))
If applied to sample data in your question - output is
If you want to change number of output columns - you just simply modify for col in (1, 2, 3) part of query.
For example if you would wanted to have 5 columns - you would use for col in (1, 2, 3, 4, 5) - that simple!!!
The standard practice is to use conditional aggregation. That is, to use CASE expressions to pick which row goes to which column, then MAX() to collapse multiple rows into individual rows...
SELECT
id,
MAX(CASE WHEN name = 'C1' THEN name END) AS co1,
MAX(CASE WHEN name = 'C1' THEN property END) AS co1_property,
MAX(CASE WHEN name = 'C2' THEN name END) AS co2,
MAX(CASE WHEN name = 'C2' THEN property END) AS co2_property,
MAX(CASE WHEN name = 'C3' THEN name END) AS co3,
MAX(CASE WHEN name = 'C3' THEN property END) AS co3_property
FROM
yourTable
GROUP BY
id
Background info:
Not having an ELSE in the CASE expression implicitly means ELSE NULL
The intention is therefore for each column to recieve NULL from every input row, except for the row being pivoted into that column
Aggregates, such as MAX() essentially skip NULL values
MAX( {NULL,NULL,'xxx',NULL,NULL} ) therefore equals 'xxx'
A similar approach "bunches" the values to the left (so that NULL values always only appears to the right...)
That approach first uses row_number() to give each row a value corresponding to which column you want to put that row in to..
WITH
sorted AS
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY name) AS seq_num
FROM
yourTable
)
SELECT
id,
MAX(CASE WHEN seq_num = 1 THEN name END) AS co1,
MAX(CASE WHEN seq_num = 1 THEN property END) AS co1_property,
MAX(CASE WHEN seq_num = 2 THEN name END) AS co2,
MAX(CASE WHEN seq_num = 2 THEN property END) AS co2_property,
MAX(CASE WHEN seq_num = 3 THEN name END) AS co3,
MAX(CASE WHEN seq_num = 3 THEN property END) AS co3_property
FROM
yourTable
GROUP BY
id

pivot table returns more than 1 row for the same ID

I have a sql code which I am using to do pivot. Code is as follows:
SELECT DISTINCT PersonID
,MAX(pivotColumn1)
,MAX(pivotColumn2) --originally these were in 2 separate rows)
FROM(SELECT srcID, PersonID, detailCode, detailValue) FROM src) AS SrcTbl
PIVOT(MAX(detailValue) FOR detailCode IN ([pivotColumn1],[pivotColumn2])) pvt
GROUP BY PersonID
In the source data the ID has 2 separate rows due to having its own ID which separates the values. I have now pivoted it and its still giving me 2 separate rows for the ID even though i grouped it and used aggregation on the pivot columns. Ay idea whats wrong with the code?
So I have all my possible detailCode listed in the IN clause. So I have null returned when the value is none but I want it all summarised in 1 row. See image below.
If those are all the options of detailCode , you can use conditional aggregation with CASE EXPRESSION instead of Pivot:
SELECT t.personID,
MAX(CASE WHEN t.detailCode = 'cas' then t.detailValue END) as cas,
MAX(CASE WHEN t.detailCode = 'buy' then t.detailValue END) as buy,
MAX(CASE WHEN t.detailCode = 'sel' then t.detailValue END) as sel,
MAX(CASE WHEN t.detailCode = 'pla' then t.detailValue END) as pla
FROM YourTable t
GROUP BY t.personID

Many rows to one row result

I hope I'm explaining this well. I'm struggling with this query:
I have this table that is something like this:
InvoiceNum
Amount
Type - where type could be item, shipping or tax.
So what I want returned is one row per invoice: InvoiceNum, ItemAmount, ShippingAmount, TaxAmount.
Here's an example:
Invoicenum Amount Type
1 $32 Item
1 $2 Shipping
1 $1 Tax
I would want returned:
InvoiceNum ItemAmount ShippingAmount TaxAmount
1 $32 $2 $1
You can summarize rows with group by, and you can select specific rows using case:
select InvoiceNum
, sum(case when Type = 'Item' then Amount end) as ItemAmount
, sum(case when Type = 'Shipping' then Amount end) as ShippingAmount
, sum(case when Type = 'Tax' then Amount end) as TaxAmount
from YourTable
group by
InvoiceNum
The case statement returns null by default, and sum ignores nulls.
You can do this with group by and sum tricks (max works too) as #Andomar shows.
Alternatively, Microsoft SQL Server supports syntax for a PIVOT operation that helps a bit in this type of query. You still have to hard-code the column names though.
SELECT InvoiceNum, [Item] AS ItemAmount, [Shipping] AS ShippingAmount, [Tax] AS TaxAmount
FROM
(SELECT InvoiceNum, Amount, Type FROM InvoiceTable ) i
PIVOT
(
MAX(Amount)
FOR Type IN ([Item], [Shipping], [Tax])
) AS pvt;