Retrieve rows based on multiple data patterns - sql

I have the following table data:
Qns: How do I retrieve only the rows highlighted in Yellow?
Thoughts: What I did try was to do a SUM(A), SUM(B), SUM(C) columns and group by ID_2 column. After which, filter using a WHERE clause on B=1 OR A=1 to retrieve the records. However, I am getting also getting the first record (A23,C1) too based on the select criteria. This is what I would like to avoid retrieving.
1) The first criteria is always to retrieve B=1 as priority for the same ID_2.
2) The second criteria is to retrieve those with A=1 only taking into account that of the first criteria. Hence, based on ID_2, if there is a valid B=1 records that has already been retrieved, it should not retrieve twice.
In this case, A34 is preferred rather than A23
(For instance, if based on ID_2, A or B both have 1, I would ONLY want
the row
whereby B=1. In addition, A67,A89 are retrieved too.
Appreciate the help in the generation of this SELECT statement.

I think this is what you want:
Select rows with B = 1
Select also rows with A = 1 provided that there is no other row
for the same ID_2 that has B = 1
If so, then this can be accomplished with this SQL:
SELECT ID_1, ID_2, A, B, C
FROM tableName t1
WHERE B = 1
OR ( A = 1
AND Not Exists (
SELECT 1
FROM tableName t2
WHERE t1.ID_2 = t2.ID_2
AND B = 1)
)
You can make an aggregated version as follows:
SELECT ID_2, SUM(A), SUM(B), SUM(C)
FROM tableName t1
WHERE B = 1
OR ( A = 1
AND Not Exists (
SELECT 1
FROM tableName t2
WHERE t1.ID_2 = t2.ID_2
AND B = 1)
)
GROUP BY ID_2

Related

Looking for performance improvements in the SQL

Scenario:
There are 2 columns in the table with data as given in the sample below.
It is possible that the table has multiple rows for the same value of 'a' column.
In the example, Considering the 'a' column, There are three rows for '1' and one row for '2'.
Sample table 't1':
|a|b |
|1|1.1|
|1|1.2|
|1|2.2|
|2|3.1|
Requirement is to get following output:
Expected Query output:
|a|b |
|1|1.2|
|2|3.1|
Requirement:
Get the row if there is only one row for a given value for column 'a'.
If there are multiple rows for the same value for column 'a' and for all rows, FLOOR(b) == a, then get MIN(a) and MAX(b)
If there are multiple rows for column 'a' and for all rows, there is 1 row of column 'b' for which
FLOOR(b) > a, then ignore that row. from the remaining rows, get MIN(a) and MAX(b)
Query I used:
select distinct min(a) over(partition by table1.a) as a,
min(b) over(partition by table1.a) as b
from (
SELECT distinct Min(table2.a) OVER (PARTITION BY table2.a) AS a,
Max(table2.b) OVER (PARTITION BY table2.a) AS b
FROM t1 table2
union
SELECT distinct Min(table3.a) OVER (PARTITION BY table3.a) AS a,
Max(table3.b) OVER (PARTITION BY table3.a) AS b
FROM t1 table3
where table3.a = FLOOR(table3.b)
) table1;
This query is working and I am getting the desired output. Looking for inputs to improve by removing union and extra select from the above script.
Note: t1 is not a table but it's a procedure call in my case and there are additional columns that it returns. It would help if the extra call to the procedure can be avoided.
This is how I would get the data you need.
select t1.a, max(t1.b)
from (select a, b, count(1) over(partition by t1.a) cnt from t1) t1
where t1.a = floor(t1.b) or cnt = 1
group by t1.a ,cnt;
It has only one procedure call so it might run significantly faster
And please note that "union" clause not only appends two data sets, but removes duplicates as well. Removing duplicates causes additional checks between data sets and therefore is leading to performance issues.
It is in most cases better to use "union all" which doesn't check for duplicates

How to return two values from PostgreSQL subquery?

I have a problem where I need to get the last item across various tables in PostgreSQL.
The following code works and returns me the type of the latest update and when it was last updated.
The problem is, this query needs to be used as a subquery, so I want to select both the type and the last updated value from this query and PostgreSQL does not seem to like this... (Subquery must return only one column)
Any suggestions?
SELECT last.type, last.max FROM (
SELECT MAX(a.updated_at), 'a' AS type FROM table_a a WHERE a.ref = 5 UNION
SELECT MAX(b.updated_at), 'b' AS type FROM table_b b WHERE b.ref = 5
) AS last ORDER BY max LIMIT 1
Query is used like this inside of a CTE;
WITH sql_query as (
SELECT id, name, address, (...other columns),
last.type, last.max FROM (
SELECT MAX(a.updated_at), 'a' AS type FROM table_a a WHERE a.ref = 5 UNION
SELECT MAX(b.updated_at), 'b' AS type FROM table_b b WHERE b.ref = 5
) AS last ORDER BY max LIMIT 1
FROM table_c
WHERE table_c.fk_id = 1
)
The inherent problem is that SQL (all SQL not just Postgres) requires that a subquery used within a select clause can only return a single value. If you think about that restriction for a while it does makes sense. The select clause is returning rows and a certain number of columns, each row.column location is a single position within a grid. You can bend that rule a bit by putting concatenations into a single position (or a single "complex type" like a JSON value) but it remains a single position in that grid regardless.
Here however you do want 2 separate columns AND you need to return both columns from the same row, so instead of LIMIT 1 I suggest using ROW_NUMBER() instead to facilitate this:
WITH LastVals as (
SELECT type
, max_date
, row_number() over(order by max_date DESC) as rn
FROM (
SELECT MAX(a.updated_at) AS max_date, 'a' AS type FROM table_a a WHERE a.ref = 5
UNION ALL
SELECT MAX(b.updated_at) AS max_date, 'b' AS type FROM table_b b WHERE b.ref = 5
)
)
, sql_query as (
SELECT id
, name, address, (...other columns)
, (select type from lastVals where rn = 1) as last_type
, (select max_date from lastVals where rn = 1) as last_date
FROM table_c
WHERE table_c.fk_id = 1
)
----
By the way in your subquery you should use UNION ALL with type being a constant like 'a' or 'b' then even if MAX(a.updated_at) was identical for 2 or more tables, the rows would still be unique because of the difference in type. UNION will attempt to remove duplicate rows but here it just isn't going to help, so avoid that wasted effort by using UNION ALL.
----
For another way to skin this cat, consider using a LEFT JOIN instead
SELECT id
, name, address, (...other columns)
, lastVals.type
, LastVals.last_date
FROM table_c
WHERE table_c.fk_id = 1
LEFT JOIN (
SELECT type
, last_date
, row_number() over(order by last_date DESC) as rn
FROM (
SELECT MAX(a.updated_at) AS last_date, 'a' AS type FROM table_a a WHERE a.ref = 5
UNION ALL
SELECT MAX(b.updated_at) AS last_date, 'b' AS type FROM table_b b WHERE b.ref = 5
)
) LastVals ON LastVals.rn = 1

SQL Get rows based on conditions

I'm currently having trouble writing the business logic to get rows from a table with id's and a flag which I have appended to it.
For example,
id: id seq num: flag: Date:
A 1 N ..
A 2 N ..
A 3 N
A 4 Y
B 1 N
B 2 Y
B 3 N
C 1 N
C 2 N
The end result I'm trying to achieve is that:
For each unique ID I just want to retrieve one row with the condition for that row being that
If the flag was a "Y" then return that row.
Else return the last "N" row.
Another thing to note is that the 'Y' flag is not always necessarily the last
I've been trying to get a case condition using a partition like
OVER (PARTITION BY A."ID" ORDER BY A."Seq num") but so far no luck.
-- EDIT:
From the table, the sample result would be:
id: id seq num: flag: date:
A 4 Y ..
B 2 Y ..
C 2 N ..
Using a window clause is the right idea. You should partition the results by the ID (as you've done), and order them so the Y flag rows come first, then all the N flag rows in descending date order, and pick the first for each id:
SELECT id, id_seq_num, flag, date
FROM (SELECT id, id_seq_num, flag, date,
ROW_NUMBER() OVER (PARTITION BY id
ORDER BY CASE flag WHEN 'Y' THEN 0
ELSE 1
END ASC,
date ASC) AS rk
FROM mytable) t
WHERE rk = 1
My approach is to take a UNION of two queries. The first query simply selects all Yes records, assuming that Yes only appears once per ID group. The second query targets only those ID having no Yes anywhere. For those records, we use the row number to select the most recent No record.
WITH cte1 AS (
SELECT id
FROM yourTable
GROUP BY id
HAVING SUM(CASE WHEN flag = 'Y' THEN 1 ELSE 0 END) = 0
),
cte2 AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY t1.id ORDER BY t1."id seq" DESC) rn
FROM yourTable t1
INNER JOIN cte1 t2
ON t1.id = t2.id
)
SELECT *
FROM yourTable
WHERE flag = 'Y'
UNION ALL
SELECT *
FROM cte2 t2
WHERE t2.rn = 1
Here's one way (with quite generic SQL):
select t1.*
from Table1 as t1
where t1.id_seq_num = COALESCE(
(select max(id_seq_num) from Table1 as T2 where t1.id = t2.id and t2.flag = 'Y') ,
(select max(id_seq_num) from Table1 as T3 where t1.id = t3.id and t3.flag = 'N') )
Available in a fiddle here: http://sqlfiddle.com/#!9/5f7f9/6
SELECT DISTINCT id, flag
FROM yourTable

