SQL one to many with conditions - sql

I'm working with two tables:
table one : order_table
table two: order_details
order_table:
- Order ID (pk)
- Submitter
- Date
order_detail:
- Order_detail_id (unique, auto generated)
- parent_order (fk, points to the order_id this line is part of)
- date
- assigned_to (i.e. department1, department2, department3, department4)
etc.
One order can have multiple order details
For instance
Order_detail_id parent_order assigned_to
0000001 0010 department1
0000002 0010 department2
0000003 0010 department1
0000004 0010 department1
0000005 0011 department1
0000006 0011 department2
0000007 0011 department3
As you can see, an order can be assigned back and forth between departments.
What I’m trying to do is to get a list of all the orders that were never assigned to a specific department regardless of how many times they were assigned back and forth.
For instance, all the unique order numbers that were never assigned at any point to department2
So far I’m getting either nothing or all data that includes assignment de department2…
I've tried so many different ways and I'm unable to get the expected results. thanks for your help

Here is one way to get orders that were never assigned to a specific department:
select od.parent_order
from order_detail od
group by od.parent_order
having sum(case when assigned_to = 'department2' then 1 else 0 end) = 0;
I like this method because it is quite general. In your case, the following is probably more efficient:
select o.*
from orders o
where not exists (select 1
from order_details od
where od.parent_order = o.orderid and
od.assigned_to = 'department2'
);

Related

Matching 3 tables one by one and geting a final column value

