SQL PIVOT without aggregate columns - sql

create table Product_Price
(
id int,
dt date,
SellerName varchar(20),
Product varchar(10),
ShippingTime varchar(20),
Price money
)
insert into Product_Price values (1, '2012-01-16','Sears','AA','2 days',32)
insert into Product_Price values (2, '2012-01-16','Amazon', 'AA','4 days', 40)
insert into Product_Price values (3, '2012-01-16','eBay','AA','1 days', 27)
insert into Product_Price values (4, '2012-01-16','Walmart','AA','Same day', 28)
insert into Product_Price values (5, '2012-01-16','Target', 'AA','3-4 days', 29)
insert into Product_Price values (6, '2012-01-16','Flipcart','AA',NULL, 30)
select *
from
(select dt, product, SellerName, sum(price) as price
from product_price group by dt, product, SellerName) t1
pivot (sum(price) for SellerName in ([amazon],[ebay]))as bob
)
I want 2 more columns in output (One is AmazonShippinTime another is eBayshippintime). How can I get these? Fiddle : http://sqlfiddle.com/#!3/2210d/1

Since you need to pivot on two columns and use different aggregates on both columns, I would use aggregate functions with a CASE expression to get the result:
select
dt,
product,
sum(case when SellerName = 'amazon' then price else 0 end) AmazonPrice,
max(case when SellerName = 'amazon' then ShippingTime end) AmazonShippingTime,
sum(case when SellerName = 'ebay' then price else 0 end) ebayPrice,
max(case when SellerName = 'ebay' then ShippingTime end) ebayShippingTime
from product_price
group by dt, product;
See SQL Fiddle with Demo. This gives a result:
| DT | PRODUCT | AMAZONPRICE | AMAZONSHIPPINGTIME | EBAYPRICE | EBAYSHIPPINGTIME |
|------------|---------|-------------|--------------------|-----------|------------------|
| 2012-01-16 | AA | 40 | 4 days | 27 | 1 days |

Related

Display only certain columns in results despite case statement in query

DB-Fiddle
CREATE TABLE inventory (
id SERIAL PRIMARY KEY,
product VARCHAR,
quantity DECIMAL,
avg_price DECIMAL,
normal_price DECIMAL
);
INSERT INTO inventory
(product, quantity, avg_price, normal_price)
VALUES
('product_01', '800', '10', '10'),
('product_01', '300', '20', '90'),
('product_01', '200', '0', '50'),
('product_01', '500', '30', '80'),
('product_01', '600', '0', '60'),
('product_01', '400', '50', '40');
Expected Result:
product | quantity | final_price |
-------------|--------------|----------------|--------------
product_01 | 800 | 10 |
product_02 | 300 | 20 |
product_03 | 200 | 50 |
product_04 | 500 | 30 |
product_05 | 600 | 60 |
product_06 | 400 | 50 |
I only want to display the column quantity and final_price.
However, I have to use a CASE statement in my query and the syntax from postgresSQL is forcing me to add the column avg_price and normal_price to the query in order to make the CASE statement work:
SELECT
iv.product AS product,
iv.avg_price AS avg_price,
iv.normal_price AS normal_price,
SUM(iv.quantity) AS quantity,
(CASE WHEN iv.avg_price = 0 THEN iv.normal_price ELSE iv.avg_price END) AS final_price
FROM inventory iv
GROUP BY 1,2,3
ORDER BY 1;
Not sure if this is possible in postgresSQL but is there a way to only display the two columns as in the expected result?
I would suggest aggregating by the expression itself:
SELECT iv.product AS product,
SUM(iv.quantity) AS quantity,
(CASE WHEN iv.avg_price = 0 THEN iv.normal_price ELSE iv.avg_price END) AS final_price
FROM inventory iv
GROUP BY iv.product, final_price
ORDER BY 1;
Use explicit GROUP BY iv.product, iv.avg_price, iv.normal_price instead of GROUP BY 1, 2, 3:
SELECT
iv.product AS product,
SUM(iv.quantity) AS quantity,
(CASE WHEN iv.avg_price = 0 THEN iv.normal_price ELSE iv.avg_price END) AS final_price
FROM inventory iv
GROUP BY iv.product, iv.avg_price, iv.normal_price
ORDER BY 1;

