Join 3 tables, Create new Row if Unique - sql

I have three tables that I'm trying to join.
I join the first two:
Purchase Orders
+-----+----------+
| ID | location |
+-----+----------+
| 1 | Canada |
| 2 | USA |
+-----+----------+
Stock
+-----+----------+----------------+
| SKU | OnOrder |PurchaseOrderID |
+-----+----------+----------------+
| ABC | 30 |2 |
| DEF | 40 |1 |
+-----+----------+----------------+
And my result is:
+-----+----------+----------------+
| SKU | OnOrder |location |
+-----+----------+----------------+
| ABC | 30 |USA |
| DEF | 40 |Canada |
+-----+----------+----------------+
And now I want to join this result with another table as shown below
ItemOrders
+-----+----------+----------------+
| SKU | Quantity |Location |
+-----+----------+----------------+
| ABC | 88 |USA |
| DEF | 99 |Mexico |
+-----+----------+----------------+
Where:
location of Join1 equals location of Table 3, and
SKU of Join 1 equals Table 3 .
If the match doesn't exist, it should make a new row
So I want my output to be:
+-----+----------+--------------------------+
| SKU | OnOrder |Quantity |Location |
+-----+----------+---------+----------------+
| ABC | 30 |88 |USA |
| DEF | 40 | |Canada |
| DEF | |99 |Mexico |
+-----+----------+---------+----------------+
I really can't wrap my head around this for some reason. I've tried two joins, a merge, two joins and a union. I'm trying to do this in laravel FWIW.
Your help is much appreciated!

I think you would create 2 queries where both return the same columns and then UNION them together.
Wrap that entire thing in a subquery and GROUP BY SKU, Location and use an aggregate function as a way to aggregate the 2 rows into one.
Aggregate functions usually ignore Null values so simply a MAX() might work.
Like,
SELECT SKU, Location, max(OnOrder) as OnOrder, max(Quantity) as Quantity
FROM
(
SELECT SKU, OnOrder, NULL as Quantity, Location
FROM {stuff to build output 1}
UNION ALL
SELECT SKU, NULL as OnOrder, Quantity, Location
FROM {stuff to build output 2}
) rs
GROUP BY SKU, Location;

Related

Join Two Tables with SUM on Second Table

