CriteriaQuery DISTINCT values in an aggregation function - spring-data-jpa

Introduction
Consider a query like the following:
SELECT
building.name AS building_name,
STRING_AGG(DISTINCT visit.visitor_name, ', ') AS visitors,
COUNT(visit.id) AS total_visits
FROM building
LEFT JOIN visit
ON building.id = visit.building_id
GROUP BY building.id
Note the DISTINCT keyword used in STRING_AGG aggregation function. The results could look something like the following:
+----------------------+-------------+--------------+
| building_name | visitors | total_visits |
+----------------------+-------------+--------------+
| Skyline Residence | Edgar, John | 6 |
| Forbidden Residence | | 0 |
| Cloud Nine Residence | John | 1 |
+----------------------+-------------+--------------+
The important part is that if John and Edgar have visited Skyline Residence 6 times, then the visitors column should not display their names multiple times like John, Edgar, Edgar, Edgar, John, John.
JPA CriteriaQuery Code
This is my JPA CriteriaQuery code so far, without DISTINCT:
CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<ResultDto> query = builder.createQuery(ResultDto.class);
Root<Building> building = query.from(Building.class);
Join<Building, Visit> visit = building.join("visits", JoinType.LEFT);
query.select(builder.construct(
ResultDto.class,
building.get("name"),
builder.function("STRING_AGG", String.class, visit.get("visitorName"), builder.literal(", ")),
builder.count(visit.get("id")) // ^ I need DISTINCT here ^
));
query.groupBy(building.get("id"));
final TypedQuery<ResultDto> typedQuery = getEntityManager().createQuery(query);
final List<ResultDto> resultList = typedQuery.getResultList();
How could I modify this code to generate the above SQL query, with DISTINCT?
Note: Do not answer with Hibernate's deprecated Criteria API solutions. It should be done using JPA's CriteriaQuery.

You can write a template for string_agg using MetadataBuilderContributor
public class SqlFunctionsMetadataBuilderContributor implements MetadataBuilderContributor {
#Override
public void contribute(MetadataBuilder metadataBuilder) {
metadataBuilder.applySqlFunction(
"string_agg_distinct",
new SQLFunctionTemplate(StandardBasicTypes.STRING, "string_agg(distinct ?1, '; ')")
);
}
}
And add in the properties:
spring.jpa.properties.hibernate.metadata_builder_contributor=<your-package>.SqlFunctionsMetadataBuilderContributor
And then use "string_agg_distinct" instead of "STRING_AGG" in function

Related

How to use dynamic table name for sub query where the dynamic value coming from its own main query in PostgreSQL?

I have formed this query to get the desired output mentioned below:
select tbl.id, tbl.label, tbl.input_type, tbl.table_name
case when tbl.input_type = 'dropdown' or tbl.input_type = 'searchable-dropdown'
then (select json_agg(opt) from tbl.table_name) as opt) end as options
from mst_config as tbl;
I want output like below:
id | label | input_type | table_name | options
----+----------------------------------------------------+---------------------+-------------------------+-----------------------------------------------------------
1 | Gender | dropdown | mst_gender | [{"id":1,"label":"MALE"},
| | | | {"id":2,"label":"FEMALE"}]
2 | SS | dropdown | mst_ss | [{"id":1,"label":"something"},
| | | | {"id":2,"label_en":"something"}]
But, I'm facing a problem while using,
select json_agg(opt) from tbl.table_name) as opt
In the above part "tbl.table_name", I wanted to use it as dynamic table name but it's not working.
Then, I have searched a lot and found something like Execute format('select * from %s', table_name), where tablename is the dynamic table name. I have even tried the same with postgres function.
But I faced an issue again while using the format method. The reason is I want to use the variable for which the value needs to come from its own main query value instead of already having it in a variable. so this one was also not working.
I would really appreciate if anyone can help me out on this. Also if there are any other possibilities available to achieve this output, help me on that as well.

T-sql: Highlight invoice numbers if they occur in a payment description field