T-SQL: Rows to Columns

I am a SQL beginner, so can anyone please help me with this?
I have a table like this
YearMonth | Customer | Currency | BusinessType | Amount
04-2020 | 123 | EUR | Budget | 500
04-2020 | 123 | EUR | Forecast | 300
04-2020 | 123 | EUR | Sales | 700
And now I need it like:
YearMonth | Customer | Currency | Amount Budget | Amount Forecast | Amount Sales
04-2020 | 123 | EUR | 500 | 300 | 700
Is something like this possible?
Thanks in advance for your help!
Use conditional aggregation:
select yearmonth, customer, currency,
sum(case when businesstype = 'Budget' then amount end) as budget,
sum(case when businesstype = 'Forecast' then amount end) as forecast,
sum(case when businesstype = 'Sales' then amount end) as sales
from t
group by yearmonth, customer, currency;
You can do aggregation :
select yearmonth, customer, currency,
sum(case when businesstype = 'budget' then amount else 0 end),
sum(case when businesstype = 'Forecast' then amount else 0 end),
sum(case when businesstype = 'Sales' then amount else 0 end)
from table t
group by yearmonth, customer, currency;
Also, you may want to add an "Other" to capture any new Business Types:
select yearmonth, customer, currency,
sum(case when businesstype = 'Budget' then amount end) as budget,
sum(case when businesstype = 'Forecast' then amount end) as forecast,
sum(case when businesstype = 'Sales' then amount end) as sales,
sum(case when businesstype not in ('Budget', 'Forecast', 'Sales') then amount end) as other
from t
group by yearmonth, customer, currency;
This type of requirements are usually resolved using T-SQL PIVOT relational operation (https://learn.microsoft.com/en-us/sql/t-sql/queries/from-using-pivot-and-unpivot?view=sql-server-ver15). For the above example I used the following query to achieve this:
-- Create a temporary table to store the data.
IF (OBJECT_ID('tempdb..##temp2') IS NOT NULL) DROP TABLE ##temp2
CREATE TABLE ##temp2 (Id int IDENTITY (1,1) PRIMARY KEY, YearMonth varchar(100), Customer int, Currency varchar(100), BusinessType varchar(100), Amount int)
INSERT ##temp2
VALUES
('04-2020', 123,'EUR','Sales', 700),
('04-2020', 123,'EUR','Budget', 500),
('04-2020', 123,'EUR','Forecast', 300)
-- Using PIVOT allows you to move rows into columns
SELECT pivot_table.YearMonth,
pivot_table.Customer,
pivot_table.Currency,
[Amount Forecast] = pivot_table.Forecast,
[Amount Budget] = pivot_table.Budget,
[Amount Sales] = pivot_table.Sales
FROM
(
SELECT YearMonth,
Customer,
Currency,
BusinessType,
Amount
FROM ##temp2
) t
PIVOT
(
SUM(Amount)
FOR BusinessType IN ([Sales], [Budget], [Forecast])
) AS pivot_table;

Restore values in a time series table

I need to restore the most actual values from a time series table for the other values in specific time.
Let is say, that we have a table like that (I use SQL Server 2016), (this is pseudo-code, I did not check whether it works):
use sample
go
-- create time series table
drop table if exists dbo.PropertyHistory
go
create table dbo.PropertyHistory (
Id int
, Timestamp datetime
, Value int
)
go
-- fill dbo.PropertyHistory
insert into
dbo.PropertyHistory(Id, Timestamp, Value)
values
(1, '2019-01-01 12:00:00', 10)
, (1, '2019-01-01 13:00:00', 20)
, (2, '2019-01-01 13:00:00', 15)
, (3, '2019-01-01 14:00:00', 1)
, (4, '2019-01-01 15:00:00', 10)
, (1, '2019-01-01 16:00:00', 6)
, (4, '2019-01-01 17:00:00', 5)
, (2, '2019-01-01 17:00:00', 50)
, (2, '2019-01-01 19:00:00', 7)
, (1, '2019-01-01 19:00:00', 44)
go
I need to for example each row with the property id = 1 to have the last actual value (actual by datetime of course) of the property id = 2.
| Id | Timestamp | Value | Property2Value |
-------------------------------------------------------
| 1 | 2019-01-01 12:00:00 | 10 | NULL |
| 1 | 2019-01-01 13:00:00 | 20 | 15 |
| 1 | 2019-01-01 16:00:00 | 6 | 15 |
| 1 | 2019-01-01 19:00:00 | 44 | 7 |
-------------------------------------------------------
The ideas:
To create the function kinda of create function A (#propertyId int, #toDateTime datetime) which finds the latest row for the specified value restricted by the datetime. And then for each row with property id = 1 cross apply to this function. The performance is bad.
I think that it is possible to somehow use cumulative sum kinda of sum (case when PropertyId = 2 then Value else 0 end) over (order by Timestamp) but it will be cumulate more and more...
So, please help me to obtain the expected result.
If I understand correctly, this is a good use of apply:
select ph1.*, ph2.value as value2
from propertyhistory ph1 outer apply
(select top (1) ph2.*
from propertyhistory ph2
where ph2.id = 2 and ph2.timestamp <= ph1.timestamp
order by ph2.timestamp desc
) ph2
where ph1.id = 1;
Here is a db<>fiddle.
You can also do this with window functions, with the following logic:
For each row in the original data, get the most recent "2" timestamp.
Get the value for the "2" timestamp.
Filter down to just the "1"s
This looks like:
select ph.*
from (select ph.*,
max(case when ph.id = 2 then ph.value end) over (partition by timestamp_2) as value_2
from (select ph.*,
max(case when ph.id = 2 then ph.timestamp end) over (order by ph.timestamp) as timestamp_2
from propertyhistory ph
) ph
) ph
where id = 1;
We can handle this requirement by a judicious use of ROW_NUMBER, combined with some pivoting logic:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY Id ORDER BY Timestamp DESC) rn
FROM dbo.PropertyHistory
)
SELECT
1 AS Id,
MAX(CASE WHEN Id = 1 THEN Timestamp END) AS Timestamp,
MAX(CASE WHEN Id = 1 THEN Value END) AS Value,
MAX(CASE WHEN Id = 2 THEN Value END) AS Property2Value
FROM cte
GROUP BY
rn
ORDER BY
MAX(CASE WHEN Id = 1 THEN Timestamp END);
Demo
The idea here is to compute a row number label for each record, numbered separately for each Id value. Then, we can aggregate by the row number, which brings the Id values from 1 and 2 into line, in a single record.

