Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using experimental CTE #1545

Open
ahartman opened this issue May 12, 2024 · 0 comments
Open

Using experimental CTE #1545

ahartman opened this issue May 12, 2024 · 0 comments

Comments

@ahartman
Copy link

ahartman commented May 12, 2024

What did you do?

Built a query using recursive CTE

What did you expect to happen?

My solution works, and it replaces a lot of crappy Swift code to transform the data.
The SQL produces data that does not need any transformation at all.
However, the solution is not very 'swifty', any ideas how to improve?

What happened instead?

Environment

GRDB flavor(s): (GRDB)
GRDB version: master
Installation method: (SPM)
Xcode version: latest
Swift version: 5
Platform(s) running GRDB: (MacCatalyst)
macOS version running Xcode: latest

Demo Project

This is about a graph, showing the frequency of waiting times for my wife's patients, i.e., the difference between the create data and the visit date of an visit in the Mac Calendar.

Scherm­afbeelding 2024-05-12 om 08 13 23

The issue in the graph data is that the data range may not be continuous, i.e., for a given time period, there are no visits with a waiting time of X weeks, like in the image for 0, 2 and 3 weeks of visit's age. Solved by left joining table 'alle' with a generated data range 'visitAgeRange'.

In addition to the graph shown in the image, I also want the sale graph as a running total up to 100%.
Solved with a window function in 'alleCum', using 'total' from another CTE table.

visitAges = try db.read { db in
                let visitAgeRangeCTE = CommonTableExpression(
                    recursive: true,
                    named: "visitAgeRange",
                    columns: ["type", "visitAge", "visitCount"],
                    literal:
                    """
                        WITH RECURSIVE
                            visitAgeRange(visitAge) AS (
                                VALUES(0)
                                UNION ALL
                                SELECT visitAge+1 FROM visitAgeRange WHERE visitage <
                                (
                                    SELECT max(visitAge)
                                    FROM visit
                                    WHERE visit.visitDate BETWEEN \(dateStart) AND \(dateEnd)
                                    AND visitCalendar IN ('Marieke', 'Marieke nieuwe')
                                )
                            ),
                            visitCountTotal AS (
                                SELECT CAST(count(*) AS FLOAT) AS total
                                FROM visit
                                WHERE visitDate BETWEEN \(dateStart) AND \(dateEnd)
                                AND visitCalendar IN ('Marieke', 'Marieke nieuwe')
                            ),
                            alle AS (
                                SELECT visitAgeRange.visitAge, count(visit.visitAge) AS visitCount
                                FROM visitAgeRange
                                LEFT JOIN visit
                                    ON visitAgeRange.visitage = visit.visitAge
                                    AND visit.visitDate BETWEEN \(dateStart) AND \(dateEnd)
                                    AND visitCalendar IN ('Marieke', 'Marieke nieuwe')
                                GROUP BY visitAgeRange.visitAge
                                )

                        SELECT 'alle' AS type, visitAge, visitCount
                        FROM alle
                        UNION
                        SELECT 'alleCum' AS type, visitAge,
                            SUM(visitCount / total) OVER (ORDER BY visitAge) AS visitCount
                        FROM alle, visitCountTotal

                        ORDER BY type, visitAge
                    """
                )
                let request = visitAgeRangeCTE.all().with(visitAgeRangeCTE)
                return try VisitAges1.fetchAll(db, request)

By the way, two notes:

  • I had probleem with data interpolation and it took me a day to see the difference of a SQL Request with sql: and literal:; maybe you can emphasise that in the documentation.
  • I found a small typo in the CTE documentation:
// An association from LeftRecord to rightCTE
let rightCTE = ...
let association = LeftRecord.association(
    to: rightCTE, 
    on: { left, right in
        left[Column("x")] = right[Column("y")]
    })

The = must be a double ==.

As said I replace a lot of complex Swift .map. .reduce and .filter to transform the original data.
However, any ideas to do the CTE in a more Swifty way?

Regards, André Hartman

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant