Optimization of a sql-query with exists - sql

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.

Related

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.

How to select shipments that have one activity but doesn't have another one

Simplified version of a table
Table ActivityHistory:
ActivityHistoryid(PK) | ShipmentID | ActivityCode | Datetime
1 | 1 | CodeA |
2 | 1 | CodeB |
3 | 1 | CodeC |
4 | 2 | CodeA |
5 | 3 | CodeA |
6 | 3 | CodeB |
7 | 4 | CodeC |
This table contains the list of activities that occurred to given shipments.
Task: I need to select shipments(shipment ids) that has "CodeA" and doesn't have a "CodeC" activity.
In this example, shipment id 2 and 3 will match the criteria.
Table Shipment: (ShipmentID(PK), other shipment related columns)
Thank you.
Try this one -
Query:
DECLARE #temp TABLE
(
ActivityHistoryid INT
, ShipmentID INT
, ActivityCode VARCHAR(20)
)
INSERT INTO #temp (ActivityHistoryid, ShipmentID, ActivityCode)
VALUES
(1, 1, 'CodeA'),
(2, 1, 'CodeB'),
(3, 1, 'CodeC'),
(4, 2, 'CodeA'),
(5, 3, 'CodeA'),
(6, 3, 'CodeB'),
(7, 4, 'CodeC')
SELECT *
FROM #temp t
WHERE ActivityCode = 'CodeA'
AND NOT EXISTS(
SELECT 1
FROM #temp t2
WHERE t2.ActivityCode = 'CodeC'
AND t2.ShipmentID = t.ShipmentID
)
Output:
ActivityHistoryid ShipmentID ActivityCode
----------------- ----------- --------------------
4 2 CodeA
5 3 CodeA

SELECT inherit values from parent in a hierarchy

I'm trying to acheive through T-SQL (in a stored procedure) a way to copy a value from a parent into the child when retrieving rows. Here is some example data:
DROP TABLE TEST_LEVELS
CREATE TABLE TEST_LEVELS(
ID INT NOT NULL
,VALUE INT NULL
,PARENT_ID INT NULL
,LEVEL_NO INT NOT NULL
)
INSERT INTO TEST_LEVELS (ID, VALUE, PARENT_ID, LEVEL_NO) VALUES (1, 10000, NULL, 1)
INSERT INTO TEST_LEVELS (ID, VALUE, PARENT_ID, LEVEL_NO) VALUES (2, NULL, 1, 2)
INSERT INTO TEST_LEVELS (ID, VALUE, PARENT_ID, LEVEL_NO) VALUES (3, NULL, 2, 3)
INSERT INTO TEST_LEVELS (ID, VALUE, PARENT_ID, LEVEL_NO) VALUES (4, 20000, NULL, 1)
INSERT INTO TEST_LEVELS (ID, VALUE, PARENT_ID, LEVEL_NO) VALUES (5, NULL, 4, 2)
INSERT INTO TEST_LEVELS (ID, VALUE, PARENT_ID, LEVEL_NO) VALUES (6, 25000, 5, 3)
INSERT INTO TEST_LEVELS (ID, VALUE, PARENT_ID, LEVEL_NO) VALUES (7, NULL, 6, 4)
Selecting the data as follows:
SELECT ID, VALUE, LEVEL_NO
FROM TEST_LEVELS
results in:
+----+-------+----------+
| ID | VALUE | LEVEL_NO |
+----+-------+----------+
| 1 | 10000 | 1 |
| 2 | NULL | 2 |
| 3 | NULL | 3 |
| 4 | 20000 | 1 |
| 5 | NULL | 2 |
| 6 | 25000 | 3 |
| 7 | NULL | 4 |
+----+-------+----------+
But I need something like this (values are inherited by the parent):
+----+-------+----------+
| ID | VALUE | LEVEL_NO |
+----+-------+----------+
| 1 | 10000 | 1 |
| 2 | 10000 | 2 |
| 3 | 10000 | 3 |
| 4 | 20000 | 1 |
| 5 | 20000 | 2 |
| 6 | 25000 | 3 |
| 7 | 25000 | 4 |
+----+-------+----------+
Can this be achieved without using cursors (it must also run on SQL Server 2005)?
Use:
;with cte
as
(
select t.ID, t.VALUE, t.PARENT_ID, t.LEVEL_NO
from #t t
where t.Value is not null
union all
select t.ID, c.Value, t.PARENT_ID, t.LEVEL_NO
from cte c
join #t t on t.PARENT_ID = c.ID
where t.Value is null
)
select c.ID, c.Value, c.LEVEL_NO
from cte c
order by c.ID
Output:
ID Value LEVEL_NO
----------- ----------- -----------
1 10000 1
2 10000 2
3 10000 3
4 20000 1
5 20000 2
6 25000 3
7 25000 4
Maybe something like this:
;WITH cte_name(ID,VALUE,PARENT_ID,LEVEL_NO)
AS
(
SELECT
tbl.ID,
tbl.VALUE,
tbl.PARENT_ID,
tbl.LEVEL_NO
FROM
TEST_LEVELS AS tbl
WHERE
tbl.PARENT_ID IS NULL
UNION ALL
SELECT
tbl.ID,
ISNULL(tbl.VALUE,cte_name.VALUE),
tbl.PARENT_ID,
tbl.LEVEL_NO
FROM
cte_name
JOIN TEST_LEVELS AS tbl
ON cte_name.ID=tbl.PARENT_ID
)
SELECT
*
FROM
cte_name
ORDER BY
ID
One way to do it:
SELECT T.ID,
case when T.VALUE IS NULL
THEN (SELECT A.VALUE FROM TEST_LEVELS A WHERE A.ID = T.PARENT_ID)
ELSE T.VALUE
END,
T.LEVEL_NO
FROM TEST_LEVELS T

