How to aggregate values from different rows in sql (HANA)? - sql

I have a table of shipments defined like so (the table is stored in a HANA database, if relevant):
CREATE COLUMN TABLE SHIPMENTS (
ShipmentID INT PRIMARY KEY,
Received INT,
Facility NVARCHAR(10),
Item NVARCHAR(20)
);
Here, the 'Received' column denotes the point in time at which each shipment is received, Facility is where the shipment is received and Item is the content of the shipment.
I have filled it with data like so:
INSERT INTO SHIPMENTS VALUES (1, 0, 'Factory', 'Production machine');
INSERT INTO SHIPMENTS VALUES (2, 0, 'Office', 'Printer');
INSERT INTO SHIPMENTS VALUES (3, 0, 'Factory', 'Coffee maker');
INSERT INTO SHIPMENTS VALUES (4, 1, 'Office', 'Coffee maker');
INSERT INTO SHIPMENTS VALUES (5, 1, 'Factory', 'Fax Machine');
INSERT INTO SHIPMENTS VALUES (6, 2, 'Office', 'Computers');
INSERT INTO SHIPMENTS VALUES (7, 2, 'Factory', 'Fridge');
INSERT INTO SHIPMENTS VALUES (8, 2, 'Factory', 'Freezer');
INSERT INTO SHIPMENTS VALUES (9, 2, 'Office', 'Fax Machine');
I would like to query the database to find, at each point in time, which items have been received up until that point. Based on an answer from another thread, I start by doing this:
SELECT Facility, Received, STRING_AGG (Item, ';') as Items
FROM (
SELECT * FROM SHIPMENTS
ORDER BY Facility, Received
)
GROUP BY Facility, Received
ORDER BY Facility, Received;
which results in
| FACILITY | RECEIVED | ITEMS
---------------------------------------------------------
1 | Factory | 0 | Production Machine;Coffee maker
2 | Factory | 1 | Fax Machine
3 | Factory | 2 | Fridge;Freezer
4 | Office | 0 | Printer
5 | Office | 1 | Coffee maker
6 | Office | 2 | Computers;Fax Machine
However, I would like this
| FACILITY | RECEIVED | ITEMS
---------------------------------------------------------
1 | Factory | 0 | Production Machine;Coffee maker
2 | Factory | 1 | Production Machine;Coffee maker;Fax Machine
3 | Factory | 2 | Production Machine;Coffee maker;Fax Machine;Fridge;Freezer
4 | Office | 0 | Printer
5 | Office | 1 | Printer;Coffee maker
6 | Office | 2 | Printer;Coffee maker;Computers;Fax Machine
I.e, each row displays what is received at that point, and everything that has already been received. Is there a nice way to do this in SQL?

You can try using a correlated query in the select clause to generate the csv data you want:
SELECT
Facility,
Received,
(SELECT STRING_AGG (s2.Item, ';') FROM SHIPMENTS s2
WHERE s2.Facility = s1.Facility AND s2.Received <= s1.Received
GROUP BY s2.Facility) AS ITEMS
FROM SHIPMENTS s1
GROUP BY
Facility,
Received
ORDER BY
Facility;

Maybe it could be a good idea to use ORDER BY clause with String_Agg function to make sure that the concatenation will be in desired order
select
distinct Facility, Received,
(
select string_agg(s.Item, ';' order by Received, ShipmentID)
from Shipments s
where
s.Facility = t.Facility and
s.Received <= t.Received
) as Items
from Shipments t

Related

How can i compare 2 items in sql that are in the same column in the same table?

