How to duplicate and merge rows through select query - sql

I've an existing Postgresql select query output that gives me,
+------+------------+------+------+
| Type | ID | Pass | Fail |
+------+------------+------+------+
| IQC | ABC_IQC_R2 | 0 | 6 |
+------+------------+------+------+
| IQC | ABC_IQC_R1 | 2 | 6 |
+------+------------+------+------+
| IQC | ABC_IQC | 498 | 8 |
+------+------------+------+------+
How do I duplicate the row of ID-> ABC_IQC into two while merging both R1 & R2 values into that row? (As shown below)
+------+---------+------------+------+------+--------+--------+
| Type | ID | R_ID | Pass | Fail | R_Pass | R_Fail |
+------+---------+------------+------+------+--------+--------+
| IQC | ABC_IQC | ABC_IQC_R2 | 498 | 8 | 0 | 6 |
+------+---------+------------+------+------+--------+--------+
| IQC | ABC_IQC | ABC_IQC_R1 | 498 | 8 | 2 | 6 |
+------+---------+------------+------+------+--------+--------+
The two logics I can think of is,
Run through the ID to search for ABC (But I'm unsure of how to match them). Duplicate the row ABC_IQC & then merge them using Lateral Join (Still unsure how)
Duplicate a column for ABC_IQC(ID column) from both R2 & R1 (now becoming R_ID). Search ID for the original ABC_IQC row and extract the value of pass and fail into both R2 & R1 row.
Here is my current query to get the initial query output,
SELECT
split_part(NewLotID, '_', 2) AS "Type",
LotSummary ->> 'ID' AS "ID",
LotSummary ->> 'Pass' AS "Pass",
LotSummary ->> 'Fail' AS "Fail"
FROM
(
SELECT
LotSummary,
regexp_replace(LotSummary ->> 'ID','[- ]','_','g') AS NewLotID
.
.
.
I'm not expecting a full answer because I've hardly provided any code, just any ideas or insights that might be helpful! Thank you in advance.

I think you want join:
with q as (
<your query here>
)
select q.type, q.id, qr.id as r_id, q.pass, q.fail,
qr.pass as r_pass, qr.fail as r_fail
from q join
q qr
on q.id = 'ABC_IQC' and qr.id like 'ABC_IQC_%';
You can actually generalize this:
with q as (
<your query here>
)
select q.type, q.id, qr.id as r_id, q.pass, q.fail,
qr.pass as r_pass, qr.fail as r_fail
from q join
q qr
on q.id ~ '^[^_]+_[^_]+$' and
qr.id like q.id || '_%';

Related

Oracle SQL query comparing multiple rows with same identifier

I'm honestly not sure how to title this - so apologies if it is unclear.
I have two tables I need to compare. One table contains tree names and nodes that belong to that tree. Each Tree_name/Tree_node combo will have its own line. For example:
Table: treenode
| TREE_NAME | TREE_NODE |
|-----------|-----------|
| 1 | A |
| 1 | B |
| 1 | C |
| 1 | D |
| 1 | E |
| 2 | A |
| 2 | B |
| 2 | D |
| 3 | C |
| 3 | D |
| 3 | E |
| 3 | F |
I have another table that contains names of queries and what tree_nodes they use. Example:
Table: queryrecord
| QUERY | TREE_NODE |
|---------|-----------|
| Alpha | A |
| Alpha | B |
| Alpha | D |
| BRAVO | A |
| BRAVO | B |
| BRAVO | D |
| CHARLIE | A |
| CHARLIE | B |
| CHARLIE | F |
I need to create an SQL where I input the QUERY name, and it returns any ‘TREE_NAME’ that includes all the nodes associated with the query. So if I input ‘ALPHA’, it would return TREE_NAME 1 & 2. If I ask it for CHARLIE, it would return nothing.
I only have read access, and don’t believe I can create temp tables, so I’m not sure if this is possible. Any advice would be amazing. Thank you!
You can use group by and having as follows:
Select t.tree_name
From tree_node t
join query_record q
on t.tree_node = q.tree_node
WHERE q.query = 'ALPHA'
Group by t.tree_name
Having count(distinct t.tree_node)
= (Select count(distinct q.tree_node) query_record q WHERE q.query = 'ALPHA');
Using an IN condition (a semi-join, which saves time over a join):
with prep (tree_node) as (select tree_node from queryrecord where query = :q)
select tree_name
from treenode
where tree_node in (select tree_node from prep)
group by tree_name
having count(*) = (select count(*) from prep)
;
:q in the prep subquery (in the with clause) is the bind variable to which you will assign the various QUERY values at runtime.
EDIT
I don't generally set up the test case on online engines; but in a comment below this answer, the OP said the query didn't work for him. So, I set up the example on SQLFiddle, here:
http://sqlfiddle.com/#!4/b575e/2
A couple of notes: for some reason, SQLFiddle thinks table names should be at most eight characters, so I had to change the second table name to queryrec (instead of queryrecord). I changed the name in the query, too, of course. And, second, I don't know how I can give bind values on SQLFiddle; I hard-coded the name 'Alpha'. (Note also that in the OP's sample data, this query value is not capitalized, while the other two are; of course, text values in SQL are case sensitive, so one should pay attention when testing.)
You can do this with a join and aggregation. The trick is to count the number of nodes in query_record before joining:
select qr.query, t.tree_name
from (select qr.*,
count(*) over (partition by query) as num_tree_node
from query_record qr
) qr join
tree_node t
on t.tree_node = qr.tree_node
where qr.query = 'ALPHA'
group by qr.query, t.tree_name, qr.num_tree_node
having count(*) = qr.num_tree_node;
Here is a db<>fiddle.

How to select oldest date row from each product using SQL

I would like to get the oldest only one from every type product and sum of the prices listed in listofproduct table. Another thing is to search only between prodacts that has at least one peace on lager.
With the SQL I managed to get all the products has at least one on the stock. But the rest I am stack...
So the sum cold be done later, that was my plan, but if you have better idea feel free to write
Here is my data:
+-------------+----------------+---------------+----------+
| IDProizvoda | NazivProizvoda | DatumKupovine | NaLageru |
+-------------+----------------+---------------+----------+
| 77 | Cokolada | 25-Feb-20 | 2 |
| 44 | fgyhufrthr | 06-Aug-20 | 5 |
| 55 | Auto | 06-Aug-23 | 0 |
| 55 | Auto | 11-Aug-20 | 200 |
| 77 | Cokolada | 06-Aug-27 | 0 |
| 77 | Cokolada | 25-Feb-20 | 10 |
| 77 | Cokolada | 25-Jan-20 | 555 |
| 77 | Cokolada | 25-Mar-20 | 40 |
+-------------+----------------+---------------+----------+
Access.ExeQuery("SELECT * FROM Products " &
"WHERE IDProizvoda IN (SELECT value FROM STRING_SPLIT(#listofproduct, ',')) " &
"AND NaLageru > 0 ")
I tried to add GROUP BY and HAVING but it does not worked because i choose the whole table. But I need Product ID and Stock field for edit it later, to subtract one from the stock for those products.
I would like to get the result:
+-------------+----------------+---------------+----------+
| IDProizvoda | NazivProizvoda | DatumKupovine | NaLageru |
+-------------+----------------+---------------+----------+
| 44 | fgyhufrthr | 06-Aug-20 | 5 |
| 55 | Auto | 11-Aug-20 | 200 |
| 77 | Cokolada | 25-Jan-20 | 555 |
+-------------+----------------+---------------+----------+
Thank you for all the help.
You can do it with a Cross Apply, this would be your SQL query:
Select P.IDProizvoda,
P.NazivProizvoda,
N.DatumKupovine,
N.NaLageru,
N.IDKupovine,
N.CenaPoKomadu
From
products P
Cross Apply
(
Select top 1 DatumKupovine,
NaLageru,
IDKupovine,
CenaPoKomadu
From products P2
where P2.IDProizvoda = P.IDProizvoda
and P2.NaLageru > 0
order by DatumKupovine
) N
group by P.IDProizvoda, P.NazivProizvoda, N.DatumKupovine, N.NaLageru, N.IDKupovine, N.CenaPoKomadu
And this your ExeQuery:
Access.ExeQuery("Select P.IDProizvoda, P.NazivProizvoda, N.DatumKupovine, N.NaLageru, N.IDKupovine, N.CenaPoKomadu From products P " &
" Cross Apply( Select top 1 DatumKupovine, NaLageru, IDKupovine, CenaPoKomadu From products P2 where P2.IDProizvoda = P.IDProizvoda and P2.NaLageru > 0 order by DatumKupovine) N " &
" where P.IDProizvoda in (Select value From STRING_SPLIT(#listofproduct, ',')) " &
" group by P.IDProizvoda, P.NazivProizvoda, N.DatumKupovine, N.NaLageru, N.IDKupovine, N.CenaPoKomadu " )
I think this is just aggregation with a filter:
SELECT IDProizvoda, NazivProizvoda, MAX(DatumKupovine),
SUM(NaLegaru)
FROM Products p
WHERE NaLegaru > 0
GROUP BY IDProizvoda, NazivProizvoda;
This should do it:
with cte as (
SELECT *, row_number() over (
partition by NazivProizvoda
order by DatumKupovine
) as rn
FROM Products
WHERE IDProizvoda IN (
SELECT value
FROM STRING_SPLIT(#listofproduct, ',')
)
AND NaLageru > 0
)
select *
from cte
where rn = 1;
By way of explanation, I'm using a common table expression to select the superset of the data you want by criteria and adding a column that enumerates each row within a group (a group being defined here as having NazivProizvoda be the same) in order of the DatumKupovine). With that done, anything that admits the value of 1 for that enumeration will be the oldest in the group. If you data is such that more than one row can be the oldest, use rank() instead of row_number().

SQL code to find if a series of lists do NOT contain a particular value

I have two tables
Jobs
+-----+------+
| Job | Name |
+-----+------+
| 1 | Foo |
| 2 | Bar |
| 3 | Baz |
| 4 | Qwe |
+-----+------+
Job_Operations
+-----+--------------+
| Job | Work_Center |
+-----+--------------+
| 1 | SomeCenter |
| 1 | Full Kit |
| 2 | SomeCenter |
| 3 | SomeCenter |
| 3 | Full Kit |
+-----+--------------+
The tables are linked on the Job column. How can I find the entries in Jobs without a corresponding 'Full Kit' entry in Job_Operations?
Desired Results
+-----+------+
| Job | Name |
+-----+------+
| 2 | Bar |
| 4 | Qwe |
+-----+------+
This seems like a straight forward NOT EXISTS query
SELECT J.*
FROM Jobs J
WHERE NOT EXISTS(SELECT *
FROM Job_Operations JO
WHERE JO.Job = J.Job
AND JO.Work_Center = 'Full Kit')
Select * from
(
select Jobs.* , job_Operations.Work_Center as wc
from Jobs
left join Job_Operations on Jobs.Job=Job_Operations.Job and Job_Operations.Work_Center='Full Kit'
) as sub1 where wc is null
In the subselect left join tells the SQL server to give me a row for every row in the Jobs table, even if it does not find a corresponding value in the job_Operations. From job_Operations only rows that contain your 'Full Kit' are regarded for the join. If the join fails, SQLsefer just returns a null for the fields in job_Operations. The outer select just fetches those rows.
Another way is to use Exists, see how that works in the other answer. But if you want to learn SQL try to get an understanding of how left, right inner and outer/full join work.
Simple solution in code below.
Also keep in mind that "working" doesn't meant "high performance".
Check SQL-plan on your specific DB.
select j.*
from job j
where j.job not in (select jo.job
from Job_Operations jo
where jo.Work_Center = 'Full Kit');

ORDER BY FIELD LIST - Subquery returns more than 1 row

What i want to do is quite simple:
Write an SQL that will return a bunch of record and order the records by some list of id from the FIELD LIST section of my SQL
TABLE SAMPLE
lessons
+----+----------------------+
| id | name |
+----+----------------------+
| 9 | Greedy algorithms |
| 5 | Maya civilization |
| 3 | eFront Beginner |
| 2 | eFront Intermediate |
+----+----------------------+
mod_comp_rule
+----+---------------------+
| id | lesson_id | comp_id |
+----+---------------------+
| 1 | 3 | 1 |
| 2 | 2 | 1 |
| 3 | 9 | 2 |
+----+---------------------+
WHAT I WANT TO GET FROM MY QUERY
SELECT * FROM lessons ORDER BY FIELD(id,'3','2','9') ASC;
MY SQL
SELECT ls.id, ls.name
FROM lessons ls
ORDER BY FIELD(ls.id,
(SELECT mcr.lesson_id FROM mod_comp_rule mcr
INNER JOIN lessons ls ON ls.id = mcr.lesson_id))
My SQL Query returned the following error
MySQL said: #1242 - Subquery returns more than 1 row
So how can i make my SQL return FIELD(id,'3','2','9') without flagging the more than 1 row error ?
I don't see why FIELD() is needed for this. A correlated query will do what you want:
SELECT ls.id, ls.name
FROM lessons ls
ORDER BY (SELECT mcr.id FROM mod_comp_rule mcr WHERE ls.id = mcr.lesson_id);

Oracle 10 SQL: FULL JOIN through Cross Reference Table

http://sqlfiddle.com/#!4/24637/1
I have three tables, (better details/data shown in sqlfiddle link), one replacing another, and a cross reference table in between. One of the fields in each of the table uses the cross reference (version), and another one of the fields in each of the tables is the same (changeID).
I need a query that when passed a list of new_version + new_changeType, along with the equivalent original_version + old_changeType (if there is an old version equivalent) PLUS any old changeIDs that were 'missed' in the conversion of data.
TABLES (fields on the same line are equivalent)
OLD_table | XREF_table | NEW_Table
original_version | original_version |
changeID | | changeID
OLD_changeType | |
| new_version | new_version
| | NEW_changeType
DATA
111,1,CT1 | 111,AAA | AAA,1,ONE
111,2,CT2 | 222,BBB | AAA,2,TWO
222,1,CT1 | 333,DDD | BBB,1,ONE
222,2,CT2 | | BBB,2,TWO
222,3,CT3 | | CCC,1,ONE
333,1,CT1 | |
444,1,CT1 | |
If passed the following list, the result set should look like so. (order doesnt matter)
AAA,BBB,CCC
| NEW_VERSION | NEW_CHANGE_TYPE| ORIGINAL_VERSION | CHANGEID | OLD_CHANGE_TYPE |
|-------------|----------------|------------------|----------|-----------------|
| AAA | ONE | 111 | 1 | CT1 |
| AAA | TWO | 111 | 2 | CT2 |
| BBB | ONE | 222 | 1 | CT1 |
| BBB | TWO | 222 | 2 | CT2 |
| CCC | ONE | (null) | (null) | (null) |
| (null) | (null) | 222 | 3 | CT3 |
I'm having trouble getting ALL the data required. I've played with the following query, however I seem to either 1) miss a row or 2) get additional rows not matching the requirements.
The following queries I've played with are as follows.
select
a.new_version,
a.Change_type,
c.original_version,
c.changeID,
c.OLD_Change_type
from NEW_TABLE a
LEFT OUTER JOIN XREF_TABLE b on a.new_version = b.new_version
FULL OUTER JOIN OLD_TABLE c on
b.original_version = c.original_version and a.changeID = c.changeID
where (b.new_version in ('AAA','BBB','CCC') or b.new_version is null);
select
a.new_version,
a.Change_type,
c.original_version,
c.changeID,
c.OLD_Change_type
from NEW_TABLE a
FULL JOIN XREF_TABLE b on a.new_version = b.new_version
FULL JOIN OLD_TABLE c on
b.original_version = c.original_version and a.changeID = c.changeID
where (a.new_version in ('AAA','BBB','CCC'));
The first returns one 'extra' row with the 333,DDD data, which is not specified from the input.
The seconds returns one less row (with the changeID from the old table "missed" from when this data was converted over.
Any thoughts or suggestions on how to solve this?
First inner join old_table and xref_table, as you are not interested in any old_table entries without an xref_table entry. Then full outer join new_table. In your WHERE clause be aware that new_table.new_version can be null, so use coalesce to use xref_table.new_version in this case to limit your results to AAA, BBB and CCC. That's all.
select
coalesce(n.new_version, x.new_version) as new_version,
n.change_type,
o.original_version,
o.changeid,
o.old_change_type
from old_table o
inner join xref_table x
on x.original_version = o.original_version
full outer join new_table n
on n.new_version = x.new_version
and n.changeid = o.changeid
where coalesce(n.new_version, x.new_version) in ('AAA','BBB','CCC')
order by 1,2,3,4,5
;
Here is your fiddle: http://sqlfiddle.com/#!4/24637/11.
BTW: Better never use random aliases like a, b and c that don't indicate what table is meant. That makes the query harder to understand. Use the table's first letter(s) or an acronym instead.