Limit join with pagination - sql

These are my db tables:
Users
| id | nme |
|----|------|
| 1 | Adam |
| 2 | Bob |
| 3 | Jan |
| 4 | Nico |
Products
| id | price |
|----|-------|
| 1 | 500 |
| 2 | 700 |
| 3 | 900 |
Orders
| id | user_id | product_id |
|----|---------|------------|
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 7 | 3 | 1 |
| 8 | 3 | 2 |
| 9 | 3 | 3 |
| 10 | 4 | 3 |
I want to get up to 2 users, and their products bought. I came with this:
SELECT
users.id AS 'user_id', products.id AS 'product_id'
FROM
users
INNER JOIN
orders ON orders.user_id = users.id
INNER JOIN
products ON products.id = orders.product_id
ORDER BY
orders.id
OFFSET 0 ROWS FETCH NEXT 2 ROWS ONLY
But this returns:
| user_id | product_id |
|---------|------------|
| 1 | 1 |
| 1 | 2 |
What I want to get is up to 2 users, not orders. I want to get this:
| user_id | product_id |
|---------|------------|
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 3 | 1 |
| 3 | 2 |
| 3 | 3 |
Any ideas?

I think you don't need a JOIN among tables but only using [Orders] table along with a window function such as DENSE_RANK() as seems the most suitable to filter out to return the result set such as
;WITH cte AS
(
SELECT *,
DENSE_RANK() OVER (ORDER BY [user_id]) AS rn
FROM [Orders]
)
SELECT [user_id], [product_id]
FROM cte
WHERE rn <= 2 -- returns only two user after sorted by their ids
ORDER BY 1, 2
Demo

You may use DENSE_RANK function with your join query as the following:
SELECT user_id, product_id
FROM
(
SELECT U.id AS 'user_id',
P.id AS 'product_id',
DENSE_RANK() OVER (ORDER BY U.id) dr
FROM users U INNER JOIN orders O
ON O.user_id = U.id
INNER JOIN products P
ON P.id = O.product_id
) T
WHERE dr between 1 and 2
ORDER BY user_id, product_id
WHERE dr between 1 and 2 allows you to perform pagination according to the order of user ids, i.e if you want to get users from 3 to 5, it will be WHERE dr between 3 and 5.
Check this demo.

Related

Join across many same tables

Theese are my tables:
users
| id | name |
|----|------|
| 1 | Bob |
| 2 | Adam |
products
| id | Name |
|----|----------|
| 1 | Keyboard |
| 2 | Mouse |
orders
| user_id | product_id |
|---------|------------|
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
I want to query all Bob orders. I can use that:
select
user_id,
product_id
from
orders o
inner join users u on u.id = o.user_id
inner join products p on p.id = o.product_id
where
o.user_id = 1
Now, for performance reasons, multiple orders tables has been added:
orders
| user_id | product_id |
|---------|------------|
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
orders_2
| user_id | product_id |
|---------|------------|
| 1 | 4 |
| 6 | 2 |
| 1 | 7 |
orders_3
| user_id | product_id |
|---------|------------|
| 1 | 8 |
| 2 | 2 |
| 4 | 1 |
How can I get all Bob's orders now? Every order_x has the same design as order. Is it possible to join them all in 1 query?
You can filter and union all orders together and then join with product.
select
user_id,
product_id
from
(
select * from orders o1 where o1.user_id = 1
union all
select * from orders_2 o2 where o2.user_id = 1
union all
select * from orders_3 o3 where o3.user_id = 1
) AS o
inner join users u on u.id = o.user_id
inner join products p on p.id = o.product_id

How do I get rental users in SQL without displaying what has already been returned