i have to produce a list with the following content:
For Example we have got an order with 3 Positions of the same Product, the same Quantity, etc
The only difference is the desired date of shipment of the customer. e.g the first position should be delievered on the first of january. The 2nd Position should be delievered on the first of April, and the third Position on the first of July.
Furthermore we can set a checkmark in our System that the customer cant split his Orders for various Reasons.
So i need to find out which Orders have the checkmark set to "NO SPLIT ORDER-SHIPMENTS" and still have gotten different shipment-dates for the positions.
I'm atm completely clueless how to tackle that Problem.
For Example Table A contains:
ordernumber|desired-date|orderposition|productid|quantitiy
123456789 | 01-01-2022 | 10 | 0815 | 100
123456798 | 01-04-2022 | 20 | 0815 | 100
123456789 | 01-07-2022 | 30 | 0815 | 100
123456789 | 04-02-2022 | 10 | 5152 | 66
In our System we have set an option that the sutomer of this order can no get split shipments. So we have an issue here. The order containst three different shipment-dates but the system wont allow that.
How can i find exactly those rows in that Table that have this Problem. I dont want to see row Number 4 of Table A only the first 3.
The following query will find the orders with different delivery dates for the same product with the same order id for the same customer.
The column names will need to be replaced with the column names in your database and may have to join 2 or more tables in the query to get all the information.
create table tableA (ordernumber int, desired_date date, orderposition int, productid int, quantitiy int);
insert into tableA values
(123456789 , '2022-01-01' , 10 , 0815 , 100),
(123456798 , '2022-04-01' , 20 , 0815 , 100),
(123456789 , '2022-07-01' , 30 , 0815 , 100),
(123456789 , '2022-02-04' , 10 , 5152 , 66 );
select
count(distinct desired_date) number_lines,
ordernumber,
productid
from tableA
group by
ordernumber,
productid
having
count(distinct desired_date) > 1
/*and
checkmark = "NO SPLIT ORDER-SHIPMENTS";*/
number_lines | ordernumber | productid
-----------: | ----------: | --------:
2 | 123456789 | 815
db<>fiddle here

How to pivot column data into a row where a maximum qty total cannot be exceeded?

Introduction:
I have come across an unexpected challenge. I'm hoping someone can help and I am interested in the best method to go about manipulating the data in accordance to this problem.
Scenario:
I need to combine column data associated to two different ID columns. Each row that I have associates an item_id and the quantity for this item_id. Please see below for an example.
+-------+-------+-------+---+
|cust_id|pack_id|item_id|qty|
+-------+-------+-------+---+
| 1 | A | 1 | 1 |
| 1 | A | 2 | 1 |
| 1 | A | 3 | 4 |
| 1 | A | 4 | 0 |
| 1 | A | 5 | 0 |
+-------+-------+-------+---+
I need to manipulate the data shown above so that 24 rows (for 24 item_ids) is combined into a single row. In the example above I have chosen 5 items to make things easier. The selection format I wish to get, assuming 5 item_ids, can be seen below.
+---------+---------+---+---+---+---+---+
| cust_id | pack_id | 1 | 2 | 3 | 4 | 5 |
+---------+---------+---+---+---+---+---+
| 1 | A | 1 | 1 | 4 | 0 | 0 |
+---------+---------+---+---+---+---+---+
However, here's the condition that is making this troublesome. The maximum total quantity for each row must not exceed 5. If the total quantity exceeds 5 a new row associated to the cust_id and pack_id must be created for the rest of the item_id quantities. Please see below for the desired output.
+---------+---------+---+---+---+---+---+
| cust_id | pack_id | 1 | 2 | 3 | 4 | 5 |
+---------+---------+---+---+---+---+---+
| 1 | A | 1 | 1 | 3 | 0 | 0 |
| 1 | A | 0 | 0 | 1 | 0 | 0 |
+---------+---------+---+---+---+---+---+
Notice how the quantities of item_ids 1, 2 and 3 summed together equal 6. This exceeds the maximum total quantity of 5 for each row. For the second row the difference is created. In this case only item_id 3 has a single quantity remaining.
Note, if a 2nd row needs to be created that total quantity displayed in that row also cannot exceed 5. There is a known item_id limit of 24. But, there is no known limit of the quantity associated for each item_id.
Here's an approach which goes from left-field a bit.
One approach would have been to do a recursive CTE, building the rows one-by-one.
Instead, I've taken an approach where I
Create a new (virtual) table with 1 row per item (so if there are 6 items, there will be 6 rows)
Group those items into groups of 5 (I've called these rn_batches)
Pivot those (based on counts per item per rn_batch)
For these, processing is relatively simple
Creating one row per item is done using INNER JOIN to a numbers table with n <= the relevant quantity.
The grouping then just assigns rn_batch = 1 for the first 5 items, rn_batch = 2 for the next 5 items, etc - until there are no more items left for that order (based on cust_id/pack_id).
Here is the code
/* Data setup */
CREATE TABLE #Order (cust_id int, pack_id varchar(1), item_id int, qty int, PRIMARY KEY (cust_id, pack_id, item_id))
INSERT INTO #Order (cust_id, pack_id, item_id, qty) VALUES
(1, 'A', 1, 1),
(1, 'A', 2, 1),
(1, 'A', 3, 4),
(1, 'A', 4, 0),
(1, 'A', 5, 0);
/* Pivot results */
WITH Nums(n) AS
(SELECT (c * 100) + (b * 10) + (a) + 1 AS n
FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) A(a)
CROSS JOIN (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) B(b)
CROSS JOIN (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) C(c)
),
ItemBatches AS
(SELECT cust_id, pack_id, item_id,
FLOOR((ROW_NUMBER() OVER (PARTITION BY cust_id, pack_id ORDER BY item_id, N.n)-1) / 5) + 1 AS rn_batch
FROM #Order O
INNER JOIN Nums N ON N.n <= O.qty
)
SELECT *
FROM (SELECT cust_id, pack_id, rn_batch, 'Item_' + LTRIM(STR(item_id)) AS item_desc
FROM ItemBatches
) src
PIVOT
(COUNT(item_desc) FOR item_desc IN ([Item_1], [Item_2], [Item_3], [Item_4], [Item_5])) pvt
ORDER BY cust_id, pack_id, rn_batch;
And here are results
cust_id pack_id rn_batch Item_1 Item_2 Item_3 Item_4 Item_5
1 A 1 1 1 3 0 0
1 A 2 0 0 1 0 0
Here's a db<>fiddle with
additional data in the #Orders table
the answer above, and also the processing with each step separated.
Notes
This approach (with the virtual numbers table) assumes a maximum of 1,000 for a given item in an order. If you need more, you can easily extend that numbers table by adding additional CROSS JOINs.
While I am in awe of the coders who made SQL Server and how it determines execution plans in millisends, for larger datasets I give SQL Server 0 chance to accurately predict how many rows will be in each step. As such, for performance, it may work better to split the code up into parts (including temp tables) similar to the db<>fiddle example.

