Replace one row with mupltiple rows form a different table - sql

I have two tables:Table 1:
Number| Code | Value
-------+------+-----
Garden |A0,C2 | 100
Garden |rest | 500
House |A0,C2 | 100
House |rest | 500
Table2:
|Code|
+-----+
|A0 |
|B1 |
|C2 |
|D3 |
|E4 |
I would like to get a Table that looks something like this:
Number| Code | Value
-------+------+-----
Garden |A0 | 100
Garden |B1 | 500
Garden |C2 | 100
Garden |D3 | 500
Garden |E4 | 500
House |A0 | 100
House |B1 | 500
House |C2 | 100
House |D3 | 500
House |E4 | 500
Does anyone know a SQL Statement on how I can get to this table
Changing either table 1 or table 2 is not possible

I'm not a fan of this solution, as the JOIN with an EXISTS is ugly. I strongly suggest fixing your data model here. You shouldn't be using delimited data in your table, and you shouldn't be using a value like 'rest' to denote you want all other values other than those previously defined. The result set you get here is a normalised dataset, and that is exactly how you should be storing your data:
CREATE TABLE Table1 (Number varchar(6), --A number is a varchar?
Code varchar(50),
[Value] int);
INSERT INTO dbo.Table1 (Number,
Code,
[Value])
VALUES ('Garden','A0,C2',100),
('Garden','rest',500),
('House','A0,C2',100),
('House','rest',500);
CREATE TABLE Table2 (Code char(2));
INSERT INTO dbo.Table2 (Code)
VALUES ('A0'),
('B1'),
('C2'),
('D3'),
('E4');
GO
WITH CTE AS(
SELECT T1.Number,
SS.[value] AS Code,
T1.[Value]
FROM dbo.Table1 T1
CROSS APPLY STRING_SPLIT(T1.Code, ',') SS)
SELECT C.Number,
T2.Code,
C.[Value]
FROM CTE C
JOIN dbo.Table2 T2 ON C.Code = T2.Code
OR (C.Code = 'rest'
AND NOT EXISTS (SELECT 1
FROM CTE e
WHERE e.Number = C.Number
AND e.Code = T2.Code))
GO
DROP TABLE dbo.Table1;
DROP TABLE dbo.Table2;
db<>fiddle
If you aren't using SQL Server 2016+, you won't be able to use STRING_SPLIT. As a result I suggest looking up an "XML Splitter" or delimitedsplit8k(_lead).

You should fix the table1 so it is not using lists represented as comma-delimited strings. You should have a separate table for this. There are many good reasons for this.
Assuming that you are stuck with someone-else's really, really bad data model, you can use a join. There are several approaches. SQL Server 2017+ has built-in string_split() function.
Then, you want to generate the rows with a cross join and bring in the values you want using left joins:
select n.number, t2.code, coalesce(t1.value, t1rest.value) as value
from (select distinct number from table1) n cross join
table2 t2 left join
table1 t1
on t1.number = n.number and ',' + t1.codes + ',' like '%,' + t2.code + ',%' left join
table1 t1rest
on t1rest.number = n.number and t1rest.codes = 'rest';
Here is a db<>fiddle

Related

SQL dividing column by a number in another table

I have two tables:
total_table =
| Title | Value |
|-------|-------|
| total | 20 |
breakdown_table =
| Title | Value |
|--------|-------|
| total | 20 | (this is the same as the above table)
| type a | 10 |
| type b | 5 |
| type c | 5 |
I would like to create a new table which includes both columns from breakdown_table but adds a 3rd column that shows the breakdown percentages (100%, 50%, 25%, 25%). How can I do this without hardcoding the denominator?
Here's some syntax I've tried but it keeps giving me errors about commas and equijoin. I'm not trying to join the tables with a key, I just want to use the single value in the total_table.
data_out = SELECT breakdown_table.*,
breakdown_table.Value / total.Value
FROM breakdown_table, total_table;
You can cross join the tables (properly):
SELECT
b.*,
100.0 * b.Value / t.Value as data_out
FROM breakdown as b cross join total as t;
You can try using cross join:
select t1.Title, t1.Value, 100 * t1.Value / t2.Value AS BPCT
from mytable t1
cross join mytable t2
where t2.Title = "total"
Very similar to other answers here but will get you the percentages in the right format.
select t1.*, format(t1.value/1.0/t2.value,'P0') as pcnt
from breakdown_table t1 cross apply total_table t2
If you wish to use the implicit join method, then you need to change the column name in total_table
select t1.*,format(value*1.0/total,'P0') as pcnt
from breakdown_table t1,(select value as total from total_table) t2

Join tables based on set of values in distinctive columns in any order

