MySQL 2 level MENU Query - sql

I'm trying to do a MySQL request to retreive a 2 level menu (parent and children)...
There's only 1 table, of course :
idCategory | Title | idCategoryParent | DisplayOrder
1 | cat1 | NULL | 1
2 | sub-cat1 | 1 | 1
3 | sub-cat2 | 1 | 2
4 | cat2 | NULL | 2
5 | sub-cat3 | 4 | 1
6 | sub-cat4 | 4 | 2
7 | cat3 | NULL | 3
I'm looking for those results :
titleCat | titleSubCat | idCategory
cat1 | sub-cat1 | 1
cat1 | sub-cat2 | 1
cat2 | sub-cat3 | 4
cat2 | sub-cat4 | 4
cat3 | NULL | 7
OR something like that would be fine too :
cat1 | null | 1
cat1 | sub-cat1 | 1
cat1 | sub-cat2 | 1
etc..
I tried with something like :
SELECT subcat.title as catTitle, cat.title as parentTitle, subcat.idCategory as catIdCategory, subcat.idCategoryParent
FROM `test_category` as cat
RIGHT OUTER JOIN test_category as subcat ON cat.idCategory=subcat.idCategoryParent
Doesn't work bad but I struggle trying to order the records...
Here's the SQL Dump if you want to try it :
--
-- Table structure for table `test_category`
--
CREATE TABLE IF NOT EXISTS `test_category` (
`idCategory` int(11) NOT NULL AUTO_INCREMENT,
`idCategoryParent` int(11) DEFAULT NULL,
`title` varchar(20) NOT NULL,
`order` int(11) NOT NULL,
PRIMARY KEY (`idCategory`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=8 ;
--
-- Dumping data for table `test_category`
--
INSERT INTO `test_category` (`idCategory`, `idCategoryParent`, `title`, `order`) VALUES
(1, NULL, 'cat1', 1),
(2, 1, 'sub-cat1', 1),
(3, 1, 'sub-cat2', 2),
(4, NULL, 'cat2', 2),
(5, 4, 'sub-cat3', 1),
(6, 4, 'sub-cat4', 2),
(7, NULL, 'cat3', 3);
Thanks! :)

Your query is almost correct, but you need to use LEFT JOIN if you want categories with no subcategories, and you should select only first-level categories from the first table.
SELECT t1.title, t2.title, t1.idCategory
FROM
test_category t1
LEFT JOIN test_category t2 ON t2.idCategoryParent=t1.idCategory
WHERE t1.idCategoryParent IS NULL
ORDER BY t1.DisplayOrder, t2.DisplayOrder

Related

pivot then group on value

I have a logs table with the following definition:
Column | Type | Collation | Nullable | Default
------------------+-----------------------+-----------+----------+---------
id | integer | | not null |
work_location_id | uuid | | not null |
hard_disk_id | integer | | not null |
and a works table with the following definition:
Column | Type | Collation | Nullable | Default
-------------+-----------------------+-----------+----------+---------
id | integer | | not null |
location_id | uuid | | not null |
f_index | integer | | not null |
f_name | character varying(40) | | not null |
f_value | character varying(40) | | not null |
The logs table has data such as:
id | work_location_id | hard_disk_id
----+--------------------------------------+--------------
1 | 40e6215d-b5c6-4896-987c-f30f3678f608 | 1
2 | 3f333df6-90a4-4fda-8dd3-9485d27cee36 | 2
3 | c17bed94-3a9c-4c21-be49-dc77f96d49dc | 3
4 | 6ecd8c99-4036-403d-bf84-cf8400f67836 | 4
5 | 6ecd8c99-4036-403d-bf84-cf8400f67836 | 5
And the works table has data such as:
id | location_id | f_index | f_name | f_value
----+--------------------------------------+---------+-------------+------------
1 | 40e6215d-b5c6-4896-987c-f30f3678f608 | 1 | plot_crop | pears
2 | 3f333df6-90a4-4fda-8dd3-9485d27cee36 | 1 | plot_crop | pears
3 | c17bed94-3a9c-4c21-be49-dc77f96d49dc | 1 | plot_crop | pears
4 | 1cdc7c05-0acd-46cb-b48a-4d3e240a4548 | 1 | plot_crop | pears
5 | dae1eee7-508f-4a76-8906-8ff7b8bfab26 | 1 | plot_crop | pears
6 | 6ecd8c99-4036-403d-bf84-cf8400f67836 | 1 | plot_id | 137
7 | 6ecd8c99-4036-403d-bf84-cf8400f67836 | 2 | farmer_name | John Smith
Desired Output
I want to be able to query the two tables and get the following output
location_id | plot_id | farmer_name
---------------------------------------+---------+-------------
40e6215d-b5c6-4896-987c-f30f3678f608 | None | None
3f333df6-90a4-4fda-8dd3-9485d27cee36 | None | None
c17bed94-3a9c-4c21-be49-dc77f96d49dc | None | None
6ecd8c99-4036-403d-bf84-cf8400f67836 | 137 | John Smith
Notice how for location_id = 6ecd8c99-4036-403d-bf84-cf8400f67836, both values are now showing in one row. I tried to use group by location_id but that didn't work, I was still getting duplicates.
I have also created a db-fiddle.
This looks like conditional aggregation:
select location_id,
max(f_value) filter (where f_name = 'plot_id') as plot_id,
max(f_value) filter (where f_name = 'farmer_name') as farmer_name
from t
group by location_id;
In other databases, you would just use:
max(case when f_name = 'plot_id' then f_value end) as plot_id
As you want to have None as text
Schema (PostgreSQL v13)
-- create table
create table logs (
id integer not null,
work_location_id uuid not null,
hard_disk_id integer not null
);
create table works (
id integer not null,
location_id uuid not null,
f_index integer not null,
f_name varchar(40) not null,
f_value varchar(40) not null
);
-- insert data into table
insert into logs (id, work_location_id, hard_disk_id) values
(1, '40e6215d-b5c6-4896-987c-f30f3678f608', 1),
(2, '3f333df6-90a4-4fda-8dd3-9485d27cee36', 2),
(3, 'c17bed94-3a9c-4c21-be49-dc77f96d49dc', 3),
(4, '6ecd8c99-4036-403d-bf84-cf8400f67836', 4),
(5, '6ecd8c99-4036-403d-bf84-cf8400f67836', 5);
insert into works (id, location_id, f_index, f_name, f_value) values
(1, '40e6215d-b5c6-4896-987c-f30f3678f608', 1, 'plot_crop', 'pears'),
(2, '3f333df6-90a4-4fda-8dd3-9485d27cee36', 1, 'plot_crop', 'pears'),
(3, 'c17bed94-3a9c-4c21-be49-dc77f96d49dc', 1, 'plot_crop', 'pears'),
(4, '1cdc7c05-0acd-46cb-b48a-4d3e240a4548', 1, 'plot_crop', 'pears'),
(5, 'dae1eee7-508f-4a76-8906-8ff7b8bfab26', 1, 'plot_crop', 'pears'),
(6, '6ecd8c99-4036-403d-bf84-cf8400f67836', 1, 'plot_id', '137'),
(7, '6ecd8c99-4036-403d-bf84-cf8400f67836', 2, 'farmer_name', 'John Smith');
Query #1
select w.location_id,
COALESCE(MAX(case
when w.f_name = 'plot_id' then w.f_value
else NULL
end),'None') as "plot_id",
COALESCE(MAX(case
when w.f_name = 'farmer_name' then w.f_value
else NULL
end),'None') as "farmer_name"
from logs l
inner join works w on w.location_id = l.work_location_id
GROUP BY location_id;
location_id
plot_id
farmer_name
3f333df6-90a4-4fda-8dd3-9485d27cee36
None
None
40e6215d-b5c6-4896-987c-f30f3678f608
None
None
6ecd8c99-4036-403d-bf84-cf8400f67836
137
John Smith
c17bed94-3a9c-4c21-be49-dc77f96d49dc
None
None
View on DB Fiddle

SQL: Combine Categories and Members in one select output

I have two tables, Category and Member:
Category: Member:
+--Id--+--Name--+ +--Id--+--Name--+--CategoryId--+
| 1 | Cat1 | | 1 | Mem1 | 2 |
| 2 | Cat2 | | 2 | Mem2 | 2 |
| 3 | Cat3 | | 3 | Mem3 | 1 |
+------+--------+ | 4 | Mem4 | 3 |
| 5 | Mem5 | 1 |
| 6 | Mem6 | 3 |
+------+--------+--------------+
I would like to have a query that gives the following output:
Combined:
+--Name--+--IsCategory--+
| Cat1 | True |
| Mem3 | False |
| Mem5 | False |
| Cat2 | True |
| Mem1 | False |
| Mem2 | False |
| Cat3 | True |
| Mem4 | False |
| Mem6 | False |
+--------+--------------+
I know how to use JOIN to output every Member with its Category name in one row and how to use UNION to list all Categories and Members in one output. Can I use a combination of these two commands to achieve the desired output? Or do I need to perform a FOR EACH loop somehow? Is it even possible to get what I want?
EDIT:
The order Cat1, Mem3, Mem5, Cat2, Mem1, Mem2,... is important for me.
JUST i tried like this can u check it
CREATE TABLE #Category
([Id] int, [Name] varchar(4))
INSERT INTO #Category
([Id], [Name])
VALUES
(1, 'Cat1'),
(2, 'Cat2'),
(3, 'Cat3')
CREATE TABLE #Member
([Id] int, [Name] varchar(4), [CategoryId] int)
;
INSERT INTO #Member
([Id], [Name], [CategoryId])
VALUES
(1, 'Mem1', 2),
(2, 'Mem2', 2),
(3, 'Mem3', 1),
(4, 'Mem4', 3),
(5, 'Mem5', 1),
(6, 'Mem6', 3)
SELECT NAME , CASE WHEN NAME LIKE'%CAT%' THEN 'TRUE' ELSE 'FALSE' END AS IS_CATEGORY FROM (SELECT * FROM #MEMBER UNION ALL
SELECT *,NULL AS COLUMN1 FROM #CATEGORY )A
output
NAME IS_CATEGORY
Mem1 FALSE
Mem2 FALSE
Mem3 FALSE
Mem4 FALSE
Mem5 FALSE
Mem6 FALSE
Cat1 TRUE
Cat2 TRUE
Cat3 TRUE
You can use union for this:
SELECT name, 0 as IsCategory
FROM member
UNION ALL
SELECT name, 1
FROM category
Based on the answers of #Chanukya and #Zohar Peled I found the following solution:
SELECT Name, IsCategory FROM
(SELECT Name as Name, 1 as IsCategory, id * 10000 as OrderNumber
FROM Category
UNION ALL
SELECT m.Name as Name, 0, c.id * 10000 + m.id as OrderNumber
FROM Members b JOIN Category c ON m.CategoryId = c.Id
) AS temp
ORDER BY OrderNumber;
This assumes that there are no more than 10000 members per category, which is fine for my current use case. However, I appreciate any suggestions for a more elegant and correct way.

SQL query check value exists in lookup based on another column value

I have Name/Value pair records in a table and I need to confirm the values exist against a lookup for each Name
KeyVal - Table of NameValue pairs
| MyID1 | MyRecNumber | MyFieldName | MyFieldValue |
|-------|-------------|-------------|--------------|
| 1 | 1 | FirstField | One |
| 2 | 1 | SecondField | Car |
| 3 | 2 | FirstField | Two |
| 4 | 2 | SecondField | Firetruck |
| 5 | 3 | FirstField | Blue |
| 6 | 3 | SecondField | Car |
LookupTable - Table to match Name Values (from KeyVal) with LookupValue (in CheckVals table)
| MyID2 | MyFieldName | LookupName |
|-------|-------------|------------|
| 1 | FirstField | FieldOne |
| 2 | SecondField | FieldTwo |
CheckVals - Table with valid values for each field
| MyID3 | LookupFieldName | LookupValue |
|-------|-----------------|-------------|
| 1 | FieldOne | One |
| 2 | FieldOne | Two |
| 3 | FieldOne | Three |
| 4 | FieldTwo | Car |
| 5 | FieldTwo | Truck |
| 6 | FieldTwo | Bus |
I have a query that will check values against a single name lookup, but am unsure how to make this check all names against the lookup table. In this query it bypasses the LookupTable as I specify the lookup value in the query itself.
DECLARE #AttributeName AS VARCHAR(50)
DECLARE #Lookup AS VARCHAR(50)
SET #AttributeName = 'SecondField'
SET #Lookup = 'FieldTwo';
SELECT
MyRecNumber,
MyFieldName,
MyFieldValue
FROM
dbo.KeyVal kv
WHERE
MyFieldName = #AttributeName
AND MyFieldValue NOT IN
(
SELECT
LookupValue
FROM
dbo.CheckVals cv
WHERE cv.LookupFieldName = #Lookup
)
Question: How can I do a lookup against all values in the KeyVal table, through the LookupTable table, to check if the value in MyFieldValue exists in CheckVals against the MyFieldName and LookupName match?
This is what I'm hoping to get - the two rows that have invalid values are returned in the query results
| MyRecNumber | MyFieldName | MyFieldValue |
|-------------|-------------|--------------|
| 2 | SecondField | Firetruck |
| 3 | FirstField | Blue |
Sample Tables
CREATE TABLE [dbo].[KeyVal](
[MyID1] [smallint] IDENTITY(1,1) NOT NULL,
[MyRecNumber] [smallint] NULL,
[MyFieldName] [varchar](50) NULL,
[MyFieldValue] [varchar](50) NULL
) ON [PRIMARY]
CREATE TABLE [dbo].[LookupTable](
[MyID2] [smallint] IDENTITY(1,1) NOT NULL,
[MyFieldName] [varchar](50) NULL,
[LookupName] [varchar](50) NULL
) ON [PRIMARY]
CREATE TABLE [dbo].[CheckVals](
[MyID3] [smallint] IDENTITY(1,1) NOT NULL,
[LookupFieldName] [varchar](50) NULL,
[LookupValue] [varchar](50) NULL
) ON [PRIMARY]
Sample Data
INSERT INTO [dbo].[KeyVal]
([MyRecNumber], [MyFieldName], [MyFieldValue])
VALUES
(1, 'FirstField', 'One'),
(1, 'SecondField', 'Car'),
(2, 'FirstField', 'Two'),
(2, 'SecondField', 'Firetruck'),
(3, 'FirstField', 'Blue'),
(3, 'SecondField', 'Car')
INSERT INTO [dbo].[LookupTable]
([MyFieldName], [LookupName])
VALUES
('FirstField', 'FieldOne'),
('SecondField', 'FieldTwo')
INSERT INTO [dbo].[CheckVals]
([LookupFieldName], [LookupValue])
VALUES
('FieldOne', 'One'),
('FieldOne', 'Two'),
('FieldOne', 'Three'),
('FieldTwo', 'Car'),
('FieldTwo', 'Truck'),
('FieldTwo', 'Bus')
Let me assume that you want the rows in the first table where the values do not match:
select kv.*
from keyval kv left join
lookuptable lt
on kv.myfieldname = lt.myfieldname left join
checkvals cv
on cv.LookupFieldName = lt.LookupName and
cv.LookupValue = kv.MyFieldValue
where cv.myid3 is null;

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.

Optimization of a sql-query with exists

I have a table:
+----+---------+-----------+--------------+-----------+
| id | item_id | attr_name | string_value | int_value |
+----+---------+-----------+--------------+-----------+
| 1 | 1 | 1 | prop_str_1 | NULL |
| 2 | 1 | 2 | prop_str_2 | NULL |
| 3 | 1 | 3 | NULL | 2 |
| 4 | 2 | 1 | prop_str_1 | NULL |
| 5 | 2 | 2 | prop_str_3 | NULL |
| 6 | 2 | 3 | NULL | 2 |
| 7 | 3 | 1 | prop_str_4 | NULL |
| 8 | 3 | 2 | prop_str_2 | NULL |
| 9 | 3 | 3 | NULL | 1 |
+----+---------+-----------+--------------+-----------+
And I want to select item_id with specific values for the attributes. But this is complicated by the fact that the fetching needs to do on several attributes. I've got to do it just using exists:
select *
from item_attribute as attr
where (name = 1 and string_value = 'prop_str_1')
and exists
(select item_id
from item_attribute
where item_id = attr.item_id and name = 2 and string_value = 'prop_str_2')
But the number of attributes can be increased, and therefore nested queries with exists will increase.
How can I rewrite this query to reduce the nested queries?
UPD:
create table item_attribute(
id int not null,
item_id int not null,
attr_name int not null,
string_value varchar(50),
int_value int,
primary key (id)
);
insert into item_attribute values (1, 1, 1, 'prop_str_1', NULL);
insert into item_attribute values (2, 1, 2, 'prop_str_2', NULL);
insert into item_attribute values (3, 1, 3, NULL, 2);
insert into item_attribute values (4, 2, 1, 'prop_str_1', NULL);
insert into item_attribute values (5, 2, 2, 'prop_str_3', NULL);
insert into item_attribute values (6, 2, 3, NULL, 2);
insert into item_attribute values (7, 3, 1, 'prop_str_4', NULL);
insert into item_attribute values (8, 3, 2, 'prop_str_2', NULL);
insert into item_attribute values (9, 3, 3, NULL, 1);
See if this works for you. It in essence does the same thing... Your first qualifier is that a given attribute name = 1 and string = 'prop_str_1', but then self-joins to attribute table again on same ID but second attribute and string
select
attr.*
from
item_attribute attr
JOIN item_attribute attr2
ON attr.item_id = attr2.item_id
and attr2.name = 2
and attr2.string_value = 'prop_str_2'
where
attr.name = 1
and string_value = 'prop_str_1'
I would also have an index on your table on (name, string_value, item_id) to increase performance of where and join conditions.