postgres insert data from an other table inside array type columns

I have tow table on Postgres 11 like so, with some ARRAY types columns.
CREATE TABLE test (
id INT UNIQUE,
category TEXT NOT NULL,
quantitie NUMERIC,
quantities INT[],
dates INT[]
);
INSERT INTO test (id, category, quantitie, quantities, dates) VALUES (1, 'cat1', 33, ARRAY[66], ARRAY[123678]);
INSERT INTO test (id, category, quantitie, quantities, dates) VALUES (2, 'cat2', 99, ARRAY[22], ARRAY[879889]);
CREATE TABLE test2 (
idweb INT UNIQUE,
quantities INT[],
dates INT[]
);
INSERT INTO test2 (idweb, quantities, dates) VALUES (1, ARRAY[34], ARRAY[8776]);
INSERT INTO test2 (idweb, quantities, dates) VALUES (3, ARRAY[67], ARRAY[5443]);
I'm trying to update data from table test2 to table test only on rows with same id. inside ARRAY of table test and keeping originals values.
I use INSERT on conflict,
how to update only 2 columns quantities and dates.
running the sql under i've got also an error that i don't understand the origin.
Schema Error: error: column "quantitie" is of type numeric but expression is of type integer[]
INSERT INTO test (SELECT * FROM test2 WHERE idweb IN (SELECT id FROM test))
ON CONFLICT (id)
DO UPDATE
SET
quantities = array_cat(EXCLUDED.quantities, test.quantities),
dates = array_cat(EXCLUDED.dates, test.dates);
https://www.db-fiddle.com/f/rs8BpjDUCciyZVwu5efNJE/0
is there a better way to update table test from table test2, or where i'm missing the sql?
update to show result needed on table test:
**Schema (PostgreSQL v11)**
| id | quantitie | quantities | dates | category |
| --- | --------- | ---------- | ----------- | --------- |
| 2 | 99 | 22 | 879889 | cat2 |
| 1 | 33 | 34,66 | 8776,123678 | cat1 |
Basically, your query fails because the structures of the tables do not match - so you cannot insert into test select * from test2.
You could work around this by adding "fake" columns to the select list, like so:
insert into test
select idweb, 'foo', 0, quantities, dates from test2 where idweb in (select id from test)
on conflict (id)
do update set
quantities = array_cat(excluded.quantities, test.quantities),
dates = array_cat(excluded.dates, test.dates);
But this looks much more convoluted than needed. Essentially, you want an update statement, so I would just recommend:
update test
set
dates = test2.dates || test.dates,
quantities = test2.quantities || test.quantities
from test2
where test.id = test2.idweb
Note that this ues || concatenation operator instead of array_cat() - it is shorter to write.
Demo on DB Fiddle:
id | category | quantitie | quantities | dates
-: | :------- | --------: | :--------- | :------------
2 | cat2 | 99 | {22} | {879889}
1 | cat1 | 33 | {34,66} | {8776,123678}