Help with MySQL statement

I have written the following SQL statement in MySQL :
USE my_database;
SELECT * FROM some_table WHERE some_column IN (1, 2, 3);
This returns a set of rows that have a column value which is a key into a row of another table (call it some_other_table).
a b c d <--this is the column with the key
1
2
3
I want to say, look up all of the rows in another table with value 1, and do something (null out some column)
Any help is appreciated.
Yes, you can use the multiple-table UPDATE syntax:
UPDATE some_other_table
JOIN some_table ON (some_table.some_key = some_other_table.id)
SET some_other_table.some_field = NULL
WHERE some_table.some_column IN (1, 2, 3);
Example:
CREATE TABLE some_table (id int, some_column int, some_key int);
CREATE TABLE some_other_table (id int, some_field int);
INSERT INTO some_table VALUES (1, 1, 1);
INSERT INTO some_table VALUES (2, 2, 2);
INSERT INTO some_table VALUES (3, 3, 3);
INSERT INTO some_table VALUES (4, 4, 4);
INSERT INTO some_table VALUES (5, 5, 5);
INSERT INTO some_other_table VALUES (1, 10);
INSERT INTO some_other_table VALUES (2, 20);
INSERT INTO some_other_table VALUES (3, 30);
INSERT INTO some_other_table VALUES (4, 40);
Before:
SELECT * FROM some_table;
+------+-------------+----------+
| id | some_column | some_key |
+------+-------------+----------+
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 3 | 3 |
| 4 | 4 | 4 |
| 5 | 5 | 5 |
+------+-------------+----------+
5 rows in set (0.00 sec)
SELECT * FROM some_other_table;
+------+------------+
| id | some_field |
+------+------------+
| 1 | 10 |
| 2 | 20 |
| 3 | 30 |
| 4 | 40 |
+------+------------+
4 rows in set (0.00 sec)
After:
SELECT * FROM some_table;
+------+-------------+----------+
| id | some_column | some_key |
+------+-------------+----------+
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 3 | 3 |
| 4 | 4 | 4 |
| 5 | 5 | 5 |
+------+-------------+----------+
5 rows in set (0.00 sec)
SELECT * FROM some_other_table;
+------+------------+
| id | some_field |
+------+------------+
| 1 | NULL |
| 2 | NULL |
| 3 | NULL |
| 4 | 40 |
+------+------------+
4 rows in set (0.00 sec)
UPDATE: Further to comments below.
Another example:
CREATE TABLE amir_effective_reference (class int, inst int, rln int, rclass int, rinst int, chg int, typ int);
CREATE TABLE amir_effective_change (chg int, txn int, rltn int, entry int, effective int);
INSERT INTO amir_effective_reference VALUES (1, 100, 1, 50, 20, 10, 5000);
INSERT INTO amir_effective_change VALUES (10, 100, 100, 500, 200);
Result:
UPDATE amir_effective_change
JOIN amir_effective_reference ON (amir_effective_reference.chg = amir_effective_change.chg)
SET amir_effective_change.effective = NULL
WHERE amir_effective_change.rltn IN (100);
SELECT * FROM amir_effective_change;
+------+------+------+-------+-----------+
| chg | txn | rltn | entry | effective |
+------+------+------+-------+-----------+
| 10 | 100 | 100 | 500 | NULL |
+------+------+------+-------+-----------+
1 row in set (0.00 sec)

MySQL 2 level MENU Query

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