SQL convert rows to columns and flatten - sql

I have been looking for the solution to this in PIVOT, UNPIVOT, and others but still don't see my scenario. I have items in a table. For simplicity we'll just say PartNum, Desc. These things can be customized. The attributes like color, height, width, depth are stored in a separate table with a code to indicate which attribute.
OrderId - PartNum - Desc (join from inv)
1 12345 - Block A
2 12345 - Block A
3 23456 - Block B
4 23456 - Block B
Two customers get 12345, and two get 23456 and they have width, height, and depth...
AttrId - OrderId - CCode - Value
1 1 WIDTH 10
2 1 HEIGHT 10
3 1 DEPTH 1
4 2 WIDTH 20
5 2 HEIGHT 10
6 2 DEPTH 1
7 3 WIDTH 10
8 3 HEIGHT 20
9 3 DEPTH 2
10 4 WIDTH 10
11 4 HEIGHT 20
12 4 DEPTH 2
I can't use pivot with an aggregate on the value because I need to group each combination of part, width, height, and depth like this
PartNum - Width - Height - Depth - Count - Area (w x h x count)
12345 10 10 1 1 100
12345 20 10 1 1 200
23456 10 20 2 2 400
I tried case statements with the CCode but I get null values in some rows so the grouping didn't work. This is in SQL Server 2019 if that makes a difference. Can someone help out with this?

Is this what you want?
select t1.partnum, t2.width, t2.height, t2.depth, count(*) as cnt
from t1 join
(select t2.orderid,
sum(case when ccode = 'width' then value end) as width,
sum(case when ccode = 'height' then value end) as height,
sum(case when ccode = 'depth' then value end) as depth
from t2
group by t2.orderid
) t2
on t2.orderid = t1.orderid
group by t1.partnum, t2.width, t2.height, t2.depth;
I might speculate that you want:
sum(t2.width * t2.height * t2.depth) as area
but the numbers disagree with the values in your question.
Here is a db<>fiddle.

Related

Finding Duplicates in a single column and return all other column data

I have a table with data that I would like to find any rows with a duplication Position # (one of my columns in the data).
I have written some code that is working but it does not allow me to see the additional Column/header information.
Data Table:
MainItem
BomLevel
Position
ComponentItem
CompDesc
TotalQty
316006
1
10
500006
Conv Kit
1
316006
1
20
562060
Battery
4
316006
1
30
VS147
Charger
1
316006
1
40
9970
Red Pad
1
316006
1
60
563844
Blue Pad
1
316006
1
60
512346
Machine
1
I would like to return:
MainItem
BomLevel
Position
ComponentItem
CompDesc
TotalQty
316006
1
60
563844
Blue Pad
1
316006
1
60
512346
Machine
1
This is the code I currently know how to write:
select
a.MainItem
, a.BomLevel
, a.Position
from reports.v_bom a
where a.MainItem = '316006'
group by a.MainItem, a.BomLevel, a.Position
having Count (*) > 1
but this will only return:
MainItem
BomLevel
Position
316006
1
60
As you've only tagged SQL the following is ANSI SQL and will work in most modern RDBMS that support analytic window functions:
with c as (
select *, Count(*) over(partition by mainitem, bomlevel, position) cnt
from t
)
select *
from c
where cnt > 1;

SQL compare avg for ProductID with exact value

Assume I have such table
OrderLineID OrderID ProductID OrderedQunatity
1 1 2 18
2 1 10 9
3 2 3 12
4 2 8 2
5 2 14 2
7 4 3 1
8 4 5 3
9 5 6 2
15 4 4 0
What I would like to do is compare every single OrderedQuantity with average orderedQuantity for exact product.
For example OrderedQuantity for OrderID = 2 and ProductID = 3 is equal to 12,
so I check average OrderedQuantity for ProductID = 3, so (12+1)/2 = 6.5 and if it is smaller than exact orderQuantity (in this example 12) I select it.
Can someone help what should i type in SELECT?
Thank you!
One method is a correlated subquery:
select t.*
from t
where t.OrderedQuantity < (select avg(t2.OrderedQuantity)
from t t2
where t2.ProductID
);
Note that some databases do integer averages of integers. So you might need avg(t2.OrderedQuantity * 1.0) to get all values less than the average.

Assign Unique Group Id To Sets of Rows with Same Column Value Separated by Other value

I have some data that looks like this:
uid radius
1 10
2 10
3 10
4 2
5 4
6 10
7 10
8 10
What I want is for each group which has the same radius value to have its own unique id, for example:
uid radius GroupdId
1 10 1
2 10 1
3 10 1
4 2 2
5 4 3
6 10 4
7 10 4
8 10 4
What I don't want is the second group with radius 10 to have the same groupid as the first group (not 1).
I'm working on SQL Server but the solution should be the same across all databases.
(I've done this before, but for the life of me, I can't remember how I did it.)
Try this:
with t as
(
select
uid,
radius,
lag(radius,1) over (order by uid) as prev_rad
from
radtable
)
select
uid,
radius,
sum
(
case when radius = coalesce(prev_rad,radius) then 0 else 1 end
)
over
(
order by uid
) + 1 as GroupID
from
t

