Laravel中Distinct查询失效:重复获取诊所数据问题
解决Laravel中Google地图定位重复显示诊所的问题
我明白你的问题:当一个诊所的某个地点提供多项服务时,你的查询会返回多条重复的诊所/位置记录(每条服务对应一条),导致前端地图上重复显示同一个诊所标记。这是因为你关联了location_services表后,每条服务都会生成一条独立的数据库记录,而distinct()在这里无法生效——因为每条记录的serviceName等字段是不同的,所以整行记录并不重复。
下面是两种可行的解决方案,你可以根据自己的需求选择:
方案一:通过SQL分组聚合直接去重
这种方式在数据库层面完成分组和服务信息合并,性能更优,适合数据量较大的场景。
修改你的查询逻辑,使用GROUP BY按诊所ID和位置ID分组,同时用GROUP_CONCAT将同一位置的所有服务名称合并成一个字符串:
use Illuminate\Support\Facades\DB; public function mapService(Request $request) { $postdata = $request->all(); // Start XML file, create parent node $dom = new \DOMDocument("1.0"); $node = $dom->createElement("markers"); $parnode = $dom->appendChild($node); $services_id = $postdata['id'] ?? ''; $apiKey = $postdata['apikey']; if( $services_id ) { $loc_services = Clinic::select( // 明确选择需要的字段,避免select(*)带来的冲突 'clinics.clinicID', 'clinics.clinicName', 'clinics.clinicFname', 'clinics.clinicLname', 'clinics.clinicAddress', 'clinics.clinicCity', 'clinics.clinicPhone', 'clinics.clinicState', 'clinics.clinicZip', 'clinics.clinicEmail', 'locations.locationID', 'locations.locationName', 'locations.locationAddress1', 'locations.locationCity', 'locations.locationState', 'locations.locationZip', 'locations.locationLat', 'locations.locationLong', 'locations.distance', // 合并同一位置的所有服务名称,用逗号分隔 DB::raw('GROUP_CONCAT(DISTINCT services.serviceName SEPARATOR ", ") as serviceNames') ) ->join('locations', 'locations.clinicID', '=', 'clinics.clinicID') ->join('location_services', 'location_services.locationID', '=', 'locations.locationID') ->join('services', 'services.serviceID', '=', 'location_services.serviceID') ->whereIn('services.serviceID',$services_id) ->where('clinics.api_key','=',$apiKey) // 按诊所和位置分组,确保每个位置只返回一条记录 ->groupBy('clinics.clinicID', 'locations.locationID') ->get(); } else { $loc_services = Clinic::select( 'clinics.*', 'locations.*' ) ->join('locations', 'locations.clinicID', '=', 'clinics.clinicID') ->where('clinics.api_key','=',$apiKey) ->get(); } foreach ($loc_services as $loc_service) { $node = $dom->createElement("marker"); $newnode = $parnode->appendChild($node); // 诊所基础信息 $newnode->setAttribute("clinicID", $loc_service->clinicID); $newnode->setAttribute("clinicName", $loc_service->clinicName); $newnode->setAttribute("clinicFname", $loc_service->clinicFname); $newnode->setAttribute("clinicLname", $loc_service->clinicLname); $newnode->setAttribute("clinicAddress", $loc_service->clinicAddress); $newnode->setAttribute("clinicCity", $loc_service->clinicCity); $newnode->setAttribute("clinicPhone", $loc_service->clinicPhone); $newnode->setAttribute("clinicstate", $loc_service->clinicState); $newnode->setAttribute("cliniczip", $loc_service->clinicZip); $newnode->setAttribute("clinicemail", $loc_service->clinicEmail); // 位置信息 $newnode->setAttribute("id", $loc_service->locationID); $newnode->setAttribute("locationName", $loc_service->locationName); $newnode->setAttribute("locationAddress1", $loc_service->locationAddress1); $newnode->setAttribute("locationCity", $loc_service->locationCity); $newnode->setAttribute("locationState", $loc_service->locationState); $newnode->setAttribute("locationZip", $loc_service->locationZip); $newnode->setAttribute("locationLat", $loc_service->locationLat); $newnode->setAttribute("locationLong", $loc_service->locationLong); $newnode->setAttribute("distance", $loc_service->distance); // 合并后的服务信息(仅当有服务筛选时存在) if(isset($loc_service->serviceNames)) { $newnode->setAttribute("serviceNames", $loc_service->serviceNames); } } echo $dom->saveXML(); }
关键修改点:
- 替换
select('*')为明确的字段列表,避免表字段冲突(比如clinicID在两个表中都存在) - 使用
GROUP_CONCAT聚合函数,将同一位置的所有服务名称合并成一个字符串 - 按
clinicID和locationID分组,确保每个诊所的每个位置只返回一条记录
如果你的MySQL版本支持JSON,可以用JSON_ARRAYAGG替代GROUP_CONCAT,生成JSON数组方便前端处理:
DB::raw('JSON_ARRAYAGG(DISTINCT services.serviceName) as serviceNames')
方案二:通过Laravel集合分组处理
如果你不想修改SQL逻辑,可以用Laravel的集合方法在内存中完成分组和去重,适合数据量较小的场景:
public function mapService(Request $request) { $postdata = $request->all(); // Start XML file, create parent node $dom = new \DOMDocument("1.0"); $node = $dom->createElement("markers"); $parnode = $dom->appendChild($node); $services_id = $postdata['id'] ?? ''; $apiKey = $postdata['apikey']; $loc_services = collect(); if( $services_id ) { // 先获取所有原始记录 $rawRecords = Clinic::select('*') ->join('locations', 'locations.clinicID', '=', 'clinics.clinicID') ->join('location_services', 'location_services.locationID', '=', 'locations.locationID') ->join('services', 'services.serviceID', '=', 'location_services.serviceID') ->whereIn('services.serviceID',$services_id) ->where('clinics.api_key','=',$apiKey) ->get(); // 按诊所+位置的组合键分组,合并服务信息 $loc_services = $rawRecords->groupBy(function($item) { return $item->clinicID . '-' . $item->locationID; })->map(function($group) { $record = $group->first(); // 提取唯一的服务名称并合并 $record->serviceNames = $group->pluck('serviceName')->unique()->implode(', '); return $record; })->values(); } else { $loc_services = Clinic::select('*') ->join('locations', 'locations.clinicID', '=', 'clinics.clinicID') ->where('clinics.api_key','=',$apiKey) ->get(); } // 后续循环逻辑和方案一一致,这里省略... foreach ($loc_services as $loc_service) { // ... 生成XML节点的代码不变 } echo $dom->saveXML(); }
关键逻辑:
- 先用原始查询获取所有包含重复的记录
- 用
groupBy方法按诊所ID-位置ID的组合字符串分组 - 对每个分组,保留第一条记录,并将分组内的所有服务名称合并成一个字符串
两种方案都能解决重复显示诊所的问题,你可以根据自己的业务场景和数据量选择合适的方式。
内容的提问来源于stack exchange,提问作者Xavier Issac
相关产品推荐
相关产品推荐