Using Pivot for multiple columns - SQL Server

I have these tables:
http://sqlfiddle.com/#!18/b871d/8
create table ItemOrder
(
ID int,
ItemNumber int,
Qty int,
Price int,
Cost int,
DateSold datetime
)
insert into ItemOrder (ID, ItemNumber, Qty, Price, Cost, DateSold)
Values
('1', '145', '5', '50', '25', '08-06-18'),
('2', '145', '5', '50', '25', '07-04-18'),
('3', '145', '5', '50', '25', '06-06-18')
Result:
| ID | ItemNumber | DateSold | Qty | Price | Cost |
|----|------------|----------------------|-----|-------|------|
| 1 | 145 | 2018-08-06T00:00:00Z | 5 | 50 | 25 |
| 2 | 145 | 2018-07-04T00:00:00Z | 5 | 50 | 25 |
| 3 | 145 | 2018-06-06T00:00:00Z | 5 | 50 | 25 |
But i was looking for a result that was split out by month like:
e.g.
| ID | ItemNumber | Aug-18 Qty | Aug-18 Price | Aug-18 Cost |July-18 Qty|July-18 Price|
|----|------------|------------|--------------|-------------|
| 1 | 145 | 5 | 50 | 25 |
and so on....
select
ID,
ItemNumber,
DateSold,
(
select ID, ItemNumber, Qty, DateSold
from ItemOrder
) x
PIVOT
(
SUM(QTY), SUM(Price), SUM(Cost) FOR DateSold in(DateSold1)
) p;
I have tried a couple of queries but cant seem to get it right. It would be great for any guidance. Thanks
I would suggest simply doing conditional aggregation:
select id, itemnumber,
sum(case when datesold >= '2018-08-01' and datesold < '2018-09-01' then qty else 0 end) as qty_201808,
sum(case when datesold >= '2018-08-01' and datesold < '2018-09-01' then price else 0 end) as price_201808,
sum(case when datesold >= '2018-07-01' and datesold < '2018-08-01' then qty else 0 end) as qty_201807,
sum(case when datesold >= '2018-07-01' and datesold < '2018-08-01' then price else 0 end) as price_201807
from itemorder
group by id, itemnumber
order by id, itemnumber;
Here is a SQL Fiddle.
WITH Table1 AS
(
select
ID,
ItemNumber,
CAST(year(DateSold) AS VARCHAR(4)) + ' ' + DATENAME(m, DateSold) AS [DateSold2],
Qty
from ItemOrder
)
select * from Table1
pivot (sum(Qty) for[DateSold2] IN ([2018 August], [2018 July], [2018 June])) as d
More what i was looking for :)
http://sqlfiddle.com/#!18/b871d/23

