SQL Pivot String Data - sql

I have a tables in SQL Server: Product
Product Table:
ImageID ProductID
------- ----------
1 P1
1 P2
1 P3
2 S1
2 S2
2 S3
3 M1
This is the output that I need:
ImageID Product1ID Product2ID Product3ID
----------- ---------- ---------- ----------
1 P1 P2 P3
2 S1 S2 S3
3 M1 null null
An ImageID can have maximum 3 ProductID
It is not necessary that all ImageID will have 3 products [eg. ImageID=3]
SELECT ImageID, [Product1ID], [Product2ID], [Product3ID]
FROM
(
SELECT ImageID, ProductID
FROM ProductTable
) AS P
PIVOT
(
max( ImageID)
FOR ProductID IN ([Product1ID], [Product2ID], [Product3ID])
) AS PVT

I would just use conditional aggregation:
SELECT ImageID,
MAX(CASE WHEN seqnum = 1 THEN ProductID END) as Product1ID,
MAX(CASE WHEN seqnum = 2 THEN ProductID END) as Product2ID,
MAX(CASE WHEN seqnum = 3 THEN ProductID END) as Product3ID
FROM (SELET pt.*, ROW_NUMBER() OVER (PARTITION BY ImageId ORDER BY ProductID) as seqnum
FROM ProductTable
) P
GROUP BY ImageID;
The key idea, though, is to use ROW_NUMBER() to enumerate the products.

Your were very close, you just needed to incorporate Row_Number()
Example
Select *
From (
Select ImageID
,Item = concat('Product',row_number() over (partition by ImageID order by ProductID),'ID')
,ProductID
From ProductTable
) src
Pivot (max(ProductID) for Item in ([Product1ID], [Product2ID], [Product3ID])) pvt

Related

Insert records from multiple rows of table to multiple columns of other table

I have an existing table structure with sample data like
Id
BookingId
Value
TypeId
AddedTime
1
100
10
T1
2021-03-22 08:51:52.6333333
2
100
20
T2
2021-03-22 08:50:55.8133333
3
100
30
T3
2021-03-22 08:50:22.1033333
4
200
50
T1
2021-03-22 08:50:22.1033333
5
200
60
T2
2021-03-22 08:50:22.1000000
6
200
70
T3
2021-03-22 08:50:22.0800000
and now data model is changed and it becomes like
Id
BookingId
Type1Value
Type2Value
Type3Value
AddedTime
Please help me what would be query to copy data from previous table to new table.
Output should be something like
Id
BookingId
Type1Value
Type2Value
Type3Value
AddedTime
1
100
10
20
30
2
200
50
60
70
I tried:
select BookingId
, Type1Value = max(case when RN=1 then Value else null end)
, Type2Value = max(case when RN=2 then Value else null end)
, Type3Value = max(case when RN=3 then Value else null end)
from (
select *
, rn = Row_Number() over (Partition By TypeId Order by AddedTime)
from Values_M
) a
where rn <= 3
group by BookingId
This will gives you the required result using conditional case expression.
Using row_number() to generate new running number Id
select Id = row_number() over (order by BookingId),
BookingId = BookingId,
Type1Value = max(case when TypeId = 'T1' then Value end),
Type2Value = max(case when TypeId = 'T2' then Value end),
Type3Value = max(case when TypeId = 'T3' then Value end),
AddedTime = min(AddedTime)
from Values_M
group by BookingId
dbfiddle
select BookingId, min(T1) as Type1Value, min(T2) as Type2Value, min(T3) as Type3Value
from table1
pivot (sum(value) for Typeid in (T1,T2,T3)) as PivotTable
group by BookingId
You can use row_number() and self join.
with cte as (
select Id, BookingId, [Value], TypeId, AddedTime
, row_number() over (partition by BookingId order by id asc) rn
from Values_M
)
select C1.rn, C1.BookingId, C1.[Value] Type1Value, C2.[Value] Type2Value, C3.[Value] Type3Value, C1.AddedTime
from cte C1
inner join cte C2 on C2.BookingId = C1.BookingId and C2.rn = 2
inner join cte C3 on C3.BookingId = C1.BookingId and C3.rn = 3
where C1.rn = 1
order by BookingId asc;

SQL group by with NULL

I have a table something like this:
ID ProductID ProductName Price
== ========= =========== =====
1 XX1 TShirt 10
2 XX1 TShirt 10
3 NULL TShirt 10
4 XX2 Shirt 20
5 XX3 Shirt1 30
Now I want this to group by ProductName and results will be as follows
ID ProductID ProductName Price
== ========= =========== =====
1 XX1 TShirt 30
4 XX2 Shirt 20
5 XX3 Shirt1 30
Thanks
ProductID seems to be irrelevant for the group, so don't use it. To get all columns you could use a CTE and a ranking function like ROW_NUMBER:
WITH CTE AS(
SELECT ID,
ProductID,
ProductName,
Price = SUM(Price) OVER (PARTITION BY ProductName),
RN = ROW_NUMBER() OVER (PARTITION BY ProductName ORDER BY ID)
FROM dbo.TableName
)
SELECT CTE.* FROM CTE
WHERE RN = 1
If you want to take the row which contains the ProductID(where it is not NULL) modify the ORDER BY:
WITH CTE AS(
SELECT ID,
ProductID,
ProductName,
Price = SUM(Price) OVER (PARTITION BY ProductName),
RN = ROW_NUMBER() OVER (PARTITION BY ProductName
ORDER BY CASE WHEN ProductID IS NOT NULL
THEN 0 ELSE 1 END, ID)
FROM dbo.TableName
)
SELECT CTE.* FROM CTE
WHERE RN = 1

