How to combine two rows under specific condition - sql

I have following table:
CREATE table table1 (id int , cd date, ct TIME, co text)
INSERT INTO table1
VALUES (0, '1/1/2018', '12:00:00', 'B'),
(1, '1/1/2018', '12:30:00', 'BC'),
(2, '1/12/2018', '12:00:00', 'B'),
(3, '1/22/2018', '12:00:00', 'BC')
I need to combine "co" column when values of "cd" and "ct" columns are the same for 'B' AND 'BC' or only values of "ct" are different and display values of record 'B' for 'B-BC'.
for the above table1 records I need following result:
"id" "cd" "ct" "co"
"0" "1/1/2018" "12:00:00 PM" "B-BC"
"2" "1/12/2018" "12:00:00 PM" "B"
"3" "1/22/2018" "12:00:00 PM" "BC"
What is the most effective way to do that in postgresql 8.4?

I created two common table expressions (CTE) to come up with a list of B and BC records that will be combined. The logic is get B record and BC record. Then do a left join from table1 to cte_A table and left join to cte_B table but cte_b.id_del is null. This will remove the id found in cte_B. Lastly, do a case when to use a new co value (B-BC) for id found in cte_A table. See demo here: http://sqlfiddle.com/#!15/62caa/49
with cte_a as (select a.id as id_keep
from table1 a
inner join table1 b on a.cd=b.cd
and a.co='B' and b.co='BC')
,cte_b as (select b.id as id_del
from table1 a
inner join table1 b on a.cd=b.cd
and a.co='B' and b.co='BC')
select t1.id,
t1.cd,
t1.ct,
case when cte_a.id_keep is null
then t1.co
else 'B-BC' end as co
from table1 t1
left join cte_a
on t1.id=cte_a.id_keep
left join cte_b
on t1.id = cte_b.id_del
where cte_b.id_del is null;

You could use string_agg:
SELECT cd, MIN(id) AS id, min(ct) AS ct, string_agg(co, '-' ORDER BY ct) AS co
FROM table1
GROUP BY cd;
DBFiddle Demo
EDIT:
Lovely answer! but ct for 'B' is not always minimum value. I need the ct, which is specifically in record 'B', which may be greater 'ct' for 'BC' or less
You could use windowed function:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER(PARTITION BY cd ORDER BY ct ASC) AS rn FROM table1
)
SELECT id, cd, ct,s.co
FROM cte
JOIN LATERAL(SELECT string_agg(co, '-' ORDER BY ct) AS co
FROM cte c2 WHERE c2.cd=cte.cd)s ON TRUE
WHERE rn=1;
DBFiddle Demo2

Related

SQL conditional INNER JOIN with two different tables

I have a following simplified sub-query that should be executed first:
(SELECT app.recordId, left(userid,1) userid, count(*)FROM
app group by userid,recordId) y
Based on the result of this subquery, if userid='A' I would like to INNER JOIN with tableA, If userid='B', I'd like to INNER JOIN with tableB.
I also want the result of both inner joins to appear. Is there a way so that I can do this without re-executing the subquery to minimize the execution time?
One approach is to use left join twice, probably with coalesce():
SELECT y.*, COALESCE(a.col1, b.col1) as col1
FROM (SELECT app.recordId, left(userid, 1) as userid, count(*) as cnt
FROM app
GROUP BY recordId, left(userid, 1)
) y LEFT JOIN
tableA a
ON y.userId = 'A' and . . . LEFT JOIN
tableB b
ON y.userId = 'B' and . . .
WHERE y.userId IN ('A', 'B');
The . . . is space for additional join conditions, which the question does not specify.
EDIT:
If you want to filter out rows that have no matches (ala an "inner join" approach):
WHERE y.userId IN ('A', 'B') and not (a.col is null and b.col is null)
where a.col and b.col are two columns used for the join condition.
You didn't say how you want the columns from the tables A and B to be handled. We also don't know how the tables are related. So all I can give you is an example.
Let's say you want a description that either comes from table A or B:
select agg.userid, a_and_b.description, agg.recordid, agg.rec_count
from
(
select left(userid,1) as userid, recordid, count(*) as rec_count
from app
group by left(userid,1), recordid
) agg
join
(
select 'A' as userid, recordid, description from a
union all
select 'B' as userid, recordid, description from b
) a_and_b on a_and_b.userid = agg.userid and a_and_b.recordid = agg.recordid;

How to get pairs from sqlite

