How to make a join in a hierarchical query - sql

I have these two tables:
CREATE TABLE Category
(
curCategory VARCHAR2(50) PRIMARY KEY,
parentCategory VARCHAR(50),
CONSTRAINT check_cat CHECK (curCategory is not null),
CONSTRAINT fk_category1 FOREIGN KEY(parentCategory) REFERENCES Category(curCategory)
);
CREATE TABLE Article
(
name VARCHAR(50)
artCategory VARCHAR(50),
CONSTRAINT pk_article PRIMARY KEY(name),
CONSTRAINT fk_artCategory FOREIGN KEY(artCategory) REFERENCES Category(curCategory)
);
What I want is something like this:
Select
level, curCategory, parentCategory
from
Category
join
article on artCategory = curCategory
start with curCategory = 'Clothes'
connect by prior curCategory = parentCategory
order siblings by curCategory;
I want to print every article which is a clothes. So what I wanted to do is go through every child category including the category itself ('Clothes') and check if the article category matches the curCategory. But when I execute my query I get zero records.

Syntax looks like Oracle so - here's one option which shows what you might do.
Use VARCAHR**2** datatype, not VARCHAR. I shortened columns' length so that the output is easier to display. Also, there was a syntax error (a missing comma at the end of the article.name column declaration).
SQL> create table category (
2 curcategory varchar2(20) primary key,
3 parentcategory varchar2(20),
4 constraint check_cat check ( curcategory is not null ),
5 constraint fk_category1 foreign key ( parentcategory )
6 references category ( curcategory )
7 );
Table created.
SQL> create table article (
2 name varchar2(20),
3 artcategory varchar2(20),
4 constraint pk_article primary key ( name ),
5 constraint fk_artcategory foreign key ( artcategory )
6 references category ( curcategory )
7 );
Table created.
Some sample data:
SQL> select * from category;
CURCATEGORY PARENTCATEGORY
-------------------- --------------------
clothes
trousers clothes
shirts clothes
SQL> select * from article;
NAME ARTCATEGORY
-------------------- --------------------
t-shirt shirts
a-shirt shirts
jeans trousers
SQL>
According to that, the hierarchy should look like this:
clothes
shirts
t-shirt
a-shirt
trousers
jeans
OK, let's make it so.
Out of two tables, using the WITH factoring clause (i.e. a CTE, common table expression), I'm UNION-ing those two tables in order to create a single parent-child source. Then, writing a hierarchical query is a simple matter.
SQL> with source as
2 (select c.parentcategory parent, c.curcategory child
3 from category c
4 union
5 select a.artcategory parent, a.name child
6 from article a
7 )
8 select lpad(' ', 2 * level - 1) || child clothes
9 from source
10 start with parent is null
11 connect by prior child = parent
12 order siblings by child;
CLOTHES
------------------------------------------------------------
clothes
shirts
a-shirt
t-shirt
trousers
jeans
6 rows selected.
SQL>

Related

SQLite3 - Counting number of duplicate and non-duplicate books each user owns