I am facing a problem at work I just have too little understanding of SQL, or databases in general for that matter, for, to even know how to search.
Hence, after countless 'views' at StackOverflow questions, here is my first own one.
I have two tables, which have no other relation to each other, other than the same set of columns (I called them item_1 to item_3) with values in them in random order.
I need to join these tables, but I can not just join them on each column, as I am looking for the same set of items in any order.
The set needs to be the same in both tables (no item more, no less), but the position is irrelevant.
Here are some dummy tables to (hopefully) explain what I mean:
table1
user_id | use_name | item_1 | item_2 | item_3
--------+----------+--------+--------+--------
1 | Tim | A | B | NULL
2 | Tom | NULL | NULL | C
3 | Sam | A | NULL | NULL
table2
role | item_1 | item_2 | item_3
---------+--------+--------+--------
type1 | A | NULL | B
type2 | A | B | C
type3 | A | NULL | NULL
I am looking for a select / join which produces a table like this:
user_name | role
----------+------
Tim | type1
Sam | type3
I tried my luck with a permutation table, but as in the real scenario we are talking about not 3 but 10 columns to consider, this seems not the best solution.
I am currently trying to achieve something useful with pivot / unpivot, but without any results so far.
I would even be very happy with just a link to an article. Or even a distinctive name for my problem I could google for :)
Thank you very much!
If I understand correctly, you want a complete match between the rows, where the items need to match exactly.
You have a poor data model. In databases, you should not store repeated values in columns. Instead, you should store them in rows.
However, it is pretty easy to unpivot the data:
with t1 as (
select t1.*, v.item,
count(*) over (partition by user_id) as cnt
from table1 t1 cross apply
(values (t1.item_1), (t1.item_2), (t1.item_3)
) v(item)
where v.item is not null
),
t2 as (
select t2.*, v.item,
count(*) over (partition by role) as cnt
from table2 t2 cross apply
(values (t2.item_1), (t2.item_2), (t2.item_3)
) v(item)
where v.item is not null
)
select t1.user_id, t1.user_name, t2.role
from t1 join
t2
on t1.item = t2.item and t1.cnt = t2.cnt
group by t1.user_id, t1.user_name, t2.role, t1.cnt
having count(*) = t1.cnt;

Group By on a single column in Oracle

In my project I have a requirement where my select query return multiple column but for summation of the amount I have to group by a single column. Details is given below.
Example: This is just a example as my query is very big with multiple joins.
Table -tb1
---------------------------
CASE | Amount | Customer
---------------------------
CS001| 50 | 1
---------------------------
CS003| 100 | 2
---------------------------
CS001 | 2000 | 3
Table -tb2
---------------------------
Customer | Name| Age
---------------------------
1| John | 69
---------------------------
2| Robert| 23
---------------------------
3| Tammy| 23
---------------------------
Now I would like to fetch all the records by joining these 2 table but with a sum of the Amount. So, I would like to group by only though CASE
Thanks for the help in advance.
Is this what you want? I have used a join with group by. You can use a left or a right join to have more records if exists in either table1 or 2
Select
a.[case] -- CASE is a reservedkeyword
,a.customer,sum(amount),
b.name,b.age from table1 a
join -- or left join/right join
table2 b
on
a.customer=b.customer
group by a.case
Is this what you want?
select tb1.*, tb2.*, sum(amount) over (partition by case) as case_amount
from tb1 join
tb2
on tb1.customer = tb2.customer;
When grouping, every column appearing the the SELECT list must either be part of the GROUP BY list or must have an aggregate function applied to it (like COUNT, SUM, MAX etc.).
SELECT
t1."CASE", t2.Customer, t2.Name, t2.Age, SUM(t1.Amount) As TotalAmount
FROM
tbl1 t1
INNER JOIN tbl2 t2
ON t1.Customer = t2.Customer
GROUP BY
t1."CASE", t2.Customer, t2.Name, t2.Age
Since CASE is a reserved word in SQL, I escaped it the Oracle way by enclosing it in double quotes.

How can I join two tables with a many-to-one relationship ordered by date?

