Find previous date based off one from another column and grouped by - sql

Bit of a hard one to explain, but basically I have table of orders, T1 for example:
---------------x---------x--------------x-----------------x
customer id | item | order date | recieved date |
---------------x---------x--------------x-----------------x
1 | Shoes | 01/12/2020 | 20/12/2020 |
1 | Bag | 22/12/2020 | 31/12/2020 |
1 | Bag | 05/01/2021 | 15/01/2021 |
1 | Hat | 07/04/2021 | 28/04/2021 |
2 | Bag | 04/06/2020 | 14/06/2020 |
3 | Shoes | 01/01/2022 | 11/01/2022 |
3 | Bag | 02/03/2022 | 23/03/2022 |
3 | Watch | 28/03/2022 | 05/08/2022 |
3 | Bag | 01/06/2022 | 13/06/2022 |
---------------x---------x--------------x-----------------x
Now say I want to find for every order of "Bags", what the last item the customer had received was and when (so as to look at which item they last received which may have influenced them to make the next purchase), so the resultant table would be something like:
---------------x---------x--------------x--------------------------x-----------------------x
customer id | item | order date | Previous Item Received | Prev Item Received Dt |
---------------x---------x--------------x--------------------------x-----------------------x
1 | Bag | 22/12/2020 | Shoes | 20/12/2020 |
1 | Bag | 05/01/2021 | Bag | 31/12/2020 |
2 | Bag | 04/06/2020 | NULL | NULL |
3 | Bag | 02/03/2022 | Shoes | 11/01/2022 |
3 | Bag | 01/06/2022 | Bag | 23/03/2022 |
---------------x---------x--------------x--------------------------x------------------------x
So if a customer orders an particular item, I want to find what their last received item was before that order was made, and what date it was received on.

Related

SQL some selections into one (or get two colums from one)

I use PostgreSql, I have two tables (for example)
Let table1 will contain stores, there are 2 types 'candy store' and 'dental store'.
Each row contains information about a customer's purchase in a particular store
In result i want to get money from each type of store group by id and the last date of purchase. Money from candy stores start sum since 2016, but money from dental stores start sum from 2018
table1:
+----+---------+------------------+-------+
| id | store | date of purchase | money |
| 1 | store 1 | 2016-01-01 | 10 |
| 1 | store 5 | 2018-01-01 | 50 |
| 2 | store 2 | 2017-01-20 | 10 |
| 2 | store 3 | 2019-02-20 | 15 |
| 3 | store 2 | 2017-02-02 | 20 |
| 3 | store 6 | 2019-01-01 | 60 |
| 1 | store 1 | 2015-01-01 | 20 |
+----+---------+------------------+-------+
table2 :
+---------+--------+
| store | type |
| store 1 | candy |
| store 2 | candy |
| store 3 | candy |
| store 4 | dental |
| store 5 | dental |
| store 6 | dental |
+---------+--------+
I want my query to return a table like this:
+----+---------------+-----------------+---------------+-----------------+
| id | money( candy) | the last date c | money(dental) | the last date d |
| 1 | 10 | 2016-01-01 | 50 | 2018-01-01 |
| 2 | 25 | 2019-02-20 | - | - |
| 3 | 20 | 2017-02-02 | 60 | 2019-01-01 |
+----+---------------+-----------------+---------------+-----------------+
if I understand correctly , this is what you want to do :
select id
, sum(money) filter (where ty.type = 'candy') candymoney
, max(purchasedate) filter (where ty.type = 'candy') candylastdate
, sum(money) filter (where ty.type = 'dental') dentalmoney
, max(purchasedate) filter (where ty.type = 'dental') dentallastdate
from table t
join storetype table st on t.store = ty.store
group by id

How do you filter in SQL by a dynamic number of date ranges?

Let's say I have these tables.
Users
| id | name |
|----|------|
| 1 | bob |
Posts
| id | title | created_at | user_id |
|----|---------------|----------------------------|---------|
| 1 | hello world | 2020-05-15 18:29:13.163687 | 1 |
| 2 | hello world 2 | 2020-06-15 18:29:13.163687 | 1 |
| 3 | hello world 3 | 2020-07-15 18:29:13.163687 | 1 |
Snoozes
| id | start_at | end_at | user_id |
|----|----------------------------|----------------------------|---------|
| 1 | 2020-05-01 18:29:13.163687 | 2020-05-30 18:29:13.163687 | 1 |
| 2 | 2020-06-01 18:29:13.163687 | 2020-06-30 18:29:13.163687 | 1 |
| 3 | 2020-07-01 18:29:13.163687 | 2020-07-13 18:29:13.163687 | 1 |
For each user, I want to get the posts that they created when they were not in snooze mode. The number of snooze mode instances they have will vary.
If done correctly with the example data, the only post I'd get back is post id 3.
You can use not exists:
select p.*
from posts p
where not exists (select 1
from snoozes s
where p.user_id = s.user_id and
p.created_at between s.start_at and s.end_at
);

Handling Many to Many