I'm creating a database that keeps track of books, users, and what books each user owns. A user can have several copies of a certain book, specified by their book id. What I'm trying to calculate in particular is show for each username the number of all books they own and the number of non-duplicated books they own. I have an attempt below but the numbers do not appear to be correct. For example after doing my select statement, it says that Sammy's total number of duplicated books is 4 and his total number of non-duplicated books is 3.
When you actually look at the data in the owns table, you can see the real values are that Sammy's total duplicated books is 3+2+1+1 = 7 books, and his total number of non-duplicated books would just be the total number of unique book_ids he has in his collection which is just 4.
I'm not sure what's wrong with the logic of my query and would appreciate some help.
Schema:
CREATE TABLE IF NOT EXISTS books(
id integer NOT NULL primary key UNIQUE,
title text NOT NULL UNIQUE,
genre text NOT NULL,
price integer NOT NULL,
units_available integer NOT NULL
);
CREATE TABLE IF NOT EXISTS users(
username text primary key NOT NULL UNIQUE,
password text NOT NULL
);
CREATE TABLE IF NOT EXISTS owns(
owners_username integer NOT NULL,
book_id integer NOT NULL,
quantity integer NOT NULL,
PRIMARY KEY (owners_username, book_id),
FOREIGN KEY (owners_username) REFERENCES users (username),
FOREIGN KEY (book_id) REFERENCES books (id)
);
Everything in owns table / all books and their quantities owned by each user:
select * from owns;
owners_username book_id quantity
--------------- --------- --------
Bobby 47911 1
Bobby 49286 1
Bobby 55622 1
Sammy 50818 3
Sammy 49290 2
Sammy 55617 1
Sammy 6555 1
Andrew 50546 1
Andrew 49290 4
Andrew 48401 1
When I attempt to count the number of duplicated and non-duplicated books each user owns:
select owners_username, dup_count, nodup_count
from
(select owners_username, count(quantity) as dup_count
from (select owners_username, quantity from owns)
group by owners_username)
natural join
(select owners_username, count(quantity) as nodup_count
from (select distinct owners_username, quantity from owns)
group by owners_username);
owners_username dup_count nodup_count
--------------- --------- -----------
Andrew 3 2
Bobby 3 1
Sammy 4 3
You need for each user the sum of the column quantity and the number of distinct book_ids:
SELECT owners_username,
SUM(quantity) dup_count,
COUNT(DISTINCT book_id) nodup_count
FROM owns
GROUP BY owners_username
See the demo.
Results:
owners_username
dup_count
nodup_count
Andrew
6
3
Bobby
3
3
Sammy
7
4

Merging multiple databases with relations in SQLite

I have couple of sqlite files (databases) with rather simple structure like:
| Id | Category | | Id | CatId | Name |
----------------- ---------------------
| 1 | A | | 1 | 2 | A |
----------------- -- relations one to many --> ---------------------
| 2 | B | | 2 | 1 | B |
---------------------
| 3 | 2 | BC |
So as you see there is a table with categories witch is related to name tabe. Problem is I have couple of sutrch file and i want to merge them into one and keep relations.
So merging first table is simple like:
ATTACH DATABASE '{databaseFilePath}' AS Db;
BEGIN;
INSERT INTO Category (Category) SELECT Category FROM Db.Category;
COMMIT;
DETACH DATABASE Db;
But this will change my id (it is set to autoincrement because in many db files there can have same id). Now I can do the same for second table with names, problem is with keeping relation as primary has changed. Is there any rational way to do this?
Here is create tables:
CREATE TABLE Category (Id INTEGER PRIMARY KEY NOT NULL UNIQUE,Category STRING);
INSERT INTO Category (Category, Id) VALUES ('B', 2), ('A', 1);
CREATE TABLE Name (Id INTEGER PRIMARY KEY UNIQUE NOT NULL,
CatId INTEGER
REFERENCES Category (Id) ON DELETE CASCADE ON UPDATE CASCADE MATCH SIMPLE, Name STRING);
INSERT INTO Name (Name,CatId,Id)VALUES ('A',1,1),('AB',1,3 ),('B',2,2);
I believe that you could base it on the following (instead of attaching the database, 2 has been appended to the 2nd set of table names (for convenience), additionally the data has been prefixed with C2 for the 2nd set of tables) :-
DROP TABLE IF EXISTS Name;
DROP TABLE IF EXISTS Name2;
DROP TABLE IF EXISTS Category;
DROP TABLE IF EXISTS Category2;
CREATE TABLE Category (Id INTEGER PRIMARY KEY NOT NULL UNIQUE,Category STRING);
INSERT INTO Category (Category, Id) VALUES ('B', 2), ('A', 1);
CREATE TABLE Name (Id INTEGER PRIMARY KEY UNIQUE NOT NULL,
CatId INTEGER
REFERENCES Category (Id) ON DELETE CASCADE ON UPDATE CASCADE MATCH SIMPLE, Name STRING);
INSERT INTO Name (Name,CatId,Id)VALUES ('A',1,1),('AB',1,3 ),('B',2,2);
CREATE TABLE Category2 (Id INTEGER PRIMARY KEY NOT NULL UNIQUE,Category STRING);
INSERT INTO Category2 (Category, Id) VALUES ('C2B', 2), ('C2A', 1);
CREATE TABLE Name2 (Id INTEGER PRIMARY KEY UNIQUE NOT NULL,
CatId INTEGER
REFERENCES Category2 (Id) ON DELETE CASCADE ON UPDATE CASCADE MATCH SIMPLE, Name STRING);
INSERT INTO Name2 (Name,CatId,Id)VALUES ('C2A',1,1),('C2AB',1,3 ),('C2B',2,2);
UPDATE Category2 SET id = id + (Max((SELECT max(id) FROM Category),(SELECT max(id) FROM Category2)));
UPDATE Name2 SET id = id + (Max((SELECT Max(id) FROM name) ,(SELECT max(id) FROM name2)));
SELECT * FROM Category2;
SELECT * FROM Name2;
INSERT INTO Category SELECT * FROM Category2 WHERE 1;
INSERT INTO name SELECT * FROM name2 WHERE 1;
SELECT * FROM Category;
SELECT * FROM Name;
Note you mention AUTOINCREMENT but haven't included it, so checking for the highest sqlite_sequence value hasn't been included.
The above relies upon the CASCADE On UPDATE, to cascade the increase to the Category.id down to the CatId.
This works by finding the highest id of both tables with the same schema and then updating the id's of the table to be merged by adding the found highest id to the id's of all rows. When the tables are the Category table the updated ID's are cascaded to the respective Name table.
The process is performed for both the pair of Category tables and the pair of Name tables.
The result (the last query is) :-

