SQL - IN clause with no match - sql

I'm trying to build a query where I can select from a table all products with a certain ID but I would also like to find out what products were not found within the IN clause.
Product Table
ID | Name
---|---------
1 | ProductA
2 | ProductB
4 | ProductD
5 | ProductE
6 | ProductF
7 | ProductG
select *
from products
where id in (2,3,7);
As you can see, product id 3 does not exist in the table.
My query will only return rows 2 and 7.
I would like a blank/null row returned if a value in the IN clause did not return anything.
Desired Results:
ID | Name
---|---------
2 | ProductB
3 | null
7 | ProductG

You can use a left join:
select i.id, p.name
from (select 2 as id union all select 3 union all select 7
) i left join
products p
on p.id = i.id

IN is not useful in this case.
Use a CTE with the ids that you want to search for and left join to the table:
with cte(id) as (select * from (values (2),(3),(7)))
select c.id, p.name
from cte c left join products p
on p.id = c.id
See the demo.
Results:
| id | Name |
| --- | -------- |
| 2 | ProductB |
| 3 | |
| 7 | ProductG |

Related

Postgresql left join

I have two tables cars and usage. I create a record in usage once a month for some of cars.
Now I want to get distinct list of cars with their latest usage that I saved.
first of all look at the tables please
cars:
| id | model | reseller_id |
|----|-------------|-------------|
| 1 | Samand Sall | 324228 |
| 2 | Saba 141 | 92933 |
usages:
| id | car_id | year | month | gas |
|----|--------|------|-------|-----|
| 1 | 2 | 2020 | 2 | 68 |
| 2 | 2 | 2020 | 3 | 94 |
| 3 | 2 | 2020 | 4 | 33 |
| 4 | 2 | 2020 | 5 | 12 |
The problem is here
I need only the latest usage of year and month
I tried a lot of ways but none of them is good enough. because sometimes this query gets me one ofnot latest records of usages.
SELECT * FROM cars AS c
LEFT JOIN
(select *
from usages
) u on (c.id = u.car_id)
order by u.gas desc
You can do this with a DISTINCT ON in the derived table:
SELECT *
FROM cars AS c
LEFT JOIN (
select distinct on (u.car_id) *
from usages u
order by u.car_id, u.year desc, u.month desc
) lu on c.id = lu.car_id
order by u.gas desc;
I think you need window function row_number. Here is the demo.
select
id,
model,
reseller_id
from
(
select
c.id,
model,
reseller_id,
row_number() over (partition by u.car_id order by u.id desc) as rn
from cars c
left join usages u
on c.id = u.car_id
) subq
where rn = 1

How to count records using an l-tree

