Combining duplicate SQLite columns after joining parent with multiple children - sql

I've got a SQLite database set up which looks like this:
I'm currently looking to join all of the child tables into the parent table. As you can see, all of the children are designed in the same way, so I was hoping that they would merge nicely together.
Unfortunately, joining the tables with a left join results in this:
With SQLite, is it possible to merge the columns so that the null values are ignored, and the remaining starting_item_ids and item_ids all display in the same column?
Any help would be greatly appreciated.
Edit: Here's a minimal reproducible example
CREATE TABLE `starting_item` (
`starting_item_id` integer PRIMARY KEY AUTOINCREMENT,
`quantity` integer
);
CREATE TABLE `starting_weapon` (
`starting_item_id` integer,
`item_id` integer,
FOREIGN KEY (`starting_item_id`) REFERENCES `starting_item` (`starting_item_id`)
);
CREATE TABLE `starting_armor` (
`starting_item_id` integer,
`item_id` integer,
FOREIGN KEY (`starting_item_id`) REFERENCES `starting_item` (`starting_item_id`)
);
CREATE TABLE `starting_gear` (
`starting_item_id` integer,
`item_id` integer,
FOREIGN KEY (`starting_item_id`) REFERENCES `starting_item` (`starting_item_id`)
);
CREATE TABLE `starting_tool` (
`starting_item_id` integer,
`item_id` integer,
FOREIGN KEY (`starting_item_id`) REFERENCES `starting_item` (`starting_item_id`)
);
INSERT INTO starting_item (quantity) VALUES (1);
INSERT INTO starting_item (quantity) VALUES (1);
INSERT INTO starting_item (quantity) VALUES (1);
INSERT INTO starting_item (quantity) VALUES (1);
INSERT INTO starting_weapon (starting_item_id, item_id) VALUES (1, 4);
INSERT INTO starting_armor (starting_item_id, item_id) VALUES (2, 7);
INSERT INTO starting_gear (starting_item_id, item_id) VALUES (3, 30);
INSERT INTO starting_tool (starting_item_id, item_id) VALUES (4, 20);
select * from starting_item
left join starting_weapon on starting_weapon.starting_item_id = starting_item.starting_item_id
left join starting_armor on starting_armor.starting_item_id = starting_item.starting_item_id
left join starting_gear on starting_gear.starting_item_id = starting_item.starting_item_id
left join starting_tool on starting_tool.starting_item_id = starting_item.starting_item_id
Current result:
+------------------+----------+------------------+---------+------------------+---------+------------------+---------+------------------+---------+
| starting_item_id | quantity | starting_item_id | item_id | starting_item_id | item_id | starting_item_id | item_id | starting_item_id | item_id |
+------------------+----------+------------------+---------+------------------+---------+------------------+---------+------------------+---------+
| 1 | 1 | 1 | 4 | | | | | | |
+------------------+----------+------------------+---------+------------------+---------+------------------+---------+------------------+---------+
| 2 | 1 | | | 2 | 7 | | | | |
+------------------+----------+------------------+---------+------------------+---------+------------------+---------+------------------+---------+
| 3 | 1 | | | | | 3 | 30 | | |
+------------------+----------+------------------+---------+------------------+---------+------------------+---------+------------------+---------+
| 4 | 1 | | | | | | | 4 | 20 |
+------------------+----------+------------------+---------+------------------+---------+------------------+---------+------------------+---------+
Intended Result:
+------------------+----------+------------------+---------+
| starting_item_id | quantity | starting_item_id | item_id |
+------------------+----------+------------------+---------+
| 1 | 1 | 1 | 4 |
+------------------+----------+------------------+---------+
| 2 | 1 | 2 | 7 |
+------------------+----------+------------------+---------+
| 3 | 1 | 3 | 30 |
+------------------+----------+------------------+---------+
| 4 | 1 | 4 | 20 |
+------------------+----------+------------------+---------+