I need to join two tables and get the most recent record only. Here is the basic form:
table1.id | table1.region | table1.important_col1
1 | NORTH AMERICA | abc
2 | CHINA | def
2 | NORTH AMERICA | hij
table2.id | table2.region | table2.transaction_date | table2.important_col2
1 | NORTH AMERICA | 2/13/2019 | xyz
1 | NORTH AMERICA | 1/13/2019 | zzz
1 | NORTH AMERICA | 12/13/2018 | xxx
desired result:
1 | NORTH AMERICA | 2/13/2019 | abc | xyz
I wanted to use this answer but it seems like I can't use it if I need to group by and then order by descending date. I will need information in multiple columns on the right hand side, but do not want duplicate rows on the left hand side.
The right hand side may have up to 100s of records per id, but I just need something that works for now. Thanks in advance.
edit: I also need to filter the right hand side on other criteria so a simple MAX(table2.transaction_date) won't work.
You can filter your table using internal window function, I used LAG for this example, but you can use ROW_NUMBER and filter several records. Using sliding windows does not change the number of records or counted as SQL aggregation, i.e. you filter using where rather than with having.
SELECT
t1.id
,t2.transaction_date
,t1.region
,t1.col1
,t2.important_col2
FROM table1 AS t1
OUTER APPLY (
SELECT
id
,transaction_date
,LAG(transaction_date,1) over (partition by id order by transaction_date desc) as prev_td
,important_col2
FROM table2
-- WHERE filter_by_col=1 -- additonal "right side" filtering
) as t2
where t1.id = t2.id
and t2.prev_td is null
Output:
1 2019-02-13 00:00:00.000 NORTH AMERICA abc xyz
I used this to test the above query:
create table table1
(id int,
region varchar(30),
col1 varchar(100));
insert into table1
values (1 ,'NORTH AMERICA' ,'abc'),
(2,'CHINA','def'),
(2,'NORTH AMERICA','hij');
create table table2
(id int,
region varchar(30),
transaction_date datetime,
important_col2 varchar(100))
insert into table2
values
(1 ,'NORTH AMERICA',convert(datetime, '02/13/19', 1),'xyz'),
(1 ,'NORTH AMERICA',convert(datetime, '01/13/19',1),'zzz'),
(1 ,'NORTH AMERICA',convert(datetime, '12/13/18',1),'xxx')
Try in this way:
select table11.id, table1.region, max(table2.transaction_date) transaction_date
from table1
inner join table2
on table1.id = table2.id
group by table1.id, table1.region
If there are more columns in table2 (other than transaction date) that you want to display as well, then aggregation alone cannot solve your question.
In MySQL 8.0 you can use window function ROW_NUMBER() to identify the most recent transaction record, as follows :
SELECT x.*
FROM (
SELECT
t1.*,
t2.*,
ROW_NUMBER() OVER(PARTITION BY t2.region ORDER BY t2.transaction_date DESC) rn
FROM table1 t1
INNER JOIN table2 t2 ON t1.region = t2.region
) x
WHERE x.rn = 1
In earlier versions of MySQL, one solution is to add a NOT EXISTS with a correlated subquery that ensures that we are joining with the most recent transaction for the current region :
SELECT t1.*, t2.*
FROM table1 t1
INNER JOIN table2 t2
ON t1.region = t2.region
AND NOT EXISTS (
SELECT 1
FROM table2
WHERE region = t2.region AND transaction_date > t2.transaction_date
)

Oracle Efficiently joining tables with subquery in FROM

Table 1:
| account_no | **other columns**...
+------------+-----------------------
| 1 |
| 2 |
| 3 |
| 4 |
Table 2:
| account_no | TX_No | Balance | History |
+------------+-------+---------+------------+
| 1 | 123 | 123 | 12.01.2011 |
| 1 | 234 | 2312 | 01.03.2011 |
| 3 | 232 | 212 | 19.02.2011 |
| 4 | 117 | 234 | 24.01.2011 |
I have multiple join query, one of the tables(Table 2) inside a query is problematic as it is a view which computes many other things, that is why each query to that table is costly. From Table 2, for each account_no in Table 1 I need the whole row with the greatest TX_NO, this is how I do it:
SELECT * FROM TABLE1 A LEFT JOIN
( SELECT
X.ACCOUNT_NO,
HISTORY,
X.BALANCE
FROM TABLE2 X INNER JOIN
(SELECT
ACCOUNT_NO,
MAX(TX_NO) AS TX_NO
FROM TABLE2
GROUP BY ACCOUNT_NO) Y ON X.ACCOUNT_NO = Y.ACCOUNT_NO) B
ON B.ACCOUNT_NO = A.ACCOUNT_NO
As I understand at first it will make the inner join for all the rows in Table2 and after that left join needed account_no's with Table1 which is what I would like to avoid.
My question: Is there a way to find the max(TX_NO) for only those accounts that are in Table1 instead of going through all? I think it will help to increase the speed of the query.
I think you are on the right track, but I don't think that you need to, and would not myself, nest the subqueries the way you have done. Instead, if you want to get each record from table 1 and the matching max record from table 2, you can try the following:
SELECT * FROM TABLE1 t1
LEFT JOIN
(
SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY account_no ORDER BY TX_No DESC) rn
FROM TABLE2 t
) t2
ON t1.account_no = t2.account_no AND
t2.rn = 1
If you want to continue with your original approach, this is how I would do it:
SELECT *
FROM TABLE1 t1
LEFT JOIN TABLE2 t2
ON t1.account_no = t2.account_no
INNER JOIN
(
SELECT account_no, MAX(TX_No) AS max_tx_no
FROM TABLE2
GROUP BY account_no
) t3
ON t2.account_no = t3.account_no AND
t2.TX_No = t3.max_tx_no
Instead of using a window function to find the greatest record per account in TABLE2, we use a second join to a subquery instead. I would expect the window function approach to perform better than this double join approach, and once you get used to it can even easier to read.
If table1 is comparatiely less expensive then you could think of doing a left outer join first which would considerable decrease the resultset and from that pick the latest transaction id records alone
select <required columns> from
(
select f.<required_columns),row_number() over (partition by account_no order by tx_id desc ) as rn
from
(
a.*,b.tx_id,b.balance,b.History
from table1 a left outer join table2 b
on a.account_no=b.account_no
)f
)g where g.rn=1