How to get not equal rows in SQL query

I have 2 tables and I want not equal rows to be fetched. How to write a query?
For example, table a contain 10 rows, table b contain 10 rows.
Equal rows in a and b is 5.
I want to take a not equal rows (not in b table)
How to fetch a table value which is not equal to b table ?
Result should be 5 record
To take rows in A but not in B:
select * from A minus select * from B
To take rows in A and B but not in both:
(select * from A union select * from B) minus (select * from A intersect select * from B)
This problem has been solved long ago. The optimal solution only reads each table once (unlike the "symmetric difference" solution which reads each table twice and does some additional work).
select 'A' as source, col1, col2, ...
from table_A
union all
select 'B' as source, col1, col2, ...
from table_B
group by col1, col2, ...
having count(*) = 1
;
If a row is present in both tables, then the count will be 2.
This assumes there are no duplicate rows in either table; if there may be duplicate rows, the HAVING condition can be modified, for example:
having count(case when source = 'A' then 1 end) = 0
or count(case when source = 'B' then 1 end) = 0
use EXCEPT
the syntax is similar to INTERSECT.
https://www.tutorialspoint.com/sql/sql-intersect-clause.htm

Merge multiple rows with same ID into one row

How can I merge multiple rows with same ID into one row.
When value in first and second row in the same column is the same or when there is value in first row and NULL in second row.
I don't want to merge when value in first and second row in the same column is different.
I have table:
ID |A |B |C
1 NULL 31 NULL
1 412 NULL 1
2 567 38 4
2 567 NULL NULL
3 2 NULL NULL
3 5 NULL NULL
4 6 1 NULL
4 8 NULL 5
4 NULL NULL 5
I want to get table:
ID |A |B |C
1 412 31 1
2 567 38 4
3 2 NULL NULL
3 5 NULL NULL
4 6 1 NULL
4 8 NULL 5
4 NULL NULL 5
I think there's a simpler solution to the above answers (which is also correct). It basically gets the merged values that can be merged within a CTE, then merges that with the data not able to be merged.
WITH CTE AS (
SELECT
ID,
MAX(A) AS A,
MAX(B) AS B,
MAX(C) AS C
FROM dbo.Records
GROUP BY ID
HAVING MAX(A) = MIN(A)
AND MAX(B) = MIN(B)
AND MAX(C) = MIN(C)
)
SELECT *
FROM CTE
UNION ALL
SELECT *
FROM dbo.Records
WHERE ID NOT IN (SELECT ID FROM CTE)
SQL Fiddle: http://www.sqlfiddle.com/#!6/29407/1/0
WITH Collapsed AS (
SELECT
ID,
A = Min(A),
B = Min(B),
C = Min(C)
FROM
dbo.MyTable
GROUP BY
ID
HAVING
EXISTS (
SELECT Min(A), Min(B), Min(C)
INTERSECT
SELECT Max(A), Max(B), Max(C)
)
)
SELECT
*
FROM
Collapsed
UNION ALL
SELECT
*
FROM
dbo.MyTable T
WHERE
NOT EXISTS (
SELECT *
FROM Collapsed C
WHERE T.ID = C.ID
);
See this working in a SQL Fiddle
This works by creating all the mergeable rows through the use of Min and Max--which should be the same for each column within an ID and which usefully exclude NULLs--then appending to this list all the rows from the table that couldn't be merged. The special trick with EXISTS ... INTERSECT allows for the case when a column has all NULL values for an ID (and thus the Min and Max are NULL and can't equal each other). That is, it functions like Min(A) = Max(A) AND Min(B) = Max(B) AND Min(C) = Max(C) but allows for NULLs to compare as equal.
Here's a slightly different (earlier) solution I gave that may offer different performance characteristics, and being more complicated, I like less, but being a single flowing query (without a UNION) I kind of like more, too.
WITH Collapsible AS (
SELECT
ID
FROM
dbo.MyTable
GROUP BY
ID
HAVING
EXISTS (
SELECT Min(A), Min(B), Min(C)
INTERSECT
SELECT Max(A), Max(B), Max(C)
)
), Calc AS (
SELECT
T.*,
Grp = Coalesce(C.ID, Row_Number() OVER (PARTITION BY T.ID ORDER BY (SELECT 1)))
FROM
dbo.MyTable T
LEFT JOIN Collapsible C
ON T.ID = C.ID
)
SELECT
ID,
A = Min(A),
B = Min(B),
C = Min(C)
FROM
Calc
GROUP BY
ID,
Grp
;
This is also in the above SQL Fiddle.
This uses similar logic as the first query to calculate whether a group should be merged, then uses this to create a grouping key that is either the same for all rows within an ID or is different for all rows within an ID. With a final Min (Max would have worked just as well) the rows that should be merged are merged because they share a grouping key, and the rows that shouldn't be merged are not because they have distinct grouping keys over the ID.
Depending on your data set, indexes, table size, and other performance factors, either of these queries may perform better, though the second query has some work to do to catch up, with two sorts instead of one.
You can try something like this:
select
isnull(t1.A, t2.A) as A,
isnull(t1.B, t2.B) as B,
isnull(t1.C, t2.C) as C
from
table_name t1
join table_name t2 on t1.ID = t2.ID and .....
You mention the concepts of first and second. How do
you define this order? Place that order defining condition
in here: .....
Also, I assume you have exactly 2 rows for each ID value.