You can have the stated desired output (minus frames, but that is just a config thing), with this query:
select * from starting_item a
join ( select * from starting_weapon
union select * from starting_armor
union select * from starting_gear
union select * from starting_tool) b
on a.starting_item_id=b.starting_item_id;
It uses a multi "union" of the identically structured subtables and joins it with the starting item table, using aliases "a" and "b".
The result is quite exactly the stated desired output:
starting_item_id quantity starting_item_id item_id
---------------- ---------- ---------------- ----------
1 1 1 4
2 1 2 7
3 1 3 30
4 1 4 20
However, in my opinion avoiding the redundant column (twice the ID) is desireable.
And I also feel somehow that a little trick to add an indicator (what kind of item we are seeing) is helpful. So I offer this for prettiness:
select * from starting_item
join ( select *, "weapon" kind from starting_weapon
union select *, "armor" kind from starting_armor
union select *, "gear" kind from starting_gear
union select *, "tool" kind from starting_tool)
using(starting_item_id);
It gets you, in case you like it:
starting_item_id quantity item_id kind
---------------- ---------- ---------- ----------
1 1 4 weapon
2 1 7 armor
3 1 30 gear
4 1 20 tool
The first change is to join with using() instead of on ..., which (in spite of using * for convenience) gets you only one ID column.
The second change is to add a column named "kind" with explicitly given content in each "unioned" part. This "unions" and "joins" up nicely and gets you a named indicator column. The advantage is that you can e.g. order by that indicator column for some tidyness.
Finally, please consider whether the unioned table used here is not actually something which you can use in your database design. I.e. use one table for all items, with an additional "kind" column.

Related

How can i query in "mulitple" many to many tables?

I have three tables with a many-to-many relationships
CREATE TABLE plate(
pid integer NOT NULL,
pname text
);
CREATE TABLE vegetables(
vid integer NOT NULL,
vname text
);
CREATE TABLE meat(
mid integer NOT NULL,
mname text
);
Many to Many relationship of the three tables:
+------+-----+
| pid | vid |
+------+-----+
| 1 | 13 |
| 1 | 12 |
| 2 | 12 |
+------------+
and:
+-------+---+
| pid |mid|
+-------+---+
| 1 | 2 |
| 1 | 3 |
| 2 | 3 |
+-------+---+
The **query** i need is to check :
when the user enter the ingrediants of the plate,
example:
vid"13","12"
and
mid"2","3"
then the query will check wether the ingrediants can form a plate or not, by checking the many to many relationship table.
i tried using the IN statment, but find no results
any help?
I believe you need to find plates having all the meats and then plates having all the vegetables. Once you have those simply process a JOIN to obtain an intersect.
select pid
from
(
-- plates having all the meats
select pid
from platemeat pm
where pm.mid in (12,13)
group by pid
having count(distinct mid) = 2
) t1
join
(
-- plates having all the vegetables
select pid
from plateveget pv
where pv.vid in (2,3)
group by pid
having count(distinct vid) = 2
) t2 on t1.pid = t2.pid

How can we clone a set of one-to-many records?

There is a one-to-many relationship between two tables. Each table has its own identity primary key id. The problem is cloning some ( or for simplicity all ) of records in Table1 and their related records in Table2, then we will make a copy of all of records in Table1 and Table2 in the same Tables.
For example consider this example:
CREATE TABLE Categories
(
Id INT IDENTITY,
OtherData INT
)
CREATE TABLE Products
(
Id INT IDENTITY,
CategoryId INT CONSTRAINT FK_Category_Products
FOREIGN KEY REFERENCES dbo.Categories(Id),
ProductData INT
)
How can we make this copy. there is no restriction in how many commands is used, in order making this to be done.
Edit:
The main problem is saving one-two-many relationship between records while they are to be copied.
Example:
Categories: Products:
| Id | OtherData | | Id | CategoryId | ProductData |
|------|-------------| |------|-------------|---------------|
| 1 | 10 | | 11 | 1 | 20 |
| 2 | 10 | | 12 | 2 | 30 |
After copy we want this data:
Categories: Products:
| Id | OtherData | | Id | CategoryId | ProductData |
|------|-------------| |------|-------------|---------------|
| 1 | 10 | | 11 | 1 | 20 |
| 2 | 10 | | 12 | 2 | 30 |
| 3 | 10 | | 13 | 3 | 20 |
| 4 | 10 | | 14 | 4 | 30 |
Thanks for any help or suggestion.
As Ivan Starostin wrote in his comment, the trick is to use Merge to populate the parent categories, output the old id and new id into a map table, and use that map table to insert the products:
-- Declare the map table
DECLARE #Map as table
(
OldId int,
NewId int
)
-- Duplicate the records of the Categories table
MERGE INTO Categories
USING
(
SELECT Id, OtherData
FROM Categories
) As s ON 1 = 0 -- Always not matched
WHEN NOT MATCHED THEN
INSERT (OtherData)
VALUES (OtherData)
OUTPUT s.Id, Inserted.Id
INTO #Map (OldID, NewId); -- Note the output clause
-- Duplicate the records in the Products table (Note the inner join)
INSERT INTO Products (CategoryId, ProductData)
SELECT NewId, ProductData
FROM Products
INNER JOIN #Map m ON CategoryId = OldId