Update an "Order" column after deleting a row?

I have a ProductsImages table which includes ImageId, Productid ,Order :
ProductId ImageId Order
----------------------------
1 1 1
1 2 2
1 3 3
1 4 4
1 5 5
When I delete a row, let's say ProductId = 1 ImageId = 1 I need to update the order column :
ProductId ImageId Order
----------------------------
1 2 1
1 3 2
1 4 3
1 5 4
After you do the delete, you can do an update:
with toupdate as (
select pi.*,
row_number() over (partition by productId order by [order]) as neworder
from ProductImages pi
)
update toupdate
set [order] = neworder;
The above updates the whole table. If you just want to update the changed product, add a where clause:
with toupdate as (
select pi.*,
row_number() over (partition by productId order by [order]) as neworder
from ProductImages pi
where pi.ProductID = #ProductId
)
update toupdate
set [order] = neworder;

SQL Query Flattens Order and Top Order Items

I need help building a SQL query that returns a flattened result of the top 2 items in an order.
The tables and relevant fields are as follows:
Order OrderItem
------- -----------
orderId orderId
productCode
quantity
I'm looking for the desired result set:
[orderId] [productCode1] [quantity1] [productCode2] [quantity2]
---------- -------------- ----------- -------------- -----------
o123 p134 3 p947 1
o456 p384 2 p576 1
The results would be grouped by orderId from Order, with the TOP 2 productCode from quantity from OrderItem. I don't care which TOP 2 get returned, just need any two.
Any help would be greatly appreciated.
select
o.orderId,
max(case when row_num = 1 then oi.ProductCode end) as ProductCode1,
max(case when row_num = 1 then oi.Quantity end) as Quantity1,
max(case when row_num = 2 then oi.ProductCode end) as ProductCode2,
max(case when row_num = 2 then oi.Quantity end) as Quantity2
from Order as o
outer apply (
select top 2
oi.*, row_number() over (order by oi.productCode) as row_num
from OrderItem as oi
where oi.orderId = o.orderId
) as oi
group by o.orderId
You can do this with conditional aggregation and the window function row_number():
select orderId,
max(case when seqnum = 1 then ProductCode end) as ProductCode1,
max(case when seqnum = 1 then Quantity end) as Quantity1,
max(case when seqnum = 2 then ProductCode end) as ProductCode2,
max(case when seqnum = 2 then Quantity end) as Quantity2
from (select oi.*,
row_number() over (partition by orderId order by quantity desc) as seqnum
from OrderItem oi
) oi
group by orderId;
Assuming you are using SQL Server 2005 or later you can create a CTE ordered by ProductCode, then in a second CTE take the rows with rank 2 and last join them to Orders
;WITH OrderItem1 AS
(
SELECT OrderID, productCode AS productCode1, quantity AS quantity1,
ROW_NUMBER() OVER (PARTITION BY OrderID ORDER BY productCode) AS RowID
FROM [OrderItem]
),
OrderItem2 AS
(
SELECT OrderID, productCode1 AS productCode2, quantity1 AS quantity2
FROM [OrderItem1]
WHERE RowID = 2
)
SELECT [Order].OrderID, [OrderItem1].[productCode1], [OrderItem1].quantity1,
[OrderItem2].productCode2, [OrderItem2].quantity2
FROM [Order]
INNER JOIN OrderItem1 ON [Order].OrderID = OrderItem1.OrderID
LEFT JOIN OrderItem2 ON [Order].OrderID = OrderItem2.OrderID
WHERE OrderItem1.RowID = 1

MS SQL - One to Many Relationship - Need to return single row