I have two tables, tickets and categories. The categories table has 3 columns of interest: id, name and path. The data looks like this:
id | Name | Path
------------------
1 | ABC | 1
2 | DEF | 1.2
3 | GHI | 1.2.3
4 | JKL | 4
5 | MNO | 4.5
6 | PQR | 4.5.6
9 | STU | 4.5.9
Note that the path column is an l-tree. What this is meant to represent is that the category with id=2 is a subcategory of id=1 and that id=3 is a subcategory of id=2.
In my tickets table, there's a column called category_id which refers to the id column in my categories table. Each ticket can have up to one category assigned to it (category_id may be null).
I'm trying to count all the tickets for each category.
Suppose my tickets table looks like this:
ticket_id | ticket_title | category_id
1 | A | 1
2 | B | 2
3 | C | 3
4 | D | 5
5 | F | 5
6 | G | 6
7 | H | 9
I would like to output:
category_id | count
1 | 3
2 | 2
3 | 1
4 | 4
5 | 4
6 | 1
9 | 1
I've found that I can get all of the tickets which belong to a given category with the following query: select * from tickets where category_id in (select id from categories where path ~ '*.1.*'); (although now that I'm writing this question I'm not convinced this is correct).
I've also attempted to perform the ticket-count-by-category problem and I came up with:
SELECT
categories.id as cid,
COUNT(*) as tickets_count
FROM tickets
LEFT JOIN categories ON tickets.category_id = categories.id
GROUP BY cid;
which outputs the following:
c_id | count
1 | 1
2 | 1
3 | 1
5 | 2
6 | 1
9 | 1
I'm not very good at SQL. Is it possible to achieve what I want?
Try this:
WITH tickets_per_path AS (
SELECT
c.path AS path,
count(*) AS count
FROM tickets t INNER JOIN categories c ON (t.category_id = c.id)
GROUP BY c.path)
SELECT
c.id,
sum(tickets_per_path.count) AS count
FROM categories c LEFT JOIN tickets_per_path ON (c.path #> tickets_per_path.path)
GROUP BY c.id
ORDER BY c.id;
Which yields the following result:
id| count
1 | 3
2 | 2
3 | 1
4 | 4
5 | 4
6 | 1
9 | 1
It roughly works like this:
the WITH clause computes the number of tickets per path (without
including the count of tickets of descendent paths).
the second select clause joins the categories table with the precomputed tickets_per_path view, but instead of an equi-join on path, it
joins by testing whether a record in the left table (categories) is
an ancestor of the right side table (using #> operator). Then it
groups by category id and sums up the ticket counts by category
including the descendant counts.
You are close, but you need a more general join:
SELECT c.id as cid, COUNT(*) as tickets_count
FROM categories c LEFT JOIN
tickets t
ON t.category_id || '.' LIKE c.id || '.%'
GROUP BY c.id;
The '.' in the comparison is just so 1.100 doesn't match 1.1.

Select query INNER JOIN issue

I have tow tables Requisitions and RequisitionDetails
Requisitions table
+---------------+-----------------+
| RequisitionID | RequisitionDate |
+---------------+-----------------+
| 1 | 2016-08-17 |
| 2 | 2016-08-18 |
| 3 | 2016-08-19 |
+---------------+-----------------+
RequisitionDetails table
+---------------------+---------------+--------+----------+------------------+
| RequisitionDetailID | RequisitionID | ItemID | Quantity | ReceivedQuantity |
+---------------------+---------------+--------+----------+------------------+
| 1 | 1 | 1 | 2 | 1 |
| 2 | 1 | 2 | 3 | 2 |
| 3 | 2 | 3 | 4 | 3 |
+---------------------+---------------+--------+----------+------------------+
I am trying to get Requisition data where Quantity is not equal to ReceivedQuantity.
i have tried the below query but its record with RequisitionID 1 twice.
How can i make the query returns the Requisition data without repeating the requisition data based on items that have Quantity is not equal to ReceivedQuantity.
SELECT
dbo.Requisitions.RequisitionID,
dbo.Requisitions.RequisitionDate
FROM dbo.Requisitions
INNER JOIN dbo.RequisitionDetails
ON dbo.Requisitions.RequisitionID = dbo.RequisitionDetails.RequisitionID
where dbo.RequisitionDetails.Quantity != dbo.RequisitionDetails.ReceivedQuantity
It's returning twice because of the two rows with RequistionID = 1 in the RequistionDetails table. Since the rows returned are exact duplicates you can simply add the DISTINCT keyword to your select to see one of them:
SELECT DISTINCT
dbo.Requisitions.RequisitionID,
dbo.Requisitions.RequisitionDate
FROM dbo.Requisitions
INNER JOIN dbo.RequisitionDetails
ON dbo.Requisitions.RequisitionID = dbo.RequisitionDetails.RequisitionID
where dbo.RequisitionDetails.Quantity!=
dbo.RequisitionDetails.ReceivedQuantity
You should also use some aliases to clean up your query:
SELECT DISTINCT
R.RequisitionID,
R.RequisitionDate
FROM dbo.Requisitions R
INNER JOIN dbo.RequisitionDetails RD ON R.RequisitionID = RD.RequisitionID
WHERE RD.Quantity != RD.ReceivedQuantity
You also can use exists for your case
select
* from requistions rq where exists(
select 1 from RequisitionDetails rd where rd.RequisitionID=rq.RequisitionID
and rd.Quantity!=rd.ReceivedQuantity)
As you don't need columns from the 2nd table you can also switch to EXISTS to avoid DISTINCT:
SELECT req.*
FROM dbo.Requisitions as req
WHERE EXISTS
( SELECT * FROM dbo.RequisitionDetails as req_det
WHERE req.RequisitionID = req_det.RequisitionID
AND Quantity <> ReceivedQuantity
)
Or IN:
SELECT req.*
FROM dbo.Requisitions
WHERE RequisitionID IN
( SELECT RequisitionID
FROM dbo.RequisitionDetails
WHERE Quantity <> ReceivedQuantity
)

Inner Join all children top(n) parent

I need to do a inner join, where i Get a concrete number of "parent elements" but all it's relations.
If i have the table Agent:
IdAgent | Name
1 | Agent1
2 | Agent2
And the table Econ
IdEcon IdAgent Number
1 | 1 | number1
2 | 2 | number21
3 | 2 | number22
4 | 2 | number23
So if I do a:
SELECT * FROM Agent LEFT JOIN Econ
I Will Get 4 rows
IdAgent | Name | IdEcon IdAgent Number
1 | Name 1 | 1 | 1 | number1
2 | Name 2 | 2 | 2 | number21
2 | Name 2 | 3 | 2 | number22
2 | Name 2 | 4 | 2 | number23
But if i do a:
SELECT top(2) * FROM Agent LEFT JOIN Econ
I Will Get 2 rows and i want 4 rows (I want the top 2 only to affect the table Agent)
How can I accomplish that?
Untested:
SELECT * FROM (Select top(2) * FROM Agent) LEFT JOIN Econ
You could select the top two agents separately and use the result as a derived table to join to Econ, as in jarlh's answer, or you could use TOP (2) WITH TIES together with ORDER BY Agent.IdAgent:
SELECT TOP (2) WITH TIES
...
FROM
dbo.Agent
LEFT JOIN dbo.Econ ON ...
ORDER BY
Agent.IdAgent ASC
;

distinct rows with group by

I have one table:
id_object | version | document
------------------------------
1 | 1 | 1
1 | 2 | 2
2 | 1 | 3
2 | 2 | 1
2 | 3 | 2
1 | 1 | 3
I want to show only one row by object with the version (max) and the document. I have tried the following"
Select Distinct
id_object ,
Max(version),
document
From
prods
Group By
id_object, document
and I get this result
1 | 1 | 1
1 | 2 | 2
2 | 1 | 3
2 | 2 | 1
2 | 3 | 2
1 | 1 | 3
As you can see, I'm getting the entire table. My question is, why?
Since you group by id_object and document, you won't get your desired result. That is because document is different for each version.
select x.id_object,
x.maxversion as version,
p.document
from
(
Select id_object, Max(version) as maxversion
From prods
Group By id_object
) x
inner join prods p on p.id_object = x.id_object
and p.version = x.maxversion
You first have to select the id_object with the max(version). That can be joined with the actual data to get the correct document.
You have to do that because you can't select columns that are not in your group by clause, except you use a aggregate function on them (like max() for instance).
(MySQL can select non aggregated columns, but please avoid that since the outcome is not always clear or even predictable)
select prods.id_object, version, document
from prods inner join
(select id_object, max(version) as ver
from prods
group by id_object) tmp on prods.id_object = tmp.id_object and prods.version = tmp.ver
Query:
SQLFIDDLEExample
SELECT p.id_object,
p.version,
p.document
FROM prods p
WHERE p.version = (SELECT Max(version)
FROM prods
WHERE id_object = p.id_object)
Result:
| ID_OBJECT | VERSION | DOCUMENT |
----------------------------------
| 1 | 2 | 2 |
| 2 | 3 | 2 |