Query with subqueries returning more than I want

I have the following tables (those tables contain many records but for sake of this example I reduced the contents only to the records I want to operate on).
Products
product_id | product_name
------------+--------------
1 | PRODUCT
Contracts
contract_id | version | status | product_id
-------------+---------+--------+------------
2 | 1 | 30 | 1
2 | 2 | 30 | 1
2 | 3 | 30 | 1
2 | 4 | 30 | 1
2 | 5 | 30 | 1
2 | 6 | 30 | 1
People
id | guid
----+------
3 | 123
9 | 456
Limits
id | type
----+------
4 | 12
5 | 14
Link_table
link_id | version | contract_id | object_type | function | obj_id
---------+---------+-------------+-------------+----------+--------
6 | 1 | 2 | XADL | ADLTYP | 4
7 | 2 | 2 | XADL | ADLTYP | 5
8 | 2 | 2 | BCBP | BCA010 | 123
10 | 3 | 2 | BCBP | BCA010 | 456
Here is the DDL for the aforementioned tables...
CREATE TABLE products (
product_id integer PRIMARY KEY,
product_name varchar(10) NOT NULL
);
CREATE TABLE contracts (
contract_id integer,
version integer,
status varchar(2) NOT NULL,
product_id integer NOT NULL REFERENCES products(product_id),
PRIMARY KEY (contract_id, version)
);
CREATE TABLE link_table (
link_id integer,
version integer,
contract_id integer NOT NULL,
object_type varchar(4) NOT NULL,
function varchar(6) NOT NULL,
obj_id integer NOT NULL,
PRIMARY KEY(link_id, version)
);
CREATE TABLE people (
id integer PRIMARY KEY,
guid integer,
CONSTRAINT person_guid UNIQUE(guid)
);
CREATE TABLE limits (
id integer PRIMARY KEY,
type varchar(2) NOT NULL
);
Now... My task is to select the latest version of the value for field type in limits table for the latest version of the value id in the people table. The table link_table decides what the latest version is. This data need to be provided with fields contract_id, status, product_name.
I tried with the following query, unfortunately I receive two rows when I am supposed to receive only one with the latest value.
SELECT c.contract_id, status, product_name, type
FROM
contracts AS c
INNER JOIN
products AS p
ON c.product_id = p.product_id
INNER JOIN
link_table AS per
ON c.contract_id = per.contract_id
INNER JOIN
link_table AS ll
ON per.contract_id = ll.contract_id
INNER JOIN
people AS peop
ON per.obj_id = peop.guid
INNER JOIN
limits AS lim
ON ll.obj_id = lim.id
WHERE
peop.id = 3
AND per.object_type = 'BCBP'
AND per.function = 'BCA010'
AND ll.object_type = 'XADL'
AND ll.function = 'ADLTYP'
AND ll.version IN ( SELECT max(version) FROM link_table WHERE link_id = ll.link_id)
AND per.version IN ( SELECT max(version) FROM link_table WHERE link_id = per.link_id)
AND c.version IN ( SELECT max(version) FROM contracts WHERE contract_id = c.contract_id );
The result I expect is
contract_id | status | product_name | type
-------------+--------+--------------+------
2 | 30 | PRODUCT | 12
However the actual outcome is
contract_id | status | product_name | type
-------------+--------+--------------+------
2 | 30 | PRODUCT | 12
2 | 30 | PRODUCT | 14
I have been struggling with this for over a day now. Could anyone tell me what I am doing wrong? This example is done with PostgreSQL but the real problem needs to be solved with ABAP's OpenSQL so I cannot use UNION.
Here is some SQL to populate the tables.
INSERT INTO products VALUES (1, 'PRODUCT');
INSERT INTO contracts VALUES (2, 1, '30', 1);
INSERT INTO contracts VALUES (2, 2, '30', 1);
INSERT INTO contracts VALUES (2, 3, '30', 1);
INSERT INTO contracts VALUES (2, 4, '30', 1);
INSERT INTO contracts VALUES (2, 5, '30', 1);
INSERT INTO contracts VALUES (2, 6, '30', 1);
INSERT INTO people VALUES (3, 123);
INSERT INTO people VALUES (9, 456);
INSERT INTO limits VALUES (4, '12');
INSERT INTO limits VALUES (5, '14');
INSERT INTO link_table VALUES (6, 1, 2, 'XADL', 'ADLTYP', 4);
INSERT INTO link_table VALUES (7, 2, 2, 'XADL', 'ADLTYP', 5);
INSERT INTO link_table VALUES (8, 2, 2, 'BCBP', 'BCA010', 123);
INSERT INTO link_table VALUES (10, 3, 2, 'BCBP', 'BCA010', 456);
EDIT
Looks like if the following records in table_link
link_id | version | contract_id | object_type | function | obj_id
---------+---------+-------------+-------------+----------+--------
6 | 1 | 2 | XADL | ADLTYP | 4
7 | 2 | 2 | XADL | ADLTYP | 5
were defined with the same link_id then my query would return exactly what I want.
link_id | version | contract_id | object_type | function | obj_id
---------+---------+-------------+-------------+----------+--------
7 | 1 | 2 | XADL | ADLTYP | 4
7 | 2 | 2 | XADL | ADLTYP | 5
Unfortunately link_id is generated each time new in production even if there is a version in the composite key... Looks like I have to find another way or look for other fields in the link table that would help me.

