activerecord sql difficult query - sql

I need to make a query with two options: first - select DISTINCT ON, secondly - order by (and order by other fields). BTW, having by don't work
At one sql forum I find a solution
WITH d AS (
SELECT DISTINCT ON ({Dlist}) {slist}
FROM {flist}
....
)
SELECT * FROM d ORDER BY {order fields}
So, how I can make this via ActiveRecord method and get back ActiveRecord::Relation
My full query seems something like that:
WITH d AS (
SELECT DISTINCT ON(item_info_id, volume) items.item_info_id, items.volume, items.*
FROM "items" INNER JOIN "item_info" ON "item_info"."id" = "items"."item_info_id" WHERE "items"."type" IN ('Product')
AND "items"."published" = 't'
AND ("items"."item_info_id" IS NOT NULL)
AND ("items"."price" BETWEEN 2 AND 823489)\
)
SELECT * FROM d ORDER_BY 'price'

Below might work for you or give you some hints
class Item < ActiveRecord::Base
def self.what_you_want_to_achieve
item_ids = where("item_info_id IS NOT NULL")
.select(" DISTINCT on(item_info_id, volume) items.item_info_id, items.volume, items.id ")
.map(&:id)
where(:id => item_ids).published.products.price_between(2,823489).order(:price)
end
I assume you know how to define scope e.g. published

Related

Joining two subqueries with Cypher in Neo4J

I have a following SQL query:
SELECT q1.customerId, q1.invoiceId, q2.workId, sum(q2.price)
FROM (select customer.id as customerId, invoice.id as invoiceId, work.id as workId from customer, invoice, workinvoice, work where customer.id=invoice.customerid and invoice.id=workinvoice.invoiceId and workinvoice.workId=work.id
) as q1, (select work.id as workId, sum((price * hours * workhours.discount) + (purchaseprice * amount * useditem.discount)) as price from worktype,workhours,work,warehouseitem,useditem where worktype.id=workhours.worktypeid and workhours.workid=work.id and work.id=useditem.workid and useditem.warehouseitemid=warehouseitem.id group by work.id
) as q2
WHERE q1.workId = q2.workId group by q1.invoiceId;
This query should return me a sum of work prices for each invoice per customer.
I would be interested to know how to do this kind of query in Neo4J. I know that there is UNION https://neo4j.com/docs/cypher-manual/current/clauses/union/. However that does seem to do what I want. I need to make two subqueries and join them from same node as in that SQL example. What would be the correct way to do this with Cypher?
There's a quite complex example of how to do a join in cypher which you can find here: https://github.com/moxious/halin/blob/master/src/api/data/queries/dbms/3.5/tasks.js#L22
Basically, the technique is that you run the first query, collect the results. Then you run the second, collect the results. Then you unwind the second, match using a filter, and return the result.
In really simplified form, it looks something like this:
CALL something() YIELD a, b
WITH collect({ a: a, b: b }) as resultSet1
CALL somethingElse YIELD a, c
WITH resultSet1, collect({ a: a, c: c }) as resultSet2
UNWIND resultSet2 as rec
WITH [item in resultSet1 WHERE item.a = rec.a][0] as match, rec
RETURN match.a, match.b, rec.c
The list comprehension bit is basically doing the join. Here we're joining on the "a" field.
I figured out the solution I wanted:
MATCH (inv:invoice)-[:WORK_INVOICE]->(w:work)<-[h:WORKHOURS]-(wt:worktype) WITH inv, w, SUM(wt.price * h.hours * h.discount) as workTimePrice OPTIONAL MATCH (w)-[u:USED_ITEM]->(i:item) WITH inv, workTimePrice + SUM(u.amount * u.discount * i.purchaseprice) as workItemPrice RETURN inv, sum(workItemPrice) as invoicePrice

Exists, or Within