I have a 3 tables database. This is a DVD rental database.
I would like to know what DVDs are currently rented to users. I use PL/SQL.
What I can't resolve is to keep the returned DVDs from showing up, especially if the same user has taken out the same DVD again.
User Table: DVD Table: Rent table:
| ID | Name | | ID | Name | | ID | USER_ID | DVD_ID | RENT_RETURN | Rent_RETURN_DATE |
| -- | ----- | | -- | ----- | | -- | ------- | ------ | ----------- | ---------------- |
| 1 | USER1 | | 1 | DVD1 | | 1 | 1 | 1 | -1 | 2020.01.01 |
| 2 | USER2 | | 2 | DVD2 | | 2 | 1 | 1 | 1 | 2020.02.01 |
| 3 | USER3 | | 3 | DVD3 | | 3 | 1 | 1 | -1 | 2020.03.01 |
| 4 | USER4 | | 4 | DVD4 | | 4 | 1 | 2 | -1 | 2020.04.01 |
| 5 | 2 | 3 | -1 | 2020.05.01 |
| 6 | 3 | 4 | -1 | 2020.06.01 |
| 7 | 3 | 2 | -1 | 2020.07.01 |
| 8 | 3 | 4 | 1 | 2020.08.01 |
What I want to reach:
| USER_NAME | DVD_NAME | RENT_DATE |
| --------- | -------- | ---------- |
| 1 | 1 | 2020.03.01 |
| 1 | 2 | 2020.04.01 |
| 2 | 3 | 2020.05.01 |
| 3 | 2 | 2020.07.01 |
I tried this but yes it's not enough:
SELECT U.NAME, D.NAME, R.RENT_RETURN_DATE
FROM USER U, DVD D, RENT R
WHERE U.ID = R.USER_ID
AND D.ID = R.DVD_ID
AND R.RENT_RETURN = 1;
Thanks in advance for your help!
One option would be using ROW_NUMBER() analytic function such as
SELECT user_name, dvd_name, rent_return_date
FROM (SELECT ROW_NUMBER() OVER
(PARTITION BY user_id, dvd_id
ORDER BY rent_return_date DESC) AS rn,
u.name AS user_name, d.name AS dvd_name, r.*
FROM rent r
JOIN "user" u
ON r.user_id = u.id
JOIN dvd d
ON r.dvd_id = d.id
WHERE rent_return = -1)
WHERE rn = 1
or directly use aggregation
SELECT u.name AS user_name,
d.name AS dvd_name,
MAX(rent_return_date) AS rent_return_date
FROM rent r
JOIN "user" u
ON r.user_id = u.id
JOIN dvd d
ON r.dvd_id = d.id
WHERE rent_return = -1
GROUP BY u.name,d.name
SELECT user_name, dvd_name, rent_return_date
FROM (SELECT ROW_NUMBER() OVER
(PARTITION BY user_id, dvd_id
ORDER BY rent_return_date DESC) AS rn,
u.name AS user_name, d.name AS dvd_name, r.*
FROM rent r
JOIN "user" u
ON r.user_id = u.id
JOIN dvd d
ON r.dvd_id = d.id
WHERE rent_return = -1)
WHERE rn = 1

SQL join master details table

I have the following two tables in SQL server:
Product:
SELECT Id, Name From Product
x------x--------------------x
| Id | Name |
x------x--------------------x
| 1 | A |
| 2 | B |
| 3 | C |
x------x--------------------x
Order:
SELECT Id, Order_Date, QTY From Order
x------x--------------------x-------x
| Id | Order_Date | QTY |
x------x--------------------x-------x
| 1 | 2014-01-13 | 10 |
| 1 | 2014-01-11 | 15 |
| 1 | 2014-01-12 | 20 |
| 2 | 2014-01-06 | 30 |
| 2 | 2014-01-08 | 40 |
x------x--------------------x-------x
I would like to get a table which contains list of all products and if there is any order placed for the product.
Example:
x------x--------------------x
| Id | Has_Order |
x------x--------------------x
| 1 | 1 |
| 2 | 1 |
| 3 | 0 |
x------x--------------------x
I tried left outer join but it includes all the rows from Order table. What is the most efficient way to write this SQL query?
You can use exists in a subquery:
select p.*,
(case when exists (select 1 from orders o where o.id = p.id)
then 1 else 0
end) as order_exists_flag
from products p;
For performance, you want an index on orders(id). I would expect this to be the fastest approach.
Just another option to consider
Select ID
,Has_Order = max(Flg)
From (
Select ID,Flg = 0 From [Product]
Union All
Select Distinct ID,Flg = 1 From [Order]
) src
Group By ID