Find specific values and trancate

How to find group values?
Example I have following SKU's:
SkuId Description
VN0A46ZERWV113000M CLASSIC
VN0A46ZERWV112000M CLASSIC
VN0A46ZERWV111500M CLASSIC
VN0A3WCVAZ31XXL Modern
VN0A3WCVAZ310XL Modern
VN0A3WCVAZ3100S Modern
VN0A3WCVAZ3100M Modern
VN0A3TE3RCO113000M Not Classic
VN0A3TE3RCO112000M Not Classic
VN0A3TE3RCO111500M Not Classic
How to describe...:) So, I need find all Sku's with the same description, find the same part in SKU, and add new row after every group. In general, the same part is first 12 characters.
Example in Result:
SkuId Description
VN0A46ZERWV113000M CLASSIC
VN0A46ZERWV112000M CLASSIC
VN0A46ZERWV111500M CLASSIC
VN0A46ZERWV1 NEW
VN0A3WCVAZ31XXL Modern
VN0A3WCVAZ310XL Modern
VN0A3WCVAZ3100S Modern
VN0A3WCVAZ3100M Modern
VN0A3WCVAZ31 NEW
VN0A3TE3RCO113000M Not Classic
VN0A3TE3RCO112000M Not Classic
VN0A3TE3RCO111500M Not Classic
VN0A3TE3RCO1 NEW
If I understand correctly you can try to use UNION ALL and substring function to make it.
use substring to get the first 12 characters from SkuId column in subquery then distinct remove duplicate first 12 characters SkuId then UNION ALL two result set.
CREATE TABLE T(
SkuId VARCHAR(100),
Description VARCHAR(100)
);
INSERT INTO T VALUES ('VN0A46ZERWV113000M' ,'CLASSIC');
INSERT INTO T VALUES ('VN0A46ZERWV112000M' ,'CLASSIC');
INSERT INTO T VALUES ('VN0A46ZERWV111500M' ,'CLASSIC');
INSERT INTO T VALUES ('VN0A3WCVAZ31XXL' ,'Modern');
INSERT INTO T VALUES ('VN0A3WCVAZ310XL' ,'Modern');
INSERT INTO T VALUES ('VN0A3WCVAZ3100S' ,'Modern');
INSERT INTO T VALUES ('VN0A3WCVAZ3100M' ,'Modern');
INSERT INTO T VALUES ('VN0A3TE3RCO113000M' ,'Not Classic');
INSERT INTO T VALUES ('VN0A3TE3RCO112000M' ,'Not Classic');
INSERT INTO T VALUES ('VN0A3TE3RCO111500M' ,'Not Classic');
Query 1:
SELECT * FROM (
select SkuId,Description
from T
UNION ALL
SELECT distinct substring(SkuId,1,12) ,'New'
FROM T
) t1
order by SkuId desc
Results:
| SkuId | Description |
|--------------------|-------------|
| VN0A46ZERWV113000M | CLASSIC |
| VN0A46ZERWV112000M | CLASSIC |
| VN0A46ZERWV111500M | CLASSIC |
| VN0A46ZERWV1 | New |
| VN0A3WCVAZ31XXL | Modern |
| VN0A3WCVAZ310XL | Modern |
| VN0A3WCVAZ3100S | Modern |
| VN0A3WCVAZ3100M | Modern |
| VN0A3WCVAZ31 | New |
| VN0A3TE3RCO113000M | Not Classic |
| VN0A3TE3RCO112000M | Not Classic |
| VN0A3TE3RCO111500M | Not Classic |
| VN0A3TE3RCO1 | New |
I think the additional rows you want are:
select skuid, 'NEW'
from (select distinct left(skuid, 12) as skuid, description
from skus
) t;
For your data and probably for your problem, this will probably do:
select distinct left(skuid, 12) as skuid, 'New'
from skus;
If you specifically want to exclude "names" that have different descriptions:
select left(skuid, 12) as skuid, 'New'
from skus
group by left(skuid, 12)
having min(description) = max(description);
You can add these into the table using insert:
insert into skus (skuid, description)
select distinct left(skuid, 12) as skuid, 'New'
from skus;
If you just want a result set, then use union and the correct order by:
select skuid, description
from ((select skuid, description, 1 as priority
from skus
) union all
(select distinct left(skuid, 12) as skuid, 'New', 2
from skus
)
) sd
order by skuid, priority;