I'm writing a some filtering logic that basically wants to first check if there's a value in the filter table, then if there is return the filtered values. When there isn't a value in the filter table just return everything. The following table does this correctly but I have to write the same select statement twice.
select *
from personTbl
where (not exists (select filterValue from filterTable where filterType = 'name') or
personTbl.name in (select filterValue from filterTable where filterType = 'name'))
Is there some better way to do this that will return true if the table is empty, or the value is contained within it?
One approach is to do a left outer join to your filter-subquery, and then select all the rows where the join either failed (meaning that the subquery returned no rows) or succeeded and had the right value:
SELECT personTbl.*
FROM personTbl
LEFT
OUTER
JOIN ( SELECT DISTINCT filterValue
FROM filterTable
WHERE filterType = 'name'
) filter
ON 1 = 1
WHERE filter.filterValue = personTbl.name
OR filter.filterValue IS NULL
;
To be honest, I'm not sure if the above is a good idea — it's not very intuitive1, and I'm not sure how well it will perform — but you can judge for yourself.
1. As evidence of its unintuitiveness, witness the mistaken claim below that it doesn't work. As of this writing, that comment has garnered two upvotes. So although the query is correct, it clearly inspires people to great confidence that it's wrong. Which is a nice party trick, but not generally desirable in production code.
You can use a collection to try to make the query more intuitive (and only require a single select from the filter table):
CREATE TYPE filterlist IS TABLE OF VARCHAR2(100);
/
SELECT p.*
FROM PersonTbl p
INNER JOIN
( SELECT CAST(
MULTISET(
SELECT filterValue
FROM filterTable
WHERE filterType = 'name'
)
AS filterlist
) AS filters
FROM DUAL ) f
ON ( f.filters IS EMPTY OR p.name MEMBER OF f.filters );

PostgreSQL - how to query "result IN ALL OF"?

I am new to PostgreSQL and I have a problem with the following query:
WITH relevant_einsatz AS (
SELECT einsatz.fahrzeug,einsatz.mannschaft
FROM einsatz
INNER JOIN bergefahrzeug ON einsatz.fahrzeug = bergefahrzeug.id
),
relevant_mannschaften AS (
SELECT DISTINCT relevant_einsatz.mannschaft
FROM relevant_einsatz
WHERE relevant_einsatz.fahrzeug IN (SELECT id FROM bergefahrzeug)
)
SELECT mannschaft.id,mannschaft.rufname,person.id,person.nachname
FROM mannschaft,person,relevant_mannschaften WHERE mannschaft.leiter = person.id AND relevant_mannschaften.mannschaft=mannschaft.id;
This query is working basically - but in "relevant_mannschaften" I am currently selecting each mannschaft, which has been to an relevant_einsatz with at least 1 bergefahrzeug.
Instead of this, I want to select into "relevant_mannschaften" each mannschaft, which has been to an relevant_einsatz WITH EACH from bergefahrzeug.
Does anybody know how to formulate this change?
The information you provide is rather rudimentary. But tuning into my mentalist skills, going out on a limb, I would guess this untangled version of the query does the job much faster:
SELECT m.id, m.rufname, p.id, p.nachname
FROM person p
JOIN mannschaft m ON m.leiter = p.id
JOIN (
SELECT e.mannschaft
FROM einsatz e
JOIN bergefahrzeug b ON b.id = e.fahrzeug -- may be redundant
GROUP BY e.mannschaft
HAVING count(DISTINCT e.fahrzeug)
= (SELECT count(*) FROM bergefahrzeug)
) e ON e.mannschaft = m.id
Explain:
In the subquery e I count how many DISTINCT mountain-vehicles (bergfahrzeug) have been used by a team (mannschaft) in all their deployments (einsatz): count(DISTINCT e.fahrzeug)
If that number matches the count in table bergfahrzeug: (SELECT count(*) FROM bergefahrzeug) - the team qualifies according to your description.
The rest of the query just fetches details from matching rows in mannschaft and person.
You don't need this line at all, if there are no other vehicles in play than bergfahrzeuge:
JOIN bergefahrzeug b ON b.id = e.fahrzeug
Basically, this is a special application of relational division. A lot more on the topic under this related question:
How to filter SQL results in a has-many-through relation
Do not know how to explain it, but here is an example how I solved this problem, just in case somebody has the some question one day.
WITH dfz AS (
SELECT DISTINCT fahrzeug,mannschaft FROM einsatz WHERE einsatz.fahrzeug IN (SELECT id FROM bergefahrzeug)
), abc AS (
SELECT DISTINCT mannschaft FROM dfz
), einsatzmannschaften AS (
SELECT abc.mannschaft FROM abc WHERE (SELECT sum(dfz.fahrzeug) FROM dfz WHERE dfz.mannschaft = abc.mannschaft) = (SELECT sum(bergefahrzeug.id) FROM bergefahrzeug)
)
SELECT mannschaft.id,mannschaft.rufname,person.id,person.nachname
FROM mannschaft,person,einsatzmannschaften WHERE mannschaft.leiter = person.id AND einsatzmannschaften.mannschaft=mannschaft.id;