I have the following tables -
Search Result
----------------
SearchResultID PK
ProductID FK
SearchQuery
WebsiteName
URL
IsFound
CreatedOn
BatchID
Name
SearchResultItem
-----------------
SearchResultItemID PK
SearchResultID FK
Name
Value
These tables have a one to many relationship, so one Search Result, can have many Search Result Items.
I can do an INNER JOIN on these tables however that obviously gives one row per each Search Result Item. Ideally I would like one row per Search Result, for example...
SearchResultID | ProductID | SearchQuery | WebsiteName | URL | IsFound |
CreatedOn | BatchID | Name | SearchResultItemID | Name 1 | Value 1 | Name 2 |
Value 2 | Name 3 | Value 3 |
Is this possible to do? And if so, can someone point me in the right direction as to how I would do this - I think it would be something like this, only in ms-sql - one to many sql select into single row - mysql
You can use the ROW_NUMBER() function to give each search result item a rank within each search result:
SELECT SearchResultItemID,
SearchResultID,
Name,
Value,
RowNumber = ROW_NUMBER() OVER(PARTITION BY SearchResultID ORDER BY SearchresultItemID)
FROM SearchResultItem;
If you have a know number of items then you can use aggregate functions to get each name/value pair:
WITH RankedItem AS
( SELECT SearchResultItemID,
SearchResultID,
Name,
Value,
RowNumber = ROW_NUMBER() OVER(PARTITION BY SearchResultID ORDER BY SearchresultItemID)
FROM SearchResultItem
)
SELECT SearchResultID,
Name1 = MIN(CASE WHEN RowNumber = 1 THEN Name END),
Value1 = MIN(CASE WHEN RowNumber = 1 then Value END),
Name2 = MIN(CASE WHEN RowNumber = 2 THEN Name END),
Value2 = MIN(CASE WHEN RowNumber = 2 then Value END),
Name3 = MIN(CASE WHEN RowNumber = 3 THEN Name END),
Value3 = MIN(CASE WHEN RowNumber = 3 then Value END),
Name4 = MIN(CASE WHEN RowNumber = 4 THEN Name END),
Value5 = MIN(CASE WHEN RowNumber = 4 then Value END)
FROM RankedItem
GROUP BY SearchResultID;
You can then join this back to your Search result table giving a full query:
WITH RankedItem AS
( SELECT SearchResultItemID,
SearchResultID,
Name,
Value,
RowNumber = ROW_NUMBER() OVER(PARTITION BY SearchResultID ORDER BY SearchresultItemID)
FROM SearchResultItem
), Items AS
( SELECT SearchResultID,
Name1 = MIN(CASE WHEN RowNumber = 1 THEN Name END),
Value1 = MIN(CASE WHEN RowNumber = 1 then Value END),
Name2 = MIN(CASE WHEN RowNumber = 2 THEN Name END),
Value2 = MIN(CASE WHEN RowNumber = 2 then Value END),
Name3 = MIN(CASE WHEN RowNumber = 3 THEN Name END),
Value3 = MIN(CASE WHEN RowNumber = 3 then Value END),
Name4 = MIN(CASE WHEN RowNumber = 4 THEN Name END),
Value4 = MIN(CASE WHEN RowNumber = 4 then Value END)
FROM RankedItem
GROUP BY SearchResultID
)
SELECT SearchResult.SearchResultID,
SearchResult.ProductID,
SearchResult.SearchQuery,
SearchResult.WebsiteName,
SearchResult.URL,
SearchResult.IsFound,
SearchResult.CreatedOn,
SearchResult.BatchID,
SearchResult.Name,
Items.Name1,
Items.Value1,
Items.Name2,
Items.Value2,
Items.Name3,
Items.Value3,
Items.Name4,
Items.Value4
FROM SearchResult
INNER JOIN Items
ON SearchResult.SearchResultID = Items.SearchResultID;
Example on SQL Fiddle
If you want to return a variable number of values then you will need to use dynamic SQL:
DECLARE #SQL NVARCHAR(MAX) = '';
SELECT #SQL = #SQL + ',[Name' + rn + '], [Value' + rn + '] '
FROM ( SELECT DISTINCT
rn = CAST(ROW_NUMBER() OVER(PARTITION BY SearchResultID ORDER BY SearchresultItemID) AS VARCHAR)
FROM SearchResultItem
) p;
SET #SQL = 'WITH RankedItem AS
( SELECT SearchResultItemID,
SearchResultID,
Name,
Value,
RowNumber = ROW_NUMBER() OVER(PARTITION BY SearchResultID ORDER BY SearchresultItemID)
FROM SearchResultItem
), UnPivoted AS
( SELECT upvt.SearchResultID,
Name = upvt.n + CAST(RowNumber AS VARCHAR),
upvt.v
FROM RankedItem
UNPIVOT
( n
FOR v IN ([Name], [Value])
) upvt
), Pivoted AS
( SELECT *
FROM UnPivoted
PIVOT
( MAX(V)
FOR Name IN (' + STUFF(#SQL, 1, 1, '') + ')
) pvt
)
SELECT SearchResult.SearchResultID,
SearchResult.ProductID,
SearchResult.SearchQuery,
SearchResult.WebsiteName,
SearchResult.URL,
SearchResult.IsFound,
SearchResult.CreatedOn,
SearchResult.BatchID,
SearchResult.Name' + #SQL + '
FROM SearchResult
INNER JOIN Pivoted
ON SearchResult.SearchResultID = Pivoted.SearchResultID;';
EXECUTE SP_EXECUTESQL #SQL;
Example on SQL Fiddle
N.B. I have intentionally used a different way of doing this in dynamic sql just to show there is more than one way to achieve the result of combining the rows.