Inner Join with Sum in oracle SQL

I'm using Oracle SQL and I have two tables, invoice and invoice_item.
invoice:
id(pk) total_invoice_price
1
2
invoice_item:
invoice total_item_price
1 10
1 20
2 25
2 35
I need that total_invoice_price be the sum of every total_item_price where invoice = id.
invoice_item.invoice is a fk that references to invoice.id
The best I could make was in the lines of:
update(
select invoice.total_invoice_price as old, SUM(invoice_item.total_item_price) as total
from invoice
inner join invoice_item
on invoice.id = invoice_item.invoice
) t
set t.old = t.total;
but it obviously doesn't work.
Tables creation:
create table invoice(
id number(5) not null,
customer_name varchar2(50) not null,
issue_date date not null,
due_date date not null,
comments varchar2(50) ,
total_invoice_price number(9) ,
constraint pk_invoice
primary key (id)
);
create table invoice_item(
id number(5) not null,
product_name varchar2(50) not null,
unit_price number(9) not null,
quantity number(9) not null,
total_item_price number(9) ,
invoice number(5) not null,
constraint pk_invoice_item
primary key (id),
constraint fk_invoice_item_invoice
foreign key (invoice)
references invoice(id)
);
I would use Merge. See below
MERGE INTO invoice tb1
USING ( SELECT invoice, SUM (total_item_price) tot_price
FROM invoice_item
GROUP BY invoice) tb2
ON (tb1.id = tb2.invoice)
WHEN MATCHED
THEN
UPDATE SET tb1.total_invoice_price = tb2.tot_price;
update
( select i.total_invoice_price, x.total_price
from invoice i
join
(
select invoice as id, sum(total_item_price) as total_price
from invoice_item
group by invoice
) x
on i.id = x.id
)
set total_invoice_price = total_price
;
Comments:
You need to aggregate within the second table, before joining. Then you join by id. In this arrangement, you will never run into issues with "uniqueness" or "primary key" being defined; the only condition that matters is that the id be unique in the "other" table, which in this case is the subquery x. Since it is an aggregation where you group by id, that uniqueness is guaranteed by the very definition of GROUP BY.
Then: It is unfortunate that you have a table invoice and a column (in a different table) also called invoice; the invoice id column should be called something like invoice_id in both tables. In my subquery, I changed the column name (from the second table) from invoice to id, by giving it that alias in the SELECT clause of the subquery.
Further comment: In a comment below this replies, the OP says he ran into an error. That means he didn't use the solution as I wrote it above. Since this is really annoying, I decided to present a full SQL*Plus session to prove that the solution is correct as written.
Create table INVOICE:
SQL> create table invoice ( id, total_invoice_price ) as
2 select 1, cast(null as number) from dual union all
3 select 2, null from dual;
Table created.
Elapsed: 00:00:00.01
SQL> select * from invoice;
ID TOTAL_INVOICE_PRICE
---------- -------------------
1
2
2 rows selected.
Create table INVOICE_ITEM:
Elapsed: 00:00:00.00
SQL> create table invoice_item ( invoice, total_item_price ) as
2 select 1, 10 from dual union all
3 select 1, 20 from dual union all
4 select 2, 25 from dual union all
5 select 2, 35 from dual;
Table created.
Elapsed: 00:00:00.01
SQL> select * from invoice_item;
INVOICE TOTAL_ITEM_PRICE
---------- ----------------
1 10
1 20
2 25
2 35
4 rows selected.
Elapsed: 00:00:00.00
UPDATE statement:
SQL> update
2 ( select i.total_invoice_price, x.total_price
3 from invoice i
4 join
5 (
6 select invoice as id, sum(total_item_price) as total_price
7 from invoice_item
8 group by invoice
9 ) x
10 on i.id = x.id
11 )
12 set total_invoice_price = total_price
13 ;
2 rows updated.
Elapsed: 00:00:00.01
SQL> select * from invoice;
ID TOTAL_INVOICE_PRICE
---------- -------------------
1 30
2 60
2 rows selected.
Elapsed: 00:00:00.02
Clean-up:
SQL> drop table invoice purge;
Table dropped.
Elapsed: 00:00:00.02
SQL> drop table invoice_item purge;
Table dropped.
Elapsed: 00:00:00.01
SQL>