SQL join exclude results

This is probably something really easy but I cant figure it out at the moment.
Table Order
+----+---------+
| id | name |
+----+---------+
| 1 | Order 1 |
| 2 | Order 2 |
| 3 | Order 3 |
+----+---------+
Table Facturationdetails
+----+----------------+
| id | name |
+----+----------------+
| 1 | Transportation |
| 2 | Regular |
| 3 | Fixed |
+----+----------------+
Table Relation:
Table Facturationdetails
+----------+---------+
| order_id | fact_id |
+----------+---------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 2 |
| 2 | 3 |
| 3 | 2 |
+----------+---------+
Now I would like to find out for which order there are no fakturationdetails1(Transportation)
select to.order_id
from table_order to
join table_facturation tf
on tf.order_id = to.order_id
where tf.fakt_id != 1
But this will return all rows:
+---+---+
| 1 | 2 |
| 1 | 3 |
| 2 | 2 |
| 2 | 3 |
| 3 | 2 |
+---+---+
And I want the results to be:
Order 2 and Order 3.
I suspect you want to use NOT EXISTS, so rather than finding rows where the fact_id isn't 1, find orders from the table where the fact_id is 1, then exclude those orders:
SELECT o.order_id
FROM table_order o
WHERE NOT EXISTS
( SELECT 1
FROM table_facturation tf
WHERE tf.order_id = o.order_id
AND tf.fakt_id = 1
);
You have not specified a DBMS, but if you are using MySQL you will get better performance using LEFT JOIN\IS NULL:
SELECT o.order_id
FROM table_order o
LEFT JOIN table_facturation tf
ON tf.order_id = o.order_id
AND tf.fakt_id = 1
WHERE tf.order_id IS NULL;
One way to approach this is with a left join and comparison in the where clause. Look for things that match, and then choose those with no match:
select to.order_id
from table_order to left join
table_facturation tf
on tf.order_id = to.order_id and
tf.fakt_id = 1
where tf.fakt_id is null;

Generate report using SUM

I've got two SQL Server 2005 tables: MainTable and Orders.
MainTable:
| MainID | Serial |
-------------------
| 1 | A00001 |
| 2 | B00002 |
Orders:
| OrderID | MainID | Name | Value |
-----------------------------------
| 1 | 2 | John | 100 |
| 2 | 2 | Mike | 200 |
| 3 | 1 | John | 150 |
| 4 | 1 | Mike | 350 |
| 5 | 1 | John | 200 |
| 6 | 2 | John | 500 |
| 7 | 1 | Mike | 50 |
I want to get something like this:
|Serial | Name | Total |
-----------------------
| A00001 | John | 350 |
| A00002 | John | 600 |
| A00001 | Mike | 400 |
| A00002 | Mike | 200 |
SELECT
m.serial,
o.name,
SUM(o.value)
FROM
main m
INNER JOIN order o ON m.mainid = o.mainid
GROUP BY
o.name,
m.serial
select serial, name, sum(value) as total
from maintable m inner join orders o on
m.mainID = o.mainID
group by
serial, name
SELECT
M.SERIAL, O.NAME, SUM(VALUE) AS TOTAL
FROM MAINTABLE M JOIN ORDERS O ON O.MAINID=M.MAINID
GROUP BY M.SERIAL, O.NAME
select Serial , name , Total
from MainTable m,
( select MainID ,name, SUM(value) Total from Orders o group by MainID , Name ) O
where m.MainID= O.MainID