I am a database and I am just playing with sqlite3 for a group project.
I have two tables that look something like this:
Table 1:
tv_show_id, tv_show_rating
Table 2:
tv_show_id, cast_id
Each tv_show has 1 unique id, but in table two there are multiple cast_ids for each tv_show
So we have something like this:
Table 1:
1234, 90
5678, 88
Table 2:
1234, "person 1"
1234, "person 2"
5678, "person 1"
5678, "person 3"
I want the following results: (person a, person b, # of shows together)
(person 1, person 2, 1)
(person 1, person 3, 1)
(person 2, person 1, 1)
(person 2, person 3, 0)
(person 3, person 1, 1)
(person 3, person 2, 0)
How can i use JOINS to get these results?
You can try this. Fiddle
select z.id,count(w.show_id) from
(
select distinct concat(x.cid,',',x.did) as id
from
(
select tt.cast_id as cid,ttt.cast_id as did
from t2 tt,t2 ttt
where tt.cast_id <> ttt.cast_id
) x
left join t2 on x.cid = t2.cast_id
) z
left join (select show_id, group_concat(cast_id order by cast_id) cid
from t2
group by show_id
union all
select show_id, group_concat(cast_id order by cast_id desc) cid
from t2
group by show_id
) w
on z.id = w.cid
group by z.id;
One way to do this (which might not be the most clever) would be to use a cross join to create a set of all possible pairs and the use a join and left join to determine if the pair have worked together.
In this query your source table is called table2:
select
cast1,
cast2,
sum(case when t2.cast_id is null then 0 else 1 end) as worked_together
from (
select distinct
t1.cast_id cast1,
t2.cast_id cast2
from table2 t1,table2 t2
where t1.cast_id != t2.cast_id
) p
join table2 t1 on p.cast1 = t1.cast_id
left join table2 t2 on t1.show_id = t2.show_id
and p.cast2 = t2.cast_id
and t1.cast_id != t2.cast_id
group by cast1, cast2;
Sample SQL Fiddle (tested with SQL.js). The query uses only ANSI SQL and is portable to other databases.
In some other databases you could have done this using a full outer join but SQLite does not support that construct.

How to use a bunch of clause in a having statement. Oracle

assume i have a query like this:
SELECT table1.id
FROM (
SELECT id, sum(column) as A
FROM table1
GROUP BY id
) a1
Left join (
SELECT id,
sum(column) as B
FROM table 2
GROUP BY Id
) a2
on table1.id=table2.id
.
.
.
.
Left join (
SELECT id, sum(column) as G
FROM table 7
GROUP BY id
) g1
on table1.id=table7.id
Having or where A+B - (C+D+E+F+G) >0
I tried both, none works.
Having return error on there's no group by in the first select and where doesn't return any rows.
First your question have some issues.
I'm going to guess you mean put alias a, b, c, d ....
instead of a1, a2, g1.
Also your left join should be something like a.id = b.id at the moment you create a subquery you have to use the alias instead of tablename.
If you fix that you should add a WHERE, I also guess you mean use the SUM() result
WHERE a.A + b.B - (c.C+ d.D+ e.E+ f.F+ g.G) > 0
.
SELECT a.id
FROM (
SELECT id, sum(column) as sumA
FROM table1
GROUP BY id
) a
Left join
(
SELECT id, sum(column) as sumB
FROM table 2
GROUP BY Id
) b
on a.id = b.id
.
.
.
.
Left join
(
SELECT id, sum(column) as sumG
FROM table 2
GROUP BY id
) g
on f.id = g.id
WHERE a.sumA + b.sumB - (c.sumC + d.sumD + e.sumE + f.sumF + g.sumG) >0
Juan has the right answer. I am just adding a SQLFiddle to help strengthen his answer. Please look at a smaller instance of the same solution here: http://sqlfiddle.com/#!4/81c275/1
Tables
create table table1(id int, col int);
insert into table1 values (1, 10);
insert into table1 values (2, 20);
insert into table1 values (2, 30);
create table table2(id int, col int);
insert into table2 values (1, 5);
insert into table2 values (2, 3);
insert into table2 values (2, 2);
create table table3(id int, col int);
insert into table3 values (1, 100);
insert into table3 values (2, 20);
insert into table3 values (2, 3);
SQL
select a1.id
from (select id, sum(col) as A from table1 group by id) a1
left join (select id, sum(col) as B from table2 group by id) a2
on a1.id = a2.id
left join (select id, sum(col) as C from table3 group by id) a3
on a1.id = a3.id
where A + B - (C) > 0
You can add more tables in the SQLFiddle with whatever values you please, and change the SQL accordingly by appending D, E, F, G etc after C in (C).
The above example will result in output of 2 since ID 2's A+B = 55 and C = 23. A+B-C > 0 for this record and therefore the output will be 2.
I believe that you need to take out the 'where' and move it up if you still need it.
So that it would look something like this,
select table1.id from(
...
...
...)
Having ((A+B)-(C+D+R+F+G)>0)
According to this site:
http://www.w3schools.com/sql/sql_having.asp

Oracle SQL - Comparing Rows

I have a problem I'm working on with Oracle SQL that goes something like this.
TABLE
PurchaseID CustID Location
----1------------1-----------A
----2------------1-----------A
----3------------2-----------A
----4------------2-----------B
----5------------2-----------A
----6------------3-----------B
----7------------3-----------B
I'm interested in querying the Table to return all instances where the same customer makes a purchase in different locations. So, for the table above, I would want:
OUTPUT
PurchaseID CustID Location
----3------------2-----------A
----4------------2-----------B
----5------------2-----------A
Any ideas on how to accomplish this? I haven't been able to think of how to do it, and most of my ideas seem like they would be pretty clunky. The database I'm using has 1MM+ records, so I don't want it to run too slowly.
Any help would be appreciated. Thanks!
SELECT *
FROM YourTable T
WHERE CustId IN (SELECT CustId
FROM YourTable
GROUP BY CustId
HAVING MIN(Location) <> MAX(Location))
You should be able to use something similar to the following:
select purchaseid, custid, location
from yourtable
where custid in (select custid
from yourtable
group by custid
having count(distinct location) >1);
See SQL Fiddle with Demo.
The subquery in the WHERE clause is returning all custids that have a total number of distinct locations that are greater than 1.
In English:
Select a row if another row exists with the same customer and a different location.
In SQL:
SELECT *
FROM atable t
WHERE EXISTS (
SELECT *
FROM atable
WHERE CustID = t.CustID
AND Location <> t.Location
);
Here's one approach using a sub-query
SELECT T1.PurchaseID
,T1.CustID
,T1.Location
FROM YourTable T1
INNER JOIN
(SELECT T2.CustID
,COUNT (DISTINCT T2.Location )
FROM YourTable T1
GROUP BY
T2.CustID
HAVING COUNT (DISTINCT T2.Location )>1
) SQ
ON SQ.CustID = T1.CustID
This should only require one full table scan.
create table test (PurchaseID number, CustID number, Location varchar2(1));
insert into test values (1,1,'A');
insert into test values (2,1,'A');
insert into test values (3,2,'A');
insert into test values (4,2,'B');
insert into test values (5,2,'A');
insert into test values (6,3,'B');
insert into test values (7,3,'A');
with repeatCustDiffLocations as (
select PurchaseID, custid, location, dense_rank () over (partition by custid order by location) r
from test)
select b.*
from repeatCustDiffLocations a, repeatCustDiffLocations b
where a.r > 1
and a.custid = b.custid;
This makes most sense to me as I was trying to return the rows with the same values throughout the table, specifically for two columns as shown in this stackoverflow answer here.
The answer to your problem in this format is:
SELECT DISTINCT a.*
FROM TEST a
INNER JOIN TEST b
ON a.CUSTOMERID = b.CUSTOMERID AND
a.LOCATION <> b.LOCATION;
However, the solution to a problem such as mine with two columns having matching values in multiple rows (2 in this instance, would yield no results because all PurchaseID's are unique):
SELECT DISTINCT a.*
FROM TEST a
INNER JOIN TEST b
ON a.CUSTOMERID = b.CUSTOMERID AND
a.PURCHASEID = b.PURCHASEID AND
a.LOCATION <> b.LOCATION;
Although, this wouldn't return the correct results based on the what needs to be queried, it shows that the query logic works
SELECT DISTINCT a.*
FROM TEST a
INNER JOIN TEST b
ON a.CUSTOMERID = b.CUSTOMERID AND
a.PURCHASEID <> b.PURCHASEID AND
a.LOCATION = b.LOCATION;
If anyone wants to try in Oracle here is the table and values to insert:
CREATE TABLE TEST (
PurchaseID integer,
CustomerID integer,
Location varchar(1));
INSERT ALL
INTO TEST VALUES (1, 1, 'A')
INTO TEST VALUES (2, 1, 'A')
INTO TEST VALUES (3, 2, 'A')
INTO TEST VALUES (4, 2, 'B')
INTO TEST VALUES (5, 2, 'A')
INTO TEST VALUES (6, 3, 'B')
INTO TEST VALUES (7, 3, 'B')
SELECT * FROM DUAL;

Join to only the "latest" record with t-sql

I've got two tables. Table "B" has a one to many relationship with Table "A", which means that there will be many records in table "B" for one record in table "A".
The records in table "B" are mainly differentiated by a date, I need to produce a resultset that includes the record in table "A" joined with only the latest record in table "B". For illustration purpose, here's a sample schema:
Table A
-------
ID
Table B
-------
ID
TableAID
RowDate
I'm having trouble formulating the query to give me the resultset I'm looking for any help would be greatly appreciated.
SELECT *
FROM tableA A
OUTER APPLY (SELECT TOP 1 *
FROM tableB B
WHERE A.ID = B.TableAID
ORDER BY B.RowDate DESC) as B
select a.*, bm.MaxRowDate
from (
select TableAID, max(RowDate) as MaxRowDate
from TableB
group by TableAID
) bm
inner join TableA a on bm.TableAID = a.ID
If you need more columns from TableB, do this:
select a.*, b.* --use explicit columns rather than * here
from (
select TableAID, max(RowDate) as MaxRowDate
from TableB
group by TableAID
) bm
inner join TableB b on bm.TableAID = b.TableAID
and bm.MaxRowDate = b.RowDate
inner join TableA a on bm.TableAID = a.ID
table B join is optional: it depends if there are other columns you want
SELECT
*
FROM
tableA A
JOIN
tableB B ON A.ID = B.TableAID
JOIN
(
SELECT Max(RowDate) AS MaxRowDate, TableAID
FROM tableB
GROUP BY TableAID
) foo ON B.TableAID = foo.TableAID AND B.RowDate= foo.MaxRowDate
With ABDateMap AS (
SELECT Max(RowDate) AS LastDate, TableAID FROM TableB GROUP BY TableAID
),
LatestBRow As (
SELECT MAX(ID) AS ID, TableAID FROM ABDateMap INNER JOIN TableB ON b.TableAID=a.ID AND b.RowDate = LastDate GROUP BY TableAID
)
SELECT columns
FROM TableA a
INNER JOIN LatestBRow m ON m.TableAID=a.ID
INNER JOIN TableB b on b.ID = m.ID
Just for the clarity's sake and to benefit those who will stumble upon this ancient question. The accepted answer would return duplicate rows if there are duplicate RowDate in Table B. A safer and more efficient way would be to utilize ROW_NUMBER():
Select a.*, b.* -- Use explicit column list rather than * here
From [Table A] a
Inner Join ( -- Use Left Join if the records missing from Table B are still required
Select *,
ROW_NUMBER() OVER (PARTITION BY TableAID ORDER BY RowDate DESC) As _RowNum
From [Table B]
) b
On b.TableAID = a.ID
Where b._RowNum = 1
Try using this:
BEGIN
DECLARE #TB1 AS TABLE (ID INT, NAME VARCHAR(30) )
DECLARE #TB2 AS TABLE (ID INT, ID_TB1 INT, PRICE DECIMAL(18,2))
INSERT INTO #TB1 (ID, NAME) VALUES (1, 'PRODUCT X')
INSERT INTO #TB1 (ID, NAME) VALUES (2, 'PRODUCT Y')
INSERT INTO #TB2 (ID, ID_TB1, PRICE) VALUES (1, 1, 3.99)
INSERT INTO #TB2 (ID, ID_TB1, PRICE) VALUES (2, 1, 4.99)
INSERT INTO #TB2 (ID, ID_TB1, PRICE) VALUES (3, 1, 5.99)
INSERT INTO #TB2 (ID, ID_TB1, PRICE) VALUES (1, 2, 0.99)
INSERT INTO #TB2 (ID, ID_TB1, PRICE) VALUES (2, 2, 1.99)
INSERT INTO #TB2 (ID, ID_TB1, PRICE) VALUES (3, 2, 2.99)
SELECT A.ID, A.NAME, B.PRICE
FROM #TB1 A
INNER JOIN #TB2 B ON A.ID = B.ID_TB1 AND B.ID = (SELECT MAX(ID) FROM #TB2 WHERE ID_TB1 = A.ID)
END
This will fetch the latest record with JOIN. I think this will help someone
SELECT cmp.*, lr_entry.lr_no FROM
(SELECT * FROM lr_entry ORDER BY id DESC LIMIT 1)
lr_entry JOIN companies as cmp ON cmp.id = lr_entry.company_id