This is such a silly question but I've been battling with it all day.
I have two tables.
products
+-----+---------+
| SKU | Quantity|
+-----+---------+
| ABC | 90 |
| DEF | 40 |
| XYZ | 33 |
+-----+---------+
orders
+-----+----------+
| SKU | OrderID |
+-----+----------+
| ABC | aaa |
| ABC | yyy |
| DEF | xxx |
| DEF | bbb |
| DEF | ccc |
+-----+----------+
I want the output to show all of the fields from table 1 and match the sum of the count of SKU from Table 2. If there's no match from table 2, it should return null or 0. So the output should be:
+-----+---------+-------+
| SKU | Quantity| Count +
+-----+---------+-------+
| ABC | 90 | 2 |
| DEF | 40 | 3 |
| XYZ | 33 | |
+-----+---------+-------+
I wrote the following query (I'm using Laravel, so excuse the Laravel syntax)
$orders=Orders::groupBy('orders.SKU')
->selectRaw('sum(orders.quantity) as quantity_sum, orders.SKU');
$ordersAllProducts = DB::table('products')
->leftJoinSub($orders, 'orders', function ($join) {
$join->on('products.SKU','=','orders.SKU');
})->get();
It almost works, but I'm getting null for the SKU as shown below.
+-----+---------+-------+
| SKU | Quantity| Count +
+-----+---------+-------+
| ABC | 90 | 2 |
| DEF | 40 | 3 |
| | 33 | |
+-----+---------+-------+
Not completely familiar with this ORM but I believe you should say products.SKU here, not orders:
->selectRaw('sum(orders.quantity) as quantity_sum, orders.SKU');
^^^^^^^^^^
Every join has two sides; a left and a right. When you LEFT JOIN you effectively say "give every row on the left, and maybe a matching row on the right, or null if there isn't a match". Orders is from the right side of the join, i.e. the side that is occasionally null
I can provide you native query to get that 3 column result (mysql query)
SKU
Quantity
count ( "-" if count = 0 )
$sql = "select SKU, Quantity,
(select IF(count(*) > 0 , count(*), "-") from orders o where o.SKU =
p.SKU) as count
from products p" ;
$result = DB::select($sql);
dd($result);
If you want to return 0 for zero counts instead of "-" just remove (if condition) from query

Turn results of count distinct into something that can be aggregated

I have a table like this:
+----------+--------------+-------------+
| category | sub_category | customer_id |
+----------+--------------+-------------+
| A | AB2 | A876 |
| A | AB2 | A876 |
| A | AA1 | A876 |
| A | AA1 | A876 |
| A | AC3 | A756 |
| B | AB2 | A876 |
| B | AA1 | A756 |
| B | AB7 | A908 |
| C | AA1 | A756 |
| C | AB7 | A908 |
| C | AC3 | A908 |
+----------+--------------+-------------+
And I want to count distinct customers so I can easily do something like:
SELECT category, sub_category, COUNT(DISTINCT customer_id) as count_of_customers
FROM tbl
GROUP BY category, sub_category
And I get a report that gives me distinct customers for each sub_category and category. But these numbers can no longer be aggregated as there needs to be de-duplication if I just need distinct customers by category only.
For e.g customer_id = 'A876' will be counted twice in category='A' (once in sub_category = 'AB2' and once in sub_category = 'AA1') if I just sum the count_of_customers from my query result.
So here is the question, I would like to make these query results "aggregatable". Looking at the problem, it looks like this just isn't possible but I am wondering if there some clever way of distributing these results across categories? so that in my reporting layer (like an excel pivot table), I can get a result that counts 'A876' once in category='A' but counts it twice when I also include sub_category in the fields. Basically converting the results into something summable.
I should mention that this is an overly simplified example. The solution will need to generalize across n different categories and sub_categories.
I am looking for an output that would easily allow me to get either of the following results in something similar to a pivot table (think tableau-like reporting tools):
+----------+--------------------+
| category | distinct_customers |
+----------+--------------------+
| A | 2 |
| B | 3 |
| C | 2 |
+----------+--------------------+
+--------------+--------------------+
| sub_category | distinct_customers |
+--------------+--------------------+
| AA1 | 2 |
| AB2 | 1 |
| AB7 | 1 |
| AC3 | 2 |
+--------------+--------------------+
My immediate thought is to assign weights to a customer_id depending on how many categories and sub_categories it occurs in but I don't know exactly how I'd go about doing this.
You can do exactly what you want -- assigning weights. But this still won't aggregate correctly. Assuming there are no duplicates:
select category, sub_category,
count(distinct customer_id),
sum(1.0 / num_cs) as weighted_customers
from (select t.*,
count(*) over (partition by customer_id) as num_cs
from t
) t
group by category, sub_category;
This weights by both category and sub_category. Obviously, you can adjust the partition by to weight by just one or the other.

select count of sold products with 2 attributes on different rows

I am trying to generate a report of every product sold of SKUABC in size 34 with inseam 33 (it is available in 33 and 31 inseam).
Table - orders_products
Table - Orders:
+-----------+------------------------+--+
| Orders_id | date_purchased | |
+-----------+------------------------+--+
| 46198 | 2020-10-18 19:43:25 | |
| 46199 | 2020-10-19 19:43:25 | |
| 46200 | 2020-10-22 19:43:25 | |
+-----------+------------------------+--+
Table - orders_products
+--------------------+-----------+-------------+----------------+--+
| orders_products_id | Orders_id | products_id | products_mode | QTY
+--------------------+-----------+-------------+----------------+--+
| 42154907 | 46198 | 878 | SKUABC |1 |
| 42154908 | 46198 | 878 | SKUABC |1 |
| 42154909 | 46198 | 282 | DIFFSKU |1 |
+--------------------+-----------+-------------+----------------+--+
Table - Orders_products_attributes (showing order_id 46198 only):
+------------------------------+-----------+--------------------+-----------------+-----------------------+--+
| orders_products_attribute_id | orders_id | orders_products_id | Product options | Product_options_value | |
+------------------------------+-----------+--------------------+-----------------+-----------------------+--+
| 167618 | 46198 | 42155189 | Color | Green | |
| 167619 | 46198 | 42155189 | Inseam | 33 | |
| 167620 | 46198 | 42155189 | Size | 34 | |
+------------------------------+-----------+--------------------+-----------------+-----------------------+--+
my sql so far:
SELECT distinct o.orders_id, op.products_model, opa.products_options_values, sum(op.products_quantity)
FROM orders o
LEFT JOIN orders_products op
ON o.orders_id = op.orders_id
LEFT JOIN orders_products_attributes opa
on op.orders_id = opa.orders_id
WHERE op.products_model in ('SKUABC')
and opa.`products_options_values` in ('36')
and o.date_purchased > '2020-10-13'
If I add in :
and opa.`products_options_values` in ('31')
it returns no results, the reason being because the inseam and size rows are separate. and the problem with the above code is that it is combining any orders/ordered products where the inseam is both 33 or 31 but I want it to be separate.
My desired out would be
+--------+------------+------------+-------------------+
| model | attribute1 | attribute2 | quantity sold sum |
+--------+------------+------------+-------------------+
| ABCSKU | 34 | 33 | 120 |
+--------+------------+------------+-------------------+
Here is a fun solution: select two products_options_values and label those in different name then everything will be easy
SELECT distinct o.orders_id, op.products_model, opa.products_options_values
AS Inseem,opa2.products_options_values AS Size, sum(op.products_quantity)
FROM orders o
LEFT JOIN orders_products op
ON o.orders_id = op.orders_id
LEFT JOIN orders_products_attributes opa
ON op.orders_id = opa.orders_id
LEFT JOIN orders_products_attributes opa2
ON op.orders_id = opa.orders_id
--your condition below
then just use opa for inseem and opa2 for size.It is stupid but work.You can even make the rows data null by adding some condition with Product option column for easier insert later.

Make a query making groups on the same result row

I have two tables. Like this.
select * from extrafieldvalues;
+----------------------------+
| id | value | type | idItem |
+----------------------------+
| 1 | 100 | 1 | 10 |
| 2 | 150 | 2 | 10 |
| 3 | 101 | 1 | 11 |
| 4 | 90 | 2 | 11 |
+----------------------------+
select * from items
+------------+
| id | name |
+------------+
| 10 | foo |
| 11 | bar |
+------------+
I need to make a query and get something like this:
+--------------------------------------+
| idItem | valtype1 | valtype2 | name |
+--------------------------------------+
| 10 | 100 | 150 | foo |
| 11 | 101 | 90 | bar |
+--------------------------------------+
The quantity of types of extra field values is variable, but every item ALWAYS uses every extra field.
If you have only two fields, then left join is an option for this:
select i.*, efv1.value as value_1, efv2.value as value_2
from items i left join
extrafieldvalues efv1
on efv1.iditem = i.id and
efv1.type = 1 left join
extrafieldvalues efv2
on efv1.iditem = i.id and
efv1.type = 2 ;
In terms of performance, two joins are probably faster than an aggregation -- and it makes it easier to bring in more columns from items. One the other hand, conditional aggregation generalizes more easily and the performance changes by little as more columns from extrafieldvalues are added to the select.
Use conditional aggregation
select iditem,
max(case when type=1 then value end) as valtype1,
max(case when type=2 then value end) as valtype2,name
from extrafieldvalues a inner join items b on a.iditem=b.id
group by iditem,name

SQL Query to Work out Every Product Combination

I require a SQL query to work out every product combination.
I have three product categories (game, accessory, upgrade) and products assigned to each of these three categories:
+----+------------+-----------+------------+
| id | category | product | prod_code |
+----+------------+-----------+------------+
| 1 | game | GTA | 100 |
| 2 | game | GTA1 | 200 |
| 3 | game | GTA2 | 300 |
| 4 | accessory | Play Pad | 400 |
| 5 | accessory | Xbox Pad | 500 |
| 6 | upgrade | Memory | 600 |
| 6 | upgrade | drive | 700 |
+----+------------+-----------+------------+
I want to take one product from each of the categories and work out every single combination:
+----+--------------+
| id | combinations |
+----+--------------+
| 1 | 100,400,600 |
| 2 | 100,500,600 |
| 3 | 100,400,700 |
| 4 | 100,500,700 |
| ? | etc |
+----+--------------+
How would I go about doing this?
Thanks in advance, Stuart
Use a CROSS JOIN:
SELECT CONCAT(t1.[prod_code], ',',
t2.[prod_code], ',',
t3.[prod_code])
FROM (
SELECT [prod_code]
FROM mytable
WHERE category = 'game') AS t1
CROSS JOIN (
SELECT [prod_code]
FROM mytable
WHERE category = 'accessory') AS t2
CROSS JOIN (
SELECT [prod_code]
FROM mytable
WHERE category = 'upgrade') AS t3
ORDER BY t1.[prod_code], t2.[prod_code], t3.[prod_code]
CROSS JOIN of derived tables, one for each category, produces the following cartesian product: 'game' products x 'accessory' products x 'upgrade' products
Demo here