Fast SQL search and sort without temporary tables - mysql

I have two tables.
Tab1:
+------------+
| id | title |
+------------+
| 1 | B |
| 2 | C |
| 3 | A |
| 4 | A |
| 5 | A |
| 6 | A |
| ... |
+------------+
PK: ID
Index: title
Tab2:
+-------------------------------------------+
| id | item_id | item_key | item_value |
+-------------------------------------------+
| 1 | 1 | value | $4 |
| 2 | 1 | url | http://h.com/ |
| 3 | 2 | value | $5 |
| 4 | 3 | url | http://i.com/ |
| 5 | 3 | value | $1 |
| 6 | 3 | url | http://y.com/ |
| 7 | 4 | value | $2 |
| 8 | 4 | url | http://z.com/ |
| 9 | 5 | value | $1 |
| 10 | 5 | url | http://123.com/ |
| ... |
+-------------------------------------------+
PK: id
Index: item_id, item_key
item_id is a foreign key from tab1.
How do I make it so I get a table of ids from Tab1 in order according to criteria from both tables. The criteria are the following:
Order ASC by title. If title is the same,
Order DESC by value. If both title and value is the same,
Prioritize items who's 'url' key contains '123.com'.
The resulting table with the ordered results would be:
+------------+
| id | title |
+------------+
| 4 | A |
| 5 | A |
| 3 | A |
| 6 | A |
| 1 | B |
| 2 | C |
| ... |
+------------+
I know I can do it with:
SELECT Tab1.id, Tab1.title
FROM Tab1
JOIN Tab2 t2_val ON t2_val.item_id = Tab1.id AND t2_val.item_key='value'
JOIN Tab2 t2_url ON t2_url.item_id = Tab1.id AND t2_url.item_key='url'
ORDER BY title,
t2_val.item_value DESC,
t2_url.item_value LIKE '%123.com%' DESC
but for large data sets, it's too slow. Is there a way to do it faster? I've set index on id and title in Tab 1, and on item key in Tab 2. Now I'd like to drop temporary tables if I could, so that means no joins, right?
How else could this be done?

First, for larger data sets, your result set is going to be larger. What are you doing with the data afterwards? The decrease in performance could be primarily related to the data coming out of the database and not to the processing in the database.
Next, what indexes do you have? The query seems to be begging for an index on tab2(item_key, item_id) to resolve the joins.
And, finally, I don't see how you can get around the final sorting for order by, because it is using values from both tables.
You are using an "entity-attribute-value" (EAV) model. This can be inherently slow when choosing lots of columns for lots of records. If you know that you have these two fields, think about changing the data model so url and value are columns in tab1.

Try this:
SELECT t1.id, t1.title
FROM Tab1 t1
INNER JOIN (SELECT item_id, MAX(item_key='value', item_value, '') AS 'value',
MAX(item_key='url', item_value, '') AS 'url'
FROM Tab2 GROUP BY item_id
) t2 ON t2.item_id = t1.id
ORDER BY t1.title, t2.value DESC, IF(t2.url LIKE '%123.com%', 0, 1);

Related

SQL (MYSQL, Postgres) Lookup/report table

