with data as(SELECT c."id",c."accountId",c."name",c."campaignType",c."status",
(CASE WHEN cb."executionDetails"->>'initiatedAt' IS NULL THEN csr."startDate"
ELSE cast(cb."executionDetails"->>'initiatedAt' as TIMESTAMP)
END) as "startDate",
CASE WHEN cb."executionDetails"->>'initiatedAt' IS NOT NULL THEN NULL
ELSE csr."timeSlot"->>'type' END as "timeSlotType",
(CASE WHEN cb."executionDetails"->>'initiatedAt' IS not NULL THEN Null ELSE
-- CASE WHEN csr."timeSlotType"->>'startTime' IS NULL THEN NULL
CASE WHEN csr."timeSlot"->>'type'='MORNING' THEN '07:00'
WHEN csr."timeSlot"->>'type'='AFTERNOON' THEN '12:00'
WHEN csr."timeSlot"->>'type'='EVENING' THEN '17:00'
WHEN csr."timeSlot"->>'type'='CUSTOM' THEN (csr."timeSlot"->>'startTime')::json->>'hour'||':'||((csr."timeSlot"->>'startTime')::json->>'minute')
ELSE csr."timeSlot"->>'startTime' END END )::TIME as "startTime",
split_part(cb."batchRunId", '-',6)::decimal as batchNumber,
'CAMPAIGN' as type
FROM "Campaigns" c
LEFT JOIN "CampaignScheduleRequests" csr
ON c."id"=csr."campaignId"
LEFT JOIN "CampaignBatches" cb
ON csr."id"=cb."requestId")
SELECT * FROM data as d
WHERE d."status" IN ('ACTIVATED')
OUTPUT of the above query
Required o/p
Start time column should be concatenation of start date and startTime
with data as(
SELECT c."id",
c."accountId",
c."name",
c."campaignType",
c."status",
coalesce((cb."executionDetails"->>'initiatedAt')::timestamp,
csr."startDate")
) as "startDate",
CASE WHEN cb."executionDetails" ? 'initiatedAt' THEN NULL
ELSE csr."timeSlot"->>'type'
END as "timeSlotType",
(CASE WHEN cb."executionDetails" ? 'initiatedAt' THEN NULL
ELSE CASE csr."timeSlot"->>'type'
WHEN 'MORNING' THEN '07:00'
WHEN 'AFTERNOON' THEN '12:00'
WHEN 'EVENING' THEN '17:00'
WHEN 'CUSTOM' THEN (csr."timeSlot"->'startTime')->>'hour'
||':'
||(csr."timeSlot"->'startTime')->>'minute'
ELSE csr."timeSlot"->>'startTime' --invalid format could cause problems with ::time
END
END )::TIME as "startTime",
split_part(cb."batchRunId", '-',6)::decimal as batchNumber,
'CAMPAIGN' as type
FROM "Campaigns" c
LEFT JOIN "CampaignScheduleRequests" csr ON c."id"=csr."campaignId"
LEFT JOIN "CampaignBatches" cb ON csr."id"=cb."requestId"
WHERE c."status" IN ('ACTIVATED')
)
SELECT *,
"startDate"+coalesce("startTime",'00:00'::time) as "newStartTimestamp"
FROM data;
Use coalesce() to shorten the null replacements:
CASE WHEN cb."executionDetails"->>'initiatedAt' IS NULL
THEN csr."startDate"
ELSE cast(cb."executionDetails"->>'initiatedAt' as TIMESTAMP)
END
is the same as
coalesce((cb."executionDetails"->>'initiatedAt')::timestamp, csr."startDate")
In CASE you can do a single expression evaluation:
CASE expression
WHEN value1 THEN...
WHEN value2 THEN...
instead of a series of checks
CASE
WHEN expression=value1 THEN...
WHEN expression=value2 THEN...
Instead of casting back to json after using the ->> operator that gives you text: (jsonb->>'key1')::json->>'key2', you can just use -> to keep json output the first time.
? operator lets you check the presence of a key json?'key1' without having to check for null in an attempted read json->>'key1' is null.
You can add time to date or timestamp directly, the same how you'd add an interval. And to avoid nullifying your intitiatedAt-based startDate when adding a null-valued startTime, you can use coalesce() again - which I think was your main question.
This question already exists:
Cannot when add ORDER BY in a CTE
Closed 9 months ago.
Is there any way I can reference the inner dense rank results and give them appropriate labels like I am trying to do? It seems like in T-SQL I just can NOT do an "Order by" in an inner query, it is producing an error like:
The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified.
But then how do I attach appropriate labels to the dense rank results in the code below?
WITH basequery AS (
SELECT c.baseentityid,
c.team,
c.providerid,
-- rank() OVER (PARTITION BY c.baseentityid ORDER BY c.version DESC) AS rn,
t.vaccinedate,
t.vaccine,
t.vaccinesource,
t.num
FROM vr.child_registration c
CROSS APPLY ( VALUES (c.bcgsource,c.bcgdate,'bcg',1), (c.opv0source,c.opv0date,'opv0',2), (c.penta1source,c.penta1date,'penta1',3), (c.pcv1source,c.pcv1date,'pcv1',4), (c.rota1source,c.rota1date,'rota1',5), (c.opv1source,c.opv1date,'opv1',6), (c.penta2source,c.penta2date,'penta2',7), (c.pcv2source,c.pcv2date,'pcv2',8), (c.rota2source,c.rota2date,'rota2',9), (c.opv2source,c.opv2date,'opv2',10), (c.penta3source,c.penta3date,'penta3',11), (c.ipvsource,c.ipvdate,'ipv',12), (c.pcv3source,c.pcv3date,'pcv3',13), (c.opv3source,c.opv3date,'opv3',14), (c.measles1source,c.measles1date,'measles1',15), (c.tcvsource,c.tcvdate,'tcv',16), (c.ipv2source,c.ipv2date,'ipv2',17), (c.measles2source,c.measles2date,'measles2',18)) t(vaccinesource, vaccinedate, vaccine, num)
WHERE t.vaccinedate IS NOT NULL AND t.vaccinedate <> ''
)
SELECT aa.baseentityid,
aa.team,
aa.vaccinedate,
aa.vaccine,
aa.vaccinesource,
aa.num,
aa.providerid,
aa.visitrank,
CASE
WHEN aa.visitrank = 0 THEN 'external vaccination'
WHEN aa.visitrank = 1 THEN 'Enrollment'
ELSE 'Visitation'
END AS visittype,
CASE
WHEN aa.providerid LIKE '%vacc%' THEN 'Vacc'
ELSE 'Non-Vacc'
END AS providertype
FROM ( SELECT a.baseentityid,
a.team,
a.vaccinedate,
a.vaccine,
a.vaccinesource,
a.num,
a.providerid,
CASE
WHEN a.vaccinesource = 'vaccinatoradministered' THEN dense_rank() OVER (PARTITION BY a.baseentityid, (
CASE
WHEN a.vaccinesource = 'vaccinatoradministered' THEN 1
ELSE 0
END) ORDER BY (CONVERT(VARCHAR(10),a.vaccinedate,111)) )
ELSE 0
END AS visitrank
FROM basequery a
WHERE a.rn = 1
GROUP BY a.baseentityid, a.team, a.vaccinedate, a.vaccine, a.vaccinesource, a.num, a.providerid
ORDER BY a.baseentityid, a.num) aa;
I get this error with my query, but I can't figure out what is wrong with it:
Query-specified return tuple has 135 columns but crosstab returns 295.
And here's my query: (The part highlighted in bold returns same number of rows when run separately in pgAdmin.)
SELECT X.*, pi.productcode, pi.productitemdesc, pi.retailsalesprice, cat.productcategorydesc FROM (
SELECT * FROM crosstab (
'SELECT a.productitem AS productitemid, l.locationcode, (CASE WHEN SUM(a.netamount) IS NOT NULL THEN SUM(a.netamount) ELSE 0 END) || ''#'' || (CASE WHEN SUM(a.quantity) IS NOT NULL THEN SUM(a.quantity) ELSE 0 END) AS sales_qty FROM invoiceitem a INNER JOIN invoiceinfo b ON a.invoice = b.invoiceid INNER JOIN locationinfo l ON b.location = l.locationid WHERE b.status !=2 AND l.locationtype = 1 AND l.status = 1 AND TO_CHAR (b.invoicedate, ''YYYY-MM-DD'')>=''2018-03-01'' AND TO_CHAR (b.invoicedate, ''YYYY-MM-DD'')<=''2018-03-03'' GROUP BY a.productitem, l.locationcode ORDER BY a.productitem',
'SELECT l.locationcode FROM locationinfo l INNER JOIN invoiceinfo b ON b.location = l.locationid
WHERE b.status !=2 AND l.locationtype = 1 AND l.status = 1 AND TO_CHAR (b.invoicedate, ''YYYY-MM-DD'')>=''2018-03-01'' AND TO_CHAR (b.invoicedate, ''YYYY-MM-DD'')<=''2018-03-03'' GROUP BY l.locationcode order by l.locationcode')
AS (productitemid int, "0007" text,"BE101" text,"BE1013" text,"BE1014" text,"BE102" text,"BE103" text,"BE1034" text,"BE104" text,"BE1040" text,"BE1043" text,"BE1044" text,"BE1045" text,"BE1046" text,"BE105" text,"BE106" text,"BE107" text,"BE108" text,"BE109" text,"BE110" text,"BE111" text,"BE112" text,"BE123" text,"BE1265" text,"BE1266" text,"BE1271" text,"BE1272" text,"BE1273" text,"BE1274" text,"BE1279" text,"BE1280" text,"BE1281" text,"BE1282" text,"BE1351" text,"BE1400" text,"BE1401" text,"BE1404" text,"BE141" text,"BE142" text,"BE193" text,"BE194" text,"BE2125" text,"BE2126" text,"BE2127" text,"BE2128" text,"BE3001" text,"BE3002" text,"BE3005" text,"BE3006" text,"BE3009" text,"BE3010" text,"BE3031" text,"BE3032" text,"BE3121" text,"BE3122" text,"BE3123" text,"BE3124" text,"BE3127" text,"BE3128" text,"BE3131" text,"BE3132" text,"BE3203" text,"BE3204" text,"BE325" text,"BE3253" text,"BE3254" text,"BE326" text,"BE332" text,"BE3503" text,"BE3504" text,"BE355" text,"BE356" text,"BE365" text,"BE366" text,"BE381" text,"BE382" text,"BE383" text,"BE384" text,"BE400" text,"BE401" text,"BE402" text,"BE403" text,"BE405" text,"BE406" text,"BE408" text,"BE409" text,"BE411" text,"BE412" text,"BE4311" text,"BE4316" text,"BE4401" text,"BE4402" text,"BE4521" text,"BE4522" text,"BE4551" text,"BE4552" text,"BE470" text,"BE473" text,"BE475" text,"BE481" text,"BE482" text,"BE601" text,"BE604" text,"BE609" text,"BE610" text,"BE7040" text,"BE7043" text,"BE7045" text,"BE7046" text,"BE7048" text,"BE7049" text,"BE708" text,"BE7111" text,"BE7112" text,"BE7127" text,"BE7128" text,"BE7217" text,"BE7218" text,"BE7307" text,"BE7308" text,"BE7351" text,"BE7352" text,"BE801" text,"BE802" text,"BE803" text,"BE804" text,"BE831" text,"BE832" text,"BE860" text,"BE861" text,"BE862" text,"BE863" text,"BE865" text,"BE981" text,"BE982" text
)) X
LEFT JOIN productitem pi ON X.productitemid = pi.productitemid
LEFT JOIN productcategory cat ON pi.productcategory = cat.productcategoryid
The bold part of
productitemid int, "0007" text,"BE101" text,"BE1013" text,"BE1014" text,"BE102" text,"BE103" text,"BE1034" text,"BE104" text,"BE1040" text,"BE1043" text,"BE1044" text,"BE1045" text,"BE1046" text,"BE105" text,"BE106" text,"BE107" text,"BE108" text,"BE109" text,"BE110" text,"BE111" text,"BE112" text,"BE123" text,"BE1265" text,"BE1266" text,"BE1271" text,"BE1272" text,"BE1273" text,"BE1274" text,"BE1279" text,"BE1280" text,"BE1281" text,"BE1282" text,"BE1351" text,"BE1400" text,"BE1401" text,"BE1404" text,"BE141" text,"BE142" text,"BE193" text,"BE194" text,"BE2125" text,"BE2126" text,"BE2127" text,"BE2128" text,"BE3001" text,"BE3002" text,"BE3005" text,"BE3006" text,"BE3009" text,"BE3010" text,"BE3031" text,"BE3032" text,"BE3121" text,"BE3122" text,"BE3123" text,"BE3124" text,"BE3127" text,"BE3128" text,"BE3131" text,"BE3132" text,"BE3203" text,"BE3204" text,"BE325" text,"BE3253" text,"BE3254" text,"BE326" text,"BE332" text,"BE3503" text,"BE3504" text,"BE355" text,"BE356" text,"BE365" text,"BE366" text,"BE381" text,"BE382" text,"BE383" text,"BE384" text,"BE400" text,"BE401" text,"BE402" text,"BE403" text,"BE405" text,"BE406" text,"BE408" text,"BE409" text,"BE411" text,"BE412" text,"BE4311" text,"BE4316" text,"BE4401" text,"BE4402" text,"BE4521" text,"BE4522" text,"BE4551" text,"BE4552" text,"BE470" text,"BE473" text,"BE475" text,"BE481" text,"BE482" text,"BE601" text,"BE604" text,"BE609" text,"BE610" text,"BE7040" text,"BE7043" text,"BE7045" text,"BE7046" text,"BE7048" text,"BE7049" text,"BE708" text,"BE7111" text,"BE7112" text,"BE7127" text,"BE7128" text,"BE7217" text,"BE7218" text,"BE7307" text,"BE7308" text,"BE7351" text,"BE7352" text,"BE801" text,"BE802" text,"BE803" text,"BE804" text,"BE831" text,"BE832" text,"BE860" text,"BE861" text,"BE862" text,"BE863" text,"BE865" text,"BE981" text,"BE982" text
AND
SELECT l.locationcode FROM locationinfo l INNER JOIN invoiceinfo b ON b.location = l.locationid
WHERE b.status !=2 AND l.locationtype = 1 AND l.status = 1 AND TO_CHAR (b.invoicedate, ''YYYY-MM-DD'')>=''2018-03-01'' AND TO_CHAR (b.invoicedate, ''YYYY-MM-DD'')<=''2018-03-03'' GROUP BY l.locationcode order by l.locationcode
When run seperately, I get 295 results which is correct. However putting it together in the whole query gets the error.
The manual:
The remaining output columns must have the type of the last column of the source_sql query's result, and there must be exactly as many of them as there are rows in the category_sql query's result.
crosstab(text, text) with a dynamic (!) SELECT query as 2nd parameter is very error prone, since the output column list is static. You should also generate the output column list dynamically. Example:
Dynamic alternative to pivot with CASE and GROUP BY
That aside, the different number of rows returned from the same query, may very well be due to a different timezone setting in the two different sessions.
You have the expression TO_CHAR(b.invoicedate, 'YYYY-MM-DD') in your predicates. If b.invoicedate is type timestamptz the result depends on the timezone setting. Consider:
SET timezone = '+10';
SELECT TO_CHAR(timestamptz '2018-04-07 23:30+0', 'YYYY-MM-DD');
to_char
----------
2018-04-08
SET timezone = '-10';
SELECT TO_CHAR(timestamptz '2018-04-07 23:30+0', 'YYYY-MM-DD');
to_char
----------
2018-04-07
Solution
To remove the dependency on the timezone setting, use absolute values.
to_char() is expensive complication in this place to begin with. Drop that and adjust the predicate.
Provide timestamptz values to match your column. This also allows indexes to be used (if applicable).
Return Table Type from A function in PostgreSQL
Use time zone names (not offsets or abbreviations) to avoid more misunderstandings.
Ignoring timezones altogether in Rails and PostgreSQL
Use dollar-quoting to clean up the quote mess.
Insert text with single quotes in PostgreSQL
I suggest this as 2nd parameter:
$$
SELECT l.locationcode
FROM locationinfo l
JOIN invoiceinfo b ON b.location = l.locationid
WHERE b.status <> 2 AND l.locationtype = 1 AND l.status = 1
AND b.invoicedate >= timestamptz '2018-03-01 Europe/Vienna'
AND b.invoicedate < timestamptz '2018-03-04 Europe/Vienna'
GROUP BY 1
ORDER BY 1
$$
Replace Europe/Vienna in my example with the time zone name defining your dates.
Note 2018-03-04 to include all of 2018-03-03 like you intended.
Can I refactor the below SQL CASE statements into single for each case ?
SELECT
CASE RDV.DOMAIN_CODE WHEN 'L' THEN CN.FAMILY_NAME ELSE NULL END AS [LEGAL_FAMILY_NAME],
CASE RDV.DOMAIN_CODE WHEN 'L' THEN CN.GIVEN_NAME ELSE NULL END AS [LEGAL_GIVEN_NAME],
CASE RDV.DOMAIN_CODE WHEN 'L' THEN CN.MIDDLE_NAMES ELSE NULL END AS [LEGAL_MIDDLE_NAMES],
CASE RDV.DOMAIN_CODE WHEN 'L' THEN CN.NAME_TITLE ELSE NULL END AS [LEGAL_NAME_TITLE],
CASE RDV.DOMAIN_CODE WHEN 'P' THEN CN.FAMILY_NAME ELSE NULL END AS [PREFERRED_FAMILY_NAME],
CASE RDV.DOMAIN_CODE WHEN 'P' THEN CN.GIVEN_NAME ELSE NULL END AS [PREFERRED_GIVEN_NAME],
CASE RDV.DOMAIN_CODE WHEN 'P' THEN CN.MIDDLE_NAMES ELSE NULL END AS [PREFERRED_MIDDLE_NAMES],
CASE RDV.DOMAIN_CODE WHEN 'P' THEN CN.NAME_TITLE ELSE NULL END AS [PREFERRED_NAME_TITLE]
FROM dbo.CLIENT_NAME CN
JOIN dbo.REFERENCE_DOMAIN_VALUE RDV
ON CN.NAME_TYPE_CODE = RDV.DOMAIN_CODE AND RDV.REFERENCE_DOMAIN_ID = '7966'
No, you will require 8 separate statements as case and other such variants can only be used in a select to modify the results of a single column, not a series of columns.
If RDV.DOMAIN_COD can only by 'P' or 'L' use NULLIf. It's cleaner.
NULLIF ( expression , expression )
NULLIF is equivalent to a searched CASE expression in which the two expressions are equal and the resulting expression is NULL.
SELECT
NullIf('P', RDV.DOMAIN_CODE) AS [LEGAL_FAMILY_NAME],
...
NullIf('L', RDV.DOMAIN_CODE) AS [PREFERRED_FAMILY_NAME],
...
Since a CASE expression returns a single value, you cannot take eight CASE expressions returning 8 values and make a single CASE expression that returns all eight.
A less efficient alternative with no cases:
SELECT LEGAL_FAMILY_NAME, LEGAL_GIVEN_NAME, LEGAL_MIDDLE_NAMES, LEGAL_NAME_TITLE,
PREFERRED_FAMILY_NAME, PREFERRED_GIVEN_NAME, PREFERRED_MIDDLE_NAMES, PREFERRED_NAME_TITLE
FROM dbo.REFERENCE_DOMAIN_VALUE RDV
LEFT OUTER JOIN
( SELECT
NAME_TYPE_CODE,
FAMILY_NAME AS [LEGAL_FAMILY_NAME],
GIVEN_NAME AS [LEGAL_GIVEN_NAME],
MIDDLE_NAMES AS [LEGAL_MIDDLE_NAMES],
NAME_TITLE AS [LEGAL_NAME_TITLE]
FROM dbo.CLIENT_NAME
WHERE NAME_TYPE_CODE = 'L') LN ON RDV.DOMAIN_CODE = LN.NAME_TYPE_CODE
LEFT OUTER JOIN
( SELECT
NAME_TYPE_CODE,
FAMILY_NAME AS [PREFERRED_FAMILY_NAME],
GIVEN_NAME AS [PREFERRED_GIVEN_NAME],
MIDDLE_NAMES AS [PREFERRED_MIDDLE_NAMES],
NAME_TITLE AS [PREFERRED_NAME_TITLE]
FROM dbo.CLIENT_NAME
WHERE NAME_TYPE_CODE = 'P') PN ON RDV.DOMAIN_CODE = PN.NAME_TYPE_CODE
WHERE RDV.REFERENCE_DOMAIN_ID = '7966'
You could also use a temp table or table variable with all 8 columns and then do two inserts. You could also use a UNION ALL. My guess is that the 8 case statements are the most efficient way. This is especially true if you have some key where you will want some type of ClientID, Legal Names, Preferred Names so you will wrap a MAX around the cases or something and group by a ClientID......
You could generate the SQL script using syscols/INFORMATION_SCHEMA.columns with two case whens for each column 'CASE WHEN DOMAIN_CODE = ''P'' THEN ' + COLUMN_NAME + ' ELSE NULL END AS PREFERRED_' + COLUMN_NAME and then another for L. You could make a LOOP so that the same code executes once for P and once for L and get it down to one loop. Then you could EXEC the string directly, or PRINT it and put it into your SQL script. Anyway for just 8 columns I would cut/paste the case statements...
But anyway in general T-SQL is limited on being able to do for eaches over columns. Either you generate the script using a code generator (done in t-sql or another programming language) or you rethink your problem in another way. But many times you get better performance from duplicate cut/paste code. And many times it isn't worth the hassle of writing an external code generator (ie just for 8 case statements).