MYSQL sorting with doctrine - mysql

as described in this question and as its answered with "Not possible"
i opened a new question
so my problem goes like this :
I have a table (ServiceProvider Table)
each service provider have many offers (Offer table) with a OneToMany relation
and each Offer have a master service (Service table) with a ManyToOne relation.
What i am trying to do is to get all the service providers and their offers in an order related to serviceProvider.points and then serviceProvider.name and each service provider's offers should be ordered by their points.
So for more clear view let's say i have the following
ServiceProvider
-------------------------------------------------
id | name | points |offers|
-------------------------------------------------
1 | c | 2 |1,2,3 |
-------------------------------------------------
2 | b | 1 |1,2 |
-------------------------------------------------
3 | a | 0 |1,3 |
-------------------------------------------------
ServiceOffer
-----------------------------------
id | name |service|
-----------------------------------
1 | a | 1 |
-----------------------------------
2 | b | 1 |
-----------------------------------
3 | c | 2 |
-----------------------------------
Service
-----------------------------------
id | name | points |
-----------------------------------
1 | a | 23 |
-----------------------------------
2 | b | 88 |
-----------------------------------
what i exactly need is a result like this
results
service_provider.name| offers.name |
-------------------------------------
c | b , a |
-------------------------------------
b | a , b |
-------------------------------------
a | b , a |
-------------------------------------
this is the query i tried but it's not working
$query->select("sp")
->from("CoreBundle:ServiceProvider","sp")
->andWhere("sp.city = :city_name")->setParameter("city_name",$cityName)
->innerJoin("sp.offers","offer")
->innerJoin("offer.service","service","with","offer.service = service")
->orderBy("sp.points DESC , sp.name ASC , service.points");
needed fields
in serviceProvider
/**
* #var ServiceOffer
*
* #ORM\OneToMany(targetEntity="ServiceOffer", mappedBy="serviceProvider")
*/
private $offers;
in service
/**
* #var integer
*
* #ORM\Column(name="points", type="integer", nullable=true)
*/
private $points;
in service offer
/**
* #ORM\ManyToOne(targetEntity="Service", inversedBy="offer")
* #ORM\JoinColumn(name="service_id", referencedColumnName="id")
*
* #Serializer\Expose
* #Serializer\Groups({"service-offer", "order-entry"})
*
* #Assert\NotBlank(message="constraint.serviceOffer.service.not_blank")
*/
private $service;

You cannot simply enumerate ORDER BY directives to Doctrine. You may give a single ->orderBy() method call, giving the field to use and direction (optional, ASC is default) as arguments, and further order fields must be add using the addOrderBy method. Your query should look like
$query->select("sp")
->from("CoreBundle:ServiceProvider","sp")
->andWhere("sp.city = :city_name")->setParameter("city_name",$cityName)
->innerJoin("sp.offers","offer")
->innerJoin("offer.service","service","with","offer.service = service")
->orderBy("sp.points", "DESC")
->addOrderBy("sp.name", "ASC")
->addOrderBy("service.points");
See the documentation on this topic.

The #SolarBear 's answer is almost correct.
$query->select("sp, offer, service")
->from("CoreBundle:ServiceProvider","sp")
->andWhere("sp.city = :city_name")->setParameter("city_name",$cityName)
->innerJoin("sp.offers","offer")
->innerJoin("offer.service","service")
->orderBy("sp.points", "DESC")
->addOrderBy("sp.name", "ASC")
->addOrderBy("service.points","DESC");
Notice, I remove the unnecessary code in the service Inner Join. And more important, add 'DESC' string in service.points order by. It should do the trick.
Let's jump to your example :
First of all, I honestly didn't understand you map in column ServiceProvider.offers. Second, I change names of these table to be more clear to show the expected result.
ServiceProvider
-------------------------------------------------
id | name | points |offers|
-------------------------------------------------
1 | C | 2 |1,2,3 |
-------------------------------------------------
2 | B | 1 |1,2 |
-------------------------------------------------
3 | A | 0 |1,3 |
-------------------------------------------------
ServiceOffer
-----------------------------------
id | name |service|
-----------------------------------
1 | Z | 1 |
-----------------------------------
2 | Y | 1 |
-----------------------------------
3 | X | 2 |
-----------------------------------
Service
-----------------------------------
id | name | points |
-----------------------------------
1 | J | 23 |
-----------------------------------
2 | K | 88 |
-----------------------------------
results
ServiceProvider.name | offers.name |
---------------------------------------------
C | X, Y, Z or X, Z, Y | <---- I explain below
---------------------------------------------
B | Y, Z or Z, Y | <---- Same
---------------------------------------------
A | X, Z |
---------------------------------------------
In this example, SeviceOffer X uses Service K (points 88). While, ServiceOffer Y and Z use Service J (points 23) so there is a draw between the ServiceProvider Y and Z, so the DB can returns they in any order.
Edit
About "offers" column. Notice you mapped as a One to Many relationship. But notice, in the given table example is sound like a Many to Many. Once, ServiceProvider has many ServiceOffer and vice-versa. So for this works properly you should create a join table and removing offers column.
And map in ServiceProvider a ManyToMany relationship.
Such as:
Providers_Offers
------------------------------
provider_id | offer_id |
------------------------------
1 | 1 |
------------------------------
1 | 2 |
------------------------------
1 | 3 |
------------------------------
2 | 1 |
------------------------------
2 | 2 |
------------------------------
3 | 1 |
------------------------------
3 | 3 |