Retrieve Result from comparing multiple colums in a single table

FID RP Area Count
1 100 0.780 1
2 100 0.906 2
2 500 0.094 2
3 100 1.000 1
4 100 1.000 1
5 100 0.784 2
5 500 0.916 2
6 100 0.332 3
6 500 0.780 3
6 555 0.643 3
In the above table, i want to retrieve the columns where Area>0.4. This will retrieve 8 rows. But i want answer in other way.
Look at Case where FID =5. In this, the area of RP 100 and 500 satisfy the criteria, but the output should be given high weigtage for RP =100. For the case where FID =6, RP=100 did not satisfy the criteria, but RP=500 and RP=555 satisfies the criteria. I want the weigtage to be given to RP=500.
Required Result:
FID RP Area Count
1 100 0.78007 1
2 100 0.90626 2
3 100 1 1
4 100 1 1
5 100 0.7835 2
6 500 0.78 3
So, you want the first row for each id where the value of Area exceeds 0.4 and "first" is ordered by RP.
Window function provide the mechanism to do this. Most databases support row_number():
select FID, RP, Area, "Count"
from (select t.*,
row_number() over (partition by fid order by rp) as seqnum
from t
where Area > 0.4
) t
where seqnum = 1;
The subquery filters the rows so only rows with valid values of Area are included. The row_number() function assigns sequential values to the rows within an fid (because of the partition by clause). The values are assigned in order by rp (due to the order by clause).

TSQL - divide rows into groups based on one field

This is modified version of my earlier question: TSQL equally divide resultset to groups and update them
I have my database with 2 tables like so:
Orders table has data like below:
OrderID OperatorID GroupID OrderDesc Status Cash ...
--------------------------------------------------------------------------
1 1 1 small_order 1 300
2 1 1 another_order 1 0
3 1 2 xxxxxxxxxxx 2 1000
5 2 2 yyyyyyyyyyy 2 150
9 5 1 xxxxxxxxxxx 1 50
10 NULL 2 xxxxxxxxxxx 1 150
11 NULL 3 xxxxxxxxxxx 1 -50
12 4 1 xxxxxxxxxxx 1 200
Operators table:
OperatorID Name GroupID Active
---------------------------------------
1 John 1 1
2 Kate 1 1
4 Jack 2 1
5 Will 1 0
6 Sam 3 0
I'm able to equally divide my recordset into equally groups using below query:
SELECT o.*, op.operatorName AS NewOperator, op.operatorID AS NewOperatorId
FROM (SELECT o.*, (ROW_NUMBER() over (ORDER BY newid()) % numoperators) + 1 AS randseqnum
FROM Orders o CROSS JOIN
(SELECT COUNT(*) AS numoperators FROM operators WHERE operators.active=1) op
WHERE o.status in (1,3)
) o JOIN
(SELECT op.*, ROW_NUMBER() over (ORDER BY newid()) AS seqnum
FROM Operators op WHERE op.active=1
) op
ON o.randseqnum = op.seqnum ORDER BY o.orderID
Demo available at: http://sqlfiddle.com/#!3/ff47b/1
Using script from above I can divide Orders to (almost) equal groups but based on number or Orders for Operator, but I need to modify it so that it will assign Operators to Orders based on sum or Cash for orders.
For example:
If I have 6 Orders with Cash values: 300, 0, 50, 150, -50, 200 they sum gives 650.
My script should assign to 3 Operators random 2 Orders with random sum of Cash for Orders.
What I would like to get is to assign for example 300,-50 to operator1, 200, 0 to second and 150, 50 to third.
Hope this sound clear :)
Here is example output that I expect to get:
ORDERID OPERATORID GROUPID DESCRIPTION STATUS CASH NEWOPERATORID
------------------------------------------------------------------------
1 1 1 small_order 1 300 2
2 1 1 another_order 1 0 1
9 5 1 xxxxxxxxxxx 1 50 4
10 (null) 2 xxxxxxxxxxx 1 150 4
11 (null) 3 xxxxxxxxxxx 1 -50 2
12 4 1 xxxxxxxxxxx 1 200 1
How can I (if I can at all) assign Operators to my Orders so that sum or Cash will be closest to average
If I'm understanding this right, could you get the result you want by ordering the Cash column by the biggest, then the smallest, then the next biggest, then the next smallest, etc. Like this:
ROW_NUMBER() over (order by CASE WHEN CashOrder % 2 = 1 then Cash else -Cash END) as splitCash
where you've provided CashOrder lower in the query with
ROW_NUMBER() over (ORDER by CASH) as CashOrder
Then you specify each of your operators depending on this split value, ie (for three operators):
splitCash%3 +1