Complex ARel query

I've got a complicated query that I can't wrap my head around (using either sql or ActiveRecord) Here are my models:
class Contact
has_many :profile_answers
end
class ProfileAnswer
belongs_to :contact
belongs_to :profile_question
end
class ProfileQuestion
has_many :profile_answers
end
I'm trying to find the number of ProfileAnswers for two contacts that have the same value for a particular ProfileQuestion. In other words:
Get the total number of profile answers that two contacts have answered with the same value for a particular profile_question
I don't want to make multiple queries and filter as I know this is possible with Sql only, i just don't know how to do it
I had considered a self join of profile_answers on profile_question_id then filtering by value being equal, but i still can't wrap my head around that. Any help is greatly appreciated.
I think this will do:
SELECT COUNT(DISTINCT profile_question_id)
FROM
( SELECT profile_question_id
FROM ProfileAnswer an
JOIN ProfileQuestion qu
ON qu.id = an.profile_question_id
WHERE contact_id IN ( id1, id2 )
GROUP BY profile_question_id
, value
HAVING COUNT(*) = 2
) AS grp
And the JOIN seems not be used. So, if ProfileAnswer.profile_question_id is NOT NULL, this will suffice:
SELECT COUNT(*)
FROM
( SELECT profile_question_id
FROM ProfileAnswer
WHERE contact_id IN ( id1, id2 )
GROUP BY profile_question_id
, value
HAVING COUNT(*) = 2
) AS grp
EDITED for two specific contacts (with ids id1 and id2).
Added the WHERE and changed the COUNT (DINSTINCT ) to COUNT(*).
Perhaps this version with JOIN can be more easily adapted to ActiveRecord.
Using JOIN
SELECT COUNT(*)
FROM ProfileAnswer a
JOIN ProfileAnswer b
ON a.profile_question_id = b.profile_question_id
AND a.value = b.value
WHERE a.contact_id = id1
AND b.contact_id = id2
Here's how I ended up doing it, thanks again #ypercube:
class ProfileAnswer < ActiveRecord::Base
def self.for_contacts(*contacts)
where :contact_id => contacts.collect(&:id)
end
def self.common_for_contacts(*contacts)
select(:profile_question_id).for_contacts(*contacts).group(:profile_question_id, :value).having("count(*) = #{contacts.length}")
end
def self.common_count_for_contacts(*contacts)
find_by_sql("select count(*) as answer_count from (#{common_for_contacts(*contacts).to_sql})").first.answer_count
end
end
# Usage
ProfileAnswer.common_count_for_contacts(contact1, contact2[, contact3...])
Still had to use a find_by_sql in the end for the nested select... not sure if there's any way around that ??
Also annoying that find_by_sql returns an array, so I had to use .first which then gives me the object that has my answer_count property on it.

MS-Access -> SELECT AS + ORDER BY = error