I have two sql-server tables: bills and payments. I am trying to create a VIEW to highlight the bill numbers if they occur in the payment description field. For example:
TABLE bll
|bllID | bllNum |
| -------- | -------- |
| 1 | qwerty123|
| 2 | qwerty345|
| 3 | 1234 |
TABLE payments
|paymentID | description |
| -------- | ---------------------------------- |
| 1 | payment of qwerty123 and qwerty345 |
I want to highlight both the 'qwerty123' and 'qwerty345' strings by adding html code to it. The code I have is this:
SELECT REPLACE(payments.description,
COALESCE((SELECT TOP 1 bll.bllNum
FROM bll
WHERE COALESCE(bll.bllNum, '') <> '' AND
PATINDEX('%' + bll.bllNum + '%', payments.description) > 0), ''),
'<font color=red>' +
COALESCE((SELECT TOP 1 bll.bllNum
FROM bll
WHERE COALESCE(bll.bllNum, '') <> '' AND
PATINDEX('%' + bll.bllNum + '%', payments.description) > 0), '') +
'</font>')
FROM payments
This works but only for the first occurrence of a bill number. If the description field has more than one bill number, the consecutive bill numbers are not highlighted. So in my example 'qwerty123' gets highlighted, but not 'qwerty345'
I need to highlight all occurrences. How can I accomplish this?
With the caveat that this is not a task best done in the database, one possible approach you could try is to use string_split to break your description into words and then join this to your Bills, doing your string manipulation on matching rows.
Note, according to the documentation, string_split is not 100% guaranteed to retain its correct ordering but always has in my usage. It could always be substituted for an alternative function to work on the same principle.
select string_agg (w.word,' ') [Description]
from (
select
case when exists (select * from bill b where b.billnum=s.value)
then Concat('<font colour="red">',s.value,'</font>') else s.value end word
from payments p
cross apply String_Split(description,' ')s
)w
Example DB Fiddle
Okay, I understand, I can put code in the front-end application by looping through the bill numbers and replacing them as they are found in the description. Just thought/ hoped there was a simple solution to this using t-sql. But I understand the difficulty.

how create left join query with sails.js