Three column SQL PIVOT

How do I do a sql pivot of data that looks like this, USING the SQL PIVOT command ?
id | field | value
---------------------------------------
1 | year | 2011
1 | month | August
2 | year | 2009
1 | day | 21
2 | day | 31
2 | month | July
3 | year | 2010
3 | month | January
3 | day | NULL
Into something that looks like this:
id | year | month | day
-----------------------------
1 2011 August 21
2 2010 July 31
3 2009 January NULL
Try something like this:
DECLARE #myTable AS TABLE([ID] INT, [Field] VARCHAR(20), [Value] VARCHAR(20))
INSERT INTO #myTable VALUES ('1', 'year', '2011')
INSERT INTO #myTable VALUES ('1', 'month', 'August')
INSERT INTO #myTable VALUES ('2', 'year', '2009')
INSERT INTO #myTable VALUES ('1', 'day', '21')
INSERT INTO #myTable VALUES ('2', 'day', '31')
INSERT INTO #myTable VALUES ('2', 'month', 'July')
INSERT INTO #myTable VALUES ('3', 'year', '2010')
INSERT INTO #myTable VALUES ('3', 'month', 'January')
INSERT INTO #myTable VALUES ('3', 'day', NULL)
SELECT [ID], [year], [month], [day]
FROM
(
SELECT [ID], [Field], [Value] FROM #myTable
) t
PIVOT
(
MIN([Value]) FOR [Field] IN ([year], [month], [day])
) AS pvt
ORDER BY pvt.[year] DESC
Which will yield results of:
ID year month day
1 2011 August 21
3 2010 January NULL
2 2009 July 31
;WITH DATA(id,field,value) AS
(
SELECT 1,'year','2011' UNION ALL
SELECT 1,'month','August' UNION ALL
SELECT 2,'year','2009' UNION ALL
SELECT 1,'day ','21' UNION ALL
SELECT 2,'day ','31' UNION ALL
SELECT 2,'month','July' UNION ALL
SELECT 3,'year','2010' UNION ALL
SELECT 3,'month','January' UNION ALL
SELECT 3,'day ',NULL
)
SELECT id,
year,
month,
day
FROM DATA PIVOT (MAX(value) FOR field IN ([year], [month], [day])) AS Pvt
SELECT
id,
MAX(CASE WHEN RK=3 THEN VAL ELSE '' END) AS "YEAR",
MAX(CASE WHEN RK=2 THEN VAL ELSE '' END) AS "MONTH",
MAX(CASE WHEN RK=1 THEN VAL ELSE '' END) AS "DAY"
FROM
(
SELect
ID,
ROW_NUMBER() OVER(PARTITION BY ID ORDER BY YEAR1 ASC) RK,
VAL
FROM TEST3)A
GROUP BY 1
ORDER BY 1;