I'm trying to make a query to retrieve the region which got the most sales for sweet products. 'grupo_produto' is the product type, and 'regiao' is the region. So I got this query:
SELECT TOP 1 r.nm_regiao, (SELECT COUNT(*)
FROM Dw_Empresa
WHERE grupo_produto='1' AND
cod_regiao = d.cod_regiao) as total
FROM Dw_Empresa d
INNER JOIN tb_regiao r ON r.cod_regiao = d.cod_regiao ORDER BY total DESC
Then when i run the query, MS-Access asks for the "total" parameter. Why it doesn't consider the newly created 'column' I made in the select clause?
Thanks in advance!
Old Question I know, but it may help someone knowing than while you cant order by aliases, you can order by column index. For example, this will work without error :
SELECT
firstColumn,
IIF(secondColumn = '', thirdColumn, secondColumn) As yourAlias
FROM
yourTable
ORDER BY
2 ASC
The results would then be ordered by the values found in the second column wich is the Alias "yourAlias".
Aliases are only usable in the query output. You can't use them in other parts of the query. Unfortunately, you'll have to copy and paste the entire subquery to make it work.
You can do it like this
select * from(
select a + b as c, * from table)
order by c
Access has some differences compared to Sql Server.
Why it doesn't consider the newly
created 'column' I made in the select
clause?
Because Access (ACE/Jet) is not compliant with the SQL-92 Standard.
Consider this example, which is valid SQL-92:
SELECT a AS x, c - b AS y
FROM MyTable
ORDER
BY x, y;
In fact, x and y the only valid elements in the ORDER BY clause because all others are out of scope (ordinal numbers of columns in the SELECT clause are valid though their use id deprecated).
However, Access chokes on the above syntax. The equivalent Access syntax is this:
SELECT a AS x, c - b AS y
FROM MyTable
ORDER
BY a, c - b;
However, I understand from #Remou's comments that a subquery in the ORDER BY clause is invalid in Access.
Try using a subquery and order the results in an outer query.
SELECT TOP 1 * FROM
(
SELECT
r.nm_regiao,
(SELECT COUNT(*)
FROM Dw_Empresa
WHERE grupo_produto='1' AND cod_regiao = d.cod_regiao) as total
FROM Dw_Empresa d
INNER JOIN tb_regiao r ON r.cod_regiao = d.cod_regiao
) T1
ORDER BY total DESC
(Not tested.)
How about:
SELECT TOP 1 r.nm_regiao
FROM (SELECT Dw_Empresa.cod_regiao,
Count(Dw_Empresa.cod_regiao) AS CountOfcod_regiao
FROM Dw_Empresa
WHERE Dw_Empresa.[grupo_produto]='1'
GROUP BY Dw_Empresa.cod_regiao
ORDER BY Count(Dw_Empresa.cod_regiao) DESC) d
INNER JOIN tb_regiao AS r
ON d.cod_regiao = r.cod_regiao
I suggest using an intermediate query.
SELECT r.nm_regiao, d.grupo_produto, COUNT(*) AS total
FROM Dw_Empresa d INNER JOIN tb_regiao r ON r.cod_regiao = d.cod_regiao
GROUP BY r.nm_regiao, d.grupo_produto;
If you call that GroupTotalsByRegion, you can then do:
SELECT TOP 1 nm_regiao, total FROM GroupTotalsByRegion
WHERE grupo_produto = '1' ORDER BY total DESC
You may think it's extra work to create the intermediate query (and, in a sense, it is), but you will also find that many of your other queries will be based off of GroupTotalsByRegion. You want to avoid repeating that logic in many other queries. By keeping it in one view, you provide a simplified route to answering many other questions.
How about use:
WITH xx AS
(
SELECT TOP 1 r.nm_regiao, (SELECT COUNT(*)
FROM Dw_Empresa
WHERE grupo_produto='1' AND
cod_regiao = d.cod_regiao) as total
FROM Dw_Empresa d
INNER JOIN tb_regiao r ON r.cod_regiao = d.cod_regiao
) SELECT * FROM xx ORDER BY total