How to allocate quantities from one table to another in a query? e.g. picked order item quantities against actual order item quantities

I am working on an order management system at the moment where orders are received and then picked. Order items are received with quantity of 1 always - e.g. if two items are in an order for the same product, there will be two lines. These are stored in the OrderItems table. Order items are picked by product and are stored in the PickedItems table, i.e.
CREATE TABLE OrderItems
(
OrderID int,
Reference int,
ProductCode varchar(20),
Quantity int
);
CREATE TABLE PickedItems
(
OrderID int,
ProductCode varchar(20),
Quantity int,
Location varchar(20) DEFAULT NULL
);
So for example the following inserts create an order with 6 line items, 3 of which are for the same product, another 2 for a different product and finally a single line for a 3rd product.
INSERT INTO OrderItems VALUES (1, 1, 'BOOK', 1);
INSERT INTO OrderItems VALUES (1, 2, 'BOOK', 1);
INSERT INTO OrderItems VALUES (1, 3, 'BOOK', 1);
INSERT INTO OrderItems VALUES (1, 4, 'PEN', 1);
INSERT INTO OrderItems VALUES (1, 5, 'PEN', 1);
INSERT INTO OrderItems VALUES (1, 6, 'CHAIR', 1);
If 2 of the product "BOOK" are picked and 1 of product "CHAIR", then the following will create those entries in the PickedItems table:
INSERT INTO PickedItems (OrderID, ProductCode, Quantity) VALUES (1, 'BOOK', 2);
INSERT INTO PickedItems (OrderID, ProductCode, Quantity) VALUES (1, 'CHAIR', 1);
The following query is currently used to find which Order Items have been picked successfully:
SELECT
OrderItems.Reference,
OrderItems.ProductCode,
OrderItems.Quantity,
CASE
WHEN SUM(OrderItemsCumulative.Quantity) <= PickedItems.Quantity THEN 1
WHEN (SUM(OrderItemsCumulative.Quantity) - 1) < PickedItems.Quantity THEN PickedItems.Quantity - (SUM(OrderItemsCumulative.Quantity) - 1)
ELSE 0
END AS QuantityPicked
FROM
OrderItems INNER JOIN
OrderItems OrderItemsCumulative ON (OrderItems.OrderID = OrderItemsCumulative.OrderID) AND (OrderItems.ProductCode = OrderItemsCumulative.ProductCode) AND (OrderItems.Reference >= OrderItemsCumulative.Reference) LEFT JOIN
PickedItems ON (OrderItems.OrderID = OrderItemsCumulative.OrderID) AND (OrderItems.ProductCode = PickedItems.ProductCode)
WHERE
OrderItems.OrderID = 1
GROUP BY
OrderItems.Reference,
OrderItems.ProductCode,
OrderItems.Quantity,
PickedItems.Quantity
This all works fine, but I'd like to extend this so that PickedItems may have multiple locations associated with it (using the Location column in the PickedItems table), and thus there could be more than entry for an Order/Product in the PickedItems table, e.g. 2 BOOKs picked at "Location A" and 1 BOOK picked at "Location B", would have the following entries.
INSERT INTO PickedItems (OrderID, ProductCode, Quantity, Location) VALUES (1, 'BOOK', 2, 'Location A');
INSERT INTO PickedItems (OrderID, ProductCode, Quantity, Location) VALUES (1, 'BOOK', 1, 'Location B');
So the query above isn't taking the location into account.
Ideally I'd want the following returned in the above scenario, but I can't seem to get it fit with the query. Would I be best off rewriting the query using a stored procedure and allocating the data on an individual basis?
+-----------+-------------+-----------------+----------------+------------+
| Reference | ProductCode | QuantityOrdered | QuantityPicked | Location |
+-----------+-------------+-----------------+----------------+------------+
| 1 | Book | 1 | 1 | Location A |
| 2 | Book | 1 | 1 | Location A |
| 3 | Book | 1 | 1 | Location B |
| 4 | Pen | 1 | 0 | NULL |
| 5 | Pen | 1 | 0 | NULL |
| 6 | Chair | 1 | 0 | NULL |
+-----------+-------------+-----------------+----------------+------------+
SQL Fiddle
Ideally you would adapt the schema to reflect the changes.
You may need to relate order reference to the location either by adding location to orderitems, or by adding reference to pickeditems, otherwise there'll always be a Cartesian on orderid/productid because they won't be unique in any of the tables.
With a stored procedure you could in theory work around the Cartesian but that would only mask the actual issue with the data model.