SQL - foreign key one table with 3 owner tables

I have the following table Widget which is going to have an owner associated with each row.
The owner can be an id from User, Company or Department Tables. I'm guessing how to set this up is to make a link table like so?
id | user | company | department
---------|----------|----------|----------
1 | 4 | NULL | NULL
2 | 6 | 3 | 6
3 | 10 | 3 | 8
and then have the Widget table use that ID as the owner provided logic is in the app that if company is not null then the owner is the company otherwise owner would be user.
a department can't exist if there's no company.
It is not a problem if you want to add three foreign key (FK) columns from the three tables (USER, COMPANY, DEPARTMENT) respectively on the WIDGET table. You can distinguish real owner using JOIN operation described below;
CREATE TABLE WIDGET (
WIDGET_NAME VARCHAR(20),
OWNER_USER_ID INTEGER REFERENCES USER(ID),
OWNER_COMPANY_ID INTEGER REFERENCES COMPANY(ID),
OWNER_DEPART_ID INTEGER REFERENCES DEPARTMENT(ID),
);
-- retrieve OWNER_USER (you can JOIN with the USER table)
SELECT OWNER_USER_ID, WIDGET_NAME FROM WIDGET WHERE OWNER_COMPANY_ID IS NULL;
-- retrieve OWNER_COMPANY (plus OWNER_DEPART) (you can JOIN with the COMPANY and DEPARTMENT table)
SELECT OWNER_COMPANY_ID, OWNER_DEPART_ID, WIDGET_NAME FROM WIDGET WHERE OWNER_COMPANY_ID IS NOT NULL;
If you want to add just a single PK column from three tables, it doesn't make sense theoretically, but you can do it under some extra conditions. You said the owner of one widget in WIDGET table is a company if company is not null. But if company is null, then the owner is a user. If user (or corresponding identifier) column in WIDGET table is always not null whether company (or corresponding identifier) column is null or not, then you can just pick up the primary key (PK) column of USER table as a single FK of WIDGET table. Why? User → Company and User → Department dependencies are generated by this condition. It means, if you select a user A, then it is trivial that there is no more two companies related to him or her, and same as between user and department.
-- Schema of USER, COMPANY, DEPARTMENT table
CREATE TABLE USER (
ID INTEGER PRIMARY KEY,
NAME VARCHAR(20),
COMPANY_ID INTEGER REFERENCES COMPANY(ID),
DEPART_ID INTEGER REFERENCES DEPARTMENT(ID)
);
CREATE TABLE COMPANY (
ID INTEGER PRIMARY KEY,
NAME VARCHAR(20)
);
CREATE TABLE DEPARTMENT (
ID INTEGER PRIMARY KEY,
NAME VARCHAR(20)
);
-- Schema of WIDGET table
CREATE TABLE WIDGET (
WIDGET_NAME VARCHAR(20),
OWNER_ID INTEGER REFERENCES USER(ID)
);
-- retrieve OWNER_USER
SELECT U.NAME AS OWNER_USER_NAME, W.WIDGET_NAME
FROM WIDGET W, USER U
WHERE U.ID = W.OWNER_ID AND U.COMPANY_ID IS NULL;
-- retrieve OWNER_COMPANY
SELECT C.NAME AS OWNER_COMPANY_NAME, W.WIDGET_NAME
FROM WIDGET W, USER U, COMPANY C
WHERE U.ID = W.OWNER_ID AND U.COMPANY_ID = C.ID;
-- retrieve OWNER_DEPARTMENT
SELECT D.NAME AS OWNER_DEPART_NAME, W.WIDGET_NAME
FROM WIDGET W, USER U, DEPARTMENT D
WHERE U.ID = W.OWNER_ID AND U.COMPANY_ID IS NOT NULL AND U.DEPART_ID IS NOT NULL AND U.DEPART_ID = D.ID;
But if user column in WIDGET table can be null even though company column is not null, then you build up another OWNER table to keep your owner information (USER, COMPANY, DEPARTMENT). Of course, each record of WIDGET must be unique so composite unique index may be needed. (See http://www.postgresql.org/docs/current/static/indexes-unique.html)
-- Schema of OWNER table
CREATE TABLE OWNER (
ID INTEGER PRIMARY KEY.
OWNER_USER_ID INTEGER REFERENCES USER(ID),
OWNER_COMPANY_ID INTEGER REFERENCES COMPANY(ID),
OWNER_DEPARTMENT_ID INTEGER REFERENCES DEPARTMENT(ID)
);
-- unique index on OWNER
CREATE UNIQUE INDEX OWNER_UIDX ON OWNER( OWNER_USER_ID, OWNER_COMPANY_ID, OWNER_DEPARTMENT_ID );
-- Schema of WIDGET table
CREATE TABLE WIDGET (
WIDGET_NAME VARCHAR(20),
OWNER_ID INTEGER REFERENCES OWNER(ID)
);

Recursive SQL select query in Oracle 11

I have 4 different tables, bommodule, bomitem and mapbomitemmodule, mapbomparentsubmodule.
bommodule-table:
CREATE TABLE "BOMMODULE"
(
"MODULEID" NUMBER(10,0) NOT NULL ENABLE,
"ISROOTMODULE" NUMBER(1,0) NOT NULL ENABLE,
"MODULENAME" VARCHAR2(255 CHAR),
.....
)
bomitem-table:
CREATE TABLE "BOMITEM"
(
"ITEMID" NUMBER(10,0) NOT NULL ENABLE,
....
)
mapbomitemmodule-table: (This table maps the items to one or more modules).
CREATE TABLE "SSIS2"."MAPBOMITEMMODULE"
(
"ITEMID" NUMBER(10,0) NOT NULL ENABLE,
"MODULEID" NUMBER(10,0) NOT NULL ENABLE,
CONSTRAINT "MAP_ITEM_MODULE_FK" FOREIGN KEY ("ITEMID") REFERENCES "BOMITEM" ("ITEMID") ENABLE,
CONSTRAINT "MAP_MODULE_ITEM_FK" FOREIGN KEY ("MODULEID") REFERENCES "BOMMODULE" ("MODULEID") ENABLE
)
mapbomparentsubmodule-table: (This table maps the module to submodules)
CREATE TABLE "MAPBOMPARENTSUBMODULE"
(
"PARENTMODULEID" NUMBER(10,0) NOT NULL ENABLE,
"SUBMODULEID" NUMBER(10,0) NOT NULL ENABLE,
CONSTRAINT "PARENTMODULE_SUBMODULE_FK" FOREIGN KEY ("SUBMODULEID") REFERENCES "BOMMODULE" ("MODULEID") ENABLE,
CONSTRAINT "SUBMODULE_PARENTMODULE_FK" FOREIGN KEY ("PARENTMODULEID") REFERENCES "BOMMODULE" ("MODULEID") ENABLE
)
So imagine a structure something like this.
root module
submodule 1
submodule 2
submodule 3
submodule 4
submodule 5
item 5
item 6
item 7
item 2
item 3
item 4
item 1
I need to find out all items that belong to a specific moduleId. All items on all submodule levels should be listed.
How can I do this? I am using Oracle 11 as database.
Thanks a lot for your help, much appreciate it!
Thanks for the interesting question
1 step. Prepare table for canonical oracle hierarhy.
select PARENTMODULEID, SUBMODULEID, 1 HTYPE
from MAPBOMPARENTSUBMODULE
union all
select null, MODULEID, 1 --link root to null parent
from BOMMODULE B
where ISROOTMODULE = 1
union all
select MODULEID, ITEMID, 2
from MAPBOMITEMMODULE
2 step. Expand the hierarchy by using connect by
select PARENTMODULEID
,SUBMODULEID
,HTYPE
,level
,lpad('|', level*3, '|')
from
(
select PARENTMODULEID, SUBMODULEID, 1 HTYPE
from MAPBOMPARENTSUBMODULE
union all
select null, MODULEID, 1
from BOMMODULE B
where ISROOTMODULE = 1
union all
select MODULEID, ITEMID, 2
from MAPBOMITEMMODULE
) ALL_HIER
connect by prior SUBMODULEID = PARENTMODULEID
and prior HTYPE = 1 --parent is always module
start with ALL_HIER.PARENTMODULEID is null
order siblings by HTYPE
3 step. Last. Join with value tables.
select PARENTMODULEID
,SUBMODULEID
,HTYPE
,ALL_VAL.VAL
,level
,rpad('|', level * 3, ' ') || ALL_VAL.VAL
from
(
select PARENTMODULEID, SUBMODULEID, 1 HTYPE
from MAPBOMPARENTSUBMODULE
union all
select null, MODULEID, 1
from BOMMODULE B
where ISROOTMODULE = 1
union all
select MODULEID, ITEMID, 2
from MAPBOMITEMMODULE
) ALL_HIER
,(
select MODULEID VAL_ID, MODULENAME VAL, 1 VTYPE
from BOMMODULE
union all
select ITEMID, 'item '||ITEMID, 2
from BOMITEM
) ALL_VAL
where ALL_VAL.VAL_ID = ALL_HIER.SUBMODULEID
and ALL_VAL.VTYPE = ALL_HIER.HTYPE
connect by prior SUBMODULEID = PARENTMODULEID
and prior HTYPE = 1
start with ALL_HIER.PARENTMODULEID is null
order siblings by HTYPE
Start by investigating the use of a hierarchical query, using the CONNECT BY clause. It is designed for exactly this type of model.
The principle for multiple tables is the same as that for a single table:
i. You identify "starting rows".
ii. You define the logic by which you identify the next level of rows based on the "current" set.
In some cases it can help to define the hierarchical query in an inline view, particularly if a single table holds all the data required for identifying both the starting rows and the connection between levels.
These queries are always tricky, and as with many SQL issues it often helps to start off simple and build up the complexity.