Select products, that have all of the parameters in column - sql

I have a Product model and ProductParams, where I store product ids, column with some additional parameters(param) and param type.
It will look like that:
id | type | param
| |
1 | color | blue
1 | type | hard
2 | color | blue
2 | type | soft
.. | ..... | .....
How do I get only first product, when I call it with params = [blue, hard]?
For now I thought of something like Product.joins{product_params}.where{product_params.param.in params}.uniq, but I get 2nd as well, because it is blue too...
I use squeel gem, to constuct SQL queries, but answer doesn't have to be in squeel as well.
EDIT
I came up with solution, but it's a bit strange:
Product.joins{product_params}.where{product_params.param.in params}.group{id}.having{count(product_params.param) == 2}
But it would be great, if there was more graceful one.

Related

How can I combine Postgresgl's ArrayField ANY option with LIKE

I'm trying to filter a queryset on the first characters of an element in an ArrayField in postgresql.
Data
--------------------
| id | registration_date | sbi_codes |
| 1 | 2007-11-13 | {9002, 1002, 85621} |
| 2 | 2010-10-11 | {1002, 9022, 9033 |
| 3 | 2019-02-02 | {9001, 8921} |
| 4 | 2012-02-02 | {120} |
I've tried the following (which obviously don't work), but I think clearly indicates what I'm trying to achieve.
select count(*)
from administrations_administration
where '90' = left(any(sbi_codes),2)
or
select count(*)
from administrations_administration
where '90%' like any(sbi_codes
So the sbi_codes can be for example 9002 or 9045, And I'm trying to filter all the records that contain an element that starts with 90.
expected result
____
| count | sbi_codes |
| 3 | 90 |
Thanks!
The thing on the left hand side of LIKE is the string, in which % is just a %. The thing on the right hand side is the pattern, in which % is a wildcard. Using ANY does't change these semantics, the pattern still goes the right.
To solve this, you could create your own operator which is like LIKE, but has its arguments reversed.

LIKE in SQL command not working as expected?

That is my database table
--------------------------------
| Product name | category |
| Item 1 | 14*#*2*#*1 |
| Item 2 | 1*#*5*#*14 |
| Item 3 | 2*#*21*#*5 |
| Item 4 | 5*#*51*#*25 |
| Item 5 | 100*#*212*#*25|
| Item 6 | 212*#*55*#*651|
---------------------------------
When we run my SQL query
SELECT * FROM product WHERE category LIKE '%1%'
It gives the following output:
--------------------------------
| Product name | category |
| Item 1 | 14*#*2*#*1 |
| Item 2 | 1*#*5*#*14 |
| Item 3 | 2*#*21*#*5 |
| Item 4 | 5*#*51*#*25 |
| Item 5 |100*#*212*#*25 |
| Item 6 |212*#*55*#*651 |
--------------------------------
But I want the following result:
-------------------------------
| Product name | category |
| Item 1 | 14*#*2*#*1 |
| Item 2 | 1*#*5*#*14 |
-------------------------------
This is a horrible data structure and you should fix it -- if you can. SQL has a great way to store lists of things. It is called a table, not a column.
Sometimes, we are stuck with other people's really, really, really, really bad decisions. If that is the case, you can use like like this:
SELECT p.*
FROM product p
WHERE '*#*' || p.category || '*#*' LIKE '%*#*1*#*%';
Note that || is the ANSI/ISO standard for string concatenation. Not all databases support this method; yours may have an alternative.
You can do it like this:
SELECT *
FROM product
WHERE category LIKE '1*#*%'
or category LIKE '%*#*1*#*%'
or category LIKE '%*#*1%'
But do not store data like this, store one category per row. Queries will perform better, and makes everything easier.
We can use 'LIKE' keyword like this
SELECT * FROM product WHERE category ='1' or (category LIKE '%1,%' and category LIKE '%,1%') or category LIKE '%,1' or a.category LIKE '1,%'
if you want to get the 2 records you can use this.
SELECT * FROM product WHERE category LIKE '%1%' limit 2

ALV display one column like two

Is it possible with cl_gui_alv_grid to make two columns with the same header?
Suppose I want to display data like this :
| Tuesday | Wednesday | Thursday |
|---------------|---------------|---------------|
| Po | Delivery | Po | Delivery | Po | Delivery |
|----|----------|----|----------|----|----------|
| 7 | 245.00 | 4 | 309.00 | 12 | 774.00 |
| 4 | 105.00 | 2 | 88.00 | 3 | 160.00 |
| 10 | 760.00 | 5 | 291.00 | 20 | 1836.00 |
...
For this I think about two solutions, but I don't know if it possible.
First solution : Make two levels of field catalog, in the first one three columns, and in the second 6 columns.
Second : Make field catalog with 3 columns, and concatenate two values under each column.
Thanks.
There is a strange workaround on a german site, which deals with inheriting from alv_grid in order to override some crucial methods, to allow it, to merge cells, the source is a well known and appreciated german abap page, but, as it says, it is in german. Let us hope, any translator engine can translate this for You in a proper way, but as it looks like, this could be a step in the right direction.... but as it seems, You should fix all columns for that ( or at least those with merged cells ).
Please refer to this and tell me, if it helped:
Merge cells of alv-grid

LINQ OrderBy. Does it always return the same ordered list?

I was trying out a simple OrderBy statement.
The target data to order is something like below:
[
{"id":40, "description":"aaa", "rate":1},
{"id":1, "description":"bbb", "rate":1},
{"id":4, "description":"ccc", "rate":2},
{"id":19, "description":"aaa", "rate":1}
]
Then I order items by the rate property.
The odd thing is that if I 'order' them, it 'skips' some items by a given offset and then 'take' only portion of the data.
For example,
var result = items.OrderBy(i => i.rate);
var result = result.Skip(2);
var result = result.Take(2);
The result looks fine for the most of it, but the 'edge case' item is not returned at all.
For example,
if the first result came back as
[{"id":40, "description":"aaa", "rate":1}, {"id":1, "description":"bbb", "rate":1}]
the second result comes back like
[{"id":1, "description":"bbb", "rate":1}, {"id":4, "description":"ccc", "rate":2}]
Item "id: 19" has not been returned with the second query call. Instead item "id: 1" has returned twice.
My guess is that the SQL OrderBy statement doesn't produce the same ordered list every single time OrderBy orders by a given property, but the exact order within a group that shares the same property can change.
What is the exact mechanism under the hood?
Short answer: LINQ to Objects uses a stable sort algorithm, so we can say that it is deterministic, and LINQ to SQL depends on the database implementation of Order By that is usually nondeterministic.
A deterministic sort algorithm is one that have always the same behavior on different runs.
In you example, you have duplicates in your OrderBy clause. For a guaranteed and predicted sort, one of the order clauses or the combination of order clauses must be unique.
In LINQ, you can achieve it by adding another OrderBy clause to refer your unique property, like in
items.OrderBy(i => i.Rate).ThenBy(i => i.ID).
Long answer:
LINQ to Objects uses a stable sort, as documented in this link: MSDN.
In LINQ to SQL, it depends on the sort algorithm of the underlying database and it is usually an unstable sort, like in MS SQL Server (MSDN).
In a stable sort, if the keys of two elements are equal, the order of the elements is preserved. In contrast, an unstable sort does not preserve the order of elements that have the same key.
So, for LINQ to SQL, the sorting is usually nondeterministic because the RDMS (Relational Database Management System, like MS SQL Server) may directly use a unstable sort algorithm with a random pivot selection or the randomness can be related with which row the database happens to access first in the file system.
For example, imagine that the size of a page in the file system can hold up to 4 rows.
The page will be full if you insert the following data:
Page 1
| Name | Value |
|------|-------|
| A | 1 |
| B | 2 |
| C | 3 |
| D | 4 |
If you need to insert a new row, the RDMS has two options:
Create a new page to allocate the new row.
Split the current page in two pages. So the first page will hold the Names A and B and the second page will hold C and D.
Suppose that the RDMS chooses option 1 (to reduce index fragmentation). If you insert a new row with Name C and Value 9, you will get:
Page 1 Page 2
| Name | Value | | Name | Value |
|------|-------| |------|-------|
| A | 1 | | C | 9 |
| B | 2 | | | |
| C | 3 | | | |
| D | 4 | | | |
Probably, the OrderBy clause in column Name will return the following:
| Name | Value |
|------|-------|
| A | 1 |
| B | 2 |
| C | 3 |
| C | 9 | -- Value 9 appears after because it was at another page
| D | 4 |
Now, suppose that the RDMS chooses option 2 (to increase the insert performance in a storage system with many spindles). If you insert a new row with Name C and Value 9, you will get:
Page 1 Page 2
| Name | Value | | Name | Value |
|------|-------| |------|-------|
| A | 1 | | C | 3 |
| B | 2 | | D | 4 |
| C | 9 | | | |
| | | | | |
Probably, the OrderBy clause in column Name will return the following:
| Name | Value |
|------|-------|
| A | 1 |
| B | 2 |
| C | 9 | -- Value 9 appears before because it was at the first page
| C | 3 |
| D | 4 |
Regarding your example:
I believe that you have mistyped something in your question, because you have used items.OrderBy(i => i.rate).Skip(2).Take(2); and the first result do not show a row with Rate = 2. This is not possible since the Skip will ignore the first two rows and they have Rate = 1, so your output must show the row with Rate = 2.
You've tagged your question with database, so I believe that you are using LINQ to SQL. In this case, results can be nondeterministic and you could get the following:
Result 1:
[{"id":40, "description":"aaa", "rate":1},
{"id":4, "description":"ccc", "rate":2}]
Result 2:
[{"id":1, "description":"bbb", "rate":1},
{"id":4, "description":"ccc", "rate":2}]
If you had used items.OrderBy(i => i.rate).ThenBy(i => i.ID).Skip(2).Take(2); then the only possible result would be:
[{"id":40, "description":"aaa", "rate":1},
{"id":4, "description":"ccc", "rate":2}]

how can I retrieve records that include *all* in a list, as opposed to *any*?

I have a table, "posts" like so:
+----+---------+------------+
| id | author | text |
+----+---------+------------+
| 1 | bob | hello |
| 2 | jim | hi bob |
+----+---------+------------+
and also "tags" like so:
+----+--------------+
| id | name |
+----+--------------+
| 1 | bugs |
| 2 | project_foo |
| 3 | project_bar |
+----+--------------+
and lastly "post_tags" like:
+------+------+
| post | tag |
+------+------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 3 |
+------+------+
Posts are tagged with any number of tags. With the above data, what is the best way for me to query "give me the posts that have ALL tags in the list (1,2)"? These would be the posts tagged as "bugs, project_foo"
A joined search with "IN" doesn't work for me as it is returning posts that match any of the listed tags, so I will receive "project_bar" bug posts as well even though they aren't even tagged as "project_foo". It would be completely fine to return posts that contain tags not in the specified list, given that the post at least has the tags mentioned. So "bugs, project_foo, project_bar" would be fine, but "bugs, project_bar" would not.
I would prefer to do this with normal ResultSet usage, but am comfortable doing in a custom ResultSource::View. Problem is, I can't figure out how to do this in raw sql either.
Is there a practical way to do what I am attempting, or is my table setup just wrong for what I am trying to achieve?
Thank you.
The SQL statement would be:
SELECT post
FROM post_tags
WHERE tag IN (1, 2)
GROUP BY post
HAVING COUNT(post) = 2
So something like the following should work with DBIx::Class:
my #tags = (1, 2);
$schema->resultset('PostTags')->search({
tag => { -in => \#tags },
}, {
columns => 'post',
group_by => 'post',
having => \[ 'COUNT(post) = ?', scalar(#tags) ],
});
This would only retrieve the post ids. You could try to add a join or prefetch to retrieve the post contents in a single statement.
I'd go with this query. It'd have a good performance but use it only if you can be sure you'll only need to check for only 2 values. If your requirements could expand to include a third or even fourth one then nwellnhof's answer is better:
SELECT post
FROM post_tags a
WHERE EXISTS (
SELECT 1 FROM post_tags b1
WHERE a.post = b1.post
AND b1.tag=1 )
AND EXISTS (
SELECT 1 FROM post_tags b2
WHERE a.post = b2.post
AND b2.tag=2 );