SQL query by compound index

Let's say I have a table items with columns id type number room, id is primary key, (type, number) is a unique compound key; And a table inventory with columns id, item_type, item_number, owner, id is primary key, (type, number) is a unique compound key.
Example:
items
| id | type | number | room |
+----+---------+--------+------+
| 1 | laptop | 1 | 12 |
| 2 | laptop | 2 | 13 |
| 3 | desktop | 1 | 13 |
inventory
| id | item_type | item_number | owner |
+----+-----------+-------------+-------+
| 1 | laptop | 1 | Joe |
| 2 | laptop | 2 | Joe |
| 3 | desktop | 1 | Susan |
How do I query all items owned by Joe? If I do
SELECT *
FROM items
WHERE (type, number) IN (
SELECT item_type, item_number FROM inventory WHERE owner = 'Joe'
)
I only get one row in the result, though subquery returns multiple rows. I can't seem to do join on multiple columns either, like
SELECT *
FROM items
JOIN inventory ON inventory.item_type = items.type,
inventory.item_number = items.number`
WHERE inventory.owner = 'Joe'
You ought to combine the join conditions with AND, not with a comma.

Create New Table From Other Table After Grouping

How can I insert to a table a value from "grouping" other table?
That means I have 2 table with different structure.
The table ORDRE with existed DATA
Table ORDRE:
ORDRE ID | CODE_DEST |
-------------------------
1 | a |
2 | b |
3 | c |
4 | a |
5 | a |
6 | b |
7 | g |
I want to INSERT the value FROM Table ORDRE INTO TABLE VOIT:
ID_VOIT | ORDRE ID | CODE_DEST |
---------------------------------------
1 | 1 | a |
1 | 4 | a |
1 | 5 | a |
2 | 2 | b |
2 | 6 | b |
3 | 3 | c |
4 | 7 | g |
This is my best guess on what you need using only the info available.
declare #Ordre table
(
ordre_id int,
code_dest char(1)
)
declare #Voit table
(
id_voit int,
ordre_id int,
code_dest char(1)
)
insert into #Ordre values
(1,'a'),
(2,'b'),
(3,'c'),
(4,'a'),
(5,'a'),
(6,'b'),
(7,'g')
insert into #Voit
select id_voit, ordre_id, rsOrdre.code_dest
from #Ordre rsOrdre
inner join
(
select code_dest, ROW_NUMBER() over (order by code_dest) as id_voit
from #Ordre
group by code_dest
) rsVoit on rsVoit.code_dest = rsOrdre.code_dest
order by id_voit, ordre_id
select * from #Voit
Working Example.
For the specific data you give as an example, this works:
insert into VOIT
select
case code_dest
when 'a' then 1
when 'b' then 2
when 'c' then 3
when 'g' then 4
else 0
end, orderId, code_dest from ORDRE order by code_dest, orderId
But it kind of sucks because it requires hard-coding in a huge case statement.
Test is here - https://data.stackexchange.com/stackoverflow/q/119442/
What I like more is moving the VOIT ID / Code_Dest associations to a new table, so then you could do an inner join instead.
insert into VOIT
select voit_id, orderId, t.code_dest
from ORDRE t
join Voit_CodeDest t2 on t.code_dest = t2.code_dest
order by code_dest, orderId
Working example of that here - https://data.stackexchange.com/stackoverflow/q/119443/