SQL inner join using multiple in statements on single table - sql

Having a bit of trouble with an SQL query I am trying to create. The table format is as follows,
ID | Data Identifier | Date Added | Data Column
1 | 1001 | 15400 | Newest Value
1 | 1001 | 15000 | Oldest Value
1 | 1001 | 15200 | Older Value
1 | 1002 | 16000 | Newest Value
2 | 1001 | 16000 | Newest Value
What I am trying to do is, for each ID in a list (1,2) , and for each Data Identifier id in (1001,1002) return just the rows with the first matching field id and date nearest and below 16001.
So the results would be :
1 | 1001 | 15400 | Newest Value
1 | 1002 | 16000 | Newest Value
2 | 1001 | 16000 | Newest Value
I have tried several manner of joins but I keep returning duplicate records. Any advice or help would be appreciated.

It seems as if you want to GROUP BY and maybe a self join onto the table.
I have the following code for you:
-- Preparing a test table
INSERT INTO #tmpTable(ID, Identifier, DateAdded, DataColumn)
SELECT 1, 1001, 15400, 'Newest Value'
UNION
SELECT 1, 1001, 15000, 'Oldest Value'
UNION
SELECT 1, 1001, 15200, 'Older Value'
UNION
SELECT 1, 1002, 16000, 'Newest Value'
UNION
SELECT 2, 1001, 16000, 'Newest Value'
-- Actual Select
SELECT b.ID, b.Identifier, b.DateAdded, DataColumn
FROM
(SELECT ID, Identifier, MAX(DateAdded) AS DateAdded
FROM #tmpTable
WHERE DateAdded < 16001
GROUP BY ID, Identifier) a
INNER JOIN #tmpTable b ON a.DateAdded = b.DateAdded
AND a.ID = b.ID
AND a.Identifier = b.Identifier

You need to create a primary key column on your table that will not be used as an aggregate. Then you can create a CTE to select the rows required and then use it to select the data.
The aggregate function MIN(ABS(15500 - DateAdded)) will return the closest value to 15500.
WITH g AS
(
SELECT MAX(UniqueKey) AS UniqueKey, ID, DataIdentifier, MIN(ABS(15500 - DateAdded)) AS "DateTest"
FROM test
GROUP BY ID, DataIdentifier
)
SELECT test.ID, test.DataIdentifier, test.DateAdded, test.DataColumn
FROM g
INNER JOIN test
ON g.UniqueKey = test.UniqueKey
EDIT:
Screenshot of working example:

I think in this case self-join would be the best, but I still don't get the nearest and below value... (may be 15400)

Related

Select rows where main value has disabled status and sub value is active

I have a table containing customer agreement numbers and a status field indicating whether that agreement is active or not - 1 for active, 0 for disabled.
A main customer number contains 5 digits, from which other subagreements can be made. These other agreements are characterized by a 10 digit number, the first 5 coming from the main number and the last 5 autogenerated.
Note that not all main agreements necessarily have subagreements.
Heres a simplified snippet of the table I currently get from my query:
+-------------+----------+------------+--+
| CustNumber| CustName | CustStatus | |
+-------------+----------+------------+--+
|12345 | Cust1 | 1 | |
|1234500001 | Cust1 | 1 | |
|1234500002 | Cust1 | 0 | |
|12346 | Cust2 | 0 | |<---
|1234600001 | Cust2 | 1 | |<---
|1234600002 | Cust2 | 0 | |
+-------------+----------+------------+--+
Query:
SELECT
custnumber,
custstatus,
custname
FROM table
WHERE LEFT(custnumber, 5) IN (
SELECT LEFT(custnumber, 5)
FROM table
GROUP BY LEFT(custnumber, 5)
HAVING Count(*) > 1
)
ORDER BY custnumber,
custstatus DESC;
From here I'm pretty lost. I'm thinking something along the lines of an inner join on a subquery but I'm really not sure.
What I'm looking for is a query that selects rows with subagreement numbers that are active but where the main agreement number is disabled.
I'm new to SQL and have spend a good while searching around for similar questions, but I actually don't know how to describe this problem in a google-friendly manner.
Join the table with itself - I am using a WITH clause for readability, but that is not necessary - and check the statuses.
with main_rows as
(
select custnumber as main_number, custname, custstatus
from mytable
where length(custnumber) = 5
)
, sub_rows as
(
select
left(custnumber, 5) as main_number,
right(custnumber, 5) as sub_number,
custname,
custstatus
from mytable
where length(custnumber) = 10
)
select
main_number,
m.custname as main_name,
s.sub_number,
s.custname as sub_name
from main_rows m
join sub_rows s using (main_number)
where m.custstatus = 0 and s.custstatus = 1
order by main_number, s.sub_number;
And here is the same thing, but shorter and just not as talkative :-)
select *
from mytable m
join mytable s on s.custnumber like m.custnumber || '_____'
where m.custstatus = 0 and s.custstatus = 1
order by s.custnumber;
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=5873044787e5fd3f32f7648dbc54a7b0
with data (CustNumber, CustName, CustStatus) as(
Select '12345' ,'Cust1',1 union all
Select '1234500001' ,'Cust1',1 union all
Select '1234500002' ,'Cust1',0 union all
Select '12346' ,'Cust2',0 union all
Select '1234600001' ,'Cust2',1 union all
Select '1234600002' ,'Cust2',0
)
,subagg (k,CustNumber, CustName, CustStatus) as(
select Left(CustNumber,5) k,CustNumber, CustName, CustStatus
from data
where len(CustNumber)=10
and CustStatus = 1
)
select s.CustNumber ActiveSunCustomer, d.CustNumber InactivePrimaryCustomer
from subagg s
join data d on d.CustNumber=s.k and d.CustStatus = 0

