Create nested table from header and details table - google-bigquery

I would like to create a nested table with order data.
I have orders and order_details tables. The key is order_id. When I explicit write the field names it works fine but my tables have like 20 fields each. Is there a way to select all fields?
This what I do now (example with a few fields):
with orders as(
select
order_id
,order_date
,store
from `orders`
)
, order_details as(
select
order_id
,order_date
,product_id
,amount
from `order_details`
)
select
orders.order_id
,orders.order_date
,orders.store
,array_agg(struct(order_details.product_id,order_details.amount)) as order_details
from orders
left join order_details using(order_id)
group by 1,2,3
I would like to do something like this:
with orders as(
select
*
from `orders`
)
, order_details as(
select
*
from `order_details`
)
select
orders.*
,array_agg(struct(select order_details.*) as order_details
from orders
left join order_details using(order_id)
group by 1,2,3,4,5.........................
Any suggestions how to do this? The group-by will have to include all fields from the orders table. Is there a way to this in a better way?

Consider below approach - does exactly what you ask
select
any_value(o).*,
array_agg(d) as order_details
from orders o
left join order_details d
using(order_id)
group by to_json_string(o)

Related

SQL order minimum Query

I'm trying to query the products not ordered since 01/01/2017 in SSMS.
So far I've come up with a couple of select count queries but have come up empty trying to figure this out. This has 3 tables attached to the query and I need the product names and IDs attached to the query result.
products table columns:
[product_id], [product_name], [brand_id], [category_id], [model_year], [list_price]
orders table columns:
[order_id], [item_id], [product_id], [quantity], [list_price], [discount]
order_items table columns:
[order_id], [customer_id], [order_status], [order_date], [required_date], [shipped_date],[store_id], [staff_id]
SELECT COUNT (*)
FROM production.products
WHERE NOT EXISTS (SELECT 1
FROM sales.orders
JOIN sales.order_items oi ON orders.order_id = oi.order_id
WHERE orders.order_id = oi.order_id
AND orders.order_date > '2017-01-01');
Break it down into three parts
Order items and orders joined in the date range
Products not in #1
WITH prodcuts_in_date_range as
(
SELECT DISTINCT oi.Product_id
FROM Order_items oi INNER JOIN Orders o on o.Order_id = oi.Order_id
WHERE oi.Order_date > '2017-01-01')
)
SELECT * FROM Products p where p.Product_Id not in (SELECT Product_id from
products_in_date_range)

Two Table Join with Sub-query?

So I am working on an exercise to improve my SQL skills and I need to modify my query to add a subquery that looks for the existence of a customer ID in the CUSTOMERS table. The exercise also notes that this is a correlated sub-query so you will have to match the customer ID from the sub-query to the customer ID in the outer query. The title of the section of this exercise is "Two Table Join with Subquery". After researching and trying for a few hours now, I've exhausted all my resources but one. Any help would be much appreciated! (I am using Oracle Apex for this.)
SELECT ORDER_ID, ORDER_MODE, CUSTOMER_ID, PRODUCT_ID
FROM ORDERS
NATURAL JOIN ORDER_ITEMS;
Using exists you can check for existence,
SELECT O.ORDER_ID, O.ORDER_MODE, O.CUSTOMER_ID, OI.PRODUCT_ID
FROM ORDERS O
INNER JOIN ORDER_ITEMS OI on O.ORDER_ID=OI.ORDER_ID
WHERE EXISTS(
SELECT * FROM CUSTOMERS C
WHERE O.CUSTOMER_ID=C.CUSTOMER_ID
);
This would do the second,
SELECT O.ORDER_ID, O.ORDER_MODE, O.CUSTOMER_ID, OI.PRODUCT_ID,P.TRANSLATED_NAME
FROM ORDERS O
INNER JOIN ORDER_ITEMS OI on O.ORDER_ID=OI.ORDER_ID
INNER JOIN PRODUCT_DESCRIPTIONS P on P.PRODUCT_ID=OI.PRODUCT_ID
WHERE EXISTS(
SELECT * FROM CUSTOMERS C
WHERE O.CUSTOMER_ID=C.CUSTOMER_ID
);

SQL sum from single table with join

I have two table orders and orderitems.
order table has id,order_total,recieved_amount
orderitems table has id,order_id,name,total_item
I want the sum of recieved_amount, order_total from the order table and sum of total_item from orderitems. so I used
SELECT SUM(`received_amount`) as totalRecieved,
SUM(`order_total`) as orderTotal
FROM `orders` AS `Order`
LEFT JOIN `order_items` AS `OrderItems`
ON (`OrderItems`.`order_id`=`Order`.`id`)
and
SELECT SUM(`received_amount`) as totalRecieved,
SUM(`order_total`) as orderTotal
FROM `orders` AS `Order`
LEFT JOIN `order_items` AS `OrderItems`
ON (`OrderItems`.`order_id`=`Order`.`id`)
group by order.id
but none of them is giving me the correct result.
You are aggregating at two levels of your data hierarchy. This causes a problem with Cartesian products, for each order.
The solution is to aggregate along order_items before doing the join:
SELECT SUM(received_amount) as totalRecieved,
SUM(order_total) as orderTotal
FROM orders o LEFT JOIN
(SELECT oi.order_id, SUM(total_items) as total_items
FROM order_items oi
GROUP BY oi.order_id
) oi
ON oi.order_id = o.id;
As an alternative, you can benefit from cte structure or a temp table like below:
;with cte (order_id, SumTotalItems) as (
SELECT oi.order_id, SUM(total_items) as SumTotalItems
FROM order_items oi
GROUP BY oi.order_id
)
select sum(o.receivedamount) as SumReceivedAmount
, sum(o.order_total) as SumOrderTotal
, cte.SumTotalItems
from orders o
left outer join cte on o.id = cte.order_id