I have 3 tables with the columns as example below:
Transaction Table (tran_table), which contains "Customer Number" value as:
CustomerN Date Usercode
Staff Table (staff), which contains "(staff's) Customer Number" and "PersonID" as:
CustomerN PersonID UserCode
And, staff title log (staff_history) by date which contains the info about whether a staff is active or left; "PersonID" and "Active" as
PersonID IsActive positionname StartDate EndDate UserCode
1 Yes branch Manager 01.01.2020 01.01.4570 daniel
1 Yes Sales representative 15.11.2018 31.12.2019 daniel
2 No Sales associate 01.01.2018 31.05.2020 mary
3 Yes Intern 01.01.2018 31.12.2019 josh
3 Yes Sales associate 01.01.2020 28.02.2020 josh
3 Yes Sales Representative 01.03.2020 01.01.4570 josh
I would like to get, if the customer who make a transaction is/was our staff (left or active), a column value that contains "IsActive", as "Active";"Left" or "NULL" for non-staff transactions.
I have tried this code below with no success
select b.AccountNumber, b.CustomerName ,aa.IsActive ,
(*) (select
(*) top 1 aa.positionname
(*) from staff_history
(*) where
(*) b.date between aa.StartDate and aa.EndDate
(*) and (b.username=aa.UserCode))
(*) as [Title],
from tran_table as b
left join staff_history as aa WITH (NOLOCK) ON b.UserName=aa.UserCode
left join staff  as ab WITH (NOLOCK) ON b.AccountNumber=ab.AccountNumber and ab.PersonId=aa.PersonId
where
aa.positionname is not null
order by aa.positionname
Edit #1 to DenStudent: Sure, the result with this code ise sth like:
CustomerN Date Usercode IsActive
15874 01.01.2020 josh Yes
8431 05.03.2020 mary No
55147 07.05.2020 daniel Yes
the problem here is, it matches the staff's situation who MADE the transaction instead of customer (is/was a staff or not). All usercodes belongs to a staff thus, there is no "null" value.
The result that i expect is:
CustomerN Date Usercode IsActive
15874 01.01.2020 josh Yes (1)
8431 05.03.2020 mary NULL (2)
55147 07.05.2020 daniel No (3)
(1) the customer number 15874 belongs to an active staff
(2) the customer number 8431 belongs to no a former or active staff
(3) the customer number 55147 belongs to a former staff
Edit #2 to Marc Guillot: there are tons of additional tables and columns. I wanted to short it to the ones that relevant to this query. however, yes, the date column in transaction table, matches with staff_history table to see the staff's title on the date of transaction. i have added several columns and the code (with(*) signed lines) if it works with you.
It's hard to say because I personally don't see what you are storing on staff_history. You are only storing the periods of activity ?, or you also store periods of inactivity ?, what happens with the EnDate for the current period ?, you leave it at null ?.
If you show us the content on those tables for your sample result, then we can help you better.
It seems that you are looking for something like this:
select b.AccountNumber, b.CustomerName, aa.IsActive, aa.positionname as [Title]
from tran_table as b
outer apply (select top 1 aa.positionname, IsActive
from staff_history as aa
where b.date between aa.StartDate and aa.EndDate
and b.username=aa.UserCode
order by b.StartDate desc) as aa
left join staff as ab on b.AccountNumber=ab.AccountNumber and ab.PersonId=aa.PersonId
where aa.positionname is not null
order by aa.positionname
This returns the positionname and state of activity at the day of the transaction (or null if it's not present in staff_history).
I have used outer apply, which is like a left join, but instead of using a condition you directly provide with a subquery all the records that you want to join.

SQL-sum over dynamic period

I have 2 tables: Customers and Actions, where each customer has uniqe ID (which can be found in each table).
Part of the customers became club members at a specific date (change between the customers). I'm trying to summarize their purchases until that date, and to get those who purchase more than (for example) 200 until they become club members.
For example, I can have the following customer:
custID purchDate purchAmount
1 2015-05-12 100
1 2015-07-12 150
1 2015-12-29 320
Now, assume that custID=1 became a club member at 2015-12-25; in that case, I'd like to get SUM(purchAmount)=250 (pay attention that I'd like to get this customer because 250>200).
I tried the following:
SELECT cust.custID, SUM(purchAmount)totAmount
FROM customers cust
JOIN actions act
ON cust.custID=act.custID
WHERE act.clubMember=1
AND cust.purchDate<act.clubMemberDate
GROUP BY cust.custID
HAVING totAmount>200;
Is it the right way to "attack" this question, or should I use something like while loop over the clubMemberDate (which telling the truth-I don't know how to do)?
I'm working with Teradata.
Your help will be appreciated.

POSTGRESQL - Finding specific product when

I've attempted to write a query but I've not managed to get it working correctly.
I'm attempting to retrieve where a specific product has been bought but where it also has been bought with other products. In the case below, I want to find where product A01 has been bought but also when it was bought with other products.
Data (extracted from tables for illustration):
Order | Product
123456 | A01
123457 | A01
123457 | B02
123458 | C03
123459 | A01
123459 | C03
Query which will return all orders with product A01 without showing other products:
SELECT
O.NUMBER
O.DATE
P.NUMBER
FROM
ORDERS O
JOIN PRODUCTS P on P.ID = O.ID
WHERE
P.NUMBER = 'A01'
I've tried to create a sub query which brings back just orders of product A01 but I don't know how to place it in the query for it to return all orders containing product A01 as well as any other product ordered with it.
Any help on this would be very grateful.
Thanks in advance.
You can use conditional SUM to detect if one ORDER group have one ore more 'A01'
CREATE TABLE orders
("Order" int, "Product" varchar(3))
;
INSERT INTO orders
("Order", "Product")
VALUES
(123456, 'A01'),
(123457, 'A01'),
(123457, 'B02'),
(123458, 'C03'),
(123459, 'A01'),
(123459, 'C03')
;
SELECT "Order"
FROM orders
GROUP BY "Order"
HAVING SUM(CASE WHEN "Product" = 'A01' THEN 1 ELSE 0 END) > 0
I appreciated Juan's including the DDL to create the database on my system. By the time I saw it, I'd already done all the same work, except that I got around the reserved word problem by naming that field Order1.
Sadly, I didn't consider that either of the offered queries worked on my system. I used MySQL.
The first one returned the A01 lines of the two orders on which other products were ordered too. I took Alex's purpose to include seeing all items of all orders that included A01. (Perhaps he wants to tell future customers what other products other customers have ordered with A01, and generate sales that way.)
The second one returned the three A01 lines.
Maybe Alex wants:
select *
from orders
where Order1 in (select Order1
from orders
where Product = 'A01')
It outputs all lines of all orders that include A01. The subquery makes a list of all orders with A01. The first query returns all lines of those orders.
In a big database, you might not want to run two queries, but this is the only way I see to get the result I understood Alex wanted. If that is what he wanted, he would have to run a second query once armed with output from the queries offered, so there's no real gain.
Good discussion. Thanks to all!
Use GROUP BY clause along with HAVING like
select "order", Product
from data
group by "order"
having count(distinct product) > 1;

Shopping basket: Products with options, how to check if a combination exists?

I have a bit of a SQL problem that I'm hoping someone can help.
On my website someone can order a product and then options for that products, e.g. they buy a car, and options such as tyres, stereo system. The customer can add multiple items to their basket for the same main item (a car), but then different options for it, such as:
car, pirelli, b&o
car, michelin, b&o
I have this DB structure to achieve this:
orders
----------
order_ref
total
orders_parts
------------
id
order_ref
part_id
quantity
orders_parts_options
--------------------
id
option
orders
------
12345 1000.01
orders_parts
------------
1001 12345 Audi 1
1002 12345 Audi 1
orders_parts_options
--------------------
1001 michelin
1001 b&o
1002 pirelli
1002 b&o
So here you can see I have two Audis in my shopping basket, one with michelin, one with pirelli, both with B&O audio systems. My question; let's say another call is made to add an item to the shopping basket for this order, e.g. another Audi with Michelin and B&O, what SQL would I need to get orders_parts.id of 1001?
I came up with this bit of rubbish:
SELECT op.id FROM orders_parts op
INNER JOIN orders_parts_options opo ON (op.id = opo.id)
WHERE op.order_ref = 12345 AND (opo.option = 'michelin' OR opo.option = 'b&o')
But I get this result
1001
1001
1002
from that. I'm guessing I need to aggregate it and have a having count = 2 in there, but just cannot work it out. Anyone smarter out there who can help me?
(just to add, the DB is normalized in real life, but for clarity I've but full text values in there).
SELECT op.id, count(op.id) as n FROM orders_parts op
INNER JOIN orders_parts_options opo ON (op.id = opo.id)
WHERE op.order_ref = 12345 AND (opo.option = 'michelin' OR opo.option = 'b&o')
GROUP BY op.id;
This query retrieves all parts (and their number) for the order_ref=12345 where option = 'michelin' or option = 'b&o'

Joining a table to two one-to-many relationship tables in SQL Server

Happy Friday folks,
I'm trying to write an SSRS report displaying data from three (actually about 12, but only three relevant) tables that have akward relationships and the SQL query behind the data is proving difficult.
There are three entities involved - a Purchase Order, a Sales Order, and a Delivery. The problem is the a Purchase Order can have many sales orders, and also many deliveries which are NOT linked to the sales orders...that would be too easy.
Both the Sales Order and Delivery tables can be linked to the Purchase Order table by foreign keys and an intermediate table each.
I need to basically list Purchase Orders, a list of sales orders and a list of deliveries next to them, with NULLs for any fields that aren't valid so that'll give the required output in SSRS/when read by a human, ie, for a purchase order with 2 sales orders and 4 delivery dates;
PO SO Delivery
1234 ABC 05/10
1234 DEF 09/10
1234 NULL 10/12
1234 NULL 14/12
The above (when grouped by PO) will tell the users there are two sales orders and four (unlinked) delivery dates.
Likewise if there are more SOs than deliveries, we need NULLs in the Delivery column;
PO SO Delivery
1234 ABC 03/08
1234 DEF NULL
1234 GHI NULL
1234 JKL NULL
Above would be the case with 4 SOs and one delivery date.
Using Left Outer joins alone gives too much duplication - in this case 8 rows, as it gives 4 delivery dates for each match on the sales order;
PO SO Delivery
1234 ABC 05/10
1234 ABC 09/10
1234 ABC 10/12
1234 ABC 14/12
1234 DEF 05/10
1234 DEF 09/10
1234 DEF 10/12
1234 DEF 14/12
It's fine that the PO column is duplicated as SSRS can visually group that - but the SO/Delivery fields can't be allowed to duplicate as this can't be got rid of in the report - if I group the column in SSRS by SO then it still spits out 4 delivery dates for each one.
The only situation our query works nice is when there is just one SO per PO. In that case the single PO and SO numbers are duplicated together for x deliveries and can both be neatly grouped in SSRS. Unfortunately this is a rare occurence in the data.
I've thought of trying to use some sort of windowing function or CROSS APPLY but both fall down as they will repeat for every PO number listed and end up spitting out too much data.
At the point of thinking this just isn't set-based enough to be doable in SQL, I know the data is horrible..
Any help much appreciated.
EDIT - basical sqlfiddle link to the table schemas. Omitted many columns which aren't relevant. http://sqlfiddle.com/#!2/5ba16
Example data...
Purchase Order
PO_Number Style
1001 Black work boots
1002 Green hat
1006 Red Scarf
Sales Order
Sales_order_number PO_number Qty Retailer
A100-21 1001 15 Walmart
A100-22 1001 29 Walmart
A200-31 1006 1000 Asda
Delivery
Delivery_ID Delivery_Date PO_number
1543285 10/05/2014 1001
1543286 12/05/2014 1001
1543287 17/05/2014 1001
1543288 21/05/2014 1002
If you assign row numbers to the elements in salesorders and deliveries, you can link on that.
Something like this
declare #salesorders table (po int, so varchar(10))
declare #deliveries table (po int, delivery date)
declare #purchaseorders table (po int)
insert #purchaseorders values (123),(456)
insert #salesorders values (123,'a'),(123,'b'),(456,'c')
insert #deliveries values (123,'2014-1-1'),(456,'2014-2-1'),(456,'2014-2-1')
select *
from
(
select numbers.number, p.po, so.so, d.delivery from #purchaseorders p
cross join (Select number from master..spt_values where type='p') numbers
left join (select *,ROW_NUMBER() over (partition by po order by so) sor from #salesorders ) so
on p.po = so.po and numbers.number = so.sor
left join (select * , ROW_NUMBER() over (partition by po order by delivery) dor from #deliveries) d
on p.po = d.po and numbers.number = d.dor
) v
where so is not null or delivery is not null
order by po,number