How to convert JSONB array of pair values to rows and columns?

Given that I have a jsonb column with an array of pair values:
[1001, 1, 1002, 2, 1003, 3]
I want to turn each pair into a row, with each pair values as columns:
| a | b |
|------|---|
| 1001 | 1 |
| 1002 | 2 |
| 1003 | 3 |
Is something like that even possible in an efficient way?
I found a few inefficient (slow) ways, like using LEAD(), or joining the same table with the value from next row, but queries take ~ 10 minutes.
DDL:
CREATE TABLE products (
id int not null,
data jsonb not null
);
INSERT INTO products VALUES (1, '[1001, 1, 10002, 2, 1003, 3]')
DB Fiddle: https://www.db-fiddle.com/f/2QnNKmBqxF2FB9XJdJ55SZ/0
Thanks!
This is not an elegant approach from a declarative standpoint, but can you please see whether this performs better for you?
with indexes as (
select id, generate_series(1, jsonb_array_length(data) / 2) - 1 as idx
from products
)
select p.id, p.data->>(2 * i.idx) as a, p.data->>(2 * i.idx + 1) as b
from indexes i
join products p on p.id = i.id;
This query
SELECT j.data
FROM products
CROSS JOIN jsonb_array_elements(data) j(data)
should run faster if you just need to unpivot all elements within the query as in the demo.
Demo
or even remove the columns coming from products table :
SELECT jsonb_array_elements(data)
FROM products
OR
If you need to return like this
| a | b |
|------|---|
| 1001 | 1 |
| 1002 | 2 |
| 1003 | 3 |
as unpivoting two columns, then use :
SELECT MAX(CASE WHEN mod(rn,2) = 1 THEN data->>(rn-1)::int END) AS a,
MAX(CASE WHEN mod(rn,2) = 0 THEN data->>(rn-1)::int END) AS b
FROM
(
SELECT p.data, row_number() over () as rn
FROM products p
CROSS JOIN jsonb_array_elements(data) j(data)) q
GROUP BY ceil(rn/2::float)
ORDER BY ceil(rn/2::float)
Demo

dividing sum of the column with each part