I'm stuck trying to model a many to many relationship. Here's a representative sample of my issue using an e-commerce model:
+------------+-------------+----------+------------+
| date | customer_id | order_id | address_id |
+------------+-------------+----------+------------+
| 12/1/2019 | 1 | 1 | 1 |
| 12/15/2019 | 2 | 1 | 1 |
| 12/15/2019 | 2 | 2 | 2 |
| 1/1/2020 | 2 | 3 | 1 |
| 1/1/2020 | 1 | 2 | 3 |
| 1/1/2020 | 1 | 3 | 2 |
| 1/2/2020 | 1 | 4 | 1 |
+------------+-------------+----------+------------+
A customer can place many orders.
A customer can ship to multiple addresses.
Addresses can have multiple customers.
How would I model a "household" junction/bridging table? In my data above, customer_id 1 and 2 could possibly be a family or business entity. What if I wanted to know on a given date, how many orders that household/entity placed, how many customers that household represented and how many locations did they ship to?
I think this is the start of how I build this model, but stuck on writing the bridging query.
orders addresses
+-------------+----------+ +-------------+------------+
| customer_id | order_id | | customer_id | address_id |
+-------------+----------+ +-------------+------------+
| 1 | 1 | | 1 | 1 |
| 1 | 2 | | 1 | 2 |
| 1 | 3 | | 1 | 3 |
| 1 | 4 | | 2 | 1 |
| 2 | 1 | | 2 | 2 |
| 2 | 2 | +-------------+------------+
| 2 | 3 |
+-------------+----------+
In a relational database you want to keep all the similar data in separate tables. This helps you with making joins later. I would recommend:

Boolean was amount ever greater than x?

Interesting question for you all. Here's a sample of my dataset (see below). I have warehouses, dates, and the change in inventory level at that specific date for a given warehouse.
Ex: Assuming 1/1/2018 is first date, warehouse 1 starts out with 100 in inventory, then 600, then 300, then 500...etc.
My question I'd like to answer in SQL: By warehouse ID, did each warehouse ever have inventory of more than 750 (yes/no)?
I can't sum the entire column, because the ending inventory (sum of column by warehouse) is likely lower than a past inventory level. Any help is appreciated!!
+--------------+------------+---------------+
| Warehouse_id | Date | Inventory_Amt |
+--------------+------------+---------------+
| 1 | 1/1/2018 | +100 |
| 1 | 6/1/2018 | +500 |
| 1 | 6/15/2018 | -300 |
| 1 | 7/1/2018 | +200 |
| 1 | 8/1/2018 | -400 |
| 1 | 12/15/2018 | +100 |
| 2 | 1/1/2018 | +10 |
| 2 | 6/1/2018 | +50 |
| 2 | 6/15/2018 | -30 |
| 2 | 7/1/2018 | +20 |
| 2 | 8/1/2018 | -40 |
| 2 | 12/15/2018 | +10 |
| 3 | 1/1/2018 | +100 |
| 3 | 6/1/2018 | +500 |
| 4 | 6/15/2018 | +300 |
| 4 | 7/1/2018 | +200 |
| 4 | 8/1/2018 | -400 |
| 4 | 12/15/2018 | +100 |
+--------------+------------+---------------+
You want a cumulative sum and then filtering:
select i.*
from (select i.*, sum(inventory_amt) over (partition by warehouse_id order by date) as inventory
from inventory i
) i
where inventory_amt > 750

Outer Join multible tables keeping all rows in common colums

I'm quite new to SQL - hope you can help:
I have several tables that all have 3 columns in common: ObjNo, Date(year-month), Product.
Each table has 1 other column, that represents an economic value (sales, count, netsales, plan ..)
I need to join all tables on the 3 common columns giving. The outcome must have one row for each existing combination of the 3 common columns. Not every combination exists in every table.
If I do full outer joins, I get ObjNo, Date, etc. for each table, but only need them once.
How can I achieve this?
+--------------+-------+--------+---------+-----------+
| tblCount | | | | |
+--------------+-------+--------+---------+-----------+
| | ObjNo | Date | Product | count |
| | 1 | 201601 | Snacks | 22 |
| | 2 | 201602 | Coffee | 23 |
| | 4 | 201605 | Tea | 30 |
| | | | | |
| tblSalesPlan | | | | |
| | ObjNo | Date | Product | salesplan |
| | 1 | 201601 | Beer | 2000 |
| | 2 | 201602 | Sancks | 2000 |
| | 5 | 201605 | Tea | 2000 |
| | | | | |
| | | | | |
| tblSales | | | | |
| | ObjNo | Date | Product | Sales |
| | 1 | 201601 | Beer | 1000 |
| | 2 | 201602 | Coffee | 2000 |
| | 3 | 201603 | Tea | 3000 |
+--------------+-------+--------+---------+-----------+
Thx
Devon
It sounds like you're using SELECT * FROM... which is giving you every field from every table. You probably only want to get the values from one table, so you should be explicit about which fields you want to include in the results.
If you're not sure which table is going to have a record for each case (i.e. there is not guaranteed to be a record in any particular table) you can use the COALESCE function to get the first non-null value in each case.
SELECT COALESCE(tbl1.ObjNo, tbl2.ObjNo, tbl3.ObjNo) AS ObjNo, ....
tbl1.Sales, tbl2.Count, tbl3.Netsales