I would like do a left join query in sails.js. I think i should use populate
I have three models
caracteristique{
id,
name,
races:{
collection: 'race',
via: 'idcaracteristique',
through: 'racecaracteristique'
},
}
race{
id,
name,
caracteristiques:{
collection: 'caracteristique',
via: 'idrace',
through: 'racecaracteristique'
}
}
RaceCarecteristique{
idrace: {
model:'race'
},
idcaracteristique: {
model: 'caracteristique'
},
bonusracial:{
type: 'number',
}
My data are:
Table Caracteristiques
id name
1 | strength
2 | dex
3 | Charisme
Table Race
id name
1 | human
2 | Org
TableRaceCarecteristique
idrace idcaracteristique bonusracial
1 | 2 | +2
This sql request give me for human, all caracteristiques and if exist bonusracial
'SELECT caracteristique.id, caracteristique.name, bonusracial
FROM caracteristique
LEFT OUTER JOIN (select idcaracteristique, bonusracial
from racecaracteristique
where idrace=$1 ) as q
ON q.idcaracteristique = caracteristique.id';
I have this result:
caracteristique.id, caracteristique.name, bonusracial
1 | strength | null
2 | dex | 2
3 | Charisme | null
How use populate to do this ?
When using a SQL-database adapter (MySQL, PQSL etc) you can utilise a method for performing actual, handwritten SQL statements. When all else fails, this might be your best bet to find an acceptable solution, within the framework.
The .sendNativeQuery() method sends your parameterized SQL statement to the native driver, and responds with a raw, non-ORM-mangled result. Actual database-schema specific tables and columns appear in the result, so you need to be careful with changes to models etc. as they might change the schema in the backend database.
The method takes two parameters, the parameterized query, and the array of values to be inserted. The array is optional and can be omitted if you have no parameters to replace in the SQL statement.
Using your already parameterized query from above, I'm sending the query to fetch the data for an "org" (orc perhaps?) in the example below. See the docs linked at the bottom.
Code time:
let query = `
SELECT caracteristique.id, caracteristique.name, bonusracial
FROM caracteristique
LEFT OUTER JOIN (select idcaracteristique, bonusracial
from racecaracteristique
where idrace=$1 ) as q
ON q.idcaracteristique = caracteristique.id`;
var rawResult = await sails.sendNativeQuery(query, [ 2 ]);
console.log(rawResult);
Docs: .sendNativeQuery()

How to translate SQL into Lambda for use in MS Entity Framework with Repository pattern usage

For example take these two tables:
Company
CompanyID | Name | Address | ...
Employee
EmployeeID | Name | Function | CompanyID | ...
where a Company has several Employees.
When we want to retrieve the Company and Employee data for a certain Employee, this simple SQL statement will do the job:
SELECT e.name as employeename, c.name as companyname
FROM Company c
INNER JOIN Employee e
ON c.CompanyID = e.CompanyID
where e.EmployeeID=3
Now, the question is how to translate this SQL statement into a 'lambda' construct. We have modelled the tables as objects in the MS Entity Framework where we also defined the relationship between the tables (.edmx file).
Also important to mention is that we use the 'Repository' pattern.
The closest I can get is something like this:
List<Company> tmp = _companyRepository.GetAll().Where
(
c.Employee.Any
(
e => e.FKEngineerID == engineerId && e.DbId == jobId
)
).ToList();
Any help is very much appreciated!
This should do, assuming your repositories are returning IQueryables of the types
var list = (from c in _companyRepository.GetAll()
join e in _employeeRepository.GetAll() on c.CompanyId equals e.CompanyId
where e.FKEngineerID == engineerId && e.DbId == jobId
select new
{
EmployeeName = e.name,
CompanyName = c.name
}).ToList();
Since you are constraining the query to a single employee (e.Employee=3) why don't you start with employees.
Also, your sql query return a custom set of columns, one column from the employee table and the other column from company table. To reflect that, you need a custom projection at the ef side.
var result = _employeeRepository.GetAll()
.Where( e => e.DbId == 3 ) // this corresponds to your e.EmployeeID = 3
.Select( e => new {
employeename = e.EmployeeName,
companyname = e.Company.CompanyName
} ) // this does the custom projection
.FirstOrDefault(); // since you want a single entry
if ( result != null ) {
// result is a value of anonymous type with
// two properties, employeename and companyname
}

How to return a function result into query?

I have a function called ClientStatus that returns a record with two fields Status_Description and Status_Date. This function receives a parameter Client_Id.
I'm trying to get the calculated client status for all the clients in the table Clients, something like:
| Client_Name | Status_Description | Status_Date |
+-------------+--------------------+-------------+
| Abc | Active | 12-12-2010 |
| Def | Inactive | 13-12-2011 |
Where Client_Name comes from the table Clients, Status_Description and Status_Date from the function result.
My first (wrong) approach was to join the table and the function like so:
SELECT c.Client_Name, cs.Status_Description, cs.Status_Date FROM Clients c
LEFT JOIN (
SELECT * FROM ClientStatus(c.ClientId) as (Status_Description text, Status_Date date)) cs
This obviously didn't work because c.ClientId could not be referenced.
Could someone explain me how can I obtain the result I am looking for?
Thanks in advance.
I think the following can give the result you expect :
SELECT c.Client_Name, d.Status_Description, d.Status_Date
FROM Clients c, ClientStatus(c.ClientId) d
I have solved my problem writing the query like this:
SELECT c.Client_Name, cs.status[1] as Description, cs.stautus[2]::date as Date
FROM (
SELECT string_to_array(translate(
(SELECT ClientStatus(ClientId))::Text, '()', ''), ',') status
FROM Clients
) cs
It is not the most elegant solution but it was the only one I could find to make this work.