I'm basically making a lookup table for my three tables but its not doing what I want. Those 3 tables were created by loading 3 different csv files.
What I'm trying to do is inserting the ID's from those tables into the lookup one.
This is what I keep getting:
---------------Lookuptable---------------
|lookup_ID|Table1_ID|Table2_ID|Table3_ID|
| 1 | 1 | | |
| 2 | 2 | | |
| 3 | 3 | | |
| | | 1 | |
| | | 2 | |
| | | 3 | |
| | | | 1 |
| | | | 2 |
| | | | 3 |
What I need is:
---------------Lookuptable---------------
|lookup_ID|Table1_ID|Table2_ID|Table3_ID |
| 1 | 1 | 1 | 1 |
| 2 | 2 | 2 | 2 |
| 3 | 3 | 3 | 3 |
I kind of get why this is happening, it inserts one row bellow every time with single inserts like
insert into Lookuptable(Table1_ID) select T1id from Table1;
and the others...
But I've tried nested ones too like
insert into Lookuptable(Table1_ID, Table2_ID, Table3_ID)
select Table1.T1id, Table2.T2id, Table2.T2id from Table1, Table2, Table3;
but still doesn't work. In fact this one just crashes the Mysql server and has an endless query on Postgres. I've tried other nested examples but none worked.
I'm also using Foreign Keys which work when I manually input a new value, but since the other tables come from loaded CSV files I have to input the values already there manually.
I'm really not sure what to do.
If I understand correctly what you want something like this should work
https://www.db-fiddle.com/f/k6CGsVXazSqJDfwKkdr6S7/1
SET #i:=0,#j:=0,#h:=0;
INSERT INTO Lookuptable
SELECT NULL,t1.ID,t2.ID,t3.ID FROM
( SELECT #i:=(#i+1) AS temp_id,ID FROM Table1 ) t1
INNER JOIN
( SELECT #j:=(#j+1) AS temp_id ,ID FROM Table2 ) t2 ON t1.temp_id=t2.temp_id
INNER JOIN
( SELECT #h:=(#h+1) AS temp_id ,ID FROM Table3 ) t3 ON t2.temp_id=t3.temp_id;

combine information from two tables into one table

I need to combine information from two tables into one table, the following table is
table 1
+----+---------------+---------+
|id_k| name | value |
+----+---------------+---------+
| 1 | enak | 4 |
| 2 | nor | 3 |
+----+---------------+---------+
table 2
+------+------+---------+
| id_d | id_k | feel |
+------+------+---------+
| 1 | 1 | good |
| 2 | 1 | better |
| 3 | 1 | verygood|
+------+------+---------+
result should be
+------+------+-------+------------------------+
| id_d | name | value | feel |
+------+------+-------+------------------------+
| 1 | enak | 4 | good, better, verygood |
| 2 | nor | 3 | |
+------+------+-------+------------------------+
this is my code [not worked]
select k.name, k.value, s.feel
from table1 as k
left join table2 as s on s.id_k=k.id_k
http://sqlfiddle.com/#!9/3a7564/1
SELECT t1.id_k,
t1.`name`,
t1.`value`,
GROUP_CONCAT(t2.feel) AS feel
FROM table1 t1
LEFT JOIN table2 t2
ON t1.id_k = t2.id_k
GROUP BY t1.id_k
You could use the gorup_concat function to concatinate the values from table2 to a coma-delimited string in the result:
SELECT table1.id_k, name, value, GROUP_CONCAT(feel SEPARATOR ', ') AS feel
FROM table1
JOIN table2 ON table1.id_k = table2.id_k
GROUP BY table1.id_k

mysql select with group by returning rows with a preference order

I have the following mysql query result:
+----+------------+-------------+
| id | title | lang |
+----+------------+--------------
| 1 | ola1 | 1 |
| 1 | hello1 | 2 |
| 1 | bonjour1 | 3 |
| 2 | ola2 | 1 |
| 2 | bonjour2 | 3 |
| 3 | hello3 | 2 |
| 4 | bonjour4 | 3 |
+----+------------+-------------+
What I want is a group_by query by id and that gives me for each id the title with a order of preference for lang field. Example:
Result for lang preference order 1, 2, 3:
+----+------------+-------------+
| id | title | lang |
+----+------------+--------------
| 1 | ola1 | 1 |
| 2 | ola2 | 1 |
| 3 | hello3 | 2 |
| 4 | bonjour4 | 3 |
+----+------------+-------------+
Result for lang preference order 3, 2, 1:
+----+------------+-------------+
| id | title | lang |
+----+------------+--------------
| 1 | bonjour1 | 3 |
| 2 | bonjour2 | 3 |
| 3 | hello3 | 2 |
| 4 | bonjour4 | 3 |
+----+------------+-------------+
Thanks!
It is either not possible, or, not with in my SQL skills to execute that in one query. I always end up using a temporary template and two SQL commands for these problems:
(assuming that your table is called Table1 and the temporary table should be called tempTable)
SELECT Table1.id, Min(Table1.lang) AS Min_Of_lang INTO tempTable FROM Table1 GROUP BY Table1.id ORDER BY Table1.id;
SELECT Table1.* FROM tempTable INNER JOIN Table1 ON (tempTable.MinOflang = Table1.lang) AND (tempTable.id = Table1.id);
The first command creates a new table (that overrides the current table if it exists). The second command uses the first table to produce the desired result set.
To change from your first desired results table to the second, use Max instead of min in the first query.
Somebody else may well have a more elegant solution than mine. Also, an extra SQL statement could be added to delete the temporary table.
This is a feature that is not defined in MySQL. The displayed value in a non-aggregated column is undetermined. read more here (MySQL Documentation).
(Standard SQL doesn't allow to include non-aggregated columns when using GROUP BY, I guess this is one of the reasons).
From your description of the what you want to do, you should simple SELECT all rows with the lang you are looking for
SELECT * FROM your_table WHERE lang = 1

Query needed to get an column values of one table which are not there in string column of another table

I have a two tables :
mysql> select * from quizquestionbank;
| ID | QuestionFilePath | CorrectAnswer |
| 1 | p.wav | 1 |
| 2 | q.wav | 2 |
| 3 | a.wav | 3 |
| 4 | b.wav | 1 |
| 5 | m.wav | 3 |
Second table is :
mysql> select * from quizuserdetails;
| ID | MSISDN | QuestionIdDetails | AnswerRecord |
| 1 | 235346 | 1,3,4,5 | S,F,S,F |
| 2 | 564574 | 4,5,67,88 | F,S,F,s |
| 3 | 500574 | 5,55,66,44,2 | F,F,F,F |
I want to get the IDs from table 1 which are not there in QuestionIdDetails column of second table.
I tried query
Select ID from quizquestionbank where ID not in (Select QuestionIdDetails from quizuserdetails where msisdn = '235346 ');
But this does not work
CAn anybody suggest a way to do it
Use find_in_set() to match the id to the list, but that's not all:
Select disting qb.ID
from quizquestionbank qb
left join quizuserdetails qd
on find_in_set(qb.id, QuestionIdDetails) > 0
and msisdn = '235346'
where qd.id is null
There's 3 key things going on here:
using a left join and including the extra condition in the join condition
the use of find_in_set(), which finds a value in a CSV string, to make the join
using a where clause that filters out matches, leaving only the missed joins

SQL sorting with multiple criteria

I have two tables.
Tab1:
+------------+
| id | title |
+------------+
| 1 | B |
| 2 | C |
| 3 | A |
| 4 | A |
| 5 | A |
| 6 | A |
| ... |
+------------+
Tab2:
+-------------------------------------------+
| id | item_id | item_key | item_value |
+-------------------------------------------+
| 1 | 1 | value | $4 |
| 2 | 1 | url | http://h.com/ |
| 3 | 2 | value | $5 |
| 4 | 3 | url | http://i.com/ |
| 5 | 3 | value | $1 |
| 6 | 3 | url | http://y.com/ |
| 7 | 4 | value | $2 |
| 8 | 4 | url | http://z.com/ |
| 9 | 5 | value | $1 |
| 10 | 5 | url | http://123.com/ |
| ... |
+-------------------------------------------+
item_id is a foreign key from tab1.
How do I make it so I get a table of ids from Tab1 in order according to criteria from both tables. The criteria are the following:
Order ASC by title. If title is the same,
Order DESC by value. If both title and value is the same,
Prioritize items who's 'url' key contains '123.com'.
The resulting table with the ordered results would be:
+------------+
| id | title |
+------------+
| 4 | A |
| 5 | A |
| 3 | A |
| 6 | A |
| 1 | B |
| 2 | C |
| ... |
+------------+
The results should include items that don't have the one, both, or none of the fields from Tab2 set.
As far as I understand, a simple join will do it. You'll have to join Tab2 twice, since you want to order by values from both rows.
SELECT Tab1.id, Tab1.title
FROM Tab1
JOIN Tab2 t2_val ON t2_val.item_id = Tab1.id AND t2_val.item_key='value'
JOIN Tab2 t2_url ON t2_url.item_id = Tab1.id AND t2_url.item_key='url'
ORDER BY title,
t2_val.item_value DESC,
t2_url.item_value LIKE '%123.com%' DESC
An SQLfiddle to test with.
A little complicated, because when you do the join you will get multiple rows. Here is an approach that aggregates tab2 before doing the join:
select t1.*
from Tab1 t1 left outer join
(select id,
max(case when item_key = 'value' then item_value end) as value,
max(case when item_key = 'url' then item_value end) as url
from Tab2 t2
group by id
) t2
on t1.id = t2.id
order by t1.title, t2.value desc,
(t2.url like '%123.com%') desc;