i have the following table in my database
i am currently using oracle 11g
the data is like this
id valus
1 2 3
100 200 300 = 600
I want to derive new column as: divide each value from the column "value" with the total sum of the column "value". Then load into the another table. The data in other table should look as
id value drived_col
1 100 100/600
2 200 200/600
3 300 300/600
thanks
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE data ( id, value ) AS
SELECT 1, 100 FROM DUAL
UNION ALL SELECT 2, 200 FROM DUAL
UNION ALL SELECT 3, 300 FROM DUAL;
CREATE TABLE derived_data AS
SELECT id,
value,
value/SUM(value) OVER ( ORDER BY NULL ) AS derived_col
FROM data;
Or if the derived_data table already exists then you can do:
INSERT INTO derived_data
SELECT id,
value,
value/SUM(value) OVER ( ORDER BY NULL ) AS derived_col
FROM data;
Query 1:
SELECT * FROM derived_data
Results:
| ID | VALUE | DERIVED_COL |
|----|-------|----------------|
| 1 | 100 | 0.166666666667 |
| 2 | 200 | 0.333333333333 |
| 3 | 300 | 0.5 |
Or if you want the derived_col as a string:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE data ( id, value ) AS
SELECT 1, 100 FROM DUAL
UNION ALL SELECT 2, 200 FROM DUAL
UNION ALL SELECT 3, 300 FROM DUAL;
CREATE TABLE derived_data AS
SELECT id,
value,
value||'/'||SUM(value) OVER ( ORDER BY NULL ) AS derived_col
FROM data;
Query 1:
SELECT * FROM derived_data
Results:
| ID | VALUE | DERIVED_COL |
|----|-------|-------------|
| 1 | 100 | 100/600 |
| 2 | 200 | 200/600 |
| 3 | 300 | 300/600 |
Assuming your table already exists, you want to use an INSERT INTO new_table SELECT to insert the data in the derived table based on a query. For the insertion query to perform the division, it needs two subqueries:
query the sum of the values
query the (id,value) pair
Because the sum of the values is a single value, constant for all rows, you can then join these subqueries together with an INNER JOIN that has no conditions:
INSERT INTO derived_table
SELECT
ot.id AS id,
ot.value AS value,
CAST(ot.value AS float)/summed.total AS derived_col
FROM
orig_table AS ot
INNER JOIN
SELECT sum(value) AS total FROM orig_table AS summed;
The CAST(ot.value AS FLOAT) is necessary if value is a column of integers. Otherwise, your division will be integer division and all of the derived values will be zero.
There is no join condition here because the summation is a single value to all rows of orig_table. If you want to apply different divisors to different rows, you would need a more complicated subquery and an appropriate join condition.

SQL:Query to check if a column meets certain criteria, if it does perform one action if it doesn't perform another

I have found it quite hard to word what I want to do in the title so I will try my best to explain now!
I have two tables which I am using:
Master_Tab and Parts_Tab
Parts_Tab has the following information:
Order_Number | Completed| Part_Number|
| 1 | Y | 64 |
| 2 | N | 32 |
| 3 | Y | 42 |
| 1 | N | 32 |
| 1 | N | 5 |
Master_Tab has the following information:
Order_Number|
1 |
2 |
3 |
4 |
5 |
I want to generate a query which will return ALL of the Order_Numbers listed in the Master_Tab on the following conditions...
For each Order_Number I want to check the Parts_Tab table to see if there are any parts which aren't complete (Completed = 'N'). For each Order_Number I then want to count the number of uncompleted parts an order has against it. If an Order_Number does not have uncompleted parts or it is not in the Parts_Table then I want the count value to be 0.
So the table that would be generated would look like this:
Order_Number | Count_of_Non_Complete_Parts|
1 | 2 |
2 | 1 |
3 | 0 |
4 | 0 |
5 | 0 |
I was hoping that using a different kind of join on the tables would do this but I am clearly missing the trick!
Any help is much appreciated!
Thanks.
I have used COALESCE to convert NULL to zero where necessary. Depending on your database platform, you may need to use another method, e.g. ISNULL or CASE.
select mt.Order_Number,
coalesce(ptc.Count, 0) as Count_of_Non_Complete_Parts
from Master_Tab mt
left outer join (
select Order_Number, count(*) as Count
from Parts_Tab
where Completed = 'N'
group by Order_Number
) ptc on mt.Order_Number = ptc.Order_Number
order by mt.Order_Number
You are looking for a LEFT JOIN.
SELECT mt.order_number, count(part_number) AS count_noncomplete_parts
FROM master_tab mt LEFT JOIN parts_tab pt
ON mt.order_number=pt.order_number AND pt.completed='N'
GROUP BY mt.order_number;
It is also possible to put pt.completed='N' into a WHERE clause, but you have to be careful of NULLs. Instead of the AND you can have
WHERE pt.completed='N' OR pr.completed IS NULL
SELECT mt.Order_Number SUM(tbl.Incomplete) Count_of_Non_Complete_Parts
FROM Master_Tab mt
LEFT JOIN (
SELECT Order_Number, CASE WHEN Completed = 'N' THEN 1 ELSE 0 END Incomplete
FROM Parts_Tab
) tbl on mt.Order_Number = tbl.Order_Number
GROUP BY mt.Order_Number
Add a WHERE clause to the outer query if you need to filter for specific order numbers.
I think it's easiest to get a subquery in there. I think this should be self-explanitory, if not feel free to ask any questions.
CREATE TABLE #Parts
(
Order_Number int,
Completed char(1),
Part_Number int
)
CREATE TABLE #Master
(
Order_Number int
)
INSERT INTO #Parts
SELECT 1, 'Y', 64 UNION ALL
SELECT 2, 'N', 32 UNION ALL
SELECT 3, 'Y', 42 UNION ALL
SELECT 1, 'N', 32 UNION ALL
SELECT 1, 'N', 5
INSERT INTO #Master
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5 UNION ALL
SELECT 6
SELECT M.Order_Number, ISNULL(Totals.NonCompletedCount, 0) FROM #Master M
LEFT JOIN (SELECT P.Order_Number, COUNT(*) AS NonCompletedCount FROM #Parts P
WHERE P.Completed = 'N'
GROUP BY P.Order_Number) Totals ON Totals.Order_Number = M.Order_Number

