Given the data table below, how can I retrieve only the row #3, querying the field "chave", based on multiple json rows?
I want to retrieve the master row where the json field (NomeCampo = id and Valor = 3) and also (NomeCampo = id2 and Valor = 5)
id id_modulo chave
624D4FB5-6197-11EA-A947-9C5C8ED7177E 17 [{"NomeCampo":"id","Valor":2},{"NomeCampo":"id2","Valor":5}]
4CF95795-4BFD-EC11-8CE5-80A589B639E0 17 [{"NomeCampo":"id","Valor":3},{"NomeCampo":"id2","Valor":4}]
DBE9275A-9BFF-EC11-8CE5-80A589B639E0 17 [{"NomeCampo":"id","Valor":3},{"NomeCampo":"id2","Valor":5}]
BE3228C6-9BFF-EC11-8CE5-80A589B639E0 17 [{"NomeCampo":"id","Valor":3},{"NomeCampo":"id2","Valor":6}]
This is the SQL that I have but it is not retrieving any row at all:
SELECT id, id_modulo, chave
FROM myTable
WHERE id_modulo = 17
AND EXISTS (SELECT *
FROM OPENJSON(chave)
WITH (NomeCampo nvarchar(max) '$.NomeCampo',
Valor nvarchar(max) '$.Valor') AS [Info]
WHERE ([Info].NomeCampo = 'id' AND [Info].Valor = '3')
AND ([Info].NomeCampo = 'id2' AND [Info].Valor = '5'))
Is this even possible to do?
This is actually a case of relational division. You need to find the set of JSON rows which have these properties, so you need to group it
SELECT
t.id,
t.id_modulo,
t.chave
FROM myTable t
WHERE t.id_modulo = 17
AND EXISTS (SELECT 1
FROM OPENJSON(t.chave) WITH (
NomeCampo nvarchar(100),
Valor nvarchar(1000)
) AS Info
WHERE (Info.NomeCampo = 'id' AND Info.Valor = '3'
OR Info.NomeCampo = 'id2' AND Info.Valor = '5')
GROUP BY ()
HAVING COUNT(*) = 2
);
Another option
SELECT
t.id,
t.id_modulo,
t.chave
FROM myTable t
WHERE t.id_modulo = 17
AND EXISTS (SELECT 1
FROM OPENJSON(t.chave) WITH (
NomeCampo nvarchar(100),
Valor nvarchar(1000)
) AS Info
GROUP BY ()
HAVING COUNT(CASE WHEN Info.NomeCampo = 'id' AND Info.Valor = '3' THEN 1 END) > 0
AND COUNT(CASE WHEN Info.NomeCampo = 'id2' AND Info.Valor = '5' THEN 1 END) > 0
);
I have stored procedure with 3 statements to update sta_fk_col_id in the status table. For test porpuses, I want to return the value in sta_fk_col_id without updating the table. Instead of UPDATE status SET sta_fk_col_id = I tried to do something like SET valido = but without seccuss.
SET valido
CREATE DEFINER=`tpcroot`#`%` PROCEDURE `sp_test_store_procedure`(IN functionId INT, out valido int)
BEGIN
-- statement 1.1
SET valido =
(SELECT CASE WHEN MAX(mon_alertlevel) >= 60 THEN 3 WHEN MAX(mon_alertlevel) < 60
AND MAX(mon_alertlevel) >= 30 THEN 2 ELSE 1 END AS color
FROM monitor INNER JOIN monitor_system ON fk_mon_id = mon_funct_id
WHERE fk_dri_id IN (110))
WHERE sta_fk_dri_id = (110);
-- statement 1.2
SET valido =
(SELECT color FROM ( SELECT MAX(sta_fk_col_id) AS color FROM status WHERE sta_fk_mty_id = 1
AND sta_fk_sys_id = 4
AND sta_fk_dri_id IS NOT NULL) helptable)
WHERE sta_fk_sys_id = 4 AND
sta_fk_mty_id = 1 AND sta_fk_dri_id IS NULL;
-- statement 2
SET valido = (SELECT CASE WHEN MAX(mon_alertlevel) >= 60 THEN 3 WHEN MAX(mon_alertlevel) <60 AND MAX(mon_alertlevel) >= 30 THEN 2 ELSE 1 END AS color
FROM monitor_system INNER JOIN monitor ON fk_mon_id = mon_funct_id AND mon_funct_id = functionId)
WHERE sta_fk_mty_id = 1 AND
sta_fk_sys_id = 4 AND
sta_fk_dri_id IS NULL AND
(SELECT countDrilldown FROM (select Count(*) AS countDrilldown FROM status
WHERE sta_fk_mty_id = 1 AND
sta_fk_sys_id = 4 AND sta_fk_dri_id IS NOT NULL) helptable) = 0;
select #valido;
END
I am calling the stored procedure like this:
CALL sp_test_store_procedure(2315, #returnValue);
select #returnValue;
I have two tables. Let's call it: SEATS and SEAT_ALLOCATION_RULE table.
Below are the table schema:
CREATE TABLE IF NOT EXISTS `SEATS` (
`SeatID` int(11) NOT NULL AUTO_INCREMENT,
`SeatName` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`SeatID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=9 ;
INSERT INTO `SEATS` (`SeatID`, `SeatName`) VALUES
(1, 'Super VIP'),
(2, 'VIP'),
(3, 'Business'),
(4, 'Economy'),
(5, 'Standing');
CREATE TABLE IF NOT EXISTS `SEAT_ALLOCATION_RULE` (
`SeatID` int(11) NOT NULL DEFAULT '0',
`Origin` varchar(50) NOT NULL DEFAULT '0',
`Destination` varchar(50) NOT NULL DEFAULT '',
`Passenger_Type` varchar(25) NOT NULL DEFAULT '',
PRIMARY KEY (`SeatID`,`Origin`,`Destination`,`Passenger_Type`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `SEAT_ALLOCATION_RULE` (`SeatID`, `Origin`, `Destination, `Passenger_Type`) VALUES
(1, 'Malaysia','',''),
(2, 'Malaysia','Singapore',''),
(3, 'Malaysia','Singapore','Senior_Citizen'),
(4, 'Bangkok','Japan','Student'),
(5, 'Cambodia','China','Senior_Citizen');
SEAT_ALLOCATION_RULE table determines which seat should a passenger be assigned to based on the following order in priority:
1. Origin, destination, and passenger_type match
2. Origin and destination match
3. Origin match
It means that if all the fields (origin, destination, and passenger_type) match, it should take higher priority than if it is just two fields match and so on. If a column is empty, it is considered as unspecified and hence has lower priority. So, for example:
If the Origin is Malaysia, Destination is Singapore, and Passenger_Type is Senior_Citizen, it should return seatID 3
If the Origin is Malaysia, Destination is Singapore, and Passenger_Type is Student, it should return seatID 2 (since it only match Origin and Destination)
If the Origin is Malaysia, Destination is US, and Passenger_Type is Student, it should return seatID 1 (since it only match Origin).
Now, based on the rules above, if the origin is Malaysia, destination is Singapore, and Passenger_Type is student, the query to return seatID is as follow:
SELECT s.SeatID, s.SeatName
FROM SEATS s
WHERE
CASE WHEN EXISTS(
select 1
from SEAT_ALLOCATION_RULE r
where s.SeatID = r.SeatID
AND r.Origin = 'Malaysia'
AND r.Destination = 'Singapore'
AND r.Passenger_Type='Student') Then 1
WHEN EXISTS(
select 1
from SEAT_ALLOCATION_RULE r
where s.SeatID = r.SeatID
AND r.Origin = 'Malaysia'
AND r.Destination = 'Singapore'
AND r.Passenger_Type='') Then 1
WHEN EXISTS(
select 1
from SEAT_ALLOCATION_RULE r
where s.SeatID = r.SeatID
AND r.Origin = 'Malaysia'
AND r.Destination = ''
AND r.Passenger_Type='') Then 1 END
However, the query above does not work as it will return seatID 1 and 2, but the expected output is only seatID 2 (since origin and destination matches and it takes higher precedence). Can someone help to correct my SQL query?
This should do the trick:
select seatid
from seat_allocation_rule sar
order by ((sar.origin = :origin) << 2) + ((sar.destination = :destination) << 1) + (sar.passenger_type = :passenger_type) desc,
((sar.origin <> '') << 2) + ((sar.destination <> '') << 1) + (sar.passenger_type <> '') asc
limit 1
To understand how:
create table testcase (
origin varchar(255),
destination varchar(255),
passenger_type varchar(255),
expected_seat int(11)
);
insert into testcase values ('Malaysia','Singapore','Senior_Citizen',3),
('Malaysia','Singapore','Student',2),
('Malaysia','US','Student',1);
select * from (
select tc.*,
sar.seatid,
case when sar.seatid = tc.expected_seat then 'Y' else '-' end as pass,
((sar.origin = tc.origin) << 2)
+ ((sar.destination = tc.destination) << 1)
+ ((sar.passenger_type = tc.passenger_type) << 0) as score,
((sar.origin <> '') << 2)
+ ((sar.destination <> '') << 1)
+ ((sar.passenger_type <> '') << 0) as priority
from seat_allocation_rule sar
cross join testcase tc
) x order by expected_seat desc, score desc, priority asc;
This fixes the existing SQL:
SELECT DISTINCT s.SeatID, s.SeatName
FROM SEATS s
LEFT JOIN SEAT_ALLOCATION_RULE r ON r.SeatID = s.SeatID
AND r.Origin = 'Malaysia'
AND (
(r.Destination = 'Singapore' AND r.Passenger_Type IN ('Student', ''))
OR
(r.Destination = '' AND r.Passenger_Type = '')
)
WHERE r.SeatID IS NOT NULL
But it's only a partial solution, and it's hand-coding logic you really want to apply based solely on the data.
A complete solution will use hypothetical inputs for your passenger's ticket info to produce all eligible seats. This is a great use of lateral joins/apply, which are sadly lacking in MySql (all of their major competitors have had these for at least two release cycles, along with other gems that are absent from the current MySql release like windowing functions, ctes, full joins... I could go on). Here's how I'd do it in Sql Server:
SELECT p.PassengerID, s.SeatID, s.SeatName
FROM Passenger p
CROSS APPLY (
SELECT TOP 1 r.SeatID
FROM SEAT_ALLOCATION_RULE r
WHERE COALESCE(NULLIF(r.Origin, ''),p.Origin) = p.Origin
AND COALESCE(NULLIF(r.Destination,''), p.Destination) = p.Destination
AND COALESCE(NULLIF(r.Passenger_Type,''),p.Passenger_Type) = p.Passenger_Type
ORDER BY
CASE WHEN r.Origin <> '' THEN 1 ELSE 0 END
+ CASE WHEN r.Destination <> '' THEN 1 ELSE 0 END
+ CASE WHEN r.Passenger_Type <> '' THEN 1 ELSE 0 END DESC
) r
INNER JOIN SEATS s ON s.SeatID = r.SeatID
WHERE p.PassengerID = /* passenger criteria here */
I know the Sql Server solution isn't much immediate help to you, but perhaps it will suggest a better MySql solution.
Without APPLY, the only way I know to do this is to first compute the MAX() match count for your passengers (how many parts of the rules match):
SELECT p.PassengerID,
MAX(CASE WHEN r.Origin <> '' THEN 1 ELSE 0 END
+ CASE WHEN r.Destination <> '' THEN 1 ELSE 0 END
+ CASE WHEN r.Passenger_Type <> '' THEN 1 ELSE 0 END) AS MatchCount
FROM Passenger p
INNER JOIN SEAT_ALLOCATION_RULE r ON COALESCE(NULLIF(r.Origin, ''),p.Origin) = p.Origin
AND COALESCE(NULLIF(r.Destination,''), p.Destination) = p.Destination
AND COALESCE(NULLIF(r.Passenger_Type,''),p.Passenger_Type) = p.Passenger_Type
GROUP BY p.PassengerID
And then use that to filter down to results that have the same number of matches:
SELECT p
FROM Passenger p
INNER JOIN ( /* matchecounts */
SELECT p.PassengerID,
MAX(CASE WHEN r.Origin <> '' THEN 1 ELSE 0 END
+ CASE WHEN r.Destination <> '' THEN 1 ELSE 0 END
+ CASE WHEN r.Passenger_Type <> '' THEN 1 ELSE 0 END) AS MatchCount
FROM Passenger p
INNER JOIN SEAT_ALLOCATION_RULE r ON COALESCE(NULLIF(r.Origin, ''),p.Origin) = p.Origin
AND COALESCE(NULLIF(r.Destination,''), p.Destination) = p.Destination
AND COALESCE(NULLIF(r.Passenger_Type,''),p.Passenger_Type) = p.Passenger_Type
GROUP BY p.PassengerID
) m ON m.PassengerID = p.PassengerID
INNER JOIN SEAT_ALLOCATION_RULE r ON COALESCE(NULLIF(r.Origin, ''),p.Origin) = p.Origin
AND COALESCE(NULLIF(r.Destination,''), p.Destination) = p.Destination
AND COALESCE(NULLIF(r.Passenger_Type,''),p.Passenger_Type) = p.Passenger_Type
INNER JOIN SEATS s ON s.SeatID = r.SeatID
WHERE m.MatchCount =
(CASE WHEN r.Origin <> '' THEN 1 ELSE 0 END
+ CASE WHEN r.Destination <> '' THEN 1 ELSE 0 END
+ CASE WHEN r.Passenger_Type <> '' THEN 1 ELSE 0 END)
AND p.PassengerID = /* Passenger criteria here */
Which repeats a lot of code as well as effort in the DB, and is not very efficient. You can repeat the passenger criteria in the nested query, but that would only help a little. This option might also return multiple records for a passenger if they match two rules equally, though you can solve this easily enough with a GROUP BY expression.
In either case, note you can improve performance and simplify code by using actual NULL values instead of empty strings for missing parts of the SEAT_ALLOCATION_RULE table.
I am trying to make a row in the end of the result set that shows the totals.
My query is this:
SELECT
[ ] = ISNULL(CAST(GEN_idPaciente AS VARCHAR)+'-'+nombrePaciente, 'TOTAL'),
[2016-11-01] = MAX([2016-11-01]),
[2016-11-02] = MAX([2016-11-02]),
[2016-11-03] = MAX([2016-11-03]),
[2016-11-04] = MAX([2016-11-04]),
TOTAL = COUNT([2016-11-01]) + COUNT([2016-11-02]) + COUNT([2016-11-03]) + COUNT([2016-11-04])
FROM
(
SELECT GEN_Paciente.GEN_idPaciente,COALESCE(GEN_ape_paternoPaciente, '')+' '+COALESCE(GEN_ape_maternoPaciente, '')+' '+COALESCE(GEN_nombrePaciente, '') AS nombrePaciente,HOS_fechaCategorizacion,HOS_nivel_riesgoCategorizacion+CAST(HOS_nivel_dependenciaCategorizacion AS VARCHAR) as riesgoDependencia
FROM HOS_Categorizacion
INNER JOIN HOS_Hospitalizacion
ON HOS_Hospitalizacion.HOS_idHospitalizacion = HOS_Categorizacion.HOS_idHospitalizacion
INNER JOIN GEN_Paciente
ON GEN_Paciente.GEN_idPaciente = HOS_Hospitalizacion.GEN_idPaciente
WHERE HOS_nivel_riesgoCategorizacion IS NOT NULL
) src
PIVOT
(
MAX(riesgoDependencia)
for HOS_fechaCategorizacion in ([2016-11-01],[2016-11-02],[2016-11-03],[2016-11-04])
) p
GROUP BY
ROLLUP(CAST(GEN_idPaciente AS VARCHAR)+'-'+nombrePaciente)
This gives me this result:
But as you can see the totals for the rows are right but the totals for the columns are wrong because I am using MAX instead of COUNT, but I only need COUNT in the TOTAL row, the others have to be MAX, so I wrote this query:
SELECT
[ ] = ISNULL(CAST(GEN_idPaciente AS VARCHAR)+'-'+nombrePaciente, 'TOTAL'),
[2016-11-01] = CASE WHEN CAST(GEN_idPaciente AS VARCHAR)+'-'+nombrePaciente IS NOT NULL THEN MAX([2016-11-01]) ELSE COUNT([2016-11-01]) END,
[2016-11-02] = CASE WHEN CAST(GEN_idPaciente AS VARCHAR)+'-'+nombrePaciente IS NOT NULL THEN MAX([2016-11-02]) ELSE COUNT([2016-11-02]) END,
[2016-11-03] = CASE WHEN CAST(GEN_idPaciente AS VARCHAR)+'-'+nombrePaciente IS NOT NULL THEN MAX([2016-11-03]) ELSE COUNT([2016-11-03]) END,
[2016-11-04] = CASE WHEN CAST(GEN_idPaciente AS VARCHAR)+'-'+nombrePaciente IS NOT NULL THEN MAX([2016-11-04]) ELSE COUNT([2016-11-04]) END,
TOTAL = COUNT([2016-11-01]) + COUNT([2016-11-02]) + COUNT([2016-11-03]) + COUNT([2016-11-04])
FROM
(
SELECT GEN_Paciente.GEN_idPaciente,COALESCE(GEN_ape_paternoPaciente, '')+' '+COALESCE(GEN_ape_maternoPaciente, '')+' '+COALESCE(GEN_nombrePaciente, '') AS nombrePaciente,HOS_fechaCategorizacion,HOS_nivel_riesgoCategorizacion+CAST(HOS_nivel_dependenciaCategorizacion AS VARCHAR) as riesgoDependencia
FROM HOS_Categorizacion
INNER JOIN HOS_Hospitalizacion
ON HOS_Hospitalizacion.HOS_idHospitalizacion = HOS_Categorizacion.HOS_idHospitalizacion
INNER JOIN GEN_Paciente
ON GEN_Paciente.GEN_idPaciente = HOS_Hospitalizacion.GEN_idPaciente
WHERE HOS_nivel_riesgoCategorizacion IS NOT NULL
) src
PIVOT
(
MAX(riesgoDependencia)
for HOS_fechaCategorizacion in ([2016-11-01],[2016-11-02],[2016-11-03],[2016-11-04])
) p
GROUP BY
ROLLUP(CAST(GEN_idPaciente AS VARCHAR)+'-'+nombrePaciente)
But that is not working
Thanks for your help!!
If I understand this correctly you want to count all columns which are not null. In this case you should just look at the condition IS NULL and not at the actual value at all. Try this:
DECLARE #tbl TABLE(ID INT IDENTITY, val1 VARCHAR(100),val2 VARCHAR(100),val3 VARCHAR(100));
INSERT INTO #tbl VALUES
('row1_val1','row1_val2',NULL)
,('row2_val1','row2_val2','row2_val3')
,(NULL,'row2_val2',NULL)
,(NULL,NULL,'row2_val3')
,(NULL,NULL,NULL);
SELECT *
,CASE WHEN val1 IS NULL THEN 0 ELSE 1 END
+CASE WHEN val2 IS NULL THEN 0 ELSE 1 END
+CASE WHEN val3 IS NULL THEN 0 ELSE 1 END AS CountOfValNotNull
FROM #tbl
UPDATE: Add a final Totals Row
You'd need ugly fiddling with a CTE, an additional sort column, UNION ALL to add another row and a sub_select.
Use the outer-most ORDER BY to get the artificial Totals-Row to the end
hint: Use the #tbl variable from above!
WITH SortedRows AS
(
SELECT ROW_NUMBER() OVER(ORDER BY ID) AS SortColumn
,*
,CASE WHEN val1 IS NULL THEN 0 ELSE 1 END
+CASE WHEN val2 IS NULL THEN 0 ELSE 1 END
+CASE WHEN val3 IS NULL THEN 0 ELSE 1 END AS CountOfValNotNull
FROM #tbl
)
SELECT tbl1.*
FROM
(
SELECT * FROM SortedRows
UNION ALL
SELECT 999999,0,'','','',(SELECT SUM(CountOfValNotNull) FROM SortedRows)
) AS tbl1
ORDER BY tbl1.SortColumn
I have code where I need to add filter into where clause that varies based on value of variable. As in example below if #X has a value of 0 then I want
to include a filter for OrderID = 10; otherwise I want to add filter for OrderID = 20 with another filter for DepartmentID either been NULL or a value of 30. This could be accomplished using a IF ELSE as below
DECLARE #X INT
-- Retrieve value for #X
IF #X = 0
BEGIN
SELECT *
FROM Customers
WHERE ProductID IS NOT NULL
AND OrderID = 10
END
ELSE
BEGIN
SELECT *
FROM Customers
WHERE ProductID IS NOT NULL
AND OrderID = 20 AND ( DepartmentID IS NULL OR DepartmentID = 30)
END
I wonder if there is someway of doing it using one SQL statement. I thought it's doable using CASE within a WHERE but SQL does not seem to allow below.
SELECT *
FROM Customers
WHERE ProductID IS NOT NULL
AND CASE WHEN #X = 0 THEN OrderID = 10 ELSE OrderID = 20 AND ( DepartmentID IS NULL OR DepartmentID = 30) END
Is there anyway of accomplishing this.
you were close,
AND OrderID = (CASE WHEN #X = 0 THEN 10 ELSE 20 END)
AND DepartmentID = (CASE WHEN #X = 0 THEN DepartmentID ELSE 30 END)
SELECT *
FROM Customers
WHERE ProductID IS NOT NULL
AND OrderID = CASE WHEN #X = 0 THEN 10 ELSE 20 END
AND ISNULL(DepartmentID,30) = CASE WHEN #X = 0 THEN ISNULL(DepartmentID,30) ELSE 30 END
You can do that without a CASE:
SELECT *
FROM Customers
WHERE ProductID IS NOT NULL
AND ((#X = 0 AND OrderID = 10) OR (#X <> 0 AND OrderID = 20 AND DepartmentID = 30))
Edit:
With the condition that you wanted to add:
SELECT *
FROM Customers
WHERE ProductID IS NOT NULL
AND ((#X = 0 AND OrderID = 10) OR (#X <> 0 AND OrderID = 20 AND (DepartmentID IS NULL OR DepartmentID = 30)))