Related

Joining three tables to get a list of tags

I have these three tables:
user_submitted_value
id | owner_id | value |
-----------------------
1 | 1 | 1337 |
2 | 2 | 1337 |
3 | 2 | 1337 |
4 | 1 | 1337 |
tag
id | owner_id | text |
---------------------------
1 | 1 | 'Tag 01' |
2 | 1 | 'Tag 02' |
3 | 1 | 'Tag 03' |
4 | 2 | 'Tag 04' |
user_submitted_value_tag
id | owner_id | tag_id | value_id |
-----------------------------------
1 | 1 | 1 | 1 |
2 | 1 | 2 | 1 |
3 | 1 | 3 | 1 |
So basically, users can submit values and enter any number of freetext tags to attach to that value. I need to store the tags as belonging to a specific user, and I need to be able to count how many times they've used each tag.
What I want to accomplish is a query that gets rows from user_submitted_value with the tags appended onto them. For example:
Query value with id 1:
id | owner_id | value | tags |
------------------------------------------------------
1 | 1 | 1337 | "'Tag 01','Tag 02','Tag 03'" |
Query all values belonging to user with id 1:
id | owner_id | value | tags |
------------------------------------------------------
1 | 1 | 1337 | "'Tag 01','Tag 02','Tag 03'" |
4 | 1 | 1337 | "" |
I know I need to JOIN one or more times, somehow, but I am not comfortable enough with SQL to figure out exactly how.
This seems like a rather arcane data format -- particularly because owner_id is repeated in all the tables.
In any case, I think the basic query that you want to get the values and tags for a given user looks like this:
select usv.owner_id,
group_concat(distinct usvt.value_id) as values,
group_concat(distinct t.text) as tags
from user_submitted_value usv join
user_submitted_value_tag usvt
on usv.value_id = usvt.value_id and usv.owner_id = usvt.owner_id join
tags t
on usvt.tag_id = t.id and usvt.owner_id = t.owner_id
group by usv_owner_id;
Here's the final solution in my case. Heavily based on the answer submitted by Gordon Linoff.
SELECT
user_submitted_value.id,
user_submitted_value.creator_id,
user_submitted_value.value,
group_concat(tag.text) AS tags
FROM user_submitted_value
LEFT JOIN user_submitted_value_tag
ON user_submitted_value.id = user_submitted_value_tag.value_id
AND user_submitted_value.creator_id = user_submitted_value_tag.creator_id
LEFT JOIN tag
ON user_submitted_valuetag.tag_id = tag.id
AND user_submitted_value_tag.creator_id = tag.creator_id
WHERE user_submitted_value.id = ?
GROUP BY user_submitted_value.id
The WHERE clause on the second JOIN can be modified to get all values for a given user.

How do I select records in MySQL with multiple columns matching map of values?

I have the following 3-column table:
+----+---------+------------+
| ID | First | Last |
+----+---------+------------+
| 1 | Maurice | Richard |
| 2 | Yvan | Cournoyer |
| 3 | Carey | Price |
| 4 | Guy | Lafleur |
| 5 | Steve | Shutt |
+----+---------+------------+
If I want to look for everyone in (Maurice,Guy) I can do select * from table where first in (Maurice,Guy).
If I want to find just Maurice Richard, I can do select * from table where first = "Maurice" and last = "Richard".
How do I do a map, an array of multiples?
[
[Maurice, Richard]
[Guy,Lafleur]
[Yvan,Cournoyer]
]
If I have an arbitrary number of entries, I cannot construct a long complex where (first = "Maurice" and last = "Richard") or (first = "Guy" and last = "Lafleur") or .....
How do I do the moral equivalent of where (first, last) in ((Guy,Lafleur),(Maurice,Richard)) ?
You can do it just like you describe it:
SELECT *
FROM mytable
WHERE (first, last) IN (('Guy','Lafleur'),('Maurice','Richard'))
Demo here

MySQL: GROUP BY with custom hierarchical functionality

I've got a permission/privileges - table looking like this:
+----+----------+----------+------+-------+
| id | name | usertype | read | write |
+----+----------+----------+------+-------+
| 1 | test | A | 0 | 0 |
| 2 | test | MU | 1 | 1 |
| 3 | test | U | 1 | 1 |
| 4 | apple | A | 1 | 1 |
| 5 | apple | MU | 1 | 0 |
| 6 | apple | U | 0 | 0 |
| 7 | flower | A | 0 | 0 |
| 8 | flower | MU | 0 | 0 |
| 9 | flower | U | 1 | 1 |
+----+----------+----------+------+-------+
there are 3 usertypes: A (admin), MU (maintenance user), U (standard user)
the usertypes are hierarchical: A > MU > U
(the usertypes are saved as CHAR(2) in the database, and unfortunately I can't change that)
now i want to build a query which implements the hierarchical logic of my usertypes.
e.g. usertype 'A' got no permission to read or write on stuff with the name 'test', thus usertypes 'MU' AND 'U' also should have no permission for that and their read = 1 and write = 1 should be ignored.
I know which usertype is currently logged in.
I somehow have to check for the minimum of read/write rights to the name for all hierarchical predecessors, i guess. but i don't know how to check that since usertype is not a number field.
this is what I've tried so far:
SELECT
name,
MIN(read),
MIN(write),
CASE
WHEN usertype = 'A' THEN 0
ELSE (CASE
WHEN usertype = 'WU' THEN 1
ELSE 2
END)
END userval
FROM
permissions
-- WHERE usertype <= :current_usertype
GROUP BY name
this seems to work, but i don't know how i can get my condition WHERE usertype <= :current_usertype working, so a usertype down in the hierarchy can't get more privileges on a name than a "higher" usertype.
any ideas?
thanks in advance!
This is how I solved my problem:
1. I added another table "permission_groups" to the database:
+----+----------+--------+
| id | usertype | value |
+----+----------+--------+
| 1 | A | 100 |
| 2 | MU | 20 |
| 3 | U | 10 |
+----+----------+--------+
2. Then I joined this table to my original table "permissions" which i showed in my question:
here i get the value of my "permission_groups" table with a subquery. this value symbolizes the hierarchical order of my different usertypes.
SELECT
perm.name,
MIN(perm.`read`),
MIN(perm.`write`),
group .value
FROM
permissions perm
LEFT JOIN permission_groups group ON group.usertype = perm.usertype
WHERE
group.value >= (SELECT value from permission_groups WHERE usertype = :current_usertype)
GROUP BY perm.name
:current_usertype is a PDO parameter in my case, which is replaced by the usertype of the current user.

MySQL Retrieve Records Where Criteria is Unique and True i.e. No other records exist

I have a legacy database that I am working with a basic column layout as such:
SampleID
CompanyID
CompanyApplication
CompanyTest1
...
CompanyTest25
Not normalized in the least, this is causing a bit of a data mining issue.
I need to get the CompanyIDs (Grouped By) where the CompanyApplication field = "Registration" but this company has none of the CompanyTest(n) fields selected for any SampleID.
The problem is, there are CompanyIDs that have both CompanyApplication and CompanyTest(n) records (multiple rows), but I want to get CompanyIDs that only have a CompanyApplication of Registration.
Data to help illustrate:
SampleID | CompanyID | CompanyApplication | CompanyTest1 | ... | CompanyTest25
------------------------------------------------------------------------
1 | 1 | Registration | | |
------------------------------------------------------------------------
2 | 1 | | True | |
------------------------------------------------------------------------
3 | 2 | Registration | | |
------------------------------------------------------------------------
4 | 2 | Registration | | |
------------------------------------------------------------------------
5 | 3 | | True | |
------------------------------------------------------------------------
6 | 3 | | | | True
I only want to retrieve rows 3 and 4 because they ONLY have Registration and no other testing.
We could start by selecting all of the rows that you are trying to exclude, and then use a NOT EXISTS to skip over CompanyIDs that are in that query.
SELECT DISTINCT ct1.company_id
FROM company_table AS ct1
WHERE ct1.CompanyApplication = "Registration"
AND NOT EXISTS(
SELECT 1
FROM company_table AS ct2
WHERE ct1.company_id = ct2.compnay_id
AND (ct2.CompanyTest1 IS NOT NULL
OR ct2.CompanyTest2 IS NOT NULL
...
OR ct2.CompanyTest25 IS NOT NULL)
)

Order by in mysql using second table

I have two tables, one is a list os stores and attributes, the second is a list of allocationsa based on these attributes.
The attribute table (stores_metadata)
| key | store_key | field | value
| 1 | 1 | size | Large
| 2 | 1 | dist | Midlands
| 3 | 2 | size | Medium
| 4 | 3 | dist | South
The allocation table (allocation)
| key | ticket_key | field | value | count
| 1 | 1 | size | Large | 10
| 2 | 1 | size | Medium| 5
I've managed to get the allocations working using the code:
SELECT store_key, quantity FROM
allocation
INNER JOIN store_metadata
ON allocation.`field` = store_metadata.`field`
AND allocation.`value` = store_metadata.`value`
This returns a list of the stores and how many items they should recieve, what I now need to do it order the stores by the distribution attribute.
Any help would be greatly appreciated.
The question isn't asked very well.
To perform ordering by any column in your result set add ORDER BY [column] to the end of the query. E.g.
SELECT store_key, quantity FROM
allocation
INNER JOIN store_metadata
ON allocation.`field` = store_metadata.`field`
AND allocation.`value` = store_metadata.`value`
ORDER BY allocation.`field`;