How do I print out 'NULL' or '0' values for column values when an element isn't found?

I need to loop through a set of values (less than 10) and see if they are in a table. If so, I need to print out all of the record values, but if the item doesn't exist, I still want it to be included in the printed result, although with NULL or 0 values. So, for example, the following query returns:
select *
from ACTOR
where ID in (4, 5, 15);
+----+-----------------------------+-------------+----------+------+
| ID | NAME | DESCRIPTION | ORDER_ID | TYPE |
+----+-----------------------------+-------------+----------+------+
| 4 | [TEST-1] | | 3 | NULL |
| 5 | [TEST-2] | | 4 | NULL |
+----+-----------------------------+-------------+----------+------+
But I want it to return
+----+-----------------------------+-------------+----------+------+
| ID | NAME | DESCRIPTION | ORDER_ID | TYPE |
+----+-----------------------------+-------------+----------+------+
| 4 | [TEST-1] | | 3 | NULL |
| 5 | [TEST-2] | | 4 | NULL |
| 15| NULL | | 0 | NULL |
+----+-----------------------------+-------------+----------+------+
Is this possible?
To get the output you want, you first have to construct a derived table containing the ACTOR.id values you desire. UNION ALL works for small data sets:
SELECT *
FROM (SELECT 4 AS actor_id
FROM DUAL
UNION ALL
SELECT 5
FROM DUAL
UNION ALL
SELECT 15
FROM DUAL) x
With that, you can OUTER JOIN to the actual table to get the results you want:
SELECT x.actor_id,
a.name,
a.description,
a.orderid,
a.type
FROM (SELECT 4 AS actor_id
FROM DUAL
UNION ALL
SELECT 5
FROM DUAL
UNION ALL
SELECT 15
FROM DUAL) x
LEFT JOIN ACTOR a ON a.id = x.actor_id
If there's no match between x and a, the a columns will be null. So if you want orderid to be zero when there's no match for id 15:
SELECT x.actor_id,
a.name,
a.description,
COALESCE(a.orderid, 0) AS orderid,
a.type
FROM (SELECT 4 AS actor_id
FROM DUAL
UNION ALL
SELECT 5
FROM DUAL
UNION ALL
SELECT 15
FROM DUAL) x
LEFT JOIN ACTOR a ON a.id = x.actor_id
Well, for that few values, you could do something ugly like this, I suppose:
SELECT
*
FROM
(
SELECT 4 AS id UNION
SELECT 5 UNION
SELECT 15
) ids
LEFT JOIN ACTOR ON ids.id = ACTOR.ID
(That should work in MySQL, I think; for Oracle you'd need to use DUAL, e.g. SELECT 4 as id FROM DUAL...)
That is only possible using a temporary table.
CREATE TABLE actor_temp (id INTEGER);
INSERT INTO actor_temp VALUES(4);
INSERT INTO actor_temp VALUES(5);
INSERT INTO actor_temp VALUES(15);
select actor_temp.id, ACTOR.* from ACTOR RIGHT JOIN actor_temp on ACTOR.id = actor_temp.id;
DROP TABLE actor_temp;
If you know the upper and lower limits on the ID, it's not too bad. Set up a view with all possible ids - the connect by trick is the simplest way - and do an outer join with your real table. Here, I've limited it to values from 1-1000.
select * from (
select ids.id, a.name, a.description, nvl(a.order_id,0), a.type
from Actor a,
(SELECT level as id from dual CONNECT BY LEVEL <= 1000) ids
where ids.id = a.id (+)
)
where id in (4,5,15);
Can you make a table that contains expected actor ids?
If so you can left join from it.
SELECT * FROM expected_actors LEFT JOIN actors USING (ID)