I have a table called Properties (pid, uid, pname, pvalue). The pid column is auto generated. Each uid (user id) could have multiple name value pairs stored in the pname and pvalue.
As an input I've multiple name value pairs for pname and pvalue which forms a complicated boolean expression.
For example: let's start w/ one name value pair. Say I want to retrieve all uid's whose 'favorite_color' is 'red'.
I wrote an SQL query:
SELECT *
FROM properties
WHERE ((pname = 'favorite_color') and (pvalue = 'red'))
The query soon gets complicated if I had to retrieve something like, fetch all uid's whose 'favorite_color' is 'red' or 'blue' and 'favorite_drink' is 'juice' or ' 'milk' and 'favorite_ hobby' is 'music' or 'art' etc.
I wrote an SQL query:
SELECT *
FROM properties
WHERE (((pname = 'favorite_color') and (pvalue = 'red'))
OR ((pname = 'favorite_color') and (pvalue = 'blue')))
AND (((pname = 'favorite_drink') and (pvalue = 'juice'))
OR ((pname = 'favorite_drink') and (pvalue = 'milk')))
AND (((pname = 'favorite_hobby') and (pvalue = 'music'))
OR ((pname = 'favorite_hobby') and (pvalue = 'art')))
I got the expression correct but unfortunately it fails because the evaluation is done on each row. What if I wanted to add more name value pairs to the where clause?
Questions:
Is it possible to write and SQL query for this?
The other idea I had was to fetch all the pname, pvalue pairs for each user, build a dynamic expression using an expression language and my input name value paris to evaluate it. I've apache's JEXL in mind.
To do the ANDs you need to do as many self-joins as you have and-ed conditions:
SELECT *
FROM Properties p1, Properties p2, Properties p3
WHERE p1.uid = p2.uid AND p1.uid = p3.uid
AND (p1.pname = 'favorite_color' AND p1.pvalue IN ('red', 'blue'))
AND (p2.pname = 'favorite_drink' AND p2.pvalue IN ('juice', 'milk'))
AND (p3.pname = 'favorite_hobby' AND p2.pvalue IN ('music', 'art'))
EDIT:
Another possibility is to denormalize the data, and then use FIND_IN_SET() or RLIKE:
SELECT uid, group_concat(concat(pname, '=', pvalue)) props
FROM Properties
GROUP BY uid
HAVING props RLIKE 'favorite_color=(red|blue)'
AND props RLIKE 'favorite_drink=(juice|milk)'
AND props RLIKE 'favorite_hobby=(music|art)'
SELECT p1.*,p2.pname,p2.pvalue,p3.pname,p3.pvalue
FROM (
(Select *
from Properties
where (pname = 'favorite_color' AND pvalue IN ('red', 'blue')) p1,
(Select *
from Properties
where (pname = 'favorite_drink' AND pvalue IN ('juice','milk')) p2,
(Select *
from Properties
where (pname = 'favorite_hobby' AND pvalue IN ('music', 'art')) p3,
)
WHERE p1.uid = p2.uid AND p1.uid = p3.uid
Related
I have multiple models in Sparx Enterprise Architect in file-based, i.e. using MS access.
I'm using a custom template to populate a table with data from object's properties, including some with <memo> fields.
This is the query i'm using in the template fragment:
SELECT obj.object_id,
obj.Stereotype,
objp.Property as Prop,
switch(objp.Value = '<memo>', objp.Notes, objp.Value LIKE '{*}',
NULL, 1=1, objp.Value) AS Val,
(SELECT tobj2.ea_guid & tobj2.Name FROM t_object tobj2 WHERE
tobj2.ea_guid = objp.Value) AS [Obj-Hyperlink]
FROM t_object obj
INNER JOIN t_objectproperties objp
ON (obj.object_id = objp.object_id)
WHERE obj.object_id = #OBJECTID# AND obj.Stereotype='Data-
Stream' AND objp.Property NOT IN ('isEncapsulated')
ORDER BY objp.Property ASC;
I found that the when these fields are longer than 249 chars I get an error message when generating the reports and the cell in the generated table is simply empty. This is also noticeable with a query:
The error I'm getting states:
"Error Processing xml document: an invalid character was found in text context"
Is there any workarround to enable including the <memo> fields' data with more than 249 chars in the reports?
Any help is much appreciated.
I've found a workaround for this by joining two queries with a "Union all". The first query will handle the non-memo fields with the switch function and the second one the memo fields without the switch function.
select
obj.object_id,
obj.Stereotype,
objp.Property as Prop,
objp.Notes AS Val,
(
SELECT
tobj2.ea_guid & tobj2.Name
FROM
t_object tobj2
WHERE
tobj2.ea_guid = objp.Value
) AS [Obj-Hyperlink]
from
t_objectproperties objp
left join t_object obj on (obj.object_id = objp.object_ID)
where
obj.object_id = #OBJECTID#
AND obj.Stereotype = 'Data-Stream'
AND objp.Property NOT IN ('isEncapsulated')
AND objp.Value = "<memo>"
UNION ALL
SELECT
obj2.object_id,
obj2.Stereotype,
objp2.Property as Prop,
switch(
objp2.Value LIKE '{*}', NULL, 1 = 1, objp2.Value
) AS Val,
(
SELECT
tobj2.ea_guid & tobj2.Name
FROM
t_object tobj2
WHERE
tobj2.ea_guid = objp2.Value
) AS [Obj-Hyperlink]
FROM
t_object obj2
INNER JOIN t_objectproperties objp2 ON (obj2.object_id = objp2.object_id)
WHERE
obj2.object_id = #OBJECTID#
AND obj2.Stereotype = 'Data-Stream'
AND objp2.Property NOT IN ('isEncapsulated')
and objp2.Value <> "<memo>"
order by
3 asc;
Thanks a lot #geertbellekens for your comment which was crucial to find this solution.
Really new to working with CI4's Model and struggling to adapt my existing MySQL JOIN queries to work with the examples in its User Guide.
I have adapted part of my code like so:
public function brand_name($brand_name_slug)
{
return $this->asArray()
->where('availability', 'in stock')
->where('sku !=', '')
->where('brand_name_slug', $brand_name_slug)
->groupBy('gtin')
->orderBy('brand_name, subbrand_name, product, size, unit')
->findAll();
}
It works fine. I have looked at examples, and figured out I can add the code ->table('shop a') and it still works, but I also need to to add the following JOIN statement:
JOIN (SELECT gtin, MIN(sale_price) AS sale_price FROM shop GROUP BY gtin) AS b ON a.gtin = b.gtin AND a.sale_price = b.sale_price
As soon as I add ->join('shop b', 'a.gtin = b.gtin and a.sale_price = b.sale_price') I get a '404 - File Not Found' error.
When I look at all examples of CI4 joins and adapt my code to fit, my foreach($shop as $row) loop generates a 'Whoops...' error because they end with a getResult() or getResultArray - instead of findAll().
Which is the way forward, and do I need to change my foreach loop.
Full MySQL statement:
SELECT * FROM shop a JOIN (SELECT gtin, MIN(sale_price) AS sale_price FROM shop GROUP BY gtin) AS b ON a.gtin = b.gtin AND a.sale_price = b.sale_price WHERE availability = 'in stock' AND sku != '' AND brand_name_slug = $brand_name_slug GROUP BY gtin ORDER BY brand_name, subbrand_name, product, size
Query builders have their limits. That's why the query method exists. If you have a complex query I'd advise you to just use $this->query();.
It will make you lose less time and effort converting something you know already works. And in the top of that, while converting complex queries you usually end up using the query builder but with big part of your SQL in it.
In your model extending CodeIgniter\Model :
$query = $this->db->query("SELECT * FROM shop a JOIN (SELECT gtin, MIN(sale_price) AS sale_price FROM shop GROUP BY gtin) AS b ON a.gtin = b.gtin AND a.sale_price = b.sale_price WHERE availability = 'in stock' AND sku != '' AND brand_name_slug = \$brand_name_slug GROUP BY gtin ORDER BY brand_name, subbrand_name, product, size");
// your array result
$result_array = $query->getResultArray();
// your object result
$result_object = $query->getResult();
BaseBuilder Class in Codeigniter expects the first join parameter to be the table name. So try passing the table name and join it on the table name itself. I haven't personally used the table aliases so I might also be wrong.
Following are the parameter that the JOIN query expects :
public function join(string $table, string $cond, string $type = '', bool $escape = null)
Here, it expects the first name be a table, so try out by switching aliases for the table's name directly.
For your second part of query, It would be better if you could show the whole error rather than just posting the first of the error.
Managed to figure it out in the end:
public function brand_name($brand_name_slug)
{
return $this
->db
->table('shop a')
->select()
->join('(SELECT sku, MIN(sale_price) AS sale_price FROM shop GROUP BY sku) AS b', 'a.sku = b.sku AND a.sale_price = b.sale_price')
->where('availability', 'in stock')
->where('a.sku !=', '')
->where('brand_name_slug', $brand_name_slug)
->groupBy('a.sku')
->orderBy('brand_name, subbrand_name, product, size, unit')
->get()
->getResult();
}
Thanks for all your pointers!
I am not so into SQL and I have the following problem woring on a MySql query. I try to explain you what I have to do.
I have this query, it works fine:
SELECT
LNG.id AS language_id,
LNG.language_name AS language_name,
LNG.language_code AS language_code,
CLP.is_default AS id_default_language
FROM Country_Language_Preference AS CLP
INNER JOIN Country AS CNT
ON CLP.country_id = CNT.id
INNER JOIN Languages AS LNG
ON CLP.language_id = LNG.id
WHERE
CNT.country_name = "Senegal"
This query have a single WHERE input parameter, this:
CNT.country_name = "Senegal"
I want to implement the following behavior: if the passed parameter have value Senegal or Rwanda perform the previous query.
If this input parameter have a different value form Senegal or Rwanda perform the same query but using this WHERE condition_
CNT.country_name = "GLOBAL"
Can I do something like this using SQL?
Using CASE Statement, this should be possible.
Try this:
SELECT
LNG.id AS language_id,
LNG.language_name AS language_name,
LNG.language_code AS language_code,
CLP.is_default AS id_default_language
FROM Country_Language_Preference AS CLP
INNER JOIN Country AS CNT
ON CLP.country_id = CNT.id
INNER JOIN Languages AS LNG
ON CLP.language_id = LNG.id
WHERE
CNT.country_name = CASE WHEN #Country = "Senegal" OR #Country = "Rwanda" THEN "Senegal"
ELSE "GLOBAL" END
Simply use OR:
WHERE (CNT.country_name = #country OR #country = 'GLOBAL')
#country is whatever parameter you are passing in.
If you want to limit to those two countries, then:
WHERE (CNT.country_name = #country OR
(#country NOT IN ('Rwanda', 'Senegal') AND CNT.country_name = 'GLOBAL')
)
But the first version seems more versatile.
We all know these excellent ABAP statements which allows finding unique values in one-liner:
it_unique = VALUE #( FOR GROUPS value OF <line> IN it_itab
GROUP BY <line>-field WITHOUT MEMBERS ( value ) ).
But what about extracting duplicates? Can one utilize GROUP BY syntax for that task or, maybe, table comprehensions are more useful here?
The only (though not very elegant) way I found is:
LOOP AT lt_marc ASSIGNING FIELD-SYMBOL(<fs_marc>) GROUP BY ( matnr = <fs_marc>-matnr
werks = <fs_marc>-werks )
ASSIGNING FIELD-SYMBOL(<group>).
members = VALUE #( FOR m IN GROUP <group> ( m ) ).
IF lines( members ) > 1.
"throw error
ENDIF.
ENDLOOP.
Is there more beautiful way of finding duplicates by arbitrary key?
So, I just put it as answer, as we with Florian weren't able to think out something better. If somebody is able to improve it, just do it.
TYPES tt_materials TYPE STANDARD TABLE OF marc WITH DEFAULT KEY.
DATA duplicates TYPE tt_materials.
LOOP AT materials INTO DATA(material)
GROUP BY ( id = material-matnr
status = material-pstat
size = GROUP SIZE )
ASCENDING REFERENCE INTO DATA(group_ref).
CHECK group_ref->*-size > 1.
duplicates = VALUE tt_materials( BASE duplicates FOR <status> IN GROUP group_ref ( <status> ) ).
ENDLOOP.
Given
TYPES: BEGIN OF key_row_type,
matnr TYPE matnr,
werks TYPE werks_d,
END OF key_row_type.
TYPES key_table_type TYPE
STANDARD TABLE OF key_row_type
WITH DEFAULT KEY.
TYPES: BEGIN OF group_row_type,
matnr TYPE matnr,
werks TYPE werks_d,
size TYPE i,
END OF group_row_type.
TYPES group_table_type TYPE
STANDARD TABLE OF group_row_type
WITH DEFAULT KEY.
TYPES tt_materials TYPE STANDARD TABLE OF marc WITH DEFAULT KEY.
DATA(materials) = VALUE tt_materials(
( matnr = '23' werks = 'US' maabc = 'B' )
( matnr = '42' werks = 'DE' maabc = 'A' )
( matnr = '42' werks = 'DE' maabc = 'B' ) ).
When
DATA(duplicates) =
VALUE key_table_type(
FOR key IN VALUE group_table_type(
FOR GROUPS group OF material IN materials
GROUP BY ( matnr = material-matnr
werks = material-werks
size = GROUP SIZE )
WITHOUT MEMBERS ( group ) )
WHERE ( size > 1 )
( matnr = key-matnr
werks = key-werks ) ).
Then
cl_abap_unit_assert=>assert_equals(
act = duplicates
exp = VALUE tt_materials( ( matnr = '42' werks = 'DE') ) ).
Readability of this solution is so bad that you should only ever use it in a method with a revealing name like collect_duplicate_keys.
Also note that the statement's length increases with a growing number of key fields, as the GROUP SIZE addition requires listing the key fields one by one as a list of simple types.
What about the classics? I'm not sure if they are deprecated or so, but my first think is about to create a table clone, DELETE ADJACENT-DUPLICATES on it and then just compare both lines( )...
I'll be eager to read new options.
I have made this query for widget. It is working properly if i pass the value directly(ie. ad_role_id). But it is not running when i use dynamic parameter(:role).
for this i have done entry in parameters too.
Please give me some suggestion on it.
hql query:
SELECT ORG.name AS orgName
,INV.documentNo AS documentNo
,INV.invoiceDate AS invoiceDate
,BP.name AS name
,DT.name AS Doctype
,INV.grandTotalAmount AS grandTotalAmount
FROM Invoice INV,
DocumentType AS DT,
BusinessPartner AS BP,
Organization AS ORG
WHERE ORG.id = INV.organization
AND BP.id = INV.businessPartner
AND INV.transactionDocument = DT.id
AND INV.salesTransaction = 'N'
AND INV.id not in (select distinct e.invoice from InvoiceLine e )
AND INV.organization.id IN (select o.id
from Organization AS o,ADRoleOrganization AS arg,ADRole AS ar
where arg.organization = o.id
and ar.id = arg.role
and arg.role = :role)
Probably your parameter configuration for role was not correct.
use Default_Filter_Expressions and configure role as fixed value in parameter.
http://wiki.openbravo.com/wiki/Projects:Selector/Default_Filter_Expressions