Select only those records where all linked records fulfill a condition

I have a classical 1:n relation between two tables as given below:
I need to select only those customers where 'NOT OrderDate IS NULL' for all their orders. I started with
SELECT Customers.Id, Customers.LastName
FROM Customers, Orders
WHERE Customers.Id = Orders.CustomerId AND NOT Orders.OrderDate IS NULL AND ...
and wanted to build further with 'FOR ALL' but failed.
I tried the suggestions given in the answers but none gave the correct results.
Here is my workaround with two temp tables as shown below:
DECLARE #TableA TABLE (
Id int,
CountA int
)
DECLARE #TableB TABLE (
Id int,
CountB int
)
INSERT INTO #TableA (Id, CountA)
SELECT Customers.Id, COUNT(Orders.Id)
FROM Customers INNER JOIN
Orders ON Customers.Id = Orders.CustomerId
GROUP BY Customers.ID
INSERT INTO #TableB (Id, CountB)
SELECT Customers.Id, COUNT(Orders.Id)
FROM Customers INNER JOIN
Orders ON Customers.Id = Orders.CustomerId
WHERE (NOT Orders.OrderDate IS NULL)
GROUP BY Customers.ID
Select tA.Id
FROM #TableA tA INNER JOIN #TableB tB on tA.Id = tB.Id
WHERE tA.CountA = tB.CountB
Both temp tables differ only in that respect that the first selects the group count without a condition in Orders and the second temp selects them with a condition. Then joining the two temp tables where CountA = CountB gives only those customers where all related Orders fulfill the condition.
If someone finds a more elegant way, please let me know.
Any suggestions how to tackle this?
In cases like this, you need to think not of finding the records where all the related records meet a condition.
Instead, think of finding all the records WHERE there does NOT EXIST a related record that breaks the condition.
The most straightforward way to write this query, is with ALL:
SELECT Customers.Id, Customers.LastName
FROM Customers
WHERE '2000-01-01' < ALL(SELECT OrderDate FROM Orders WHERE Orders.CustomerId = Customers.Id)
You could also write it as a group query on the orders table, something like
WITH CustomerOrderDateRange(CustomerId, MinOrderDate, MaxOrderDate) AS (
SELECT CustomerId, MIN(OrderDate), MAX(OrderDate)
FROM Orders
GROUP BY CustomerId
)
SELECT Customers.Id, Customers.LastName
FROM Customers
JOIN CustomerOrderDateRange
ON Customers.Id = CustomerOrderDateRange.CustomerId
WHERE
CustomerOrderDateRange.MinOrderDate > '2000-01-01'
I think this is cleaner if you need multiple criteria, for example a max date range as well.
Just locate the records you want to exclude from it, and put in a not in clause
select *
from Customers
where Customers.Id not in (
SELECT Customers.Id
FROM Customers
join Orders on Customers.Id = Orders.CustomerId
WHERE Orders.OrderDate < '2000-01-01'
group by Customers.Id
)

Select all orders by one customer

I have three tables, orders, orders_details and customers. I need to select orders by one customer for the orders table so I did this
orders columns:
id
customer_id
created
vat
discount
amount
paid
orders_details columns:
id
order_id
cost
qty
product
The SQL I used
SELECT
orders.*,
SUM(orders_details.qty*orders_details.cost) as amount,
SUM(orders_details.qty) AS qty
FROM
orders,
orders_details,
customers
WHERE
orders.customer_id = customers.id
AND orders_details.order_id = orders.id
AND orders.customer_id = 1
but I am getting a wrong qty of 30 instead of 20 and the amount is wrong
If you want to aggregate per order you need a GROUP BY clause. Also you should use proper JOIN syntax, and might consider using aliases to make the query more compact.
SELECT
o.*,
SUM(od.qty * od.cost) AS amount,
SUM(od.qty) AS qty
FROM orders o
INNER JOIN orders_details od ON od.order_id = o.id
INNER JOIN customers c ON o.customer_id = c.id -- not used, might be excluded
WHERE o.customer_id =1
GROUP BY o.id
Depending on what database system you are using you might need to include all columns referenced in o.* in the GROUP BY:
GROUP BY o.id, o.customer_id, o.created, o.vat, o.discount, o.amount, o.paid
Last note: as you don't seem to use any data from the customers table you probably could exclude that table altogether.
You're missing a GROUP BY clause which I'm guessing should be on orders.id.